Completed
Pull Request — master (#5304)
by Damian
10:55
created

i18n::pluralise()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 21
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 21
rs 9.3142
cc 3
eloc 14
nc 2
nop 4
1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 65 and the first side effect is on line 2.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

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

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
2049
		$injectionArray = null;
2050
		for($i = 0; $i < $argNum; $i++) {
2051
			if (is_array($argList[$i])) {   //we have reached the injectionArray
2052
				$injectionArray = $argList[$i]; //any array in the args will be the injection array
2053
			}
2054
		}
2055
2056
		// Find best translation
2057
		$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...
2058
		$returnValue = static::with_translators(function(Zend_Translate_Adapter $adapter) use ($entity, $locale) {
2059
			// Return translation only if we found a match thats not the entity itself (Zend fallback)
2060
			$translation = $adapter->translate($entity, $locale);
2061
			if($translation && $translation != $entity) {
2062
				return $translation;
2063
			}
2064
			return null;
2065
		});
2066
2067
		// Fall back to default string argument
2068
		if($returnValue === null) {
2069
			$returnValue = (is_string($string)) ? $string : '';
2070
		}
2071
2072
		// inject the variables from injectionArray (if present)
2073
		if($injectionArray) {
2074
			$regex = '/\{[\w\d]*\}/i';
2075
			if(!preg_match($regex, $returnValue)) {
2076
				// Legacy mode: If no injection placeholders are found,
2077
				// replace sprintf placeholders in fixed order.
2078
				// Fail silently in case the translation is outdated
2079
				preg_match_all('/%[s,d]/', $returnValue, $returnValueArgs);
2080
				if($returnValueArgs) foreach($returnValueArgs[0] as $i => $returnValueArg) {
2081
					if($i >= count($injectionArray)) {
2082
						$injectionArray[] = '';
2083
					}
2084
				}
2085
				$replaced = vsprintf($returnValue, array_values($injectionArray));
2086
				if($replaced) $returnValue = $replaced;
2087
			} else if(!ArrayLib::is_associative($injectionArray)) {
2088
				// Legacy mode: If injection placeholders are found,
2089
				// but parameters are passed without names, replace them in fixed order.
2090
				$returnValue = preg_replace_callback(
2091
					$regex,
2092
					function($matches) use(&$injectionArray) {
0 ignored issues
show
Unused Code introduced by
The parameter $matches 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...
2093
						return $injectionArray ? array_shift($injectionArray) : '';
2094
					},
2095
					$returnValue
2096
				);
2097
			} else {
2098
				// Standard placeholder replacement with named injections and variable order.
2099
				foreach($injectionArray as $variable => $injection) {
2100
					$placeholder = '{'.$variable.'}';
2101
					$returnValue = str_replace($placeholder, $injection, $returnValue, $count);
2102
					if(!$count) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $count of type integer|null is loosely compared to false; this is ambiguous if the integer can be zero. 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 integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
2103
						Injector::inst()->get('Logger')->log('notice', sprintf(
2104
							"Couldn't find placeholder '%s' in translation string '%s' (id: '%s')",
2105
							$placeholder,
2106
							$returnValue,
2107
							$entity
2108
						));
2109
					}
2110
				}
2111
			}
2112
		}
2113
2114
		return $returnValue;
2115
	}
2116
2117
	/**
2118
	 * Pluralise an item or items.
2119
	 *
2120
	 * @param string $singular Singular form
2121
	 * @param string $plural Plural form
2122
	 * @param int $number Number of items (natural number only)
2123
	 * @param bool $prependNumber Include number in result
2124
	 * @return string Result with the number and pluralised form appended. E.g. '1 page'
2125
	 */
2126
	public static function pluralise($singular, $plural, $number, $prependNumber = true) {
2127
		$locale = static::get_locale();
2128
		$form = static::with_translators(
2129
			function(Zend_Translate_Adapter $adapter) use ($singular, $plural, $number, $locale) {
2130
				// Return translation only if we found a match thats not the entity itself (Zend fallback)
2131
				$result = $adapter->plural($singular, $plural, $number, $locale);
2132
				if($result) {
2133
					return $result;
2134
				}
2135
				return null;
2136
			}
2137
		);
2138
		if($prependNumber) {
2139
			return _t('i18n.PLURAL', '{number} {form}', [
2140
				'number' => $number,
2141
				'form' => $form
2142
			]);
2143
		} else {
2144
			return $form;
2145
		}
2146
	}
2147
2148
	/**
2149
	 * Loop over all translators in order of precedence, and return the first non-null value
2150
	 * returned via $callback
2151
	 *
2152
	 * @param callable $callback Callback which is given the translator
2153
	 * @return mixed First non-null result from $callback, or null if none matched
2154
	 */
2155
	protected static function with_translators($callback) {
2156
		// get current locale (either default or user preference)
2157
		$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...
2158
		$lang = i18n::get_lang_from_locale($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...
2159
2160
		// Only call getter if static isn't already defined (for performance reasons)
2161
		$translatorsByPrio = self::$translators ?: self::get_translators();
2162
2163
		foreach($translatorsByPrio as $priority => $translators) {
2164
			/** @var Zend_Translate $translator */
2165
			foreach($translators as $name => $translator) {
2166
				$adapter = $translator->getAdapter();
2167
2168
				// at this point, we need to ensure the language and locale are loaded
2169
				// as include_by_locale() doesn't load a fallback.
2170
2171
				// TODO Remove reliance on global state, by refactoring into an i18nTranslatorManager
2172
				// which is instanciated by core with a $clean instance variable.
2173
2174
				if(!$adapter->isAvailable($lang)) {
2175
					i18n::include_by_locale($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...
2176
				}
2177
2178
				if(!$adapter->isAvailable($locale)) {
2179
					i18n::include_by_locale($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...
2180
				}
2181
2182
				$result = call_user_func($callback, $adapter);
2183
				if($result !== null) {
2184
					return $result;
2185
				}
2186
			}
2187
		}
2188
2189
		// Nothing matched
2190
		return null;
2191
	}
2192
2193
2194
	/**
2195
	 * @return array Array of priority keys to instances of Zend_Translate, mapped by name.
2196
	 */
2197
	public static function get_translators() {
2198
		if(!Zend_Translate::getCache()) {
2199
			Zend_Translate::setCache(self::get_cache());
2200
		}
2201
2202
		if(!self::$translators) {
0 ignored issues
show
Bug Best Practice introduced by
The expression self::$translators 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...
2203
			$defaultPriority = 10;
2204
			self::$translators[$defaultPriority] = array(
2205
				'core' => new Zend_Translate(array(
2206
					'adapter' => 'i18nRailsYamlAdapter',
2207
					'locale' => Config::inst()->get('i18n', 'default_locale'),
2208
					'disableNotices' => true,
2209
				))
2210
			);
2211
2212
			i18n::include_by_locale('en');
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...
2213
			i18n::include_by_locale('en_US');
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...
2214
		}
2215
2216
		return self::$translators;
2217
	}
2218
2219
	/**
2220
	 * @param String
2221
	 * @return Zend_Translate
2222
	 */
2223
	public static function get_translator($name) {
2224
		foreach(self::get_translators() as $priority => $translators) {
2225
			if(isset($translators[$name])) return $translators[$name];
2226
		}
2227
		return false;
2228
	}
2229
2230
	/**
2231
	 * @param Zend_Translate Needs to implement {@link i18nTranslateAdapterInterface}
2232
	 * @param String If left blank will override the default translator.
2233
	 * @param Int
2234
	 */
2235
	public static function register_translator($translator, $name, $priority = 10) {
2236
		if (!is_int($priority)) throw new InvalidArgumentException("register_translator expects an int priority");
2237
2238
		// Ensure it's not there. If it is, we're replacing it. It may exist in a different priority.
2239
		self::unregister_translator($name);
2240
2241
		// Add our new translator
2242
		if(!isset(self::$translators[$priority])) self::$translators[$priority] = array();
2243
		self::$translators[$priority][$name] = $translator;
2244
2245
		// Resort array, ensuring highest priority comes first
2246
		krsort(self::$translators);
2247
2248
		i18n::include_by_locale('en_US');
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...
2249
		i18n::include_by_locale('en');
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...
2250
	}
2251
2252
	/**
2253
	 * @param String
2254
	 */
2255
	public static function unregister_translator($name) {
2256
		foreach (self::get_translators() as $priority => $translators) {
2257
			if (isset($translators[$name])) unset(self::$translators[$priority][$name]);
2258
		}
2259
	}
2260
2261
	/**
2262
	 * Get a list of commonly used languages
2263
	 *
2264
	 * @param boolean $native Use native names for languages instead of English ones
2265
	 * @return list of languages in the form 'code' => 'name'
2266
	 */
2267
	public static function get_common_languages($native = false) {
2268
		$languages = array();
2269
		foreach (Config::inst()->get('i18n', 'common_languages') as $code => $name) {
0 ignored issues
show
Bug introduced by
The expression \Config::inst()->get('i18n', 'common_languages') of type array|integer|double|string|boolean is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
2270
			$languages[$code] = ($native ? $name['native'] : $name['name']);
2271
		}
2272
		return $languages;
2273
	}
2274
2275
	/**
2276
	 * Get a list of commonly used locales
2277
	 *
2278
	 * @param boolean $native Use native names for locale instead of English ones
2279
	 * @return list of languages in the form 'code' => 'name'
2280
	 */
2281
	public static function get_common_locales($native = false) {
2282
		$languages = array();
2283
		foreach (Config::inst()->get('i18n', 'common_locales') as $code => $name) {
0 ignored issues
show
Bug introduced by
The expression \Config::inst()->get('i18n', 'common_locales') of type array|integer|double|string|boolean is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
2284
			$languages[$code] = ($native ? $name['native'] : $name['name']);
2285
		}
2286
		return $languages;
2287
	}
2288
2289
	/**
2290
	 * Get a list of locales (code => language and country)
2291
	 *
2292
	 * @deprecated since version 4.0
2293
	 * @return list of languages in the form 'code' => 'name'
2294
	 */
2295
	public static function get_locale_list() {
2296
		Deprecation::notice('4.0', 'Use the "i18n.all_locales" config setting instead');
2297
		return (array)Config::inst()->get('i18n', 'all_locales');
2298
	}
2299
2300
	/**
2301
	 * Matches a given locale with the closest translation available in the system
2302
	 *
2303
	 * @param string $locale locale code
2304
	 * @return string Locale of closest available translation, if available
2305
	 */
2306
	public static function get_closest_translation($locale) {
2307
2308
		// Check if exact match
2309
		$pool = self::get_existing_translations();
2310
		if(isset($pool[$locale])) return $locale;
2311
2312
		// Fallback to best locale for common language
2313
		$lang = self::get_lang_from_locale($locale);
2314
		$candidate = self::get_locale_from_lang($lang);
2315
		if(isset($pool[$candidate])) return $candidate;
2316
	}
2317
2318
	/**
2319
	 * Searches the root-directory for module-directories
2320
	 * (identified by having a _config.php on their first directory-level).
2321
	 * Finds locales by filename convention ("<locale>.<extension>", e.g. "de_AT.yml").
2322
	 *
2323
	 * @return array
2324
	 */
2325
	public static function get_existing_translations() {
2326
		$locales = array();
2327
2328
		// TODO Inspect themes
2329
		$modules = SS_ClassLoader::instance()->getManifest()->getModules();
2330
2331
		foreach($modules as $module) {
2332
			if(!file_exists("{$module}/lang/")) continue;
2333
2334
			$allLocales = Config::inst()->get('i18n', 'all_locales');
2335
			$moduleLocales = scandir("{$module}/lang/");
2336
			foreach($moduleLocales as $moduleLocale) {
2337
				$locale = pathinfo($moduleLocale, PATHINFO_FILENAME);
2338
				$ext = pathinfo($moduleLocale, PATHINFO_EXTENSION);
2339
				if($locale && in_array($ext, array('php','yml'))) {
2340
					// Normalize locale to include likely region tag, avoid repetition in locale labels
2341
					// TODO Replace with CLDR list of actually available languages/regions
2342
					// Only allow explicitly registered locales, otherwise we'll get into trouble
2343
					// if the locale doesn't exist in Zend's CLDR data
2344
					$fullLocale = self::get_locale_from_lang($locale);
2345
					if(isset($allLocales[$fullLocale])) {
2346
						$locales[$fullLocale] = $allLocales[$fullLocale];
2347
					}
2348
				}
2349
			}
2350
		}
2351
2352
		// sort by title (not locale)
2353
		asort($locales);
2354
2355
		return $locales;
2356
	}
2357
2358
	/**
2359
	 * Get a name from a language code (two characters, e.g. "en").
2360
	 *
2361
	 * @see get_locale_name()
2362
	 *
2363
	 * @param mixed $code Language code
2364
	 * @param boolean $native If true, the native name will be returned
2365
	 * @return Name of the language
2366
	 */
2367
	public static function get_language_name($code, $native = false) {
2368
		$langs = Config::inst()->get('i18n', 'common_languages');
2369
		if($native) {
2370
			return (isset($langs[$code]['native'])) ? $langs[$code]['native'] : false;
2371
		} else {
2372
			return (isset($langs[$code]['name'])) ? $langs[$code]['name'] : false;
2373
		}
2374
	}
2375
2376
	/**
2377
	 * Get a name from a locale code (xx_YY).
2378
	 *
2379
	 * @see get_language_name()
2380
	 *
2381
	 * @param mixed $code locale code
2382
	 * @return Name of the locale
2383
	 */
2384
	public static function get_locale_name($code) {
2385
		$langs = self::config()->all_locales;
0 ignored issues
show
Documentation introduced by
The property all_locales does not exist on object<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...
2386
		return isset($langs[$code]) ? $langs[$code] : false;
2387
	}
2388
2389
	/**
2390
	 * Get a code from an English language name
2391
	 *
2392
	 * @param mixed $name Name of the language
2393
	 * @return Language code (if the name is not found, it'll return the passed name)
2394
	 */
2395
	public static function get_language_code($name) {
2396
		$code = array_search($name,self::get_common_languages());
2397
		return ($code ? $code : $name);
2398
	}
2399
2400
	/**
2401
	 * Get the current tinyMCE language
2402
	 *
2403
	 * @return Language
2404
	 */
2405
	public static function get_tinymce_lang() {
2406
		$lang = Config::inst()->get('i18n', 'tinymce_lang');
2407
		if(isset($lang[self::get_locale()])) {
2408
			return $lang[self::get_locale()];
2409
		}
2410
2411
		return 'en';
2412
	}
2413
2414
	/**
2415
	 * Searches the root-directory for module-directories
2416
	 * (identified by having a _config.php on their first directory-level
2417
	 * and a language-file with the default locale in the /lang-subdirectory).
2418
	 *
2419
	 * @return array
2420
	 */
2421
	public static function get_translatable_modules() {
2422
		$translatableModules = array();
2423
2424
		$baseDir = Director::baseFolder();
2425
		$modules = scandir($baseDir);
2426
		foreach($modules as $module) {
2427
			$moduleDir = $baseDir . DIRECTORY_SEPARATOR . $module;
2428
			if(
2429
				is_dir($moduleDir)
2430
				&& is_file($moduleDir . DIRECTORY_SEPARATOR . "_config.php")
2431
				&& is_file($moduleDir . DIRECTORY_SEPARATOR . "lang" . DIRECTORY_SEPARATOR
2432
					. Config::inst()->get('i18n', 'default_locale') . ".php")
2433
			) {
2434
				$translatableModules[] = $module;
2435
			}
2436
		}
2437
		return $translatableModules;
2438
	}
2439
2440
	/**
2441
	 * Returns the "short" language name from a locale,
2442
	 * e.g. "en_US" would return "en".
2443
	 *
2444
	 * @param string $locale E.g. "en_US"
2445
	 * @return string Short language code, e.g. "en"
2446
	 */
2447
	public static function get_lang_from_locale($locale) {
2448
		return preg_replace('/(_|-).*/', '', $locale);
2449
	}
2450
2451
	/**
2452
	 * Provides you "likely locales"
2453
	 * for a given "short" language code. This is a guess,
2454
	 * as we can't disambiguate from e.g. "en" to "en_US" - it
2455
	 * could also mean "en_UK". Based on the Unicode CLDR
2456
	 * project.
2457
	 * @see http://www.unicode.org/cldr/data/charts/supplemental/likely_subtags.html
2458
	 *
2459
	 * @param string $lang Short language code, e.g. "en"
2460
	 * @return string Long locale, e.g. "en_US"
2461
	 */
2462
	public static function get_locale_from_lang($lang) {
2463
		$subtags = Config::inst()->get('i18n', 'likely_subtags');
2464
		if(preg_match('/\-|_/', $lang)) {
2465
			return str_replace('-', '_', $lang);
2466
		} else if(isset($subtags[$lang])) {
2467
			return $subtags[$lang];
2468
		} else {
2469
			return $lang . '_' . strtoupper($lang);
2470
		}
2471
	}
2472
2473
	/**
2474
	 * Gets a RFC 1766 compatible language code,
2475
	 * e.g. "en-US".
2476
	 *
2477
	 * @see http://www.ietf.org/rfc/rfc1766.txt
2478
	 * @see http://tools.ietf.org/html/rfc2616#section-3.10
2479
	 *
2480
	 * @param string $locale
2481
	 * @return string
2482
	 */
2483
	public static function convert_rfc1766($locale) {
2484
		return str_replace('_','-', $locale);
2485
	}
2486
2487
	/**
2488
	 * Given a PHP class name, finds the module where it's located.
2489
	 *
2490
	 * @param  string $name
2491
	 * @return string
2492
	 */
2493
	public static function get_owner_module($name) {
2494
		$manifest = SS_ClassLoader::instance()->getManifest();
2495
		$path     = $manifest->getItemPath($name);
2496
2497
		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...
2498
			return false;
2499
		}
2500
2501
		$path = Director::makeRelative($path);
2502
		$path = str_replace('\\', '/', $path);
2503
2504
		$parts = explode('/', trim($path, '/'));
2505
		return array_shift($parts);
2506
	}
2507
2508
	/**
2509
	 * Validates a "long" locale format (e.g. "en_US")
2510
	 * by checking it against {@link $all_locales}.
2511
	 *
2512
	 * To add a locale to {@link $all_locales}, use the following example
2513
	 * in your mysite/_config.php:
2514
	 * <code>
2515
	 * i18n::$allowed_locales['xx_XX'] = '<Language name>';
2516
	 * </code>
2517
	 *
2518
	 * Note: Does not check for {@link $allowed_locales}.
2519
	 *
2520
	 * @return boolean
2521
	 */
2522
	public static function validate_locale($locale) {
2523
		// Convert en-US to en_US
2524
		$locale = str_replace('-', '_', $locale);
2525
		return (array_key_exists($locale, Config::inst()->get('i18n', 'all_locales')));
2526
	}
2527
2528
	/**
2529
	 * Set the current locale, used as the default for
2530
	 * any localized classes, such as {@link FormField} or {@link DBField}
2531
	 * instances. Locales can also be persisted in {@link Member->Locale},
2532
	 * for example in the {@link CMSMain} interface the Member locale
2533
	 * overrules the global locale value set here.
2534
	 *
2535
	 * @param string $locale Locale to be set. See
2536
	 *                       http://unicode.org/cldr/data/diff/supplemental/languages_and_territories.html for a list
2537
	 *                       of possible locales.
2538
	 */
2539
	public static function set_locale($locale) {
2540
		if ($locale) self::$current_locale = $locale;
2541
	}
2542
2543
	/**
2544
	 * Get the current locale.
2545
	 * Used by {@link Member::populateDefaults()}
2546
	 *
2547
	 * @return string Current locale in the system
2548
	 */
2549
	public static function get_locale() {
2550
		return (!empty(self::$current_locale)) ? self::$current_locale : Config::inst()->get('i18n', 'default_locale');
2551
	}
2552
2553
	/**
2554
	 * This is the "fallback locale", in case resources with the "current locale"
2555
	 * (set through {@link set_locale()}) can't be found.
2556
	 *
2557
	 * If you just want to globally read/write a different locale (e.g. in a CMS interface),
2558
	 * please use {@link get_locale()} and {@link set_locale()} instead.
2559
	 *
2560
	 * For example, {@link Requirements::add_i18n_javascript()} and {@link i18n::include_by_class()}
2561
	 * use this "fallback locale" value to include fallback language files.
2562
	 *
2563
	 * @deprecated since version 4.0; Use the "i18n.default_locale" config setting instead
2564
	 * @return String
2565
	 */
2566
	public static function default_locale() {
2567
		Deprecation::notice('4.0', 'Use the "i18n.default_locale" config setting instead');
2568
		return Config::inst()->get('i18n', 'default_locale');
2569
	}
2570
2571
	/**
2572
	 * See {@link default_locale()} for usage.
2573
	 *
2574
	 * @deprecated since version 4.0; Use the "i18n.default_locale" config setting instead
2575
	 * @param String $locale
2576
	 */
2577
	public static function set_default_locale($locale) {
2578
		Deprecation::notice('4.0', 'Use the "i18n.default_locale" config setting instead');
2579
		Config::inst()->update('i18n', 'default_locale', $locale);
2580
	}
2581
2582
	/**
2583
	 * Returns the script direction in format compatible with the HTML "dir" attribute.
2584
	 *
2585
	 * @see http://www.w3.org/International/tutorials/bidi-xhtml/
2586
	 * @param String $locale Optional locale incl. region (underscored)
2587
	 * @return String "rtl" or "ltr"
2588
	 */
2589
	public static function get_script_direction($locale = null) {
2590
		require_once 'Zend/Locale/Data.php';
2591
		if(!$locale) $locale = i18n::get_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...
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...
2592
		try {
2593
			$dir = Zend_Locale_Data::getList($locale, 'layout');
2594
		} catch(Zend_Locale_Exception $e) {
2595
			$dir = Zend_Locale_Data::getList(i18n::get_lang_from_locale($locale), 'layout');
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...
2596
		}
2597
2598
		return ($dir && $dir['characters'] == 'right-to-left') ? 'rtl' : 'ltr';
0 ignored issues
show
Bug Best Practice introduced by
The expression $dir 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...
2599
	}
2600
2601
	/**
2602
	 * Includes all available language files for a certain defined locale.
2603
	 *
2604
	 * @param string $locale All resources from any module in locale $locale will be loaded
2605
	 * @param Boolean $clean Clean old caches?
2606
	 */
2607
	public static function include_by_locale($locale, $clean = false) {
2608
		if($clean) {
2609
			self::flush();
2610
		}
2611
2612
		// Get list of module => path pairs, and then just the names
2613
		$modules = SS_ClassLoader::instance()->getManifest()->getModules();
2614
		$moduleNames = array_keys($modules);
2615
2616
		// Remove the "project" module from the list - we'll add it back specially later if needed
2617
		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...
2618
		if (($idx = array_search($project, $moduleNames)) !== false) array_splice($moduleNames, $idx, 1);
2619
2620
		// Get the order from the config syste,
2621
		$order = Config::inst()->get('i18n', 'module_priority');
2622
2623
		// Find all modules that don't have their order specified by the config system
2624
		$unspecified = array_diff($moduleNames, $order);
2625
2626
		// If the placeholder "other_modules" exists in the order array, replace it by the unspecified modules
2627
		if (($idx = array_search('other_modules', $order)) !== false) array_splice($order, $idx,  1, $unspecified);
2628
		// Otherwise just jam them on the front
2629
		else array_splice($order, 0, 0, $unspecified);
2630
2631
		// Put the project module back in at the begining if it wasn't specified by the config system
2632
		if (!in_array($project, $order)) array_unshift($order, $project);
2633
2634
		$sortedModules = array();
2635
		foreach ($order as $module) {
2636
			if (isset($modules[$module])) $sortedModules[$module] = $modules[$module];
2637
		}
2638
		$sortedModules = array_reverse($sortedModules, true);
2639
2640
		// Loop in reverse order, meaning the translator with the highest priority goes first
2641
		$translators = array_reverse(self::get_translators(), true);
2642
		foreach($translators as $priority => $translators) {
2643
			foreach($translators as $name => $translator) {
2644
				$adapter = $translator->getAdapter();
2645
2646
				// Load translations from modules
2647
				foreach($sortedModules as $module) {
2648
					$filename = $adapter->getFilenameForLocale($locale);
2649
					$filepath = "{$module}/lang/" . $filename;
2650
2651
					if($filename && !file_exists($filepath)) continue;
2652
					$adapter->addTranslation(
2653
						array('content' => $filepath, 'locale' => $locale)
2654
					);
2655
				}
2656
2657
				// Load translations from themes
2658
				// TODO Replace with theme listing once implemented in TemplateManifest
2659
				$themesBase = Director::baseFolder() . '/themes';
2660
				if(is_dir($themesBase)) {
2661
					foreach(scandir($themesBase) as $theme) {
2662
						if(
2663
							strpos($theme, Config::inst()->get('SSViewer', 'theme')) === 0
2664
							&& file_exists("{$themesBase}/{$theme}/lang/")
2665
						) {
2666
							$filename = $adapter->getFilenameForLocale($locale);
2667
							$filepath = "{$themesBase}/{$theme}/lang/" . $filename;
2668
							if($filename && !file_exists($filepath)) continue;
2669
							$adapter->addTranslation(
2670
								array('content' => $filepath, 'locale' => $locale)
2671
							);
2672
						}
2673
					}
2674
				}
2675
2676
				// Add empty translations to ensure the locales are "registered" with isAvailable(),
2677
				// and the next invocation of include_by_locale() doesn't cause a new reparse.
2678
				$adapter->addTranslation(
2679
					array(
2680
						// Cached by content hash, so needs to be locale dependent
2681
						'content' => array($locale => $locale),
2682
						'locale' => $locale,
2683
						'usetranslateadapter' => true
2684
					)
2685
				);
2686
			}
2687
		}
2688
	}
2689
2690
	/**
2691
	 * Given a class name (a "locale namespace"), will search for its module and, if available,
2692
	 * will load the resources for the currently defined locale.
2693
	 * If not available, the original English resource will be loaded instead (to avoid blanks)
2694
	 *
2695
	 * @param string $class Resources for this class will be included, according to the set locale.
2696
	 */
2697
	public static function include_by_class($class) {
2698
		$module = self::get_owner_module($class);
2699
2700
		$translators = array_reverse(self::get_translators(), true);
2701
		foreach($translators as $priority => $translators) {
2702
			foreach($translators as $name => $translator) {
2703
				$adapter = $translator->getAdapter();
2704
				$filename = $adapter->getFilenameForLocale(self::get_locale());
2705
				$filepath = "{$module}/lang/" . $filename;
2706
				if($filename && !file_exists($filepath)) continue;
2707
				$adapter->addTranslation(array(
2708
					'content' => $filepath,
2709
					'locale' => self::get_locale()
2710
				));
2711
			}
2712
		}
2713
	}
2714
2715
	public static function get_template_global_variables() {
2716
		return array(
2717
			'i18nLocale' => 'get_locale',
2718
			'get_locale',
2719
			'i18nScriptDirection' => 'get_script_direction',
2720
		);
2721
	}
2722
2723
}
2724