locale.js 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. /**
  2. * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  4. */
  5. /**
  6. * @module utils/locale
  7. */
  8. /* globals console */
  9. import { _translate } from './translation-service';
  10. const RTL_LANGUAGE_CODES = [ 'ar', 'fa', 'he', 'ku', 'ug' ];
  11. /**
  12. * Represents the localization services.
  13. */
  14. export default class Locale {
  15. /**
  16. * Creates a new instance of the locale class. Learn more about
  17. * {@glink features/ui-language configuring the language of the editor}.
  18. *
  19. * @param {Object} [options] Locale configuration.
  20. * @param {String} [options.uiLanguage='en'] The editor UI language code in the
  21. * [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639-1) format. See {@link #uiLanguage}.
  22. * @param {String} [options.contentLanguage] The editor content language code in the
  23. * [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639-1) format. If not specified, the same as `options.language`.
  24. * See {@link #contentLanguage}.
  25. */
  26. constructor( options = {} ) {
  27. /**
  28. * The editor UI language code in the [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639-1) format.
  29. *
  30. * If the {@link #contentLanguage content language} was not specified in the `Locale` constructor,
  31. * it also defines the language of the content.
  32. *
  33. * @readonly
  34. * @member {String}
  35. */
  36. this.uiLanguage = options.uiLanguage || 'en';
  37. /**
  38. * The editor content language code in the [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639-1) format.
  39. *
  40. * Usually the same as the {@link #uiLanguage editor language}, it can be customized by passing an optional
  41. * argument to the `Locale` constructor.
  42. *
  43. * @readonly
  44. * @member {String}
  45. */
  46. this.contentLanguage = options.contentLanguage || this.uiLanguage;
  47. /**
  48. * Text direction of the {@link #uiLanguage editor UI language}. Either `'ltr'` or `'rtl'`.
  49. *
  50. * @readonly
  51. * @member {String}
  52. */
  53. this.uiLanguageDirection = getLanguageDirection( this.uiLanguage );
  54. /**
  55. * Text direction of the {@link #contentLanguage editor content language}.
  56. *
  57. * If the content language was passed directly to the `Locale` constructor, this property represents the
  58. * direction of that language.
  59. *
  60. * If the {@link #contentLanguage editor content language} was derived from the {@link #uiLanguage editor language},
  61. * the content language direction is the same as the {@link #uiLanguageDirection UI language direction}.
  62. *
  63. * The value is either `'ltr'` or `'rtl'`.
  64. *
  65. * @readonly
  66. * @member {String}
  67. */
  68. this.contentLanguageDirection = getLanguageDirection( this.contentLanguage );
  69. /**
  70. * Translates the given message to the {@link #uiLanguage}. This method is also available in
  71. * {@link module:core/editor/editor~Editor#t `Editor`} and {@link module:ui/view~View#t `View`}.
  72. *
  73. * This method's context is statically bound to the `Locale` instance and **should always be called as a function**:
  74. *
  75. * const t = locale.t;
  76. * t( 'Label' );
  77. *
  78. * The message can be either a string or an object implementing the {@link module:utils/translation-service~Message} interface.
  79. *
  80. * The message may contain placeholders (`%<index>`) for value(s) that are passed as a `values` parameter.
  81. * For an array of values, the `%<index>` will be changed to an element of that array at the given index.
  82. * For a single value passed as the second argument, only the `%0` placeholders will be changed to the provided value.
  83. *
  84. * t( 'Created file "%0" in %1ms.', [ fileName, timeTaken ] );
  85. * t( 'Created file "%0", fileName );
  86. *
  87. * The message supports plural forms. To specify the plural form, use the `plural` property. Singular or plural form
  88. * will be chosen depending on the first value from the passed `values`. The value of the `plural` property is used
  89. * as a default plural translation when the translation for the target language is missing.
  90. *
  91. * t( { string: 'Add a space', plural: 'Add %0 spaces' }, 1 ); // 'Add a space' for the English language.
  92. * t( { string: 'Add a space', plural: 'Add %0 spaces' }, 5 ); // 'Add 5 spaces' for the English language.
  93. * t( { string: '%1 a space', plural: '%1 %0 spaces' }, [ 2, 'Add' ] ); // 'Add 2 spaces' for the English language.
  94. *
  95. * t( { string: 'Add a space', plural: 'Add %0 spaces' }, 1 ); // 'Dodaj spację' for the Polish language.
  96. * t( { string: 'Add a space', plural: 'Add %0 spaces' }, 5 ); // 'Dodaj 5 spacji' for the Polish language.
  97. * t( { string: '%1 a space', plural: '%1 %0 spaces' }, [ 2, 'Add' ] ); // 'Dodaj 2 spacje' for the Polish language.
  98. *
  99. * * The message should provide an ID using the `id` property when the message strings are not unique and their
  100. * translations should be different.
  101. *
  102. * translate( 'en', { string: 'image', id: 'ADD_IMAGE' } );
  103. * translate( 'en', { string: 'image', id: 'AN_IMAGE' } );
  104. *
  105. * @method #t
  106. * @param {String|module:utils/translation-service~Message} message A message that will be localized (translated).
  107. * @param {String|Number|Array.<String|Number>} [values] A value or an array of values that will fill message placeholders.
  108. * For messages supporting plural forms the first value will determine the plural form.
  109. * @returns {String}
  110. */
  111. this.t = ( message, values ) => this._t( message, values );
  112. }
  113. /**
  114. * The editor UI language code in the [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639-1) format.
  115. *
  116. * **Note**: This property was deprecated. Please use {@link #uiLanguage} and {@link #contentLanguage}
  117. * properties instead.
  118. *
  119. * @deprecated
  120. * @member {String}
  121. */
  122. get language() {
  123. /**
  124. * The {@link module:utils/locale~Locale#language `Locale#language`} property was deprecated and will
  125. * be removed in the near future. Please use the {@link #uiLanguage} and {@link #contentLanguage} properties instead.
  126. *
  127. * @error locale-deprecated-language-property
  128. */
  129. console.warn(
  130. 'locale-deprecated-language-property: ' +
  131. 'The Locale#language property has been deprecated and will be removed in the near future. ' +
  132. 'Please use #uiLanguage and #contentLanguage properties instead.' );
  133. return this.uiLanguage;
  134. }
  135. /**
  136. * An unbound version of the {@link #t} method.
  137. *
  138. * @private
  139. * @param {String|module:utils/translation-service~Message} message
  140. * @param {Number|String|Array.<Number|String>} [values]
  141. * @returns {String}
  142. */
  143. _t( message, values = [] ) {
  144. if ( !Array.isArray( values ) ) {
  145. values = [ values ];
  146. }
  147. if ( typeof message === 'string' ) {
  148. message = { string: message };
  149. }
  150. const hasPluralForm = !!message.plural;
  151. const quantity = hasPluralForm ? values[ 0 ] : 1;
  152. const translatedString = _translate( this.uiLanguage, message, quantity );
  153. return interpolateString( translatedString, values );
  154. }
  155. }
  156. // Fills the `%0, %1, ...` string placeholders with values.
  157. function interpolateString( string, values ) {
  158. return string.replace( /%(\d+)/g, ( match, index ) => {
  159. return ( index < values.length ) ? values[ index ] : match;
  160. } );
  161. }
  162. // Helps determine whether a language is LTR or RTL.
  163. //
  164. // @param {String} language The ISO 639-1 language code.
  165. // @returns {String} 'ltr' or 'rtl
  166. function getLanguageDirection( languageCode ) {
  167. return RTL_LANGUAGE_CODES.includes( languageCode ) ? 'rtl' : 'ltr';
  168. }