1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* CultureInfo class file. |
5
|
|
|
* |
6
|
|
|
* This program is free software; you can redistribute it and/or modify |
7
|
|
|
* it under the terms of the BSD License. |
8
|
|
|
* |
9
|
|
|
* Copyright(c) 2004 by Qiang Xue. All rights reserved. |
10
|
|
|
* |
11
|
|
|
* To contact the author write to {@link mailto:[email protected] Qiang Xue} |
12
|
|
|
* The latest version of PRADO can be obtained from: |
13
|
|
|
* {@link http://prado.sourceforge.net/} |
14
|
|
|
* |
15
|
|
|
* @author Wei Zhuo <weizhuo[at]gmail[dot]com> |
16
|
|
|
* @author Fabio Bas <ctrlaltca[at]gmail[dot]com> |
17
|
|
|
* @package Prado\I18N\core |
18
|
|
|
*/ |
19
|
|
|
|
20
|
|
|
namespace Prado\I18N\core; |
21
|
|
|
|
22
|
|
|
use Exception; |
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* CultureInfo class. |
26
|
|
|
* |
27
|
|
|
* Represents information about a specific culture including the |
28
|
|
|
* names of the culture, the calendar used, as well as access to |
29
|
|
|
* culture-specific objects that provide methods for common operations, |
30
|
|
|
* such as formatting dates, numbers, and currency. |
31
|
|
|
* |
32
|
|
|
* The CultureInfo class holds culture-specific information, such as the |
33
|
|
|
* associated language, sublanguage, country/region, calendar, and cultural |
34
|
|
|
* conventions. |
35
|
|
|
* |
36
|
|
|
* The culture names follow the format "<languagecode>_<country/regioncode>", |
37
|
|
|
* where <languagecode> is a lowercase two-letter code derived from ISO 639 |
38
|
|
|
* codes. You can find a full list of the ISO-639 codes at |
39
|
|
|
* http://www.ics.uci.edu/pub/ietf/http/related/iso639.txt |
40
|
|
|
* |
41
|
|
|
* The <country/regioncode2> is an uppercase two-letter code derived from |
42
|
|
|
* ISO 3166. A copy of ISO-3166 can be found at |
43
|
|
|
* http://www.chemie.fu-berlin.de/diverse/doc/ISO_3166.html |
44
|
|
|
* |
45
|
|
|
* For example, Australian English is "en_AU". |
46
|
|
|
* |
47
|
|
|
* @author Xiang Wei Zhuo <weizhuo[at]gmail[dot]com> |
48
|
|
|
* @author Fabio Bas <ctrlaltca[at]gmail[dot]com> |
49
|
|
|
* @package Prado\I18N\core |
50
|
|
|
*/ |
51
|
|
|
class CultureInfo |
52
|
|
|
{ |
53
|
|
|
/** |
54
|
|
|
* The ICU data array, shared by all instances of this class. |
55
|
|
|
* @var array |
56
|
|
|
*/ |
57
|
|
|
protected static $data = []; |
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* The current culture. |
61
|
|
|
* @var string |
62
|
|
|
*/ |
63
|
|
|
protected $culture; |
64
|
|
|
|
65
|
|
|
/** |
66
|
|
|
* A list of resource bundles keys |
67
|
|
|
* @var array |
68
|
|
|
*/ |
69
|
|
|
protected static $bundleNames = [ |
70
|
|
|
'Core' => null, |
71
|
|
|
'Currencies' => 'ICUDATA-curr', |
72
|
|
|
'Languages' => 'ICUDATA-lang', |
73
|
|
|
'Countries' => 'ICUDATA-region', |
74
|
|
|
'zoneStrings' => 'ICUDATA-zone', |
75
|
|
|
]; |
76
|
|
|
|
77
|
|
|
/** |
78
|
|
|
* A list of properties that are accessable/writable. |
79
|
|
|
* @var array |
80
|
|
|
*/ |
81
|
|
|
protected $properties = []; |
82
|
|
|
|
83
|
|
|
/** |
84
|
|
|
* Culture type, all. |
85
|
|
|
* @see getCultures() |
86
|
|
|
* @var int |
87
|
|
|
*/ |
88
|
|
|
const ALL = 0; |
89
|
|
|
|
90
|
|
|
/** |
91
|
|
|
* Culture type, neutral. |
92
|
|
|
* @see getCultures() |
93
|
|
|
* @var int |
94
|
|
|
*/ |
95
|
|
|
const NEUTRAL = 1; |
96
|
|
|
|
97
|
|
|
/** |
98
|
|
|
* Culture type, specific. |
99
|
|
|
* @see getCultures() |
100
|
|
|
* @var int |
101
|
|
|
*/ |
102
|
|
|
const SPECIFIC = 2; |
103
|
|
|
|
104
|
|
|
/** |
105
|
|
|
* Display the culture name. |
106
|
|
|
* @return string the culture name. |
107
|
|
|
* @see getName() |
108
|
|
|
*/ |
109
|
|
|
public function __toString() |
110
|
|
|
{ |
111
|
|
|
return $this->getName(); |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
/** |
115
|
|
|
* Allow functions that begins with 'set' to be called directly |
116
|
|
|
* as an attribute/property to retrieve the value. |
117
|
|
|
* @param mixed $name |
118
|
|
|
* @return mixed |
119
|
|
|
*/ |
120
|
|
View Code Duplication |
public function __get($name) |
|
|
|
|
121
|
|
|
{ |
122
|
|
|
$getProperty = 'get' . $name; |
123
|
|
|
if (in_array($getProperty, $this->properties)) { |
124
|
|
|
return $this->$getProperty(); |
125
|
|
|
} else { |
126
|
|
|
throw new Exception('Property ' . $name . ' does not exists.'); |
127
|
|
|
} |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
/** |
131
|
|
|
* Allow functions that begins with 'set' to be called directly |
132
|
|
|
* as an attribute/property to set the value. |
133
|
|
|
* @param mixed $name |
134
|
|
|
* @param mixed $value |
135
|
|
|
*/ |
136
|
|
View Code Duplication |
public function __set($name, $value) |
|
|
|
|
137
|
|
|
{ |
138
|
|
|
$setProperty = 'set' . $name; |
139
|
|
|
if (in_array($setProperty, $this->properties)) { |
140
|
|
|
$this->$setProperty($value); |
141
|
|
|
} else { |
142
|
|
|
throw new Exception('Property ' . $name . ' can not be set.'); |
143
|
|
|
} |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
|
147
|
|
|
/** |
148
|
|
|
* Initializes a new instance of the CultureInfo class based on the |
149
|
|
|
* culture specified by name. E.g. <code>new CultureInfo('en_AU');</cdoe> |
150
|
|
|
* The culture indentifier must be of the form |
151
|
|
|
* "language_(country/region/variant)". |
152
|
|
|
* @param string $culture a culture name, e.g. "en_AU". |
153
|
|
|
* @return return new CultureInfo. |
|
|
|
|
154
|
|
|
*/ |
155
|
|
|
public function __construct($culture = 'en') |
156
|
|
|
{ |
157
|
|
|
$this->properties = get_class_methods($this); |
158
|
|
|
|
159
|
|
|
if (empty($culture)) { |
160
|
|
|
$culture = 'en'; |
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
$this->setCulture($culture); |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
/** |
167
|
|
|
* Gets the CultureInfo that for this culture string |
168
|
|
|
* @param mixed $culture |
169
|
|
|
* @return CultureInfo invariant culture info is "en". |
170
|
|
|
*/ |
171
|
|
|
public static function getInstance($culture) |
172
|
|
|
{ |
173
|
|
|
if (!isset(self::$instances[$culture])) { |
174
|
|
|
self::$instances[$culture] = new CultureInfo($culture); |
175
|
|
|
} |
176
|
|
|
return self::$instances[$culture]; |
177
|
|
|
} |
178
|
|
|
|
179
|
|
|
/** |
180
|
|
|
* Determine if a given culture is valid. Simply checks that the |
181
|
|
|
* culture data exists. |
182
|
|
|
* @param string $culture a culture |
183
|
|
|
* @return bool true if valid, false otherwise. |
184
|
|
|
*/ |
185
|
|
|
public static function validCulture($culture) |
186
|
|
|
{ |
187
|
|
|
return in_array($culture, self::getCultures()); |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
/** |
191
|
|
|
* Set the culture for the current instance. The culture indentifier |
192
|
|
|
* must be of the form "<language>_(country/region)". |
193
|
|
|
* @param string $culture culture identifier, e.g. "fr_FR_EURO". |
194
|
|
|
*/ |
195
|
|
|
protected function setCulture($culture) |
196
|
|
|
{ |
197
|
|
|
if (!empty($culture)) { |
198
|
|
|
if (!preg_match('/^[_\\w]+$/', $culture)) { |
199
|
|
|
throw new Exception('Invalid culture supplied: ' . $culture); |
200
|
|
|
} |
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
$this->culture = $culture; |
204
|
|
|
} |
205
|
|
|
|
206
|
|
|
/** |
207
|
|
|
* Load the ICU culture data for the specific culture identifier. |
208
|
|
|
* @param string $culture the culture identifier. |
|
|
|
|
209
|
|
|
*/ |
210
|
|
|
protected function loadCultureData($key) |
211
|
|
|
{ |
212
|
|
|
foreach(self::$bundleNames as $bundleKey => $bundleName) |
213
|
|
|
{ |
214
|
|
|
if($key == $bundleKey) |
215
|
|
|
{ |
216
|
|
|
if(!array_key_exists($this->culture, self::$data)) |
217
|
|
|
self::$data[$this->culture] = []; |
218
|
|
|
|
219
|
|
|
self::$data[$this->culture][$bundleKey] = \ResourceBundle::create($this->culture, $bundleName, true); |
220
|
|
|
break; |
221
|
|
|
} |
222
|
|
|
} |
223
|
|
|
} |
224
|
|
|
|
225
|
|
|
/** |
226
|
|
|
* Find the specific ICU data information from the data. |
227
|
|
|
* The path to the specific ICU data is separated with a slash "/". |
228
|
|
|
* E.g. To find the default calendar used by the culture, the path |
229
|
|
|
* "calendar/default" will return the corresponding default calendar. |
230
|
|
|
* @param string $path the data you want to find. |
231
|
|
|
* @param string $key bundle name. |
232
|
|
|
* @return mixed the specific ICU data. |
233
|
|
|
*/ |
234
|
|
|
public function findInfo($path='/', $key = null) |
235
|
|
|
{ |
236
|
|
|
if($key === null) |
237
|
|
|
{ |
238
|
|
|
// try to guess the bundle from the path. Always defaults to "Core". |
239
|
|
|
$key = 'Core'; |
240
|
|
|
foreach(self::$bundleNames as $bundleName => $icuBundleName) |
241
|
|
|
{ |
242
|
|
|
if(strpos($path, $bundleName) === 0) |
243
|
|
|
{ |
244
|
|
|
$key = $bundleName; |
245
|
|
|
break; |
246
|
|
|
} |
247
|
|
|
} |
248
|
|
|
} |
249
|
|
|
|
250
|
|
|
if(!array_key_exists($this->culture, self::$data)) |
251
|
|
|
$this->loadCultureData($key); |
252
|
|
|
if(!array_key_exists($this->culture, self::$data) || !array_key_exists($key, self::$data[$this->culture])) |
253
|
|
|
return []; |
254
|
|
|
|
255
|
|
|
return $this->searchResources(self::$data[$this->culture][$key], $path); |
256
|
|
|
} |
257
|
|
|
|
258
|
|
|
/** |
259
|
|
|
* Search the array for a specific value using a path separated using |
260
|
|
|
* slash "/" separated path. e.g to find $info['hello']['world'], |
261
|
|
|
* the path "hello/world" will return the corresponding value. |
262
|
|
|
* @param array $info the array for search |
|
|
|
|
263
|
|
|
* @param string $path slash "/" separated array path. |
264
|
|
|
* @return mixed the value array using the path |
265
|
|
|
*/ |
266
|
|
|
private function searchResources($resource, $path='/') |
267
|
|
|
{ |
268
|
|
|
$index = explode('/', $path); |
269
|
|
|
for($i = 0, $k = count($index); $i < $k; ++$i) |
270
|
|
|
{ |
271
|
|
|
if(is_object($resource)) |
272
|
|
|
$resource = $resource->get($index[$i]); |
273
|
|
|
} |
274
|
|
|
|
275
|
|
|
return $this->simplify($resource); |
276
|
|
|
} |
277
|
|
|
|
278
|
|
|
/** |
279
|
|
|
* Gets the culture name in the format |
280
|
|
|
* "<languagecode2>_(country/regioncode2)". |
281
|
|
|
* @return string culture name. |
282
|
|
|
*/ |
283
|
|
|
public function getName() |
284
|
|
|
{ |
285
|
|
|
return $this->culture; |
286
|
|
|
} |
287
|
|
|
|
288
|
|
|
/** |
289
|
|
|
* Gets the default calendar used by the culture, e.g. "gregorian". |
290
|
|
|
* @return string the default calendar. |
291
|
|
|
*/ |
292
|
|
|
public function getCalendar() |
293
|
|
|
{ |
294
|
|
|
return $this->findInfo('calendar/default'); |
295
|
|
|
} |
296
|
|
|
|
297
|
|
|
/** |
298
|
|
|
* Gets the culture name in the language that the culture is set |
299
|
|
|
* to display. Returns <code>array('Language','Country');</code> |
300
|
|
|
* 'Country' is omitted if the culture is neutral. |
301
|
|
|
* @return array array with language and country as elements, localized. |
302
|
|
|
*/ |
303
|
|
|
public function getNativeName() |
304
|
|
|
{ |
305
|
|
|
$lang = substr($this->culture, 0, 2); |
306
|
|
|
$reg = substr($this->culture, 3, 2); |
307
|
|
|
$language = $this->findInfo("Languages/{$lang}"); |
308
|
|
|
$region = $this->findInfo("Countries/{$reg}"); |
309
|
|
|
if($region) |
|
|
|
|
310
|
|
|
return $language.' ('.$region.')'; |
311
|
|
|
else |
312
|
|
|
return $language; |
313
|
|
|
} |
314
|
|
|
|
315
|
|
|
/** |
316
|
|
|
* Gets the culture name in English. |
317
|
|
|
* Returns <code>array('Language','Country');</code> |
318
|
|
|
* 'Country' is omitted if the culture is neutral. |
319
|
|
|
* @return string language (country), it may locale code string if english name does not exist. |
320
|
|
|
*/ |
321
|
|
|
public function getEnglishName() |
322
|
|
|
{ |
323
|
|
|
$lang = substr($this->culture, 0, 2); |
324
|
|
|
$reg = substr($this->culture, 3, 2); |
325
|
|
|
$culture = $this->getInvariantCulture(); |
326
|
|
|
|
327
|
|
|
$language = $culture->findInfo("Languages/{$lang}"); |
328
|
|
|
if (count($language) == 0) { |
329
|
|
|
return $this->culture; |
330
|
|
|
} |
331
|
|
|
|
332
|
|
|
$region = $culture->findInfo("Countries/{$reg}"); |
333
|
|
|
if($region) |
|
|
|
|
334
|
|
|
return $language.' ('.$region.')'; |
335
|
|
|
else |
336
|
|
|
return $language; |
337
|
|
|
} |
338
|
|
|
|
339
|
|
|
/** |
340
|
|
|
* Gets the CultureInfo that is culture-independent (invariant). |
341
|
|
|
* Any changes to the invariant culture affects all other |
342
|
|
|
* instances of the invariant culture. |
343
|
|
|
* The invariant culture is assumed to be "en"; |
344
|
|
|
* @return CultureInfo invariant culture info is "en". |
345
|
|
|
*/ |
346
|
|
|
public static function getInvariantCulture() |
347
|
|
|
{ |
348
|
|
|
static $invariant; |
349
|
|
|
if ($invariant === null) { |
350
|
|
|
$invariant = new CultureInfo(); |
351
|
|
|
} |
352
|
|
|
return $invariant; |
353
|
|
|
} |
354
|
|
|
|
355
|
|
|
/** |
356
|
|
|
* Gets a value indicating whether the current CultureInfo |
357
|
|
|
* represents a neutral culture. Returns true if the culture |
358
|
|
|
* only contains two characters. |
359
|
|
|
* @return bool true if culture is neutral, false otherwise. |
360
|
|
|
*/ |
361
|
|
|
public function getIsNeutralCulture() |
362
|
|
|
{ |
363
|
|
|
return strlen($this->culture) == 2; |
364
|
|
|
} |
365
|
|
|
|
366
|
|
|
/** |
367
|
|
|
* Gets the list of supported cultures filtered by the specified |
368
|
|
|
* culture type. This is an EXPENSIVE function, it needs to traverse |
369
|
|
|
* a list of ICU files in the data directory. |
370
|
|
|
* This function can be called statically. |
371
|
|
|
* @param int $type culture type, CultureInfo::ALL, CultureInfo::NEUTRAL |
372
|
|
|
* or CultureInfo::SPECIFIC. |
373
|
|
|
* @return array list of culture information available. |
374
|
|
|
*/ |
375
|
|
|
public static function getCultures($type=CultureInfo::ALL) |
376
|
|
|
{ |
377
|
|
|
$all = \ResourceBundle::getLocales(''); |
378
|
|
|
|
379
|
|
|
switch($type) |
380
|
|
|
{ |
381
|
|
|
case CultureInfo::ALL : |
382
|
|
|
return $all; |
383
|
|
View Code Duplication |
case CultureInfo::NEUTRAL : |
|
|
|
|
384
|
|
|
foreach($all as $key => $culture) |
385
|
|
|
{ |
386
|
|
|
if(strlen($culture) != 2) |
387
|
|
|
unset($all[$key]); |
388
|
|
|
} |
389
|
|
|
return $all; |
390
|
|
View Code Duplication |
case CultureInfo::SPECIFIC : |
|
|
|
|
391
|
|
|
foreach($all as $key => $culture) |
392
|
|
|
{ |
393
|
|
|
if(strlen($culture) == 2) |
394
|
|
|
unset($all[$key]); |
395
|
|
|
} |
396
|
|
|
return $all; |
397
|
|
|
} |
398
|
|
|
} |
399
|
|
|
|
400
|
|
|
/** |
401
|
|
|
* Simplify a single element array into its own value. |
402
|
|
|
* E.g. <code>array(0 => array('hello'), 1 => 'world');</code> |
403
|
|
|
* becomes <code>array(0 => 'hello', 1 => 'world');</code> |
404
|
|
|
* @param array $array with single elements arrays |
|
|
|
|
405
|
|
|
* @return array simplified array. |
406
|
|
|
*/ |
407
|
|
|
protected function simplify($obj) |
408
|
|
|
{ |
409
|
|
|
if(is_scalar($obj)) { |
410
|
|
|
return $obj; |
|
|
|
|
411
|
|
|
} elseif($obj instanceof \ResourceBundle) { |
412
|
|
|
$array = []; |
413
|
|
|
foreach($obj as $k => $v) |
|
|
|
|
414
|
|
|
$array[$k] = $v; |
415
|
|
|
} else { |
416
|
|
|
$array = $obj; |
417
|
|
|
} |
418
|
|
|
|
419
|
|
|
for($i = 0, $k = count($array); $i<$k; ++$i) |
420
|
|
|
{ |
421
|
|
|
$key = key($array); |
422
|
|
|
if ($key !== null |
423
|
|
|
&& is_array($array[$key]) |
424
|
|
|
&& count($array[$key]) == 1) { |
425
|
|
|
$array[$key] = $array[$key][0]; |
426
|
|
|
} |
427
|
|
|
next($array); |
428
|
|
|
} |
429
|
|
|
return $array; |
430
|
|
|
} |
431
|
|
|
|
432
|
|
|
/** |
433
|
|
|
* Get a list of countries in the language of the localized version. |
434
|
|
|
* @return array a list of localized country names. |
435
|
|
|
*/ |
436
|
|
|
public function getCountries() |
437
|
|
|
{ |
438
|
|
|
return $this->simplify($this->findInfo('Countries', 'Countries')); |
439
|
|
|
} |
440
|
|
|
|
441
|
|
|
/** |
442
|
|
|
* Get a list of currencies in the language of the localized version. |
443
|
|
|
* @return array a list of localized currencies. |
444
|
|
|
*/ |
445
|
|
|
public function getCurrencies() |
446
|
|
|
{ |
447
|
|
|
static $arr; |
448
|
|
|
if($arr === null) |
449
|
|
|
{ |
450
|
|
|
$arr = $this->findInfo('Currencies', 'Currencies'); |
451
|
|
|
foreach($arr as $k => $v) |
452
|
|
|
$arr[$k] = $this->simplify($v); |
453
|
|
|
} |
454
|
|
|
return $arr; |
455
|
|
|
} |
456
|
|
|
|
457
|
|
|
/** |
458
|
|
|
* Get a list of languages in the language of the localized version. |
459
|
|
|
* @return array list of localized language names. |
460
|
|
|
*/ |
461
|
|
|
public function getLanguages() |
462
|
|
|
{ |
463
|
|
|
return $this->simplify($this->findInfo('Languages', 'Languages')); |
464
|
|
|
} |
465
|
|
|
|
466
|
|
|
/** |
467
|
|
|
* Get a list of scripts in the language of the localized version. |
468
|
|
|
* @return array list of localized script names. |
469
|
|
|
*/ |
470
|
|
|
public function getScripts() |
471
|
|
|
{ |
472
|
|
|
return $this->simplify($this->findInfo('Scripts', 'Languages')); |
473
|
|
|
} |
474
|
|
|
|
475
|
|
|
/** |
476
|
|
|
* Get a list of timezones in the language of the localized version. |
477
|
|
|
* @return array list of localized timezones. |
478
|
|
|
*/ |
479
|
|
|
public function getTimeZones() |
480
|
|
|
{ |
481
|
|
|
static $arr; |
482
|
|
|
if($arr === null) |
483
|
|
|
{ |
484
|
|
|
$validPrefixes = array('Africa', 'America', 'Antarctica', 'Arctic', 'Asia', 'Atlantic', 'Australia', 'Etc', 'Europe', 'Indian', 'Pacific'); |
485
|
|
|
$tmp = $this->findInfo('zoneStrings', 'zoneStrings'); |
486
|
|
|
foreach($tmp as $k => $v) |
487
|
|
|
{ |
488
|
|
|
foreach($validPrefixes as $prefix) |
489
|
|
|
{ |
490
|
|
|
if(strpos($k, $prefix) === 0) |
491
|
|
|
{ |
492
|
|
|
$arr[] = str_replace(':', '/', $k); |
493
|
|
|
break; |
494
|
|
|
} |
495
|
|
|
} |
496
|
|
|
} |
497
|
|
|
} |
498
|
|
|
return $arr; |
499
|
|
|
} |
500
|
|
|
} |
501
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.