1: <?php
2: /**
3: * CLocale class file.
4: *
5: * @author Qiang Xue <qiang.xue@gmail.com>
6: * @link http://www.yiiframework.com/
7: * @copyright 2008-2013 Yii Software LLC
8: * @license http://www.yiiframework.com/license/
9: */
10:
11: /**
12: * CLocale represents the data relevant to a locale.
13: *
14: * The data includes the number formatting information and date formatting information.
15: *
16: * @property string $id The locale ID (in canonical form).
17: * @property CNumberFormatter $numberFormatter The number formatter for this locale.
18: * @property CDateFormatter $dateFormatter The date formatter for this locale.
19: * @property string $decimalFormat The decimal format.
20: * @property string $currencyFormat The currency format.
21: * @property string $percentFormat The percent format.
22: * @property string $scientificFormat The scientific format.
23: * @property array $monthNames Month names indexed by month values (1-12).
24: * @property array $weekDayNames The weekday names indexed by weekday values (0-6, 0 means Sunday, 1 Monday, etc.).
25: * @property string $aMName The AM name.
26: * @property string $pMName The PM name.
27: * @property string $dateFormat Date format.
28: * @property string $timeFormat Date format.
29: * @property string $dateTimeFormat Datetime format, i.e., the order of date and time.
30: * @property string $orientation The character orientation, which is either 'ltr' (left-to-right) or 'rtl' (right-to-left).
31: * @property array $pluralRules Plural forms expressions.
32: *
33: * @author Qiang Xue <qiang.xue@gmail.com>
34: * @package system.i18n
35: * @since 1.0
36: */
37: class CLocale extends CComponent
38: {
39: /**
40: * @var string the directory that contains the locale data. If this property is not set,
41: * the locale data will be loaded from 'framework/i18n/data'.
42: * @since 1.1.0
43: */
44: public static $dataPath;
45:
46: private $_id;
47: private $_data;
48: private $_dateFormatter;
49: private $_numberFormatter;
50:
51: /**
52: * Returns the instance of the specified locale.
53: * Since the constructor of CLocale is protected, you can only use
54: * this method to obtain an instance of the specified locale.
55: * @param string $id the locale ID (e.g. en_US)
56: * @return CLocale the locale instance
57: */
58: public static function getInstance($id)
59: {
60: static $locales=array();
61: if(isset($locales[$id]))
62: return $locales[$id];
63: else
64: return $locales[$id]=new CLocale($id);
65: }
66:
67: /**
68: * @return array IDs of the locales which the framework can recognize
69: */
70: public static function getLocaleIDs()
71: {
72: static $locales;
73: if($locales===null)
74: {
75: $locales=array();
76: $dataPath=self::$dataPath===null ? dirname(__FILE__).DIRECTORY_SEPARATOR.'data' : self::$dataPath;
77: $folder=@opendir($dataPath);
78: while(($file=@readdir($folder))!==false)
79: {
80: $fullPath=$dataPath.DIRECTORY_SEPARATOR.$file;
81: if(substr($file,-4)==='.php' && is_file($fullPath))
82: $locales[]=substr($file,0,-4);
83: }
84: closedir($folder);
85: sort($locales);
86: }
87: return $locales;
88: }
89:
90: /**
91: * Constructor.
92: * Since the constructor is protected, please use {@link getInstance}
93: * to obtain an instance of the specified locale.
94: * @param string $id the locale ID (e.g. en_US)
95: * @throws CException if given locale id is not recognized
96: */
97: protected function __construct($id)
98: {
99: $this->_id=self::getCanonicalID($id);
100: $dataPath=self::$dataPath===null ? dirname(__FILE__).DIRECTORY_SEPARATOR.'data' : self::$dataPath;
101: $dataFile=$dataPath.DIRECTORY_SEPARATOR.$this->_id.'.php';
102: if(is_file($dataFile))
103: $this->_data=require($dataFile);
104: else
105: throw new CException(Yii::t('yii','Unrecognized locale "{locale}".',array('{locale}'=>$id)));
106: }
107:
108: /**
109: * Converts a locale ID to its canonical form.
110: * In canonical form, a locale ID consists of only underscores and lower-case letters.
111: * @param string $id the locale ID to be converted
112: * @return string the locale ID in canonical form
113: */
114: public static function getCanonicalID($id)
115: {
116: return strtolower(str_replace('-','_',$id));
117: }
118:
119: /**
120: * @return string the locale ID (in canonical form)
121: */
122: public function getId()
123: {
124: return $this->_id;
125: }
126:
127: /**
128: * @return CNumberFormatter the number formatter for this locale
129: */
130: public function getNumberFormatter()
131: {
132: if($this->_numberFormatter===null)
133: $this->_numberFormatter=new CNumberFormatter($this);
134: return $this->_numberFormatter;
135: }
136:
137: /**
138: * @return CDateFormatter the date formatter for this locale
139: */
140: public function getDateFormatter()
141: {
142: if($this->_dateFormatter===null)
143: $this->_dateFormatter=new CDateFormatter($this);
144: return $this->_dateFormatter;
145: }
146:
147: /**
148: * @param string $currency 3-letter ISO 4217 code. For example, the code "USD" represents the US Dollar and "EUR" represents the Euro currency.
149: * @return string the localized currency symbol. Null if the symbol does not exist.
150: */
151: public function getCurrencySymbol($currency)
152: {
153: return isset($this->_data['currencySymbols'][$currency]) ? $this->_data['currencySymbols'][$currency] : null;
154: }
155:
156: /**
157: * @param string $name symbol name
158: * @return string symbol
159: */
160: public function getNumberSymbol($name)
161: {
162: return isset($this->_data['numberSymbols'][$name]) ? $this->_data['numberSymbols'][$name] : null;
163: }
164:
165: /**
166: * @return string the decimal format
167: */
168: public function getDecimalFormat()
169: {
170: return $this->_data['decimalFormat'];
171: }
172:
173: /**
174: * @return string the currency format
175: */
176: public function getCurrencyFormat()
177: {
178: return $this->_data['currencyFormat'];
179: }
180:
181: /**
182: * @return string the percent format
183: */
184: public function getPercentFormat()
185: {
186: return $this->_data['percentFormat'];
187: }
188:
189: /**
190: * @return string the scientific format
191: */
192: public function getScientificFormat()
193: {
194: return $this->_data['scientificFormat'];
195: }
196:
197: /**
198: * @param integer $month month (1-12)
199: * @param string $width month name width. It can be 'wide', 'abbreviated' or 'narrow'.
200: * @param boolean $standAlone whether the month name should be returned in stand-alone format
201: * @return string the month name
202: */
203: public function getMonthName($month,$width='wide',$standAlone=false)
204: {
205: if($standAlone)
206: return isset($this->_data['monthNamesSA'][$width][$month]) ? $this->_data['monthNamesSA'][$width][$month] : $this->_data['monthNames'][$width][$month];
207: else
208: return isset($this->_data['monthNames'][$width][$month]) ? $this->_data['monthNames'][$width][$month] : $this->_data['monthNamesSA'][$width][$month];
209: }
210:
211: /**
212: * Returns the month names in the specified width.
213: * @param string $width month name width. It can be 'wide', 'abbreviated' or 'narrow'.
214: * @param boolean $standAlone whether the month names should be returned in stand-alone format
215: * @return array month names indexed by month values (1-12)
216: */
217: public function getMonthNames($width='wide',$standAlone=false)
218: {
219: if($standAlone)
220: return isset($this->_data['monthNamesSA'][$width]) ? $this->_data['monthNamesSA'][$width] : $this->_data['monthNames'][$width];
221: else
222: return isset($this->_data['monthNames'][$width]) ? $this->_data['monthNames'][$width] : $this->_data['monthNamesSA'][$width];
223: }
224:
225: /**
226: * @param integer $day weekday (0-7, 0 and 7 means Sunday)
227: * @param string $width weekday name width. It can be 'wide', 'abbreviated' or 'narrow'.
228: * @param boolean $standAlone whether the week day name should be returned in stand-alone format
229: * @return string the weekday name
230: */
231: public function getWeekDayName($day,$width='wide',$standAlone=false)
232: {
233: $day=$day%7;
234: if($standAlone)
235: return isset($this->_data['weekDayNamesSA'][$width][$day]) ? $this->_data['weekDayNamesSA'][$width][$day] : $this->_data['weekDayNames'][$width][$day];
236: else
237: return isset($this->_data['weekDayNames'][$width][$day]) ? $this->_data['weekDayNames'][$width][$day] : $this->_data['weekDayNamesSA'][$width][$day];
238: }
239:
240: /**
241: * Returns the week day names in the specified width.
242: * @param string $width weekday name width. It can be 'wide', 'abbreviated' or 'narrow'.
243: * @param boolean $standAlone whether the week day name should be returned in stand-alone format
244: * @return array the weekday names indexed by weekday values (0-6, 0 means Sunday, 1 Monday, etc.)
245: */
246: public function getWeekDayNames($width='wide',$standAlone=false)
247: {
248: if($standAlone)
249: return isset($this->_data['weekDayNamesSA'][$width]) ? $this->_data['weekDayNamesSA'][$width] : $this->_data['weekDayNames'][$width];
250: else
251: return isset($this->_data['weekDayNames'][$width]) ? $this->_data['weekDayNames'][$width] : $this->_data['weekDayNamesSA'][$width];
252: }
253:
254: /**
255: * @param integer $era era (0,1)
256: * @param string $width era name width. It can be 'wide', 'abbreviated' or 'narrow'.
257: * @return string the era name
258: */
259: public function getEraName($era,$width='wide')
260: {
261: return $this->_data['eraNames'][$width][$era];
262: }
263:
264: /**
265: * @return string the AM name
266: */
267: public function getAMName()
268: {
269: return $this->_data['amName'];
270: }
271:
272: /**
273: * @return string the PM name
274: */
275: public function getPMName()
276: {
277: return $this->_data['pmName'];
278: }
279:
280: /**
281: * @param string $width date format width. It can be 'full', 'long', 'medium' or 'short'.
282: * @return string date format
283: */
284: public function getDateFormat($width='medium')
285: {
286: return $this->_data['dateFormats'][$width];
287: }
288:
289: /**
290: * @param string $width time format width. It can be 'full', 'long', 'medium' or 'short'.
291: * @return string date format
292: */
293: public function getTimeFormat($width='medium')
294: {
295: return $this->_data['timeFormats'][$width];
296: }
297:
298: /**
299: * @return string datetime format, i.e., the order of date and time.
300: */
301: public function getDateTimeFormat()
302: {
303: return $this->_data['dateTimeFormat'];
304: }
305:
306: /**
307: * @return string the character orientation, which is either 'ltr' (left-to-right) or 'rtl' (right-to-left)
308: * @since 1.1.2
309: */
310: public function getOrientation()
311: {
312: return isset($this->_data['orientation']) ? $this->_data['orientation'] : 'ltr';
313: }
314:
315: /**
316: * @return array plural forms expressions
317: */
318: public function getPluralRules()
319: {
320: return isset($this->_data['pluralRules']) ? $this->_data['pluralRules'] : array(0=>'true');
321: }
322:
323: /**
324: * Converts a locale ID to a language ID.
325: * A language ID consists of only the first group of letters before an underscore or dash.
326: * @param string $id the locale ID to be converted
327: * @return string the language ID
328: * @since 1.1.9
329: */
330: public function getLanguageID($id)
331: {
332: // normalize id
333: $id = $this->getCanonicalID($id);
334: // remove sub tags
335: if(($underscorePosition=strpos($id, '_'))!== false)
336: {
337: $id = substr($id, 0, $underscorePosition);
338: }
339: return $id;
340: }
341:
342: /**
343: * Converts a locale ID to a script ID.
344: * A script ID consists of only the last four characters after an underscore or dash.
345: * @param string $id the locale ID to be converted
346: * @return string the script ID
347: * @since 1.1.9
348: */
349: public function getScriptID($id)
350: {
351: // normalize id
352: $id = $this->getCanonicalID($id);
353: // find sub tags
354: if(($underscorePosition=strpos($id, '_'))!==false)
355: {
356: $subTag = explode('_', $id);
357: // script sub tags can be distinguished from territory sub tags by length
358: if (strlen($subTag[1])===4)
359: {
360: $id = $subTag[1];
361: }
362: else
363: {
364: $id = null;
365: }
366: }
367: else
368: {
369: $id = null;
370: }
371: return $id;
372: }
373:
374: /**
375: * Converts a locale ID to a territory ID.
376: * A territory ID consists of only the last two to three letter or digits after an underscore or dash.
377: * @param string $id the locale ID to be converted
378: * @return string the territory ID
379: * @since 1.1.9
380: */
381: public function getTerritoryID($id)
382: {
383: // normalize id
384: $id = $this->getCanonicalID($id);
385: // find sub tags
386: if (($underscorePosition=strpos($id, '_'))!== false)
387: {
388: $subTag = explode('_', $id);
389: // territory sub tags can be distinguished from script sub tags by length
390: if (isset($subTag[2]) && strlen($subTag[2])<4)
391: {
392: $id = $subTag[2];
393: }
394: elseif (strlen($subTag[1])<4)
395: {
396: $id = $subTag[1];
397: }
398: else
399: {
400: $id = null;
401: }
402: }
403: else
404: {
405: $id = null;
406: }
407: return $id;
408: }
409:
410: /**
411: * Gets a localized name from i18n data file (one of framework/i18n/data/ files).
412: *
413: * @param string $id array key from an array named by $category.
414: * @param string $category data category. One of 'languages', 'scripts' or 'territories'.
415: * @return string the localized name for the id specified. Null if data does not exist.
416: * @since 1.1.9
417: */
418: public function getLocaleDisplayName($id=null, $category='languages')
419: {
420: $id = $this->getCanonicalID($id);
421: if (($category == 'languages') && (isset($this->_data[$category][$id])))
422: {
423: return $this->_data[$category][$id];
424: }
425: elseif (($category == 'scripts') && ($val=$this->getScriptID($id)) && (isset($this->_data[$category][$val])))
426: {
427: return $this->_data[$category][$val];
428: }
429: elseif (($category == 'territories') && ($val=$this->getTerritoryID($id)) && (isset($this->_data[$category][$val])))
430: {
431: return $this->_data[$category][$val];
432: }
433: elseif (isset($this->_data[$category][$id]))
434: {
435: return $this->_data[$category][$id];
436: }
437: else {
438: return null;
439: }
440: }
441:
442: /**
443: * @param string $id Unicode language identifier from IETF BCP 47. For example, the code "en_US" represents U.S. English and "en_GB" represents British English.
444: * @return string the local display name for the language. Null if the language code does not exist.
445: * @since 1.1.9
446: */
447: public function getLanguage($id)
448: {
449: $id = $this->getLanguageID($id);
450: return $this->getLocaleDisplayName($id, 'languages');
451: }
452:
453: /**
454: * @param string $id Unicode script identifier from IETF BCP 47. For example, the code "en_US" represents U.S. English and "en_GB" represents British English.
455: * @return string the local display name for the script. Null if the script code does not exist.
456: * @since 1.1.9
457: */
458: public function getScript($id)
459: {
460: return $this->getLocaleDisplayName($id, 'scripts');
461: }
462:
463: /**
464: * @param string $id Unicode territory identifier from IETF BCP 47. For example, the code "en_US" represents U.S. English and "en_GB" represents British English.
465: * @return string the local display name for the territory. Null if the territory code does not exist.
466: * @since 1.1.9
467: */
468: public function getTerritory($id)
469: {
470: return $this->getLocaleDisplayName($id, 'territories');
471: }
472: }
473: