sfCultureInfo.class.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708
  1. <?php
  2. /**
  3. * sfCultureInfo class file.
  4. *
  5. * This program is free software; you can redistribute it and/or modify
  6. * it under the terms of the BSD License.
  7. *
  8. * Copyright(c) 2004 by Qiang Xue. All rights reserved.
  9. *
  10. * To contact the author write to {@link mailto:qiang.xue@gmail.com Qiang Xue}
  11. * The latest version of PRADO can be obtained from:
  12. * {@link http://prado.sourceforge.net/}
  13. *
  14. * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
  15. * @version $Id: sfCultureInfo.class.php 19912 2009-07-06 08:04:41Z FabianLange $
  16. * @package symfony
  17. * @subpackage i18n
  18. */
  19. /**
  20. * sfCultureInfo class.
  21. *
  22. * Represents information about a specific culture including the
  23. * names of the culture, the calendar used, as well as access to
  24. * culture-specific objects that provide methods for common operations,
  25. * such as formatting dates, numbers, and currency.
  26. *
  27. * The sfCultureInfo class holds culture-specific information, such as the
  28. * associated language, sublanguage, country/region, calendar, and cultural
  29. * conventions. This class also provides access to culture-specific
  30. * instances of sfDateTimeFormatInfo and sfNumberFormatInfo. These objects
  31. * contain the information required for culture-specific operations,
  32. * such as formatting dates, numbers and currency.
  33. *
  34. * The culture names follow the format "<languagecode>_<country/regioncode>",
  35. * where <languagecode> is a lowercase two-letter code derived from ISO 639
  36. * codes. You can find a full list of the ISO-639 codes at
  37. * http://www.ics.uci.edu/pub/ietf/http/related/iso639.txt
  38. *
  39. * The <country/regioncode2> is an uppercase two-letter code derived from
  40. * ISO 3166. A copy of ISO-3166 can be found at
  41. * http://www.chemie.fu-berlin.de/diverse/doc/ISO_3166.html
  42. *
  43. * For example, Australian English is "en_AU".
  44. *
  45. * @author Xiang Wei Zhuo <weizhuo[at]gmail[dot]com>
  46. * @version v1.0, last update on Sat Dec 04 13:41:46 EST 2004
  47. * @package symfony
  48. * @subpackage i18n
  49. */
  50. class sfCultureInfo
  51. {
  52. /**
  53. * ICU data filename extension.
  54. * @var string
  55. */
  56. protected $dataFileExt = '.dat';
  57. /**
  58. * The ICU data array.
  59. * @var array
  60. */
  61. protected $data = array();
  62. /**
  63. * The current culture.
  64. * @var string
  65. */
  66. protected $culture;
  67. /**
  68. * Directory where the ICU data is stored.
  69. * @var string
  70. */
  71. protected $dataDir;
  72. /**
  73. * A list of ICU date files loaded.
  74. * @var array
  75. */
  76. protected $dataFiles = array();
  77. /**
  78. * The current date time format info.
  79. * @var sfDateTimeFormatInfo
  80. */
  81. protected $dateTimeFormat;
  82. /**
  83. * The current number format info.
  84. * @var sfNumberFormatInfo
  85. */
  86. protected $numberFormat;
  87. /**
  88. * A list of properties that are accessable/writable.
  89. * @var array
  90. */
  91. protected $properties = array();
  92. /**
  93. * Culture type, all.
  94. * @see getCultures()
  95. * @var int
  96. */
  97. const ALL = 0;
  98. /**
  99. * Culture type, neutral.
  100. * @see getCultures()
  101. * @var int
  102. */
  103. const NEUTRAL = 1;
  104. /**
  105. * Culture type, specific.
  106. *
  107. * @see getCultures()
  108. * @var int
  109. */
  110. const SPECIFIC = 2;
  111. /**
  112. * Gets the sfCultureInfo that for this culture string.
  113. *
  114. * @param string $culture The culture for this instance
  115. * @return sfCultureInfo Invariant culture info is "en"
  116. */
  117. public static function getInstance($culture = 'en')
  118. {
  119. static $instances = array();
  120. if (!isset($instances[$culture]))
  121. {
  122. $instances[$culture] = new sfCultureInfo($culture);
  123. }
  124. return $instances[$culture];
  125. }
  126. /**
  127. * Displays the culture name.
  128. *
  129. * @return string the culture name.
  130. * @see getName()
  131. */
  132. public function __toString()
  133. {
  134. return $this->getName();
  135. }
  136. /**
  137. * Allows functions that begins with 'set' to be called directly
  138. * as an attribute/property to retrieve the value.
  139. *
  140. * @param string $name The property to get
  141. * @return mixed
  142. */
  143. public function __get($name)
  144. {
  145. $getProperty = 'get'.$name;
  146. if (in_array($getProperty, $this->properties))
  147. {
  148. return $this->$getProperty();
  149. }
  150. else
  151. {
  152. throw new sfException(sprintf('Property %s does not exists.', $name));
  153. }
  154. }
  155. /**
  156. * Allows functions that begins with 'set' to be called directly
  157. * as an attribute/property to set the value.
  158. *
  159. * @param string $name The property to set
  160. * @param string $value The property value
  161. */
  162. public function __set($name, $value)
  163. {
  164. $setProperty = 'set'.$name;
  165. if (in_array($setProperty, $this->properties))
  166. {
  167. $this->$setProperty($value);
  168. }
  169. else
  170. {
  171. throw new sfException(sprintf('Property %s can not be set.', $name));
  172. }
  173. }
  174. /**
  175. * Initializes a new instance of the sfCultureInfo class based on the
  176. * culture specified by name. E.g. <code>new sfCultureInfo('en_AU');</code>
  177. * The culture indentifier must be of the form
  178. * "<language>_(country/region/variant)".
  179. *
  180. * @param string $culture a culture name, e.g. "en_AU".
  181. * @return return new sfCultureInfo.
  182. */
  183. public function __construct($culture = 'en')
  184. {
  185. $this->properties = get_class_methods($this);
  186. if (empty($culture))
  187. {
  188. $culture = 'en';
  189. }
  190. $this->dataDir = self::dataDir();
  191. $this->dataFileExt = self::fileExt();
  192. $this->setCulture($culture);
  193. $this->loadCultureData('root');
  194. $this->loadCultureData($culture);
  195. }
  196. /**
  197. * Gets the default directory for the ICU data.
  198. * The default is the "data" directory for this class.
  199. *
  200. * @return string directory containing the ICU data.
  201. */
  202. protected static function dataDir()
  203. {
  204. return dirname(__FILE__).'/data/';
  205. }
  206. /**
  207. * Gets the filename extension for ICU data. Default is ".dat".
  208. *
  209. * @return string filename extension for ICU data.
  210. */
  211. protected static function fileExt()
  212. {
  213. return '.dat';
  214. }
  215. /**
  216. * Determines if a given culture is valid. Simply checks that the
  217. * culture data exists.
  218. *
  219. * @param string $culture a culture
  220. * @return boolean true if valid, false otherwise.
  221. */
  222. static public function validCulture($culture)
  223. {
  224. if (preg_match('/^[a-z]{2}(_[A-Z]{2,5}){0,2}$/', $culture))
  225. {
  226. return is_file(self::dataDir().$culture.self::fileExt());
  227. }
  228. return false;
  229. }
  230. /**
  231. * Sets the culture for the current instance. The culture indentifier
  232. * must be of the form "<language>_(country/region)".
  233. *
  234. * @param string $culture culture identifier, e.g. "fr_FR_EURO".
  235. */
  236. protected function setCulture($culture)
  237. {
  238. if (!empty($culture))
  239. {
  240. if (!preg_match('/^[a-z]{2}(_[A-Z]{2,5}){0,2}$/', $culture))
  241. {
  242. throw new sfException(sprintf('Invalid culture supplied: %s', $culture));
  243. }
  244. }
  245. $this->culture = $culture;
  246. }
  247. /**
  248. * Loads the ICU culture data for the specific culture identifier.
  249. *
  250. * @param string $culture the culture identifier.
  251. */
  252. protected function loadCultureData($culture)
  253. {
  254. $file_parts = explode('_', $culture);
  255. $current_part = $file_parts[0];
  256. $files = array($current_part);
  257. for ($i = 1, $max = count($file_parts); $i < $max; $i++)
  258. {
  259. $current_part .= '_'.$file_parts[$i];
  260. $files[] = $current_part;
  261. }
  262. foreach ($files as $file)
  263. {
  264. $filename = $this->dataDir.$file.$this->dataFileExt;
  265. if (is_file($filename) == false)
  266. {
  267. throw new sfException(sprintf('Data file for "%s" was not found.', $file));
  268. }
  269. if (in_array($filename, $this->dataFiles) == false)
  270. {
  271. array_unshift($this->dataFiles, $file);
  272. $data = &$this->getData($filename);
  273. $this->data[$file] = &$data;
  274. if (isset($data['__ALIAS']))
  275. {
  276. $this->loadCultureData($data['__ALIAS'][0]);
  277. }
  278. unset($data);
  279. }
  280. }
  281. }
  282. /**
  283. * Gets the data by unserializing the ICU data from disk.
  284. * The data files are cached in a static variable inside
  285. * this function.
  286. *
  287. * @param string $filename the ICU data filename
  288. * @return array ICU data
  289. */
  290. protected function &getData($filename)
  291. {
  292. static $data = array();
  293. static $files = array();
  294. if (!in_array($filename, $files))
  295. {
  296. $data[$filename] = unserialize(file_get_contents($filename));
  297. $files[] = $filename;
  298. }
  299. return $data[$filename];
  300. }
  301. /**
  302. * Finds the specific ICU data information from the data.
  303. * The path to the specific ICU data is separated with a slash "/".
  304. * E.g. To find the default calendar used by the culture, the path
  305. * "calendar/default" will return the corresponding default calendar.
  306. * Use merge=true to return the ICU including the parent culture.
  307. * E.g. The currency data for a variant, say "en_AU" contains one
  308. * entry, the currency for AUD, the other currency data are stored
  309. * in the "en" data file. Thus to retrieve all the data regarding
  310. * currency for "en_AU", you need to use findInfo("Currencies,true);.
  311. *
  312. * @param string $path the data you want to find.
  313. * @param boolean $merge merge the data from its parents.
  314. * @return mixed the specific ICU data.
  315. */
  316. protected function findInfo($path = '/', $merge = false)
  317. {
  318. $result = array();
  319. foreach ($this->dataFiles as $section)
  320. {
  321. $info = $this->searchArray($this->data[$section], $path);
  322. if ($info)
  323. {
  324. if ($merge)
  325. {
  326. $result = array_merge($info, $result);
  327. }
  328. else
  329. {
  330. return $info;
  331. }
  332. }
  333. }
  334. return $result;
  335. }
  336. /**
  337. * Searches the array for a specific value using a path separated using
  338. * slash "/" separated path. e.g to find $info['hello']['world'],
  339. * the path "hello/world" will return the corresponding value.
  340. *
  341. * @param array $info the array for search
  342. * @param string $path slash "/" separated array path.
  343. * @return mixed the value array using the path
  344. */
  345. protected function searchArray($info, $path = '/')
  346. {
  347. $index = explode('/', $path);
  348. $array = $info;
  349. for ($i = 0, $max = count($index); $i < $max; $i++)
  350. {
  351. $k = $index[$i];
  352. if ($i < $max - 1 && isset($array[$k]))
  353. {
  354. $array = $array[$k];
  355. }
  356. else if ($i == $max - 1 && isset($array[$k]))
  357. {
  358. return $array[$k];
  359. }
  360. }
  361. }
  362. /**
  363. * Gets the culture name in the format
  364. * "<languagecode2>_(country/regioncode2)".
  365. *
  366. * @return string culture name.
  367. */
  368. public function getName()
  369. {
  370. return $this->culture;
  371. }
  372. /**
  373. * Gets the sfDateTimeFormatInfo that defines the culturally appropriate
  374. * format of displaying dates and times.
  375. *
  376. * @return sfDateTimeFormatInfo date time format information for the culture.
  377. */
  378. public function getDateTimeFormat()
  379. {
  380. if (is_null($this->dateTimeFormat))
  381. {
  382. $calendar = $this->getCalendar();
  383. $info = $this->findInfo("calendar/{$calendar}", true);
  384. $this->setDateTimeFormat(new sfDateTimeFormatInfo($info));
  385. }
  386. return $this->dateTimeFormat;
  387. }
  388. /**
  389. * Sets the date time format information.
  390. *
  391. * @param sfDateTimeFormatInfo $dateTimeFormat the new date time format info.
  392. */
  393. public function setDateTimeFormat($dateTimeFormat)
  394. {
  395. $this->dateTimeFormat = $dateTimeFormat;
  396. }
  397. /**
  398. * Gets the default calendar used by the culture, e.g. "gregorian".
  399. *
  400. * @return string the default calendar.
  401. */
  402. public function getCalendar()
  403. {
  404. $info = $this->findInfo('calendar/default');
  405. return $info[0];
  406. }
  407. /**
  408. * Gets the culture name in the language that the culture is set
  409. * to display. Returns <code>array('Language','Country');</code>
  410. * 'Country' is omitted if the culture is neutral.
  411. *
  412. * @return array array with language and country as elements, localized.
  413. */
  414. public function getNativeName()
  415. {
  416. $lang = substr($this->culture, 0, 2);
  417. $reg = substr($this->culture, 3, 2);
  418. $language = $this->findInfo("Languages/{$lang}");
  419. $region = $this->findInfo("Countries/{$reg}");
  420. if ($region)
  421. {
  422. return $language[0].' ('.$region[0].')';
  423. }
  424. else
  425. {
  426. return $language[0];
  427. }
  428. }
  429. /**
  430. * Gets the culture name in English.
  431. * Returns <code>array('Language','Country');</code>
  432. * 'Country' is omitted if the culture is neutral.
  433. *
  434. * @return array array with language and country as elements.
  435. */
  436. public function getEnglishName()
  437. {
  438. $lang = substr($this->culture, 0, 2);
  439. $reg = substr($this->culture, 3, 2);
  440. $culture = $this->getInvariantCulture();
  441. $language = $culture->findInfo("Languages/{$lang}");
  442. if (count($language) == 0)
  443. {
  444. return $this->culture;
  445. }
  446. $region = $culture->findInfo("Countries/{$reg}");
  447. return $region ? $language[0].' ('.$region[0].')' : $language[0];
  448. }
  449. /**
  450. * Gets the sfCultureInfo that is culture-independent (invariant).
  451. * Any changes to the invariant culture affects all other
  452. * instances of the invariant culture.
  453. * The invariant culture is assumed to be "en";
  454. *
  455. * @return sfCultureInfo invariant culture info is "en".
  456. */
  457. static function getInvariantCulture()
  458. {
  459. static $invariant;
  460. if (is_null($invariant))
  461. {
  462. $invariant = new sfCultureInfo();
  463. }
  464. return $invariant;
  465. }
  466. /**
  467. * Gets a value indicating whether the current sfCultureInfo
  468. * represents a neutral culture. Returns true if the culture
  469. * only contains two characters.
  470. *
  471. * @return boolean true if culture is neutral, false otherwise.
  472. */
  473. public function getIsNeutralCulture()
  474. {
  475. return strlen($this->culture) == 2;
  476. }
  477. /**
  478. * Gets the sfNumberFormatInfo that defines the culturally appropriate
  479. * format of displaying numbers, currency, and percentage.
  480. *
  481. * @return sfNumberFormatInfo the number format info for current culture.
  482. */
  483. public function getNumberFormat()
  484. {
  485. if (is_null($this->numberFormat))
  486. {
  487. $elements = $this->findInfo('NumberElements');
  488. $patterns = $this->findInfo('NumberPatterns');
  489. $currencies = $this->getCurrencies();
  490. $data = array('NumberElements' => $elements, 'NumberPatterns' => $patterns, 'Currencies' => $currencies);
  491. $this->setNumberFormat(new sfNumberFormatInfo($data));
  492. }
  493. return $this->numberFormat;
  494. }
  495. /**
  496. * Sets the number format information.
  497. *
  498. * @param sfNumberFormatInfo $numberFormat the new number format info.
  499. */
  500. public function setNumberFormat($numberFormat)
  501. {
  502. $this->numberFormat = $numberFormat;
  503. }
  504. /**
  505. * Gets the sfCultureInfo that represents the parent culture of the
  506. * current sfCultureInfo
  507. *
  508. * @return sfCultureInfo parent culture information.
  509. */
  510. public function getParent()
  511. {
  512. if (strlen($this->culture) == 2)
  513. {
  514. return $this->getInvariantCulture();
  515. }
  516. return new sfCultureInfo(substr($this->culture, 0, 2));
  517. }
  518. /**
  519. * Gets the list of supported cultures filtered by the specified
  520. * culture type. This is an EXPENSIVE function, it needs to traverse
  521. * a list of ICU files in the data directory.
  522. * This function can be called statically.
  523. *
  524. * @param int $type culture type, sfCultureInfo::ALL, sfCultureInfo::NEUTRAL
  525. * or sfCultureInfo::SPECIFIC.
  526. * @return array list of culture information available.
  527. */
  528. static function getCultures($type = sfCultureInfo::ALL)
  529. {
  530. $dataDir = sfCultureInfo::dataDir();
  531. $dataExt = sfCultureInfo::fileExt();
  532. $dir = dir($dataDir);
  533. $neutral = array();
  534. $specific = array();
  535. while (false !== ($entry = $dir->read()))
  536. {
  537. if (is_file($dataDir.$entry) && substr($entry, -4) == $dataExt && $entry != 'root'.$dataExt)
  538. {
  539. $culture = substr($entry, 0, -4);
  540. if (strlen($culture) == 2)
  541. {
  542. $neutral[] = $culture;
  543. }
  544. else
  545. {
  546. $specific[] = $culture;
  547. }
  548. }
  549. }
  550. $dir->close();
  551. switch ($type)
  552. {
  553. case sfCultureInfo::ALL:
  554. $all = array_merge($neutral, $specific);
  555. sort($all);
  556. return $all;
  557. break;
  558. case sfCultureInfo::NEUTRAL:
  559. return $neutral;
  560. break;
  561. case sfCultureInfo::SPECIFIC:
  562. return $specific;
  563. break;
  564. }
  565. }
  566. /**
  567. * Simplifies a single element array into its own value.
  568. * E.g. <code>array(0 => array('hello'), 1 => 'world');</code>
  569. * becomes <code>array(0 => 'hello', 1 => 'world');</code>
  570. *
  571. * @param array $array with single elements arrays
  572. * @return array simplified array.
  573. */
  574. static protected function simplify($array)
  575. {
  576. foreach ($array as &$item)
  577. {
  578. if (is_array($item) && count($item) == 1)
  579. {
  580. $item = $item[0];
  581. }
  582. }
  583. return $array;
  584. }
  585. /**
  586. * Gets a list of countries in the language of the localized version.
  587. *
  588. * @return array a list of localized country names.
  589. */
  590. public function getCountries()
  591. {
  592. return $this->simplify($this->findInfo('Countries', true));
  593. }
  594. /**
  595. * Gets a list of currencies in the language of the localized version.
  596. *
  597. * @return array a list of localized currencies.
  598. */
  599. public function getCurrencies()
  600. {
  601. return $this->findInfo('Currencies', true);
  602. }
  603. /**
  604. * Gets a list of languages in the language of the localized version.
  605. *
  606. * @return array list of localized language names.
  607. */
  608. public function getLanguages()
  609. {
  610. return $this->simplify($this->findInfo('Languages', true));
  611. }
  612. /**
  613. * Gets a list of scripts in the language of the localized version.
  614. *
  615. * @return array list of localized script names.
  616. */
  617. public function getScripts()
  618. {
  619. return $this->simplify($this->findInfo('Scripts', true));
  620. }
  621. /**
  622. * Gets a list of timezones in the language of the localized version.
  623. *
  624. * @return array list of localized timezones.
  625. */
  626. public function getTimeZones()
  627. {
  628. return $this->simplify($this->findInfo('zoneStrings', true));
  629. }
  630. }