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