Completed
Push — master ( 90072e...c81959 )
by Daniel
11:23
created

i18n::get_common_languages()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 5
nc 3
nop 1
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\i18n;
4
5
use SilverStripe\Control\Director;
6
use SilverStripe\Core\Config\Configurable;
7
use SilverStripe\Core\Injector\Injectable;
8
use SilverStripe\Core\Injector\Injector;
9
use SilverStripe\Core\Manifest\ClassLoader;
10
use SilverStripe\Dev\Deprecation;
11
use SilverStripe\i18n\Messages\MessageProvider;
12
use SilverStripe\View\SSViewer;
13
use SilverStripe\View\TemplateGlobalProvider;
14
use SilverStripe\View\ThemeResourceLoader;
15
use InvalidArgumentException;
16
17
/**
18
 * Base-class for storage and retrieval of translated entities.
19
 *
20
 * Please see the 'translatable' module for managing translations of database-content.
21
 *
22
 * <b>Usage</b>
23
 *
24
 * PHP:
25
 * <code>
26
 * _t('MyNamespace.MYENTITY', 'My default natural language value');
27
 * _t('MyNamespace.MYENTITY', 'My default natural language value', 'My explanatory context');
28
 * sprintf(_t('MyNamespace.MYENTITY', 'Counting %s things'), 42);
29
 * </code>
30
 *
31
 * Templates:
32
 * <code>
33
 * <%t MyNamespace.MYENTITY 'My default natural language value' %>
34
 * <%t MyNamespace.MYENTITY 'Counting %s things' s=$ThingsCount %>
35
 * <%t MyNamespace.MYENTITY 'Counting {count} things' count=$ThingsCount %>
36
 * </code>
37
 *
38
 * Javascript (see framework/client/dist/js/i18n.js):
39
 * <code>
40
 * ss.i18n._t('MyEntity.MyNamespace','My default natural language value');
41
 * </code>
42
 *
43
 * File-based i18n-translations always have a "locale" (e.g. 'en_US').
44
 * Common language names (e.g. 'en') are mainly used in the 'translatable' module
45
 * database-entities.
46
 *
47
 * <b>Text Collection</b>
48
 *
49
 * Features a "textcollector-mode" that parses all files with a certain extension
50
 * (currently *.php and *.ss) for new translatable strings. Textcollector will write
51
 * updated string-tables to their respective folders inside the module, and automatically
52
 * namespace entities to the classes/templates they are found in (e.g. $lang['en_US']['AssetAdmin']['UPLOADFILES']).
53
 *
54
 * Caution: Does not apply any character-set conversion, it is assumed that all content
55
 * is stored and represented in UTF-8 (Unicode). Please make sure your files are created with the correct
56
 * character-set, and your HTML-templates render UTF-8.
57
 *
58
 * Caution: The language file has to be stored in the same module path as the "filename namespaces"
59
 * on the entities. So an entity stored in $lang['en_US']['AssetAdmin']['DETAILSTAB'] has to
60
 * in the language file cms/lang/en_US.php, as the referenced file (AssetAdmin.php) is stored
61
 * in the "cms" module.
62
 *
63
 * <b>Locales</b>
64
 *
65
 * For the i18n class, a "locale" consists of a language code plus a region code separated by an underscore,
66
 * for example "de_AT" for German language ("de") in the region Austria ("AT").
67
 * See http://www.w3.org/International/articles/language-tags/ for a detailed description.
68
 *
69
 * @see http://doc.silverstripe.org/i18n
70
 * @see http://www.w3.org/TR/i18n-html-tech-lang
71
 *
72
 * @author Bernat Foj Capell <[email protected]>
73
 */
74
class i18n implements TemplateGlobalProvider
75
{
76
    use Injectable;
77
    use Configurable;
78
79
    /**
80
     * This static variable is used to store the current defined locale.
81
     *
82
     * @var string
83
     */
84
    protected static $current_locale = '';
85
86
    /**
87
     * @config
88
     * @var string
89
     */
90
    private static $default_locale = 'en_US';
91
92
    /**
93
     * @config
94
     * @var string
95
     */
96
    private static $date_format = 'yyyy-MM-dd';
97
98
    /**
99
     * @config
100
     * @var string
101
     */
102
    private static $time_format = 'H:mm';
103
104
    /**
105
     * List of prioritised modules, in lowest to highest priority.
106
     *
107
     * @config
108
     * @var array
109
     */
110
    private static $module_priority = [];
111
112
    /**
113
     * Config for ltr/rtr of specific locales.
114
     * Will default to ltr.
115
     *
116
     * @config
117
     * @var array
118
     */
119
    private static $text_direction = [
120
        'ar' => 'rtl',
121
        'dv' => 'rtl',
122
        'fa' => 'rtl',
123
        'ha_Arab' => 'rtl',
124
        'he' => 'rtl',
125
        'ku' => 'rtl',
126
        'pa_Arab' => 'rtl',
127
        'ps' => 'rtl',
128
        'syr' => 'rtl',
129
        'ug' => 'rtl',
130
        'ur' => 'rtl',
131
        'uz_Arab' => 'rtl',
132
    ];
133
134
    /**
135
     * An exhaustive list of possible locales (code => language and country)
136
     *
137
     * @config
138
     * @var array
139
     */
140
    private static $all_locales = array (
141
        'aa_DJ' => 'Afar (Djibouti)',
142
        'ab_GE' => 'Abkhazian (Georgia)',
143
        'abr_GH' => 'Abron (Ghana)',
144
        'ace_ID' => 'Achinese (Indonesia)',
145
        'ady_RU' => 'Adyghe (Russia)',
146
        'af_ZA' => 'Afrikaans (South Africa)',
147
        'ak_GH' => 'Akan (Ghana)',
148
        'am_ET' => 'Amharic (Ethiopia)',
149
        'ar_AE' => 'Arabic (United Arab Emirates)',
150
        'ar_BH' => 'Arabic (Bahrain)',
151
        'ar_DZ' => 'Arabic (Algeria)',
152
        'ar_EG' => 'Arabic (Egypt)',
153
        'ar_EH' => 'Arabic (Western Sahara)',
154
        'ar_IQ' => 'Arabic (Iraq)',
155
        'ar_JO' => 'Arabic (Jordan)',
156
        'ar_KW' => 'Arabic (Kuwait)',
157
        'ar_LB' => 'Arabic (Lebanon)',
158
        'ar_LY' => 'Arabic (Libya)',
159
        'ar_MA' => 'Arabic (Morocco)',
160
        'ar_MR' => 'Arabic (Mauritania)',
161
        'ar_OM' => 'Arabic (Oman)',
162
        'ar_PS' => 'Arabic (Palestinian Territory)',
163
        'ar_QA' => 'Arabic (Qatar)',
164
        'ar_SA' => 'Arabic (Saudi Arabia)',
165
        'ar_SD' => 'Arabic (Sudan)',
166
        'ar_SY' => 'Arabic (Syria)',
167
        'ar_TD' => 'Arabic (Chad)',
168
        'ar_TN' => 'Arabic (Tunisia)',
169
        'ar_YE' => 'Arabic (Yemen)',
170
        'as_IN' => 'Assamese (India)',
171
        'ast_ES' => 'Asturian (Spain)',
172
        'auv_FR' => 'Auvergnat (France)',
173
        'av_RU' => 'Avaric (Russia)',
174
        'awa_IN' => 'Awadhi (India)',
175
        'ay_BO' => 'Aymara (Bolivia)',
176
        'ay_PE' => 'Aymara (Peru)',
177
        'az_AZ' => 'Azerbaijani (Azerbaijan)',
178
        'az_IR' => 'Azerbaijani (Iran)',
179
        'ba_RU' => 'Bashkir (Russia)',
180
        'ban_ID' => 'Balinese (Indonesia)',
181
        'bcc_PK' => 'Balochi, Southern (Pakistan)',
182
        'bcl_PH' => 'Bicolano, Central (Philippines)',
183
        'be_BY' => 'Belarusian (Belarus)',
184
        'bew_ID' => 'Betawi (Indonesia)',
185
        'bg_BG' => 'Bulgarian (Bulgaria)',
186
        'bgc_IN' => 'Haryanvi (India)',
187
        'bgn_PK' => 'Balochi, Western (Pakistan)',
188
        'bgp_PK' => 'Balochi, Easter (Pakistan)',
189
        'bhb_IN' => 'Bhili (India)',
190
        'bhi_IN' => 'Bhilali (India)',
191
        'bhk_PH' => 'Bicolano, Albay (Philippines)',
192
        'bho_IN' => 'Bhojpuri (India)',
193
        'bho_MU' => 'Bhojpuri (Mauritius)',
194
        'bho_NP' => 'Bhojpuri (Nepal)',
195
        'bi_VU' => 'Bislama (Vanuatu)',
196
        'bjj_IN' => 'Kanauji (India)',
197
        'bjn_ID' => 'Banjar (Indonesia)',
198
        'bm_ML' => 'Bambara (Mali)',
199
        'bn_BD' => 'Bengali (Bangladesh)',
200
        'bn_IN' => 'Bengali (India)',
201
        'bo_CN' => 'Tibetan (China)',
202
        'bqi_IR' => 'Bakhtiari (Iran)',
203
        'brh_PK' => 'Brahui (Pakistan)',
204
        'bs_BA' => 'Bosnian (Bosnia and Herzegovina)',
205
        'btk_ID' => 'Batak (Indonesia)',
206
        'buc_YT' => 'Bushi (Mayotte)',
207
        'bug_ID' => 'Buginese (Indonesia)',
208
        'ca_AD' => 'Catalan (Andorra)',
209
        'ca_ES' => 'Catalan (Spain)',
210
        'ce_RU' => 'Chechen (Russia)',
211
        'ceb_PH' => 'Cebuano (Philippines)',
212
        'cgg_UG' => 'Chiga (Uganda)',
213
        'ch_GU' => 'Chamorro (Guam)',
214
        'chk_FM' => 'Chuukese (Micronesia)',
215
        'crk_CA' => 'Cree, Plains (Canada)',
216
        'cs_CZ' => 'Czech (Czech Republic)',
217
        'cwd_CA' => 'Cree, Woods (Canada)',
218
        'cy_GB' => 'Welsh (United Kingdom)',
219
        'da_DK' => 'Danish (Denmark)',
220
        'da_GL' => 'Danish (Greenland)',
221
        'dcc_IN' => 'Deccan (India)',
222
        'de_AT' => 'German (Austria)',
223
        'de_BE' => 'German (Belgium)',
224
        'de_CH' => 'German (Switzerland)',
225
        'de_DE' => 'German (Germany)',
226
        'de_LI' => 'German (Liechtenstein)',
227
        'de_LU' => 'German (Luxembourg)',
228
        'dgo_IN' => 'Dogri (India)',
229
        'dhd_IN' => 'Dhundari (India)',
230
        'diq_TR' => 'Dimli (Turkey)',
231
        'dje_NE' => 'Zarma (Niger)',
232
        'dv_MV' => 'Divehi (Maldives)',
233
        'dz_BT' => 'Dzongkha (Bhutan)',
234
        'ee_GH' => 'Ewe (Ghana)',
235
        'el_CY' => 'Greek (Cyprus)',
236
        'el_GR' => 'Greek (Greece)',
237
        'en_AS' => 'English (American Samoa)',
238
        'en_AU' => 'English (Australia)',
239
        'en_BM' => 'English (Bermuda)',
240
        'en_BS' => 'English (Bahamas)',
241
        'en_CA' => 'English (Canada)',
242
        'en_DE' => 'English (Germany)',
243
        'en_ES' => 'English (Spain)',
244
        'en_FR' => 'English (France)',
245
        'en_GB' => 'English (United Kingdom)',
246
        'en_HK' => 'English (Hong Kong SAR China)',
247
        'en_IE' => 'English (Ireland)',
248
        'en_IN' => 'English (India)',
249
        'en_IT' => 'English (Italy)',
250
        'en_JM' => 'English (Jamaica)',
251
        'en_KE' => 'English (Kenya)',
252
        'en_LR' => 'English (Liberia)',
253
        'en_MM' => 'English (Myanmar)',
254
        'en_MW' => 'English (Malawi)',
255
        'en_MY' => 'English (Malaysia)',
256
        'en_NL' => 'English (Netherlands)',
257
        'en_NZ' => 'English (New Zealand)',
258
        'en_PH' => 'English (Philippines)',
259
        'en_SG' => 'English (Singapore)',
260
        'en_TT' => 'English (Trinidad and Tobago)',
261
        'en_US' => 'English (United States)',
262
        'en_ZA' => 'English (South Africa)',
263
        'eo_XX' => 'Esperanto',
264
        'es_419' => 'Spanish (Latin America)',
265
        'es_AR' => 'Spanish (Argentina)',
266
        'es_BO' => 'Spanish (Bolivia)',
267
        'es_CL' => 'Spanish (Chile)',
268
        'es_CO' => 'Spanish (Colombia)',
269
        'es_CR' => 'Spanish (Costa Rica)',
270
        'es_CU' => 'Spanish (Cuba)',
271
        'es_DO' => 'Spanish (Dominican Republic)',
272
        'es_EC' => 'Spanish (Ecuador)',
273
        'es_ES' => 'Spanish (Spain)',
274
        'es_GQ' => 'Spanish (Equatorial Guinea)',
275
        'es_GT' => 'Spanish (Guatemala)',
276
        'es_HN' => 'Spanish (Honduras)',
277
        'es_MX' => 'Spanish (Mexico)',
278
        'es_NI' => 'Spanish (Nicaragua)',
279
        'es_PA' => 'Spanish (Panama)',
280
        'es_PE' => 'Spanish (Peru)',
281
        'es_PH' => 'Spanish (Philippines)',
282
        'es_PR' => 'Spanish (Puerto Rico)',
283
        'es_PY' => 'Spanish (Paraguay)',
284
        'es_SV' => 'Spanish (El Salvador)',
285
        'es_US' => 'Spanish (United States)',
286
        'es_UY' => 'Spanish (Uruguay)',
287
        'es_VE' => 'Spanish (Venezuela)',
288
        'et_EE' => 'Estonian (Estonia)',
289
        'eu_ES' => 'Basque (Spain)',
290
        'fa_AF' => 'Persian (Afghanistan)',
291
        'fa_IR' => 'Persian (Iran)',
292
        'fa_PK' => 'Persian (Pakistan)',
293
        'fan_GQ' => 'Fang (Equatorial Guinea)',
294
        'fi_FI' => 'Finnish (Finland)',
295
        'fi_SE' => 'Finnish (Sweden)',
296
        'fil_PH' => 'Filipino (Philippines)',
297
        'fj_FJ' => 'Fijian (Fiji)',
298
        'fo_FO' => 'Faroese (Faroe Islands)',
299
        'fon_BJ' => 'Fon (Benin)',
300
        'fr_002' => 'French (Africa)',
301
        'fr_BE' => 'French (Belgium)',
302
        'fr_CA' => 'French (Canada)',
303
        'fr_CH' => 'French (Switzerland)',
304
        'fr_DZ' => 'French (Algeria)',
305
        'fr_FR' => 'French (France)',
306
        'fr_GF' => 'French (French Guiana)',
307
        'fr_GP' => 'French (Guadeloupe)',
308
        'fr_HT' => 'French (Haiti)',
309
        'fr_KM' => 'French (Comoros)',
310
        'fr_MA' => 'French (Morocco)',
311
        'fr_MQ' => 'French (Martinique)',
312
        'fr_MU' => 'French (Mauritius)',
313
        'fr_NC' => 'French (New Caledonia)',
314
        'fr_PF' => 'French (French Polynesia)',
315
        'fr_PM' => 'French (Saint Pierre and Miquelon)',
316
        'fr_RE' => 'French (Reunion)',
317
        'fr_SC' => 'French (Seychelles)',
318
        'fr_SN' => 'French (Senegal)',
319
        'fr_US' => 'French (United States)',
320
        'fuv_NG' => 'Fulfulde (Nigeria)',
321
        'ga_GB' => 'Irish (United Kingdom)',
322
        'ga_IE' => 'Irish (Ireland)',
323
        'gaa_GH' => 'Ga (Ghana)',
324
        'gbm_IN' => 'Garhwali (India)',
325
        'gcr_GF' => 'Guianese Creole French (French Guiana)',
326
        'gd_GB' => 'Scottish Gaelic (United Kingdom)',
327
        'gil_KI' => 'Gilbertese (Kiribati)',
328
        'gl_ES' => 'Galician (Spain)',
329
        'glk_IR' => 'Gilaki (Iran)',
330
        'gn_PY' => 'Guarani (Paraguay)',
331
        'gno_IN' => 'Gondi, Northern (India)',
332
        'gsw_CH' => 'Swiss German (Switzerland)',
333
        'gsw_LI' => 'Swiss German (Liechtenstein)',
334
        'gu_IN' => 'Gujarati (India)',
335
        'guz_KE' => 'Gusii (Kenya)',
336
        'ha_NE' => 'Hausa (Niger)',
337
        'ha_NG' => 'Hausa (Nigeria)',
338
        'haw_US' => 'Hawaiian (United States)',
339
        'haz_AF' => 'Hazaragi (Afghanistan)',
340
        'he_IL' => 'Hebrew (Israel)',
341
        'hi_IN' => 'Hindi (India)',
342
        'hil_PH' => 'Hiligaynon (Philippines)',
343
        'hne_IN' => 'Chhattisgarhi (India)',
344
        'hno_PK' => 'Hindko, Northern (Pakistan)',
345
        'hoc_IN' => 'Ho (India)',
346
        'hr_AT' => 'Croatian (Austria)',
347
        'hr_BA' => 'Croatian (Bosnia and Herzegovina)',
348
        'hr_HR' => 'Croatian (Croatia)',
349
        'ht_HT' => 'Haitian (Haiti)',
350
        'hu_AT' => 'Hungarian (Austria)',
351
        'hu_HU' => 'Hungarian (Hungary)',
352
        'hu_RO' => 'Hungarian (Romania)',
353
        'hu_RS' => 'Hungarian (Serbia)',
354
        'hy_AM' => 'Armenian (Armenia)',
355
        'id_ID' => 'Indonesian (Indonesia)',
356
        'ig_NG' => 'Igbo (Nigeria)',
357
        'ilo_PH' => 'Iloko (Philippines)',
358
        'inh_RU' => 'Ingush (Russia)',
359
        'is_IS' => 'Icelandic (Iceland)',
360
        'it_CH' => 'Italian (Switzerland)',
361
        'it_FR' => 'Italian (France)',
362
        'it_HR' => 'Italian (Croatia)',
363
        'it_IT' => 'Italian (Italy)',
364
        'it_SM' => 'Italian (San Marino)',
365
        'it_US' => 'Italian (United States)',
366
        'iu_CA' => 'Inuktitut (Canada)',
367
        'ja_JP' => 'Japanese (Japan)',
368
        'jv_ID' => 'Javanese (Indonesia)',
369
        'ka_GE' => 'Georgian (Georgia)',
370
        'kam_KE' => 'Kamba (Kenya)',
371
        'kbd_RU' => 'Kabardian (Russia)',
372
        'kfy_IN' => 'Kumauni (India)',
373
        'kha_IN' => 'Khasi (India)',
374
        'khn_IN' => 'Khandesi (India)',
375
        'ki_KE' => 'Kikuyu (Kenya)',
376
        'kj_NA' => 'Kuanyama (Namibia)',
377
        'kk_CN' => 'Kazakh (China)',
378
        'kk_KZ' => 'Kazakh (Kazakhstan)',
379
        'kl_DK' => 'Kalaallisut (Denmark)',
380
        'kl_GL' => 'Kalaallisut (Greenland)',
381
        'kln_KE' => 'Kalenjin (Kenya)',
382
        'km_KH' => 'Khmer (Cambodia)',
383
        'kn_IN' => 'Kannada (India)',
384
        'ko_KR' => 'Korean (Korea)',
385
        'koi_RU' => 'Komi-Permyak (Russia)',
386
        'kok_IN' => 'Konkani (India)',
387
        'kos_FM' => 'Kosraean (Micronesia)',
388
        'kpv_RU' => 'Komi-Zyrian (Russia)',
389
        'krc_RU' => 'Karachay-Balkar (Russia)',
390
        'kru_IN' => 'Kurukh (India)',
391
        'ks_IN' => 'Kashmiri (India)',
392
        'ku_IQ' => 'Kurdish (Iraq)',
393
        'ku_IR' => 'Kurdish (Iran)',
394
        'ku_SY' => 'Kurdish (Syria)',
395
        'ku_TR' => 'Kurdish (Turkey)',
396
        'kum_RU' => 'Kumyk (Russia)',
397
        'kxm_TH' => 'Khmer, Northern (Thailand)',
398
        'ky_KG' => 'Kirghiz (Kyrgyzstan)',
399
        'la_VA' => 'Latin (Vatican)',
400
        'lah_PK' => 'Lahnda (Pakistan)',
401
        'lb_LU' => 'Luxembourgish (Luxembourg)',
402
        'lbe_RU' => 'Lak (Russia)',
403
        'lc_XX' => 'LOLCAT',
404
        'lez_RU' => 'Lezghian (Russia)',
405
        'lg_UG' => 'Ganda (Uganda)',
406
        'lij_IT' => 'Ligurian (Italy)',
407
        'lij_MC' => 'Ligurian (Monaco)',
408
        'ljp_ID' => 'Lampung (Indonesia)',
409
        'lmn_IN' => 'Lambadi (India)',
410
        'ln_CD' => 'Lingala (Congo - Kinshasa)',
411
        'ln_CG' => 'Lingala (Congo - Brazzaville)',
412
        'lo_LA' => 'Lao (Laos)',
413
        'lrc_IR' => 'Luri, Northern (Iran)',
414
        'lt_LT' => 'Lithuanian (Lithuania)',
415
        'luo_KE' => 'Luo (Kenya)',
416
        'luy_KE' => 'Luyia (Kenya)',
417
        'lv_LV' => 'Latvian (Latvia)',
418
        'mad_ID' => 'Madurese (Indonesia)',
419
        'mai_IN' => 'Maithili (India)',
420
        'mai_NP' => 'Maithili (Nepal)',
421
        'mak_ID' => 'Makasar (Indonesia)',
422
        'mdf_RU' => 'Moksha (Russia)',
423
        'mdh_PH' => 'Maguindanao (Philippines)',
424
        'mer_KE' => 'Meru (Kenya)',
425
        'mfa_TH' => 'Malay, Pattani (Thailand)',
426
        'mfe_MU' => 'Morisyen (Mauritius)',
427
        'mg_MG' => 'Malagasy (Madagascar)',
428
        'mh_MH' => 'Marshallese (Marshall Islands)',
429
        'mi_NZ' => 'te reo Māori (New Zealand)',
430
        'min_ID' => 'Minangkabau (Indonesia)',
431
        'mk_MK' => 'Macedonian (Macedonia)',
432
        'ml_IN' => 'Malayalam (India)',
433
        'mn_CN' => 'Mongolian (China)',
434
        'mn_MN' => 'Mongolian (Mongolia)',
435
        'mni_IN' => 'Manipuri (India)',
436
        'mr_IN' => 'Marathi (India)',
437
        'ms_BN' => 'Malay (Brunei)',
438
        'ms_CC' => 'Malay (Cocos Islands)',
439
        'ms_ID' => 'Malay (Indonesia)',
440
        'ms_MY' => 'Malay (Malaysia)',
441
        'ms_SG' => 'Malay (Singapore)',
442
        'mt_MT' => 'Maltese (Malta)',
443
        'mtr_IN' => 'Mewari (India)',
444
        'mup_IN' => 'Malvi (India)',
445
        'muw_IN' => 'Mundari (India)',
446
        'my_MM' => 'Burmese (Myanmar)',
447
        'myv_RU' => 'Erzya (Russia)',
448
        'na_NR' => 'Nauru (Nauru)',
449
        'nb_NO' => 'Norwegian Bokmal (Norway)',
450
        'nb_SJ' => 'Norwegian Bokmal (Svalbard and Jan Mayen)',
451
        'nd_ZW' => 'North Ndebele (Zimbabwe)',
452
        'ndc_MZ' => 'Ndau (Mozambique)',
453
        'ne_IN' => 'Nepali (India)',
454
        'ne_NP' => 'Nepali (Nepal)',
455
        'ng_NA' => 'Ndonga (Namibia)',
456
        'ngl_MZ' => 'Lomwe (Mozambique)',
457
        'niu_NU' => 'Niuean (Niue)',
458
        'nl_AN' => 'Dutch (Netherlands Antilles)',
459
        'nl_AW' => 'Dutch (Aruba)',
460
        'nl_BE' => 'Dutch (Belgium)',
461
        'nl_NL' => 'Dutch (Netherlands)',
462
        'nl_SR' => 'Dutch (Suriname)',
463
        'nn_NO' => 'Norwegian Nynorsk (Norway)',
464
        'nod_TH' => 'Thai, Northern (Thailand)',
465
        'noe_IN' => 'Nimadi (India)',
466
        'nso_ZA' => 'Northern Sotho (South Africa)',
467
        'ny_MW' => 'Nyanja (Malawi)',
468
        'ny_ZM' => 'Nyanja (Zambia)',
469
        'nyn_UG' => 'Nyankole (Uganda)',
470
        'om_ET' => 'Oromo (Ethiopia)',
471
        'or_IN' => 'Oriya (India)',
472
        'pa_IN' => 'Punjabi (India)',
473
        'pag_PH' => 'Pangasinan (Philippines)',
474
        'pap_AN' => 'Papiamento (Netherlands Antilles)',
475
        'pap_AW' => 'Papiamento (Aruba)',
476
        'pau_PW' => 'Palauan (Palau)',
477
        'pl_PL' => 'Polish (Poland)',
478
        'pl_UA' => 'Polish (Ukraine)',
479
        'pon_FM' => 'Pohnpeian (Micronesia)',
480
        'ps_AF' => 'Pashto (Afghanistan)',
481
        'ps_PK' => 'Pashto (Pakistan)',
482
        'pt_AO' => 'Portuguese (Angola)',
483
        'pt_BR' => 'Portuguese (Brazil)',
484
        'pt_CV' => 'Portuguese (Cape Verde)',
485
        'pt_GW' => 'Portuguese (Guinea-Bissau)',
486
        'pt_MZ' => 'Portuguese (Mozambique)',
487
        'pt_PT' => 'Portuguese (Portugal)',
488
        'pt_ST' => 'Portuguese (Sao Tome and Principe)',
489
        'pt_TL' => 'Portuguese (East Timor)',
490
        'qu_BO' => 'Quechua (Bolivia)',
491
        'qu_PE' => 'Quechua (Peru)',
492
        'rcf_RE' => 'R�union Creole French (Reunion)',
493
        'rej_ID' => 'Rejang (Indonesia)',
494
        'rif_MA' => 'Tarifit (Morocco)',
495
        'rjb_IN' => 'Rajbanshi (India)',
496
        'rm_CH' => 'Rhaeto-Romance (Switzerland)',
497
        'rmt_IR' => 'Domari (Iran)',
498
        'rn_BI' => 'Rundi (Burundi)',
499
        'ro_MD' => 'Romanian (Moldova)',
500
        'ro_RO' => 'Romanian (Romania)',
501
        'ro_RS' => 'Romanian (Serbia)',
502
        'ru_BY' => 'Russian (Belarus)',
503
        'ru_KG' => 'Russian (Kyrgyzstan)',
504
        'ru_KZ' => 'Russian (Kazakhstan)',
505
        'ru_RU' => 'Russian (Russia)',
506
        'ru_SJ' => 'Russian (Svalbard and Jan Mayen)',
507
        'ru_UA' => 'Russian (Ukraine)',
508
        'rw_RW' => 'Kinyarwanda (Rwanda)',
509
        'sa_IN' => 'Sanskrit (India)',
510
        'sah_RU' => 'Yakut (Russia)',
511
        'sas_ID' => 'Sasak (Indonesia)',
512
        'sat_IN' => 'Santali (India)',
513
        'sck_IN' => 'Sadri (India)',
514
        'sco_GB' => 'Scots (United Kingdom)',
515
        'sco_SCO' => 'Scots',
516
        'sd_IN' => 'Sindhi (India)',
517
        'sd_PK' => 'Sindhi (Pakistan)',
518
        'se_NO' => 'Northern Sami (Norway)',
519
        'sg_CF' => 'Sango (Central African Republic)',
520
        'si_LK' => 'Sinhalese (Sri Lanka)',
521
        'sid_ET' => 'Sidamo (Ethiopia)',
522
        'sk_RS' => 'Slovak (Serbia)',
523
        'sk_SK' => 'Slovak (Slovakia)',
524
        'sl_AT' => 'Slovenian (Austria)',
525
        'sl_SI' => 'Slovenian (Slovenia)',
526
        'sm_AS' => 'Samoan (American Samoa)',
527
        'sm_WS' => 'Samoan (Samoa)',
528
        'sn_ZW' => 'Shona (Zimbabwe)',
529
        'so_DJ' => 'Somali (Djibouti)',
530
        'so_ET' => 'Somali (Ethiopia)',
531
        'so_SO' => 'Somali (Somalia)',
532
        'sou_TH' => 'Thai, Southern (Thailand)',
533
        'sq_AL' => 'Albanian (Albania)',
534
        'sr_BA' => 'Serbian (Bosnia and Herzegovina)',
535
        'sr_ME' => 'Serbian (Montenegro)',
536
        'sr_RS' => 'Serbian (Serbia)',
537
        'ss_SZ' => 'Swati (Swaziland)',
538
        'ss_ZA' => 'Swati (South Africa)',
539
        'st_LS' => 'Southern Sotho (Lesotho)',
540
        'st_ZA' => 'Southern Sotho (South Africa)',
541
        'su_ID' => 'Sundanese (Indonesia)',
542
        'sv_AX' => 'Swedish (Aland Islands)',
543
        'sv_FI' => 'Swedish (Finland)',
544
        'sv_SE' => 'Swedish (Sweden)',
545
        'sw_KE' => 'Swahili (Kenya)',
546
        'sw_SO' => 'Swahili (Somalia)',
547
        'sw_TZ' => 'Swahili (Tanzania)',
548
        'sw_UG' => 'Swahili (Uganda)',
549
        'swb_KM' => 'Comorian (Comoros)',
550
        'swb_YT' => 'Comorian (Mayotte)',
551
        'swv_IN' => 'Shekhawati (India)',
552
        'ta_IN' => 'Tamil (India)',
553
        'ta_LK' => 'Tamil (Sri Lanka)',
554
        'ta_MY' => 'Tamil (Malaysia)',
555
        'ta_SG' => 'Tamil (Singapore)',
556
        'tcy_IN' => 'Tulu (India)',
557
        'te_IN' => 'Telugu (India)',
558
        'tet_TL' => 'Tetum (East Timor)',
559
        'tg_TJ' => 'Tajik (Tajikistan)',
560
        'th_TH' => 'Thai (Thailand)',
561
        'ti_ER' => 'Tigrinya (Eritrea)',
562
        'ti_ET' => 'Tigrinya (Ethiopia)',
563
        'tk_IR' => 'Turkmen (Iran)',
564
        'tk_TM' => 'Turkmen (Turkmenistan)',
565
        'tkl_TK' => 'Tokelau (Tokelau)',
566
        'tl_PH' => 'Tagalog (Philippines)',
567
        'tl_US' => 'Tagalog (United States)',
568
        'tn_BW' => 'Tswana (Botswana)',
569
        'tn_ZA' => 'Tswana (South Africa)',
570
        'to_TO' => 'Tonga (Tonga)',
571
        'tr_CY' => 'Turkish (Cyprus)',
572
        'tr_DE' => 'Turkish (Germany)',
573
        'tr_MK' => 'Turkish (Macedonia)',
574
        'tr_TR' => 'Turkish (Turkey)',
575
        'ts_MZ' => 'Tsonga (Mozambique)',
576
        'ts_ZA' => 'Tsonga (South Africa)',
577
        'tsg_PH' => 'Tausug (Philippines)',
578
        'tt_RU' => 'Tatar (Russia)',
579
        'tts_TH' => 'Thai, Northeastern (Thailand)',
580
        'tvl_TV' => 'Tuvalu (Tuvalu)',
581
        'tw_GH' => 'Twi (Ghana)',
582
        'ty_PF' => 'Tahitian (French Polynesia)',
583
        'tyv_RU' => 'Tuvinian (Russia)',
584
        'tzm_MA' => 'Tamazight, Central Atlas (Morocco)',
585
        'udm_RU' => 'Udmurt (Russia)',
586
        'ug_CN' => 'Uighur (China)',
587
        'uk_UA' => 'Ukrainian (Ukraine)',
588
        'uli_FM' => 'Ulithian (Micronesia)',
589
        'ur_IN' => 'Urdu (India)',
590
        'ur_PK' => 'Urdu (Pakistan)',
591
        'uz_AF' => 'Uzbek (Afghanistan)',
592
        'uz_UZ' => 'Uzbek (Uzbekistan)',
593
        've_ZA' => 'Venda (South Africa)',
594
        'vi_US' => 'Vietnamese (United States)',
595
        'vi_VN' => 'Vietnamese (Vietnam)',
596
        'vmw_MZ' => 'Waddar (Mozambique)',
597
        'wal_ET' => 'Walamo (Ethiopia)',
598
        'war_PH' => 'Waray (Philippines)',
599
        'wbq_IN' => 'Waddar (India)',
600
        'wbr_IN' => 'Wagdi (India)',
601
        'wo_MR' => 'Wolof (Mauritania)',
602
        'wo_SN' => 'Wolof (Senegal)',
603
        'wtm_IN' => 'Mewati (India)',
604
        'xh_ZA' => 'Xhosa (South Africa)',
605
        'xnr_IN' => 'Kangri (India)',
606
        'xog_UG' => 'Soga (Uganda)',
607
        'yap_FM' => 'Yapese (Micronesia)',
608
        'yo_NG' => 'Yoruba (Nigeria)',
609
        'za_CN' => 'Zhuang (China)',
610
        'zh_CN' => 'Chinese (China)',
611
        'zh_HK' => 'Chinese (Hong Kong SAR China)',
612
        'zh_MO' => 'Chinese (Macao SAR China)',
613
        'zh_SG' => 'Chinese (Singapore)',
614
        'zh_TW' => 'Chinese (Taiwan)',
615
        'zh_US' => 'Chinese (United States)',
616
        'zh_cmn' => 'Chinese (Mandarin)',
617
        'zh_yue' => 'Chinese (Cantonese)',
618
        'zu_ZA' => 'Zulu (South Africa)'
619
    );
620
621
    /**
622
     * @config
623
     * @var array $common_languages A list of commonly used languages, in the form
624
     * langcode => array( EnglishName, NativeName)
625
     */
626
    private static $common_languages = array(
627
        'af' => array(
628
            'name' => 'Afrikaans',
629
            'native' => 'Afrikaans'
630
        ),
631
        'sq' => array(
632
            'name' => 'Albanian',
633
            'native' => 'shqip'
634
        ),
635
        'ar' => array(
636
            'name' => 'Arabic',
637
            'native' => '&#1575;&#1604;&#1593;&#1585;&#1576;&#1610;&#1577;'
638
        ),
639
        'eu' => array(
640
            'name' => 'Basque',
641
            'native' => 'euskera'
642
        ),
643
        'be' => array(
644
            'name' => 'Belarusian',
645
            'native' =>
646
                '&#1041;&#1077;&#1083;&#1072;&#1088;&#1091;&#1089;&#1082;&#1072;&#1103; &#1084;&#1086;&#1074;&#1072;'
647
        ),
648
        'bn' => array(
649
            'name' => 'Bengali',
650
            'native' => '&#2476;&#2494;&#2434;&#2482;&#2494;'
651
        ),
652
        'bg' => array(
653
            'name' => 'Bulgarian',
654
            'native' => '&#1073;&#1098;&#1083;&#1075;&#1072;&#1088;&#1089;&#1082;&#1080;'
655
        ),
656
        'ca' => array(
657
            'name' => 'Catalan',
658
            'native' => 'catal&agrave;'
659
        ),
660
        'zh_yue' => array(
661
            'name' => 'Chinese (Cantonese)',
662
            'native' => '&#24291;&#26481;&#35441; [&#24191;&#19996;&#35805;]'
663
        ),
664
        'zh_cmn' => array(
665
            'name' => 'Chinese (Mandarin)',
666
            'native' => '&#26222;&#36890;&#35441; [&#26222;&#36890;&#35805;]'
667
        ),
668
        'hr' => array(
669
            'name' => 'Croatian',
670
            'native' => 'Hrvatski'
671
        ),
672
        'zh' => array(
673
            'name' => 'Chinese',
674
            'native' => '&#20013;&#25991;'
675
        ),
676
        'cs' => array(
677
            'name' => 'Czech',
678
            'native' => '&#x010D;e&#353;tina'
679
        ),
680
        'cy' => array(
681
            'name' => 'Welsh',
682
            'native' => 'Welsh/Cymraeg'
683
        ),
684
        'da' => array(
685
            'name' => 'Danish',
686
            'native' => 'dansk'
687
        ),
688
        'nl' => array(
689
            'name' => 'Dutch',
690
            'native' => 'Nederlands'
691
        ),
692
        'en' => array(
693
            'name' => 'English',
694
            'native' => 'English'
695
        ),
696
        'eo' => array(
697
            'name' => 'Esperanto',
698
            'native' => 'Esperanto'
699
        ),
700
        'et' => array(
701
            'name' => 'Estonian',
702
            'native' => 'eesti keel'
703
        ),
704
        'fo' => array(
705
            'name' => 'Faroese',
706
            'native' => 'F&oslash;royska'
707
        ),
708
        'fi' => array(
709
            'name' => 'Finnish',
710
            'native' => 'suomi'
711
        ),
712
        'fr' => array(
713
            'name' => 'French',
714
            'native' => 'fran&ccedil;ais'
715
        ),
716
        'gd' => array(
717
            'name' => 'Gaelic',
718
            'native' => 'Gaeilge'
719
        ),
720
        'gl' => array(
721
            'name' => 'Galician',
722
            'native' => 'Galego'
723
        ),
724
        'de' => array(
725
            'name' => 'German',
726
            'native' => 'Deutsch'
727
        ),
728
        'el' => array(
729
            'name' => 'Greek',
730
            'native' => '&#949;&#955;&#955;&#951;&#957;&#953;&#954;&#940;'
731
        ),
732
        'gu' => array(
733
            'name' => 'Gujarati',
734
            'native' => '&#2711;&#2753;&#2716;&#2736;&#2750;&#2724;&#2752;'
735
        ),
736
        'ha' => array(
737
            'name' => 'Hausa',
738
            'native' => '&#1581;&#1614;&#1608;&#1618;&#1587;&#1614;'
739
        ),
740
        'he' => array(
741
            'name' => 'Hebrew',
742
            'native' => '&#1506;&#1489;&#1512;&#1497;&#1514;'
743
        ),
744
        'hi' => array(
745
            'name' => 'Hindi',
746
            'native' => '&#2361;&#2367;&#2344;&#2381;&#2342;&#2368;'
747
        ),
748
        'hu' => array(
749
            'name' => 'Hungarian',
750
            'native' => 'magyar'
751
        ),
752
        'is' => array(
753
            'name' => 'Icelandic',
754
            'native' => '&Iacute;slenska'
755
        ),
756
        'io' => array(
757
            'name' => 'Ido',
758
            'native' => 'Ido'
759
        ),
760
        'id' => array(
761
            'name' => 'Indonesian',
762
            'native' => 'Bahasa Indonesia'
763
        ),
764
        'ga' => array(
765
            'name' => 'Irish',
766
            'native' => 'Irish'
767
        ),
768
        'it' => array(
769
            'name' => 'Italian',
770
            'native' => 'italiano'
771
        ),
772
        'ja' => array(
773
            'name' => 'Japanese',
774
            'native' => '&#26085;&#26412;&#35486;'
775
        ),
776
        'jv' => array(
777
            'name' => 'Javanese',
778
            'native' => 'basa Jawa'
779
        ),
780
        'ko' => array(
781
            'name' => 'Korean',
782
            'native' => '&#54620;&#44397;&#50612;'
783
        ),
784
        'ku' => array(
785
            'name' => 'Kurdish',
786
            'native' => 'Kurd&iacute;'
787
        ),
788
        'lv' => array(
789
            'name' => 'Latvian',
790
            'native' => 'latvie&#353;u'
791
        ),
792
        'lt' => array(
793
            'name' => 'Lithuanian',
794
            'native' => 'lietuvi&#353;kai'
795
        ),
796
        'lmo' => array(
797
            'name' => 'Lombard',
798
            'native' => 'Lombardo'
799
        ),
800
        'mk' => array(
801
            'name' => 'Macedonian',
802
            'native' => '&#1084;&#1072;&#1082;&#1077;&#1076;&#1086;&#1085;&#1089;&#1082;&#1080;'
803
        ),
804
        'mi' => array(
805
            'name' => 'te reo Māori',
806
            'native' => 'te reo Māori'
807
        ),
808
        'ms' => array(
809
            'name' => 'Malay',
810
            'native' => 'Bahasa melayu'
811
        ),
812
        'mt' => array(
813
            'name' => 'Maltese',
814
            'native' => 'Malti'
815
        ),
816
        'mr' => array(
817
            'name' => 'Marathi',
818
            'native' => '&#2350;&#2352;&#2366;&#2336;&#2368;'
819
        ),
820
        'ne' => array(
821
            'name' => 'Nepali',
822
            'native' => '&#2344;&#2375;&#2346;&#2366;&#2354;&#2368;'
823
        ),
824
        'nb' => array(
825
            'name' => 'Norwegian',
826
            'native' => 'Norsk'
827
        ),
828
        'om' => array(
829
            'name' => 'Oromo',
830
            'native' => 'Afaan Oromo'
831
        ),
832
        'fa' => array(
833
            'name' => 'Persian',
834
            'native' => '&#1601;&#1575;&#1585;&#1587;&#1609;'
835
        ),
836
        'pl' => array(
837
            'name' => 'Polish',
838
            'native' => 'polski'
839
        ),
840
        'pt_PT' => array(
841
            'name' => 'Portuguese (Portugal)',
842
            'native' => 'portugu&ecirc;s (Portugal)'
843
        ),
844
        'pt_BR' => array(
845
            'name' => 'Portuguese (Brazil)',
846
            'native' => 'portugu&ecirc;s (Brazil)'
847
        ),
848
        'pa' => array(
849
            'name' => 'Punjabi',
850
            'native' => '&#2602;&#2672;&#2588;&#2622;&#2604;&#2624;'
851
        ),
852
        'qu' => array(
853
            'name' => 'Quechua',
854
            'native' => 'Quechua'
855
        ),
856
        'rm' => array(
857
            'name' => 'Romansh',
858
            'native' => 'rumantsch'
859
        ),
860
        'ro' => array(
861
            'name' => 'Romanian',
862
            'native' => 'rom&acirc;n'
863
        ),
864
        'ru' => array(
865
            'name' => 'Russian',
866
            'native' => '&#1056;&#1091;&#1089;&#1089;&#1082;&#1080;&#1081;'
867
        ),
868
        'sco' => array(
869
            'name' => 'Scots',
870
            'native' => 'Scoats leid, Lallans'
871
        ),
872
        'sr' => array(
873
            'name' => 'Serbian',
874
            'native' => '&#1089;&#1088;&#1087;&#1089;&#1082;&#1080;'
875
        ),
876
        'sk' => array(
877
            'name' => 'Slovak',
878
            'native' => 'sloven&#269;ina'
879
        ),
880
        'sl' => array(
881
            'name' => 'Slovenian',
882
            'native' => 'sloven&#353;&#269;ina'
883
        ),
884
        'es' => array(
885
            'name' => 'Spanish',
886
            'native' => 'espa&ntilde;ol'
887
        ),
888
        'sv' => array(
889
            'name' => 'Swedish',
890
            'native' => 'Svenska'
891
        ),
892
        'tl' => array(
893
            'name' => 'Tagalog',
894
            'native' => 'Tagalog'
895
        ),
896
        'ta' => array(
897
            'name' => 'Tamil',
898
            'native' => '&#2980;&#2990;&#3007;&#2996;&#3021;'
899
        ),
900
        'te' => array(
901
            'name' => 'Telugu',
902
            'native' => '&#3108;&#3142;&#3122;&#3137;&#3095;&#3137;'
903
        ),
904
        'to' => array(
905
            'name' => 'Tonga',
906
            'native' => 'chiTonga'
907
        ),
908
        'ts' => array(
909
            'name' => 'Tsonga',
910
            'native' => 'xiTshonga'
911
        ),
912
        'tn' => array(
913
            'name' => 'Tswana',
914
            'native' => 'seTswana'
915
        ),
916
        'tr' => array(
917
            'name' => 'Turkish',
918
            'native' => 'T&uuml;rk&ccedil;e'
919
        ),
920
        'tk' => array(
921
            'name' => 'Turkmen',
922
            'native' => '&#1090;&#1199;&#1088;&#1082;m&#1077;&#1085;&#1095;&#1077;'
923
        ),
924
        'tw' => array(
925
            'name' => 'Twi',
926
            'native' => 'twi'
927
        ),
928
        'uk' => array(
929
            'name' => 'Ukrainian',
930
            'native' => '&#1059;&#1082;&#1088;&#1072;&#1111;&#1085;&#1089;&#1100;&#1082;&#1072;'
931
        ),
932
        'ur' => array(
933
            'name' => 'Urdu',
934
            'native' => '&#1575;&#1585;&#1583;&#1608;'
935
        ),
936
        'uz' => array(
937
            'name' => 'Uzbek',
938
            'native' => '&#1118;&#1079;&#1073;&#1077;&#1082;'
939
        ),
940
        've' => array(
941
            'name' => 'Venda',
942
            'native' => 'tshiVen&#x1E13;a'
943
        ),
944
        'vi' => array(
945
            'name' => 'Vietnamese',
946
            'native' => 'ti&#7871;ng vi&#7879;t'
947
        ),
948
        'wa' => array(
949
            'name' => 'Walloon',
950
            'native' => 'walon'
951
        ),
952
        'wo' => array(
953
            'name' => 'Wolof',
954
            'native' => 'Wollof'
955
        ),
956
        'xh' => array(
957
            'name' => 'Xhosa',
958
            'native' => 'isiXhosa'
959
        ),
960
        'yi' => array(
961
            'name' => 'Yiddish',
962
            'native' => '&#1522;&#1460;&#1491;&#1497;&#1513;'
963
        ),
964
        'zu' => array(
965
            'name' => 'Zulu',
966
            'native' => 'isiZulu'
967
        )
968
    );
969
970
    /**
971
     * @config
972
     * @var array $common_locales
973
     * Sorted alphabtically by the common language name,
974
     * not the locale key.
975
     */
976
    private static $common_locales = array(
977
        'af_ZA' => array(
978
            'name' => 'Afrikaans',
979
            'native' => 'Afrikaans'
980
        ),
981
        'sq_AL' => array(
982
            'name' => 'Albanian',
983
            'native' => 'shqip'
984
        ),
985
        'ar_EG' => array(
986
            'name' => 'Arabic',
987
            'native' => '&#1575;&#1604;&#1593;&#1585;&#1576;&#1610;&#1577;'
988
        ),
989
        'eu_ES' => array(
990
            'name' => 'Basque',
991
            'native' => 'euskera'
992
        ),
993
        'be_BY' => array(
994
            'name' => 'Belarusian',
995
            'native' =>
996
                '&#1041;&#1077;&#1083;&#1072;&#1088;&#1091;&#1089;&#1082;&#1072;&#1103; &#1084;&#1086;&#1074;&#1072;'
997
        ),
998
        'bn_BD' => array(
999
            'name' => 'Bengali',
1000
            'native' => '&#2476;&#2494;&#2434;&#2482;&#2494;'
1001
        ),
1002
        'bg_BG' => array(
1003
            'name' => 'Bulgarian',
1004
            'native' => '&#1073;&#1098;&#1083;&#1075;&#1072;&#1088;&#1089;&#1082;&#1080;'
1005
        ),
1006
        'ca_ES' => array(
1007
            'name' => 'Catalan',
1008
            'native' => 'catal&agrave;'
1009
        ),
1010
        'zh_CN' => array(
1011
            'name' => 'Chinese',
1012
            'native' => '中国的'
1013
        ),
1014
        'zh_yue' => array(
1015
            'name' => 'Chinese (Cantonese)',
1016
            'native' => '&#24291;&#26481;&#35441; [&#24191;&#19996;&#35805;]'
1017
        ),
1018
        'zh_cmn' => array(
1019
            'name' => 'Chinese (Mandarin)',
1020
            'native' => '&#26222;&#36890;&#35441; [&#26222;&#36890;&#35805;]'
1021
        ),
1022
        'hr_HR' => array(
1023
            'name' => 'Croatian',
1024
            'native' => 'Hrvatski'
1025
        ),
1026
        'cs_CZ' => array(
1027
            'name' => 'Czech',
1028
            'native' => '&#x010D;e&#353;tina'
1029
        ),
1030
        'cy_GB' => array(
1031
            'name' => 'Welsh',
1032
            'native' => 'Welsh/Cymraeg'
1033
        ),
1034
        'da_DK' => array(
1035
            'name' => 'Danish',
1036
            'native' => 'dansk'
1037
        ),
1038
        'nl_NL' => array(
1039
            'name' => 'Dutch',
1040
            'native' => 'Nederlands'
1041
        ),
1042
        'nl_BE' => array(
1043
            'name' => 'Dutch (Belgium)',
1044
            'native' => 'Nederlands (Belgi&euml;)'
1045
        ),
1046
        'en_NZ' => array(
1047
            'name' => 'English (NZ)',
1048
            'native' => 'English (NZ)'
1049
        ),
1050
        'en_US' => array(
1051
            'name' => 'English (US)',
1052
            'native' => 'English (US)'
1053
        ),
1054
        'en_GB' => array(
1055
            'name' => 'English (UK)',
1056
            'native' => 'English (UK)'
1057
        ),
1058
        'eo_XX' => array(
1059
            'name' => 'Esperanto',
1060
            'native' => 'Esperanto'
1061
        ),
1062
        'et_EE' => array(
1063
            'name' => 'Estonian',
1064
            'native' => 'eesti keel'
1065
        ),
1066
        'fo_FO' => array(
1067
            'name' => 'Faroese',
1068
            'native' => 'F&oslash;royska'
1069
        ),
1070
        'fi_FI' => array(
1071
            'name' => 'Finnish',
1072
            'native' => 'suomi'
1073
        ),
1074
        'fr_FR' => array(
1075
            'name' => 'French',
1076
            'native' => 'fran&ccedil;ais'
1077
        ),
1078
        'fr_BE' => array(
1079
            'name' => 'French (Belgium)',
1080
            'native' => 'Fran&ccedil;ais (Belgique)'
1081
        ),
1082
        'gd_GB' => array(
1083
            'name' => 'Gaelic',
1084
            'native' => 'Gaeilge'
1085
        ),
1086
        'gl_ES' => array(
1087
            'name' => 'Galician',
1088
            'native' => 'Galego'
1089
        ),
1090
        'de_DE' => array(
1091
            'name' => 'German',
1092
            'native' => 'Deutsch'
1093
        ),
1094
        'el_GR' => array(
1095
            'name' => 'Greek',
1096
            'native' => '&#949;&#955;&#955;&#951;&#957;&#953;&#954;&#940;'
1097
        ),
1098
        'gu_IN' => array(
1099
            'name' => 'Gujarati',
1100
            'native' => '&#2711;&#2753;&#2716;&#2736;&#2750;&#2724;&#2752;'
1101
        ),
1102
        'ha_NG' => array(
1103
            'name' => 'Hausa',
1104
            'native' => '&#1581;&#1614;&#1608;&#1618;&#1587;&#1614;'
1105
        ),
1106
        'he_IL' => array(
1107
            'name' => 'Hebrew',
1108
            'native' => '&#1506;&#1489;&#1512;&#1497;&#1514;'
1109
        ),
1110
        'hi_IN' => array(
1111
            'name' => 'Hindi',
1112
            'native' => '&#2361;&#2367;&#2344;&#2381;&#2342;&#2368;'
1113
        ),
1114
        'hu_HU' => array(
1115
            'name' => 'Hungarian',
1116
            'native' => 'magyar'
1117
        ),
1118
        'is_IS' => array(
1119
            'name' => 'Icelandic',
1120
            'native' => '&Iacute;slenska'
1121
        ),
1122
        'id_ID' => array(
1123
            'name' => 'Indonesian',
1124
            'native' => 'Bahasa Indonesia'
1125
        ),
1126
        'ga_IE' => array(
1127
            'name' => 'Irish',
1128
            'native' => 'Irish'
1129
        ),
1130
        'it_IT' => array(
1131
            'name' => 'Italian',
1132
            'native' => 'italiano'
1133
        ),
1134
        'ja_JP' => array(
1135
            'name' => 'Japanese',
1136
            'native' => '&#26085;&#26412;&#35486;'
1137
        ),
1138
        'jv_ID' => array(
1139
            'name' => 'Javanese',
1140
            'native' => 'basa Jawa'
1141
        ),
1142
        'ko_KR' => array(
1143
            'name' => 'Korean',
1144
            'native' => '&#54620;&#44397;&#50612; [&#38867;&#22283;&#35486;]'
1145
        ),
1146
        'ku_IQ' => array(
1147
            'name' => 'Kurdish',
1148
            'native' => 'Kurd&iacute;'
1149
        ),
1150
        'lv_LV' => array(
1151
            'name' => 'Latvian',
1152
            'native' => 'latvie&#353;u'
1153
        ),
1154
        'lt_LT' => array(
1155
            'name' => 'Lithuanian',
1156
            'native' => 'lietuvi&#353;kai'
1157
        ),
1158
        'mk_MK' => array(
1159
            'name' => 'Macedonian',
1160
            'native' => '&#1084;&#1072;&#1082;&#1077;&#1076;&#1086;&#1085;&#1089;&#1082;&#1080;'
1161
        ),
1162
        'mi_NZ' => array(
1163
            'name' => 'te reo Māori',
1164
            'native' => 'te reo Māori'
1165
        ),
1166
        'ms_MY' => array(
1167
            'name' => 'Malay',
1168
            'native' => 'Bahasa melayu'
1169
        ),
1170
        'mt_MT' => array(
1171
            'name' => 'Maltese',
1172
            'native' => 'Malti'
1173
        ),
1174
        'mr_IN' => array(
1175
            'name' => 'Marathi',
1176
            'native' => '&#2350;&#2352;&#2366;&#2336;&#2368;'
1177
        ),
1178
        'ne_NP' => array(
1179
            'name' => 'Nepali',
1180
            'native' => '&#2344;&#2375;&#2346;&#2366;&#2354;&#2368;'
1181
        ),
1182
        'nb_NO' => array(
1183
            'name' => 'Norwegian',
1184
            'native' => 'Norsk'
1185
        ),
1186
        'om_ET' => array(
1187
            'name' => 'Oromo',
1188
            'native' => 'Afaan Oromo'
1189
        ),
1190
        'fa_IR' => array(
1191
            'name' => 'Persian',
1192
            'native' => '&#1601;&#1575;&#1585;&#1587;&#1609;'
1193
        ),
1194
        'pl_PL' => array(
1195
            'name' => 'Polish',
1196
            'native' => 'polski'
1197
        ),
1198
        'pt_PT' => array(
1199
            'name' => 'Portuguese (Portugal)',
1200
            'native' => 'portugu&ecirc;s (Portugal)'
1201
        ),
1202
        'pt_BR' => array(
1203
            'name' => 'Portuguese (Brazil)',
1204
            'native' => 'portugu&ecirc;s (Brazil)'
1205
        ),
1206
        'pa_IN' => array(
1207
            'name' => 'Punjabi',
1208
            'native' => '&#2602;&#2672;&#2588;&#2622;&#2604;&#2624;'
1209
        ),
1210
        'qu_PE' => array(
1211
            'name' => 'Quechua',
1212
            'native' => 'Quechua'
1213
        ),
1214
        'rm_CH' => array(
1215
            'name' => 'Romansh',
1216
            'native' => 'rumantsch'
1217
        ),
1218
        'ro_RO' => array(
1219
            'name' => 'Romanian',
1220
            'native' => 'rom&acirc;n'
1221
        ),
1222
        'ru_RU' => array(
1223
            'name' => 'Russian',
1224
            'native' => '&#1056;&#1091;&#1089;&#1089;&#1082;&#1080;&#1081;'
1225
        ),
1226
        'sco_SCO' => array(
1227
            'name' => 'Scots',
1228
            'native' => 'Scoats leid, Lallans'
1229
        ),
1230
        'sr_RS' => array(
1231
            'name' => 'Serbian',
1232
            'native' => '&#1089;&#1088;&#1087;&#1089;&#1082;&#1080;'
1233
        ),
1234
        'sk_SK' => array(
1235
            'name' => 'Slovak',
1236
            'native' => 'sloven&#269;ina'
1237
        ),
1238
        'sl_SI' => array(
1239
            'name' => 'Slovenian',
1240
            'native' => 'sloven&#353;&#269;ina'
1241
        ),
1242
        'es_ES' => array(
1243
            'name' => 'Spanish',
1244
            'native' => 'espa&ntilde;ol'
1245
        ),
1246
        'sv_SE' => array(
1247
            'name' => 'Swedish',
1248
            'native' => 'Svenska'
1249
        ),
1250
        'tl_PH' => array(
1251
            'name' => 'Tagalog',
1252
            'native' => 'Tagalog'
1253
        ),
1254
        'ta_IN' => array(
1255
            'name' => 'Tamil',
1256
            'native' => '&#2980;&#2990;&#3007;&#2996;&#3021;'
1257
        ),
1258
        'te_IN' => array(
1259
            'name' => 'Telugu',
1260
            'native' => '&#3108;&#3142;&#3122;&#3137;&#3095;&#3137;'
1261
        ),
1262
        'to_TO' => array(
1263
            'name' => 'Tonga',
1264
            'native' => 'chiTonga'
1265
        ),
1266
        'ts_ZA' => array(
1267
            'name' => 'Tsonga',
1268
            'native' => 'xiTshonga'
1269
        ),
1270
        'tn_ZA' => array(
1271
            'name' => 'Tswana',
1272
            'native' => 'seTswana'
1273
        ),
1274
        'tr_TR' => array(
1275
            'name' => 'Turkish',
1276
            'native' => 'T&uuml;rk&ccedil;e'
1277
        ),
1278
        'tk_TM' => array(
1279
            'name' => 'Turkmen',
1280
            'native' => '&#1090;&#1199;&#1088;&#1082;m&#1077;&#1085;&#1095;&#1077;'
1281
        ),
1282
        'tw_GH' => array(
1283
            'name' => 'Twi',
1284
            'native' => 'twi'
1285
        ),
1286
        'uk_UA' => array(
1287
            'name' => 'Ukrainian',
1288
            'native' => '&#1059;&#1082;&#1088;&#1072;&#1111;&#1085;&#1089;&#1100;&#1082;&#1072;'
1289
        ),
1290
        'ur_PK' => array(
1291
            'name' => 'Urdu',
1292
            'native' => '&#1575;&#1585;&#1583;&#1608;'
1293
        ),
1294
        'uz_UZ' => array(
1295
            'name' => 'Uzbek',
1296
            'native' => '&#1118;&#1079;&#1073;&#1077;&#1082;'
1297
        ),
1298
        've_ZA' => array(
1299
            'name' => 'Venda',
1300
            'native' => 'tshiVen&#x1E13;a'
1301
        ),
1302
        'vi_VN' => array(
1303
            'name' => 'Vietnamese',
1304
            'native' => 'ti&#7871;ng vi&#7879;t'
1305
        ),
1306
        'wo_SN' => array(
1307
            'name' => 'Wolof',
1308
            'native' => 'Wollof'
1309
        ),
1310
        'xh_ZA' => array(
1311
            'name' => 'Xhosa',
1312
            'native' => 'isiXhosa'
1313
        ),
1314
        'zu_ZA' => array(
1315
            'name' => 'Zulu',
1316
            'native' => 'isiZulu'
1317
        ),
1318
    );
1319
1320
    /**
1321
     * @config
1322
     * @var array
1323
     */
1324
    private static $tinymce_lang = array(
1325
        'ar_EG' => 'ar',
1326
        'ca_AD' => 'ca',
1327
        'ca_ES' => 'ca',
1328
        'cs_CZ' => 'cs',
1329
        'cy_GB' => 'cy',
1330
        'da_DK' => 'da',
1331
        'da_GL' => 'da',
1332
        'de_AT' => 'de',
1333
        'de_BE' => 'de',
1334
        'de_CH' => 'de',
1335
        'de_DE' => 'de',
1336
        'de_LI' => 'de',
1337
        'de_LU' => 'de',
1338
        'de_BR' => 'de',
1339
        'de_US' => 'de',
1340
        'el_CY' => 'el',
1341
        'el_GR' => 'el',
1342
        'es_AR' => 'es',
1343
        'es_BO' => 'es',
1344
        'es_CL' => 'es',
1345
        'es_CO' => 'es',
1346
        'es_CR' => 'es',
1347
        'es_CU' => 'es',
1348
        'es_DO' => 'es',
1349
        'es_EC' => 'es',
1350
        'es_ES' => 'es',
1351
        'es_GQ' => 'es',
1352
        'es_GT' => 'es',
1353
        'es_HN' => 'es',
1354
        'es_MX' => 'es',
1355
        'es_NI' => 'es',
1356
        'es_PA' => 'es',
1357
        'es_PE' => 'es',
1358
        'es_PH' => 'es',
1359
        'es_PR' => 'es',
1360
        'es_PY' => 'es',
1361
        'es_SV' => 'es',
1362
        'es_UY' => 'es',
1363
        'es_VE' => 'es',
1364
        'es_AD' => 'es',
1365
        'es_BZ' => 'es',
1366
        'es_US' => 'es',
1367
        'fa_AF' => 'fa',
1368
        'fa_IR' => 'fa',
1369
        'fa_PK' => 'fa',
1370
        'fi_FI' => 'fi',
1371
        'fi_SE' => 'fi',
1372
        'fr_BE' => 'fr',
1373
        'fr_BF' => 'fr',
1374
        'fr_BI' => 'fr',
1375
        'fr_BJ' => 'fr',
1376
        'fr_CA' => 'fr_ca',
1377
        'fr_CF' => 'fr',
1378
        'fr_CG' => 'fr',
1379
        'fr_CH' => 'fr',
1380
        'fr_CI' => 'fr',
1381
        'fr_CM' => 'fr',
1382
        'fr_DJ' => 'fr',
1383
        'fr_DZ' => 'fr',
1384
        'fr_FR' => 'fr',
1385
        'fr_GA' => 'fr',
1386
        'fr_GF' => 'fr',
1387
        'fr_GN' => 'fr',
1388
        'fr_GP' => 'fr',
1389
        'fr_HT' => 'fr',
1390
        'fr_KM' => 'fr',
1391
        'fr_LU' => 'fr',
1392
        'fr_MA' => 'fr',
1393
        'fr_MC' => 'fr',
1394
        'fr_MG' => 'fr',
1395
        'fr_ML' => 'fr',
1396
        'fr_MQ' => 'fr',
1397
        'fr_MU' => 'fr',
1398
        'fr_NC' => 'fr',
1399
        'fr_NE' => 'fr',
1400
        'fr_PF' => 'fr',
1401
        'fr_PM' => 'fr',
1402
        'fr_RE' => 'fr',
1403
        'fr_RW' => 'fr',
1404
        'fr_SC' => 'fr',
1405
        'fr_SN' => 'fr',
1406
        'fr_SY' => 'fr',
1407
        'fr_TD' => 'fr',
1408
        'fr_TG' => 'fr',
1409
        'fr_TN' => 'fr',
1410
        'fr_VU' => 'fr',
1411
        'fr_WF' => 'fr',
1412
        'fr_YT' => 'fr',
1413
        'fr_GB' => 'fr',
1414
        'fr_US' => 'fr',
1415
        'he_IL' => 'he',
1416
        'hu_HU' => 'hu',
1417
        'hu_AT' => 'hu',
1418
        'hu_RO' => 'hu',
1419
        'hu_RS' => 'hu',
1420
        'is_IS' => 'is',
1421
        'it_CH' => 'it',
1422
        'it_IT' => 'it',
1423
        'it_SM' => 'it',
1424
        'it_FR' => 'it',
1425
        'it_HR' => 'it',
1426
        'it_US' => 'it',
1427
        'it_VA' => 'it',
1428
        'ja_JP' => 'ja',
1429
        'ko_KP' => 'ko',
1430
        'ko_KR' => 'ko',
1431
        'ko_CN' => 'ko',
1432
        'mi_NZ' => 'mi_NZ',
1433
        'nb_NO' => 'nb',
1434
        'nb_SJ' => 'nb',
1435
        'nl_AN' => 'nl',
1436
        'nl_AW' => 'nl',
1437
        'nl_BE' => 'nl',
1438
        'nl_NL' => 'nl',
1439
        'nl_SR' => 'nl',
1440
        'nn_NO' => 'nn',
1441
        'pl_PL' => 'pl',
1442
        'pl_UA' => 'pl',
1443
        'pt_AO' => 'pt',
1444
        'pt_BR' => 'pt',
1445
        'pt_CV' => 'pt',
1446
        'pt_GW' => 'pt',
1447
        'pt_MZ' => 'pt',
1448
        'pt_PT' => 'pt',
1449
        'pt_ST' => 'pt',
1450
        'pt_TL' => 'pt',
1451
        'ro_MD' => 'ro',
1452
        'ro_RO' => 'ro',
1453
        'ro_RS' => 'ro',
1454
        'ru_BY' => 'ru',
1455
        'ru_KG' => 'ru',
1456
        'ru_KZ' => 'ru',
1457
        'ru_RU' => 'ru',
1458
        'ru_SJ' => 'ru',
1459
        'ru_UA' => 'ru',
1460
        'si_LK' => 'si',
1461
        'sk_SK' => 'sk',
1462
        'sk_RS' => 'sk',
1463
        'sq_AL' => 'sq',
1464
        'sr_BA' => 'sr',
1465
        'sr_ME' => 'sr',
1466
        'sr_RS' => 'sr',
1467
        'sv_FI' => 'sv',
1468
        'sv_SE' => 'sv',
1469
        'tr_CY' => 'tr',
1470
        'tr_TR' => 'tr',
1471
        'tr_DE' => 'tr',
1472
        'tr_MK' => 'tr',
1473
        'uk_UA' => 'uk',
1474
        'vi_VN' => 'vi',
1475
        'vi_US' => 'vi',
1476
        'zh_CN' => 'zh-cn',
1477
        'zh_HK' => 'zh-cn',
1478
        'zh_MO' => 'zh-cn',
1479
        'zh_SG' => 'zh-cn',
1480
        'zh_TW' => 'zh-tw',
1481
        'zh_ID' => 'zh-cn',
1482
        'zh_MY' => 'zh-cn',
1483
        'zh_TH' => 'zh-cn',
1484
        'zh_US' => 'zn-cn',
1485
1486
    );
1487
1488
    /**
1489
     * @config
1490
     * @var array $likely_subtags Provides you "likely locales"
1491
     * for a given "short" language code. This is a guess,
1492
     * as we can't disambiguate from e.g. "en" to "en_US" - it
1493
     * could also mean "en_UK".
1494
     * @see http://www.unicode.org/cldr/data/charts/supplemental/likely_subtags.html
1495
     */
1496
    private static $likely_subtags = array(
1497
        'aa' => 'aa_ET',
1498
        'ab' => 'ab_GE',
1499
        'ady' => 'ady_RU',
1500
        'af' => 'af_ZA',
1501
        'ak' => 'ak_GH',
1502
        'am' => 'am_ET',
1503
        'ar' => 'ar_EG',
1504
        'as' => 'as_IN',
1505
        'ast' => 'ast_ES',
1506
        'av' => 'av_RU',
1507
        'ay' => 'ay_BO',
1508
        'az' => 'az_AZ',
1509
        'az_Cyrl' => 'az_AZ',
1510
        'az_Arab' => 'az_IR',
1511
        'az_IR' => 'az_IR',
1512
        'ba' => 'ba_RU',
1513
        'be' => 'be_BY',
1514
        'bg' => 'bg_BG',
1515
        'bi' => 'bi_VU',
1516
        'bn' => 'bn_BD',
1517
        'bo' => 'bo_CN',
1518
        'bs' => 'bs_BA',
1519
        'ca' => 'ca_ES',
1520
        'ce' => 'ce_RU',
1521
        'ceb' => 'ceb_PH',
1522
        'ch' => 'ch_GU',
1523
        'chk' => 'chk_FM',
1524
        'crk' => 'crk_CA',
1525
        'cs' => 'cs_CZ',
1526
        'cwd' => 'cwd_CA',
1527
        'cy' => 'cy_GB',
1528
        'da' => 'da_DK',
1529
        'de' => 'de_DE',
1530
        'dv' => 'dv_MV',
1531
        'dz' => 'dz_BT',
1532
        'ee' => 'ee_GH',
1533
        'efi' => 'efi_NG',
1534
        'el' => 'el_GR',
1535
        'en' => 'en_US',
1536
        'es' => 'es_ES',
1537
        'et' => 'et_EE',
1538
        'eu' => 'eu_ES',
1539
        'eo' => 'eo_XX',
1540
        'fa' => 'fa_IR',
1541
        'fi' => 'fi_FI',
1542
        'fil' => 'fil_PH',
1543
        'fj' => 'fj_FJ',
1544
        'fo' => 'fo_FO',
1545
        'fr' => 'fr_FR',
1546
        'fur' => 'fur_IT',
1547
        'fy' => 'fy_NL',
1548
        'ga' => 'ga_IE',
1549
        'gaa' => 'gaa_GH',
1550
        'gd' => 'gd_GB',
1551
        'gil' => 'gil_KI',
1552
        'gl' => 'gl_ES',
1553
        'gn' => 'gn_PY',
1554
        'gu' => 'gu_IN',
1555
        'ha' => 'ha_NG',
1556
        'ha_Arab' => 'ha_SD',
1557
        'ha_SD' => 'ha_SD',
1558
        'haw' => 'haw_US',
1559
        'he' => 'he_IL',
1560
        'hi' => 'hi_IN',
1561
        'hil' => 'hil_PH',
1562
        'ho' => 'ho_PG',
1563
        'hr' => 'hr_HR',
1564
        'ht' => 'ht_HT',
1565
        'hu' => 'hu_HU',
1566
        'hy' => 'hy_AM',
1567
        'id' => 'id_ID',
1568
        'ig' => 'ig_NG',
1569
        'ii' => 'ii_CN',
1570
        'ilo' => 'ilo_PH',
1571
        'inh' => 'inh_RU',
1572
        'is' => 'is_IS',
1573
        'it' => 'it_IT',
1574
        'iu' => 'iu_CA',
1575
        'ja' => 'ja_JP',
1576
        'jv' => 'jv_ID',
1577
        'ka' => 'ka_GE',
1578
        'kaj' => 'kaj_NG',
1579
        'kam' => 'kam_KE',
1580
        'kbd' => 'kbd_RU',
1581
        'kha' => 'kha_IN',
1582
        'kk' => 'kk_KZ',
1583
        'kl' => 'kl_GL',
1584
        'km' => 'km_KH',
1585
        'kn' => 'kn_IN',
1586
        'ko' => 'ko_KR',
1587
        'koi' => 'koi_RU',
1588
        'kok' => 'kok_IN',
1589
        'kos' => 'kos_FM',
1590
        'kpe' => 'kpe_LR',
1591
        'kpv' => 'kpv_RU',
1592
        'krc' => 'krc_RU',
1593
        'ks' => 'ks_IN',
1594
        'ku' => 'ku_IQ',
1595
        'ku_Latn' => 'ku_TR',
1596
        'ku_TR' => 'ku_TR',
1597
        'kum' => 'kum_RU',
1598
        'kxm' => 'kxm_TH',
1599
        'ky' => 'ky_KG',
1600
        'la' => 'la_VA',
1601
        'lah' => 'lah_PK',
1602
        'lb' => 'lb_LU',
1603
        'lbe' => 'lbe_RU',
1604
        'lez' => 'lez_RU',
1605
        'ln' => 'ln_CD',
1606
        'lo' => 'lo_LA',
1607
        'lt' => 'lt_LT',
1608
        'lv' => 'lv_LV',
1609
        'mai' => 'mai_IN',
1610
        'mdf' => 'mdf_RU',
1611
        'mdh' => 'mdh_PH',
1612
        'mg' => 'mg_MG',
1613
        'mh' => 'mh_MH',
1614
        'mi' => 'mi_NZ',
1615
        'mk' => 'mk_MK',
1616
        'ml' => 'ml_IN',
1617
        'mn' => 'mn_MN',
1618
        'mn_CN' => 'mn_CN',
1619
        'mn_Mong' => 'mn_CN',
1620
        'mr' => 'mr_IN',
1621
        'ms' => 'ms_MY',
1622
        'mt' => 'mt_MT',
1623
        'my' => 'my_MM',
1624
        'myv' => 'myv_RU',
1625
        'na' => 'na_NR',
1626
        'nb' => 'nb_NO',
1627
        'ne' => 'ne_NP',
1628
        'niu' => 'niu_NU',
1629
        'nl' => 'nl_NL',
1630
        'nn' => 'nn_NO',
1631
        'nr' => 'nr_ZA',
1632
        'nso' => 'nso_ZA',
1633
        'ny' => 'ny_MW',
1634
        'om' => 'om_ET',
1635
        'or' => 'or_IN',
1636
        'os' => 'os_GE',
1637
        'pa' => 'pa_IN',
1638
        'pa_Arab' => 'pa_PK',
1639
        'pa_PK' => 'pa_PK',
1640
        'pag' => 'pag_PH',
1641
        'pap' => 'pap_AN',
1642
        'pau' => 'pau_PW',
1643
        'pl' => 'pl_PL',
1644
        'pon' => 'pon_FM',
1645
        'ps' => 'ps_AF',
1646
        'pt' => 'pt_BR',
1647
        'qu' => 'qu_PE',
1648
        'rm' => 'rm_CH',
1649
        'rn' => 'rn_BI',
1650
        'ro' => 'ro_RO',
1651
        'ru' => 'ru_RU',
1652
        'rw' => 'rw_RW',
1653
        'sa' => 'sa_IN',
1654
        'sah' => 'sah_RU',
1655
        'sat' => 'sat_IN',
1656
        'sd' => 'sd_IN',
1657
        'se' => 'se_NO',
1658
        'sg' => 'sg_CF',
1659
        'si' => 'si_LK',
1660
        'sid' => 'sid_ET',
1661
        'sk' => 'sk_SK',
1662
        'sl' => 'sl_SI',
1663
        'sm' => 'sm_WS',
1664
        'sn' => 'sn_ZW',
1665
        'so' => 'so_SO',
1666
        'sq' => 'sq_AL',
1667
        'sr' => 'sr_RS',
1668
        'ss' => 'ss_ZA',
1669
        'st' => 'st_ZA',
1670
        'su' => 'su_ID',
1671
        'sv' => 'sv_SE',
1672
        'sw' => 'sw_TZ',
1673
        'swb' => 'swb_KM',
1674
        'ta' => 'ta_IN',
1675
        'te' => 'te_IN',
1676
        'tet' => 'tet_TL',
1677
        'tg' => 'tg_TJ',
1678
        'th' => 'th_TH',
1679
        'ti' => 'ti_ET',
1680
        'tig' => 'tig_ER',
1681
        'tk' => 'tk_TM',
1682
        'tkl' => 'tkl_TK',
1683
        'tl' => 'tl_PH',
1684
        'tn' => 'tn_ZA',
1685
        'to' => 'to_TO',
1686
        'tpi' => 'tpi_PG',
1687
        'tr' => 'tr_TR',
1688
        'trv' => 'trv_TW',
1689
        'ts' => 'ts_ZA',
1690
        'tsg' => 'tsg_PH',
1691
        'tt' => 'tt_RU',
1692
        'tts' => 'tts_TH',
1693
        'tvl' => 'tvl_TV',
1694
        'tw' => 'tw_GH',
1695
        'ty' => 'ty_PF',
1696
        'tyv' => 'tyv_RU',
1697
        'udm' => 'udm_RU',
1698
        'ug' => 'ug_CN',
1699
        'uk' => 'uk_UA',
1700
        'uli' => 'uli_FM',
1701
        'und' => 'en_US',
1702
        'und_AD' => 'ca_AD',
1703
        'und_AE' => 'ar_AE',
1704
        'und_AF' => 'fa_AF',
1705
        'und_AL' => 'sq_AL',
1706
        'und_AM' => 'hy_AM',
1707
        'und_AN' => 'pap_AN',
1708
        'und_AO' => 'pt_AO',
1709
        'und_AR' => 'es_AR',
1710
        'und_AS' => 'sm_AS',
1711
        'und_AT' => 'de_AT',
1712
        'und_AW' => 'nl_AW',
1713
        'und_AX' => 'sv_AX',
1714
        'und_AZ' => 'az_AZ',
1715
        'und_Arab' => 'ar_EG',
1716
        'und_Arab_CN' => 'ug_CN',
1717
        'und_Arab_DJ' => 'ar_DJ',
1718
        'und_Arab_ER' => 'ar_ER',
1719
        'und_Arab_IL' => 'ar_IL',
1720
        'und_Arab_IN' => 'ur_IN',
1721
        'und_Arab_PK' => 'ur_PK',
1722
        'und_Armn' => 'hy_AM',
1723
        'und_BA' => 'bs_BA',
1724
        'und_BD' => 'bn_BD',
1725
        'und_BE' => 'nl_BE',
1726
        'und_BF' => 'fr_BF',
1727
        'und_BG' => 'bg_BG',
1728
        'und_BH' => 'ar_BH',
1729
        'und_BI' => 'rn_BI',
1730
        'und_BJ' => 'fr_BJ',
1731
        'und_BL' => 'fr_BL',
1732
        'und_BN' => 'ms_BN',
1733
        'und_BO' => 'es_BO',
1734
        'und_BR' => 'pt_BR',
1735
        'und_BT' => 'dz_BT',
1736
        'und_BY' => 'be_BY',
1737
        'und_Beng' => 'bn_BD',
1738
        'und_CD' => 'fr_CD',
1739
        'und_CF' => 'sg_CF',
1740
        'und_CG' => 'ln_CG',
1741
        'und_CH' => 'de_CH',
1742
        'und_CI' => 'fr_CI',
1743
        'und_CL' => 'es_CL',
1744
        'und_CM' => 'fr_CM',
1745
        'und_CN' => 'zh_CN',
1746
        'und_CO' => 'es_CO',
1747
        'und_CR' => 'es_CR',
1748
        'und_CU' => 'es_CU',
1749
        'und_CV' => 'pt_CV',
1750
        'und_CY' => 'el_CY',
1751
        'und_CZ' => 'cs_CZ',
1752
        'und_Cans' => 'cwd_CA',
1753
        'und_Cyrl' => 'ru_RU',
1754
        'und_Cyrl_BA' => 'sr_BA',
1755
        'und_Cyrl_GE' => 'ab_GE',
1756
        'und_DE' => 'de_DE',
1757
        'und_DJ' => 'aa_DJ',
1758
        'und_DK' => 'da_DK',
1759
        'und_DO' => 'es_DO',
1760
        'und_DZ' => 'ar_DZ',
1761
        'und_Deva' => 'hi_IN',
1762
        'und_EC' => 'es_EC',
1763
        'und_EE' => 'et_EE',
1764
        'und_EG' => 'ar_EG',
1765
        'und_EH' => 'ar_EH',
1766
        'und_ER' => 'ti_ER',
1767
        'und_ES' => 'es_ES',
1768
        'und_ET' => 'am_ET',
1769
        'und_Ethi' => 'am_ET',
1770
        'und_FI' => 'fi_FI',
1771
        'und_FJ' => 'fj_FJ',
1772
        'und_FM' => 'chk_FM',
1773
        'und_FO' => 'fo_FO',
1774
        'und_FR' => 'fr_FR',
1775
        'und_GA' => 'fr_GA',
1776
        'und_GE' => 'ka_GE',
1777
        'und_GF' => 'fr_GF',
1778
        'und_GH' => 'ak_GH',
1779
        'und_GL' => 'kl_GL',
1780
        'und_GN' => 'fr_GN',
1781
        'und_GP' => 'fr_GP',
1782
        'und_GQ' => 'fr_GQ',
1783
        'und_GR' => 'el_GR',
1784
        'und_GT' => 'es_GT',
1785
        'und_GU' => 'ch_GU',
1786
        'und_GW' => 'pt_GW',
1787
        'und_Geor' => 'ka_GE',
1788
        'und_Grek' => 'el_GR',
1789
        'und_Gujr' => 'gu_IN',
1790
        'und_Guru' => 'pa_IN',
1791
        'und_HK' => 'zh_HK',
1792
        'und_HN' => 'es_HN',
1793
        'und_HR' => 'hr_HR',
1794
        'und_HT' => 'ht_HT',
1795
        'und_HU' => 'hu_HU',
1796
        'und_Hani' => 'zh_CN',
1797
        'und_Hans' => 'zh_CN',
1798
        'und_Hant' => 'zh_TW',
1799
        'und_Hebr' => 'he_IL',
1800
        'und_ID' => 'id_ID',
1801
        'und_IL' => 'he_IL',
1802
        'und_IN' => 'hi_IN',
1803
        'und_IQ' => 'ar_IQ',
1804
        'und_IR' => 'fa_IR',
1805
        'und_IS' => 'is_IS',
1806
        'und_IT' => 'it_IT',
1807
        'und_JO' => 'ar_JO',
1808
        'und_JP' => 'ja_JP',
1809
        'und_Jpan' => 'ja_JP',
1810
        'und_KG' => 'ky_KG',
1811
        'und_KH' => 'km_KH',
1812
        'und_KM' => 'ar_KM',
1813
        'und_KP' => 'ko_KP',
1814
        'und_KR' => 'ko_KR',
1815
        'und_KW' => 'ar_KW',
1816
        'und_KZ' => 'ru_KZ',
1817
        'und_Khmr' => 'km_KH',
1818
        'und_Knda' => 'kn_IN',
1819
        'und_Kore' => 'ko_KR',
1820
        'und_LA' => 'lo_LA',
1821
        'und_LB' => 'ar_LB',
1822
        'und_LI' => 'de_LI',
1823
        'und_LK' => 'si_LK',
1824
        'und_LS' => 'st_LS',
1825
        'und_LT' => 'lt_LT',
1826
        'und_LU' => 'fr_LU',
1827
        'und_LV' => 'lv_LV',
1828
        'und_LY' => 'ar_LY',
1829
        'und_Laoo' => 'lo_LA',
1830
        'und_Latn_CN' => 'ii_CN',
1831
        'und_Latn_CY' => 'tr_CY',
1832
        'und_Latn_DZ' => 'fr_DZ',
1833
        'und_Latn_ET' => 'om_ET',
1834
        'und_Latn_KM' => 'fr_KM',
1835
        'und_Latn_MA' => 'fr_MA',
1836
        'und_Latn_MK' => 'sq_MK',
1837
        'und_Latn_SY' => 'fr_SY',
1838
        'und_Latn_TD' => 'fr_TD',
1839
        'und_Latn_TN' => 'fr_TN',
1840
        'und_MA' => 'ar_MA',
1841
        'und_MC' => 'fr_MC',
1842
        'und_MD' => 'ro_MD',
1843
        'und_ME' => 'sr_ME',
1844
        'und_MF' => 'fr_MF',
1845
        'und_MG' => 'mg_MG',
1846
        'und_MH' => 'mh_MH',
1847
        'und_MK' => 'mk_MK',
1848
        'und_ML' => 'fr_ML',
1849
        'und_MM' => 'my_MM',
1850
        'und_MN' => 'mn_MN',
1851
        'und_MO' => 'zh_MO',
1852
        'und_MQ' => 'fr_MQ',
1853
        'und_MR' => 'ar_MR',
1854
        'und_MT' => 'mt_MT',
1855
        'und_MV' => 'dv_MV',
1856
        'und_MW' => 'ny_MW',
1857
        'und_MX' => 'es_MX',
1858
        'und_MY' => 'ms_MY',
1859
        'und_MZ' => 'pt_MZ',
1860
        'und_Mlym' => 'ml_IN',
1861
        'und_Mong' => 'mn_CN',
1862
        'und_Mymr' => 'my_MM',
1863
        'und_NC' => 'fr_NC',
1864
        'und_NE' => 'ha_NE',
1865
        'und_NG' => 'ha_NG',
1866
        'und_NI' => 'es_NI',
1867
        'und_NL' => 'nl_NL',
1868
        'und_NO' => 'nb_NO',
1869
        'und_NP' => 'ne_NP',
1870
        'und_NR' => 'na_NR',
1871
        'und_NU' => 'niu_NU',
1872
        'und_OM' => 'ar_OM',
1873
        'und_Orya' => 'or_IN',
1874
        'und_PA' => 'es_PA',
1875
        'und_PE' => 'es_PE',
1876
        'und_PF' => 'ty_PF',
1877
        'und_PG' => 'tpi_PG',
1878
        'und_PH' => 'fil_PH',
1879
        'und_PK' => 'ur_PK',
1880
        'und_PL' => 'pl_PL',
1881
        'und_PM' => 'fr_PM',
1882
        'und_PR' => 'es_PR',
1883
        'und_PS' => 'ar_PS',
1884
        'und_PT' => 'pt_PT',
1885
        'und_PW' => 'pau_PW',
1886
        'und_PY' => 'gn_PY',
1887
        'und_QA' => 'ar_QA',
1888
        'und_RE' => 'fr_RE',
1889
        'und_RO' => 'ro_RO',
1890
        'und_RS' => 'sr_RS',
1891
        'und_RU' => 'ru_RU',
1892
        'und_RW' => 'rw_RW',
1893
        'und_SA' => 'ar_SA',
1894
        'und_SD' => 'ar_SD',
1895
        'und_SE' => 'sv_SE',
1896
        'und_SI' => 'sl_SI',
1897
        'und_SJ' => 'nb_SJ',
1898
        'und_SK' => 'sk_SK',
1899
        'und_SM' => 'it_SM',
1900
        'und_SN' => 'fr_SN',
1901
        'und_SO' => 'so_SO',
1902
        'und_SR' => 'nl_SR',
1903
        'und_ST' => 'pt_ST',
1904
        'und_SV' => 'es_SV',
1905
        'und_SY' => 'ar_SY',
1906
        'und_Sinh' => 'si_LK',
1907
        'und_TD' => 'ar_TD',
1908
        'und_TG' => 'ee_TG',
1909
        'und_TH' => 'th_TH',
1910
        'und_TJ' => 'tg_TJ',
1911
        'und_TK' => 'tkl_TK',
1912
        'und_TL' => 'tet_TL',
1913
        'und_TM' => 'tk_TM',
1914
        'und_TN' => 'ar_TN',
1915
        'und_TO' => 'to_TO',
1916
        'und_TR' => 'tr_TR',
1917
        'und_TV' => 'tvl_TV',
1918
        'und_TW' => 'zh_TW',
1919
        'und_Taml' => 'ta_IN',
1920
        'und_Telu' => 'te_IN',
1921
        'und_Thaa' => 'dv_MV',
1922
        'und_Thai' => 'th_TH',
1923
        'und_Tibt' => 'bo_CN',
1924
        'und_UA' => 'uk_UA',
1925
        'und_UY' => 'es_UY',
1926
        'und_UZ' => 'uz_UZ',
1927
        'und_VA' => 'la_VA',
1928
        'und_VE' => 'es_VE',
1929
        'und_VN' => 'vi_VN',
1930
        'und_VU' => 'fr_VU',
1931
        'und_WF' => 'fr_WF',
1932
        'und_WS' => 'sm_WS',
1933
        'und_YE' => 'ar_YE',
1934
        'und_YT' => 'fr_YT',
1935
        'und_ZW' => 'sn_ZW',
1936
        'ur' => 'ur_PK',
1937
        'uz' => 'uz_UZ',
1938
        'uz_AF' => 'uz_AF',
1939
        'uz_Arab' => 'uz_AF',
1940
        've' => 've_ZA',
1941
        'vi' => 'vi_VN',
1942
        'wal' => 'wal_ET',
1943
        'war' => 'war_PH',
1944
        'wo' => 'wo_SN',
1945
        'xh' => 'xh_ZA',
1946
        'yap' => 'yap_FM',
1947
        'yo' => 'yo_NG',
1948
        'za' => 'za_CN',
1949
        'zh' => 'zh_CN',
1950
        'zh_HK' => 'zh_HK',
1951
        'zh_Hani' => 'zh_CN',
1952
        'zh_Hant' => 'zh_TW',
1953
        'zh_MO' => 'zh_MO',
1954
        'zh_TW' => 'zh_TW',
1955
        'zu' => 'zu_ZA',
1956
    );
1957
1958
    /**
1959
     * Map of rails plurals into standard order (fewest to most)
1960
     * Note: Default locale only supplies one|other, but non-default locales
1961
     * can specify custom plurals.
1962
     *
1963
     * @config
1964
     * @var array
1965
     */
1966
    private static $plurals = [
1967
        'zero',
1968
        'one',
1969
        'two',
1970
        'few',
1971
        'many',
1972
        'other',
1973
    ];
1974
1975
    /**
1976
     * Plural forms in default (en) locale
1977
     *
1978
     * @var array
1979
     */
1980
    private static $default_plurals = [
1981
        'one',
1982
        'other',
1983
    ];
1984
1985
    /**
1986
     * Warn if _t() invoked without a default.
1987
     *
1988
     * @config
1989
     * @var bool
1990
     */
1991
    private static $missing_default_warning = true;
1992
1993
    /**
1994
     * This is the main translator function. Returns the string defined by $entity according to the
1995
     * currently set locale.
1996
     *
1997
     * Also supports pluralisation of strings. Pass in a `count` argument, as well as a
1998
     * default value with `|` pipe-delimited options for each plural form.
1999
     *
2000
     * @param string $entity Entity that identifies the string. It must be in the form
2001
     * "Namespace.Entity" where Namespace will be usually the class name where this
2002
     * string is used and Entity identifies the string inside the namespace.
2003
     * @param mixed $arg,... Additional arguments are parsed as such:
0 ignored issues
show
Bug introduced by
There is no parameter named $arg,.... Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
2004
     *  - Next string argument is a default. Pass in a `|` pipe-delimited value with `{count}`
2005
     *    to do pluralisation.
2006
     *  - Any other string argument after default is context for i18nTextCollector
2007
     *  - Any array argument in any order is an injection parameter list. Pass in a `count`
2008
     *    injection parameter to pluralise.
2009
     * @return string
2010
     */
2011
    public static function _t($entity, $arg = null)
0 ignored issues
show
Unused Code introduced by
The parameter $arg is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
2012
    {
2013
        // Detect args
2014
        $default = null;
2015
        $injection = [];
2016
        foreach (array_slice(func_get_args(), 1) as $arg) {
2017
            if (is_array($arg)) {
2018
                $injection = $arg;
2019
            } elseif (!isset($default)) {
2020
                $default = $arg ?: '';
2021
            }
2022
        }
2023
2024
        // Encourage the provision of default values so that text collector can discover new strings
2025
        if (!$default && static::config()->get('missing_default_warning')) {
2026
            user_error("Missing default for localisation key $entity", E_USER_WARNING);
2027
        }
2028
2029
        // Deprecate legacy injection format (`string %s, %d`)
2030
        // inject the variables from injectionArray (if present)
2031
        $sprintfArgs = [];
2032
        if ($default && !preg_match('/\{[\w\d]*\}/i', $default) && preg_match('/%[s,d]/', $default)) {
2033
            Deprecation::notice('5.0', 'sprintf style localisation variables are deprecated');
2034
            $sprintfArgs = array_values($injection);
2035
            $injection = [];
2036
        }
2037
2038
        // If injection isn't associative, assume legacy injection format
2039
        $failUnlessSprintf = false;
2040
        if ($injection && array_values($injection) === $injection) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $injection of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
2041
            $failUnlessSprintf = true; // Note: Will trigger either a deprecation error or exception below
2042
            $sprintfArgs = array_values($injection);
2043
            $injection = [];
2044
        }
2045
2046
        // Detect plurals: Has a {count} argument as well as a `|` pipe delimited string (if provided)
2047
        $isPlural = isset($injection['count']);
2048
        $count = $isPlural ? $injection['count'] : null;
2049
        // Refine check against default
2050
        if ($isPlural && $default && !static::parse_plurals($default)) {
2051
            $isPlural = false;
2052
        }
2053
2054
        // Pass back to translation backend
2055
        if ($isPlural) {
2056
            $result = static::getMessageProvider()->pluralise($entity, $default, $injection, $count);
2057
        } else {
2058
            $result = static::getMessageProvider()->translate($entity, $default, $injection);
2059
        }
2060
2061
        // Sometimes default is omitted, so we don't know we have %s injection format until after translation
2062
        if (!$default && !preg_match('/\{[\w\d]*\}/i', $result) && preg_match('/%[s,d]/', $result)) {
2063
            Deprecation::notice('5.0', 'sprintf style localisation is deprecated');
2064
            if ($injection) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $injection of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
2065
                $sprintfArgs = array_values($injection);
2066
            }
2067
        } elseif ($failUnlessSprintf) {
2068
            // Note: After removing deprecated code, you can move this error up into the is-associative check
2069
            // Neither default nor translated strings were %s substituted, and our array isn't associative
2070
            throw new InvalidArgumentException('Injection must be an associative array');
2071
        }
2072
2073
        // @deprecated (see above)
2074
        if ($sprintfArgs) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $sprintfArgs of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
2075
            return vsprintf($result, $sprintfArgs);
2076
        }
2077
2078
        return $result;
2079
    }
2080
2081
    /**
2082
     * Split plural string into standard CLDR array form.
2083
     * A string is considered a pluralised form if it has a {count} argument, and
2084
     * a single `|` pipe-delimiting character.
2085
     *
2086
     * Note: Only splits in the default (en) locale as the string form contains limited metadata.
2087
     *
2088
     * @param string $string Input string
2089
     * @return array List of plural forms, or empty array if not plural
2090
     */
2091
    public static function parse_plurals($string)
2092
    {
2093
        if (strstr($string, '|') && strstr($string, '{count}')) {
2094
            $keys = i18n::config()->get('default_plurals');
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
2095
            $values = explode('|', $string);
2096
            if (count($keys) == count($values)) {
2097
                return array_combine($keys, $values);
2098
            }
2099
        }
2100
        return [];
2101
    }
2102
2103
    /**
2104
     * Convert CLDR array plural form to `|` pipe-delimited string.
2105
     * Unlike parse_plurals, this supports all locale forms (not just en)
2106
     *
2107
     * @param array $plurals
2108
     * @return string Delimited string, or null if not plurals
2109
     */
2110
    public static function encode_plurals($plurals)
2111
    {
2112
        // Validate against global plural list
2113
        $forms = i18n::config()->get('plurals');
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
2114
        $forms = array_combine($forms, $forms);
2115
        $intersect = array_intersect_key($plurals, $forms);
2116
        if ($intersect) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $intersect of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
2117
            return implode('|', $intersect);
2118
        }
2119
        return null;
2120
    }
2121
2122
    /**
2123
     * Get a list of commonly used languages
2124
     *
2125
     * @param bool $native Use native names for languages instead of English ones
2126
     * @return array list of languages in the form 'code' => 'name'
2127
     */
2128
    public static function get_common_languages($native = false)
2129
    {
2130
        $languages = array();
2131
        foreach (i18n::config()->get('common_languages') as $code => $name) {
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
2132
            $languages[$code] = ($native ? $name['native'] : $name['name']);
2133
        }
2134
        return $languages;
2135
    }
2136
2137
    /**
2138
     * Get a list of commonly used locales
2139
     *
2140
     * @param bool $native Use native names for locale instead of English ones
2141
     * @return array list of languages in the form 'code' => 'name'
2142
     */
2143
    public static function get_common_locales($native = false)
2144
    {
2145
        $languages = array();
2146
        foreach (i18n::config()->get('common_locales') as $code => $name) {
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
2147
            $languages[$code] = ($native ? $name['native'] : $name['name']);
2148
        }
2149
        return $languages;
2150
    }
2151
2152
    /**
2153
     * Matches a given locale with the closest translation available in the system
2154
     *
2155
     * @param string $locale locale code
2156
     * @return string Locale of closest available translation, if available
2157
     */
2158
    public static function get_closest_translation($locale)
2159
    {
2160
        // Check if exact match
2161
        $pool = self::get_existing_translations();
2162
        if (isset($pool[$locale])) {
2163
            return $locale;
2164
        }
2165
2166
        // Fallback to best locale for common language
2167
        $lang = self::get_lang_from_locale($locale);
2168
        $candidate = self::get_locale_from_lang($lang);
2169
        if (isset($pool[$candidate])) {
2170
            return $candidate;
2171
        }
2172
        return null;
2173
    }
2174
2175
    /**
2176
     * Searches the root-directory for module-directories
2177
     * (identified by having a _config.php on their first directory-level).
2178
     * Finds locales by filename convention ("<locale>.<extension>", e.g. "de_AT.yml").
2179
     *
2180
     * @return array
2181
     */
2182
    public static function get_existing_translations()
2183
    {
2184
        $locales = array();
2185
        foreach (static::get_lang_dirs() as $langPath) {
2186
            $allLocales = i18n::config()->get('all_locales');
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
2187
            $langFiles = scandir($langPath);
2188
            foreach ($langFiles as $langFile) {
2189
                $locale = pathinfo($langFile, PATHINFO_FILENAME);
2190
                $ext = pathinfo($langFile, PATHINFO_EXTENSION);
2191
                if ($locale && $ext === 'yml') {
2192
                    // Normalize locale to include likely region tag, avoid repetition in locale labels
2193
                    $fullLocale = self::get_locale_from_lang($locale);
2194
                    if (isset($allLocales[$fullLocale])) {
2195
                        $locales[$fullLocale] = $allLocales[$fullLocale];
2196
                    }
2197
                }
2198
            }
2199
        }
2200
2201
        // sort by title (not locale)
2202
        asort($locales);
2203
2204
        return $locales;
2205
    }
2206
2207
    /**
2208
     * Get a name from a language code (two characters, e.g. "en").
2209
     *
2210
     * @see get_locale_name()
2211
     *
2212
     * @param mixed $code Language code
2213
     * @param boolean $native If true, the native name will be returned
2214
     * @return string Name of the language
2215
     */
2216
    public static function get_language_name($code, $native = false)
2217
    {
2218
        $langs = i18n::config()->get('common_languages');
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
2219
        if ($native) {
2220
            return (isset($langs[$code]['native'])) ? $langs[$code]['native'] : false;
2221
        } else {
2222
            return (isset($langs[$code]['name'])) ? $langs[$code]['name'] : false;
2223
        }
2224
    }
2225
2226
    /**
2227
     * Get a name from a locale code (xx_YY).
2228
     *
2229
     * @see get_language_name()
2230
     *
2231
     * @param string $code locale code
2232
     * @return string Name of the locale
2233
     */
2234
    public static function get_locale_name($code)
2235
    {
2236
        $langs = self::config()->all_locales;
0 ignored issues
show
Documentation introduced by
The property all_locales does not exist on object<SilverStripe\Core\Config\Config_ForClass>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
2237
        return isset($langs[$code]) ? $langs[$code] : false;
2238
    }
2239
2240
    /**
2241
     * Get a code from an English language name
2242
     *
2243
     * @param string $name Name of the language
2244
     * @return string Language code (if the name is not found, it'll return the passed name)
2245
     */
2246
    public static function get_language_code($name)
2247
    {
2248
        $code = array_search($name, self::get_common_languages());
2249
        return ($code ? $code : $name);
2250
    }
2251
2252
    /**
2253
     * Get the current tinyMCE language
2254
     *
2255
     * @return string Language
2256
     */
2257
    public static function get_tinymce_lang()
2258
    {
2259
        $lang = i18n::config()->get('tinymce_lang');
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
2260
        if (isset($lang[self::get_locale()])) {
2261
            return $lang[self::get_locale()];
2262
        }
2263
2264
        return 'en';
2265
    }
2266
2267
    /**
2268
     * Returns the "short" language name from a locale,
2269
     * e.g. "en_US" would return "en".
2270
     *
2271
     * @param string $locale E.g. "en_US"
2272
     * @return string Short language code, e.g. "en"
2273
     */
2274
    public static function get_lang_from_locale($locale)
2275
    {
2276
        return preg_replace('/(_|-).*/', '', $locale);
2277
    }
2278
2279
    /**
2280
     * Provides you "likely locales"
2281
     * for a given "short" language code. This is a guess,
2282
     * as we can't disambiguate from e.g. "en" to "en_US" - it
2283
     * could also mean "en_UK". Based on the Unicode CLDR
2284
     * project.
2285
     * @see http://www.unicode.org/cldr/data/charts/supplemental/likely_subtags.html
2286
     *
2287
     * @param string $lang Short language code, e.g. "en"
2288
     * @return string Long locale, e.g. "en_US"
2289
     */
2290
    public static function get_locale_from_lang($lang)
2291
    {
2292
        $subtags = i18n::config()->get('likely_subtags');
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
2293
        if (preg_match('/\-|_/', $lang)) {
2294
            return str_replace('-', '_', $lang);
2295
        } elseif (isset($subtags[$lang])) {
2296
            return $subtags[$lang];
2297
        } else {
2298
            return $lang . '_' . strtoupper($lang);
2299
        }
2300
    }
2301
2302
    /**
2303
     * Gets a RFC 1766 compatible language code,
2304
     * e.g. "en-US".
2305
     *
2306
     * @see http://www.ietf.org/rfc/rfc1766.txt
2307
     * @see http://tools.ietf.org/html/rfc2616#section-3.10
2308
     *
2309
     * @param string $locale
2310
     * @return string
2311
     */
2312
    public static function convert_rfc1766($locale)
2313
    {
2314
        return str_replace('_', '-', $locale);
2315
    }
2316
2317
    /**
2318
     * Given a PHP class name, finds the module where it's located.
2319
     *
2320
     * @param  string $name
2321
     * @return string
2322
     */
2323
    public static function get_owner_module($name)
2324
    {
2325
        $manifest = ClassLoader::instance()->getManifest();
2326
        $path     = $manifest->getItemPath($name);
2327
2328
        if (!$path) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $path of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
2329
            return false;
2330
        }
2331
2332
        $path = Director::makeRelative($path);
2333
        $path = str_replace('\\', '/', $path);
2334
2335
        $parts = explode('/', trim($path, '/'));
2336
        return array_shift($parts);
2337
    }
2338
2339
    /**
2340
     * Validates a "long" locale format (e.g. "en_US")
2341
     * by checking it against {@link $all_locales}.
2342
     *
2343
     * To add a locale to {@link $all_locales}, use the following example
2344
     * in your mysite/_config.php:
2345
     * <code>
2346
     * i18n::$allowed_locales['xx_XX'] = '<Language name>';
2347
     * </code>
2348
     *
2349
     * Note: Does not check for {@link $allowed_locales}.
2350
     *
2351
     * @param string $locale
2352
     * @return bool
2353
     */
2354
    public static function validate_locale($locale)
2355
    {
2356
        // Convert en-US to en_US
2357
        $locale = str_replace('-', '_', $locale);
2358
        return array_key_exists($locale, i18n::config()->get('all_locales'));
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
2359
    }
2360
2361
    /**
2362
     * Set the current locale, used as the default for
2363
     * any localized classes, such as {@link FormField} or {@link DBField}
2364
     * instances. Locales can also be persisted in {@link Member->Locale},
2365
     * for example in the {@link CMSMain} interface the Member locale
2366
     * overrules the global locale value set here.
2367
     *
2368
     * @param string $locale Locale to be set. See
2369
     *                       http://unicode.org/cldr/data/diff/supplemental/languages_and_territories.html for a list
2370
     *                       of possible locales.
2371
     */
2372
    public static function set_locale($locale)
2373
    {
2374
        if ($locale) {
2375
            self::$current_locale = $locale;
2376
        }
2377
    }
2378
2379
    /**
2380
     * Get the current locale.
2381
     * Used by {@link Member::populateDefaults()}
2382
     *
2383
     * @return string Current locale in the system
2384
     */
2385
    public static function get_locale()
2386
    {
2387
        return self::$current_locale ?: i18n::config()->get('default_locale');
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
2388
    }
2389
2390
    /**
2391
     * Returns the script direction in format compatible with the HTML "dir" attribute.
2392
     *
2393
     * @see http://www.w3.org/International/tutorials/bidi-xhtml/
2394
     * @param string $locale Optional locale incl. region (underscored)
2395
     * @return string "rtl" or "ltr"
2396
     */
2397
    public static function get_script_direction($locale = null)
2398
    {
2399
        $dirs = static::config()->get('text_direction');
2400
        if (!$locale) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $locale of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
2401
            $locale = i18n::get_locale();
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
2402
        }
2403
        if (isset($dirs[$locale])) {
2404
            return $dirs[$locale];
2405
        }
2406
        $lang = static::get_lang_from_locale($locale);
2407
        if (isset($dirs[$lang])) {
2408
            return $dirs[$lang];
2409
        }
2410
        return 'ltr';
2411
    }
2412
2413
    /**
2414
     * Get sorted modules
2415
     *
2416
     * @return array Array of module names -> path
2417
     */
2418
    public static function get_sorted_modules()
2419
    {
2420
        // Get list of module => path pairs, and then just the names
2421
        $modules = ClassLoader::instance()->getManifest()->getModules();
2422
        $moduleNames = array_keys($modules);
2423
2424
        // Remove the "project" module from the list - we'll add it back specially later if needed
2425
        global $project;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
2426
        if (($idx = array_search($project, $moduleNames)) !== false) {
2427
            array_splice($moduleNames, $idx, 1);
2428
        }
2429
2430
        // Get the order from the config syste (lowest to highest)
2431
        $order = i18n::config()->get('module_priority');
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
2432
2433
        // Find all modules that don't have their order specified by the config system
2434
        $unspecified = array_diff($moduleNames, $order);
2435
2436
        // If the placeholder "other_modules" exists in the order array, replace it by the unspecified modules
2437
        if (($idx = array_search('other_modules', $order)) !== false) {
2438
            array_splice($order, $idx, 1, $unspecified);
2439
        } else {
2440
            // Otherwise just jam them on the front
2441
            array_splice($order, 0, 0, $unspecified);
2442
        }
2443
2444
        // Put the project at end (highest priority)
2445
        if (!in_array($project, $order)) {
2446
            $order[] = $project;
2447
        }
2448
2449
        $sortedModules = array();
2450
        foreach ($order as $module) {
2451
            if (isset($modules[$module])) {
2452
                $sortedModules[$module] = $modules[$module];
2453
            }
2454
        }
2455
        $sortedModules = array_reverse($sortedModules, true);
2456
        return $sortedModules;
2457
    }
2458
2459
    /**
2460
     * Find the list of prioritised /lang folders in this application
2461
     *
2462
     * @return array
2463
     */
2464
    public static function get_lang_dirs()
2465
    {
2466
        $paths = [];
2467
2468
        // Search sorted modules
2469
        foreach (static::get_sorted_modules() as $module => $path) {
2470
            $langPath = "{$path}/lang/";
2471
            if (is_dir($langPath)) {
2472
                $paths[] = $langPath;
2473
            }
2474
        }
2475
2476
        // Search theme dirs
2477
        $locator = ThemeResourceLoader::instance();
2478
        foreach (SSViewer::get_themes() as $theme) {
2479
            if ($locator->getSet($theme)) {
2480
                continue;
2481
            }
2482
            $path = $locator->getPath($theme);
2483
            $langPath = "{$path}/lang/";
2484
            if (is_dir($langPath)) {
2485
                $paths[] = $langPath;
2486
            }
2487
        }
2488
2489
        return $paths;
2490
    }
2491
2492
    public static function get_template_global_variables()
2493
    {
2494
        return array(
2495
            'i18nLocale' => 'get_locale',
2496
            'get_locale',
2497
            'i18nScriptDirection' => 'get_script_direction',
2498
        );
2499
    }
2500
2501
    /**
2502
     * @return MessageProvider
2503
     */
2504
    public static function getMessageProvider()
2505
    {
2506
        return Injector::inst()->get(MessageProvider::class);
2507
    }
2508
}
2509