update-data.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. function bailout($message)
  11. {
  12. exit($message."\n");
  13. }
  14. function check_dir($source)
  15. {
  16. if (!file_exists($source)) {
  17. bailout('The directory '.$source.' does not exist');
  18. }
  19. if (!is_dir($source)) {
  20. bailout('The file '.$source.' is not a directory');
  21. }
  22. }
  23. function check_command($command)
  24. {
  25. exec('which '.$command, $output, $result);
  26. if ($result !== 0) {
  27. bailout('The command "'.$command.'" is not installed');
  28. }
  29. }
  30. function clear_directory($directory)
  31. {
  32. $iterator = new \DirectoryIterator($directory);
  33. foreach ($iterator as $file) {
  34. if (!$file->isDot()) {
  35. if ($file->isDir()) {
  36. clear_directory($file->getPathname());
  37. } else {
  38. unlink($file->getPathname());
  39. }
  40. }
  41. }
  42. }
  43. function make_directory($directory)
  44. {
  45. if (!file_exists($directory)) {
  46. mkdir($directory);
  47. }
  48. if (!is_dir($directory)) {
  49. bailout('The file '.$directory.' already exists but is no directory');
  50. }
  51. }
  52. function list_files($directory, $extension)
  53. {
  54. $files = array();
  55. $iterator = new \DirectoryIterator($directory);
  56. foreach ($iterator as $file) {
  57. if (!$file->isDot() && substr($file->getFilename(), -strlen($extension)) === $extension) {
  58. $files[] = substr($file->getFilename(), 0, -strlen($extension));
  59. }
  60. }
  61. return $files;
  62. }
  63. function genrb($source, $target)
  64. {
  65. exec('genrb -d '.$target.' '.$source.DIRECTORY_SEPARATOR.'*.txt', $output, $result);
  66. if ($result !== 0) {
  67. bailout('genrb failed');
  68. }
  69. }
  70. function genrb_file($target, $source, $locale)
  71. {
  72. exec('genrb -v -d '.$target.' '.$source.DIRECTORY_SEPARATOR.$locale.'.txt', $output, $result);
  73. if ($result !== 0) {
  74. bailout('genrb failed');
  75. }
  76. }
  77. function load_resource_bundle($locale, $directory)
  78. {
  79. $bundle = new \ResourceBundle($locale, $directory);
  80. if (null === $bundle) {
  81. bailout('The resource bundle for locale '.$locale.' could not be loaded from directory '.$directory);
  82. }
  83. return $bundle;
  84. }
  85. function get_data($index, $dataDir, $locale = 'en', $constraint = null)
  86. {
  87. $data = array();
  88. $bundle = load_resource_bundle($locale, __DIR__.DIRECTORY_SEPARATOR.$dataDir);
  89. foreach ($bundle->get($index) as $code => $name) {
  90. if (null !== $constraint) {
  91. if ($constraint($code)) {
  92. $data[$code] = $name;
  93. }
  94. continue;
  95. }
  96. $data[$code] = $name;
  97. }
  98. $collator = new \Collator($locale);
  99. $collator->asort($data);
  100. return $data;
  101. }
  102. function create_stub_datafile($locale, $target, $data) {
  103. $template = <<<TEMPLATE
  104. <?php
  105. /*
  106. * This file is part of the Symfony package.
  107. *
  108. * (c) Fabien Potencier <fabien@symfony.com>
  109. *
  110. * For the full copyright and license information, please view the LICENSE
  111. * file that was distributed with this source code.
  112. */
  113. return %s;
  114. TEMPLATE;
  115. $data = var_export($data, true);
  116. $data = preg_replace('/array \(/', 'array(', $data);
  117. $data = preg_replace('/\n {1,10}array\(/', 'array(', $data);
  118. $data = preg_replace('/ /', ' ', $data);
  119. $data = sprintf($template, $data);
  120. file_put_contents($target.DIRECTORY_SEPARATOR.$locale.'.php', $data);
  121. }
  122. if ($GLOBALS['argc'] !== 2) {
  123. bailout(<<<MESSAGE
  124. Usage: php update-data.php [icu-data-directory]
  125. Updates the ICU resources in Symfony2 from the given ICU data directory. You
  126. can checkout the ICU data directory via SVN:
  127. $ svn co http://source.icu-project.org/repos/icu/icu/trunk/source/data icu-data
  128. MESSAGE
  129. );
  130. }
  131. // Verify that all required directories exist
  132. $source = $GLOBALS['argv'][1];
  133. check_dir($source);
  134. $source = realpath($source);
  135. check_dir($source.DIRECTORY_SEPARATOR.'curr');
  136. check_dir($source.DIRECTORY_SEPARATOR.'lang');
  137. check_dir($source.DIRECTORY_SEPARATOR.'locales');
  138. check_dir($source.DIRECTORY_SEPARATOR.'region');
  139. check_command('genrb');
  140. // Convert the *.txt resource bundles to *.res files
  141. $target = __DIR__;
  142. $currDir = $target.DIRECTORY_SEPARATOR.'curr';
  143. $langDir = $target.DIRECTORY_SEPARATOR.'lang';
  144. $localesDir = $target.DIRECTORY_SEPARATOR.'locales';
  145. $namesDir = $target.DIRECTORY_SEPARATOR.'names';
  146. $namesGeneratedDir = $namesDir.DIRECTORY_SEPARATOR.'generated';
  147. $regionDir = $target.DIRECTORY_SEPARATOR.'region';
  148. make_directory($currDir);
  149. clear_directory($currDir);
  150. genrb_file($currDir, $source.DIRECTORY_SEPARATOR.'curr', 'en');
  151. genrb_file($currDir, $source.DIRECTORY_SEPARATOR.'curr', 'supplementalData');
  152. // It seems \ResourceBundle does not like locale names with uppercase chars then we rename the binary file
  153. // See: http://bugs.php.net/bug.php?id=54025
  154. $filename_from = $currDir.DIRECTORY_SEPARATOR.'supplementalData.res';
  155. $filename_to = $currDir.DIRECTORY_SEPARATOR.'supplementaldata.res';
  156. if (!rename($filename_from, $filename_to)) {
  157. bailout('The file '.$filename_from.' could not be renamed');
  158. }
  159. make_directory($langDir);
  160. clear_directory($langDir);
  161. genrb($source.DIRECTORY_SEPARATOR.'lang', $langDir);
  162. make_directory($localesDir);
  163. clear_directory($localesDir);
  164. genrb($source.DIRECTORY_SEPARATOR.'locales', $localesDir);
  165. make_directory($regionDir);
  166. clear_directory($regionDir);
  167. genrb($source.DIRECTORY_SEPARATOR.'region', $regionDir);
  168. make_directory($namesDir);
  169. clear_directory($namesDir);
  170. make_directory($namesGeneratedDir);
  171. clear_directory($namesGeneratedDir);
  172. // Discover the list of supported locales, which are the names of the resource
  173. // bundles in the "locales" directory
  174. $supportedLocales = list_files($localesDir, '.res');
  175. sort($supportedLocales);
  176. // Delete unneeded locales
  177. foreach ($supportedLocales as $key => $supportedLocale) {
  178. // Delete all aliases from the list
  179. // i.e., "az_AZ" is an alias for "az_Latn_AZ"
  180. $localeBundleOrig = file_get_contents($source.DIRECTORY_SEPARATOR.'locales'.DIRECTORY_SEPARATOR.$supportedLocale.'.txt');
  181. // The key "%%ALIAS" is not accessible through the \ResourceBundle class
  182. if (strpos($localeBundleOrig, '%%ALIAS') !== false) {
  183. unset($supportedLocales[$key]);
  184. }
  185. // Delete locales that have no content (i.e. only "Version" key)
  186. $localeBundle = load_resource_bundle($supportedLocale, $localesDir);
  187. // There seems to be no other way for identifying all keys in this specific
  188. // resource bundle
  189. $bundleKeys = array();
  190. foreach ($localeBundle as $bundleKey => $_) {
  191. $bundleKeys[] = $bundleKey;
  192. }
  193. if ($bundleKeys === array('Version')) {
  194. unset($supportedLocales[$key]);
  195. }
  196. }
  197. // Discover the list of locales for which individual language/region names
  198. // exist. This list contains for example "de" and "de_CH", but not "de_DE" which
  199. // is equal to "de"
  200. $translatedLocales = array_unique(array_merge(
  201. list_files($langDir, '.res'),
  202. list_files($regionDir, '.res')
  203. ));
  204. sort($translatedLocales);
  205. // For each translated locale, generate a list of locale names
  206. // Each locale name has the form: "Language (Script, Region, Variant1, ...)
  207. // Script, Region and Variants are optional. If none of them is available,
  208. // the braces are not printed.
  209. foreach ($translatedLocales as $translatedLocale) {
  210. // Don't include ICU's root resource bundle
  211. if ($translatedLocale === 'root') {
  212. continue;
  213. }
  214. $langBundle = load_resource_bundle($translatedLocale, $langDir);
  215. $regionBundle = load_resource_bundle($translatedLocale, $regionDir);
  216. $localeNames = array();
  217. foreach ($supportedLocales as $supportedLocale) {
  218. // Don't include ICU's root resource bundle
  219. if ($supportedLocale === 'root') {
  220. continue;
  221. }
  222. $lang = \Locale::getPrimaryLanguage($supportedLocale);
  223. $script = \Locale::getScript($supportedLocale);
  224. $region = \Locale::getRegion($supportedLocale);
  225. $variants = \Locale::getAllVariants($supportedLocale);
  226. // Currently the only available variant is POSIX, which we don't want
  227. // to include in the list
  228. if (count($variants) > 0) {
  229. continue;
  230. }
  231. $langName = $langBundle->get('Languages')->get($lang);
  232. $extras = array();
  233. // Some languages are simply not translated
  234. // Example: "az" (Azerbaijani) has no translation in "af" (Afrikaans)
  235. if (!$langName) {
  236. continue;
  237. }
  238. // "af" (Afrikaans) has no "Scripts" block
  239. if (!$langBundle->get('Scripts')) {
  240. continue;
  241. }
  242. // "as" (Assamese) has no "Variants" block
  243. if (!$langBundle->get('Variants')) {
  244. continue;
  245. }
  246. // Discover the name of the script part of the locale
  247. // i.e. in zh_Hans_MO, "Hans" is the script
  248. if ($script) {
  249. // Some languages are translated together with their script,
  250. // i.e. "zh_Hans" is translated as "Simplified Chinese"
  251. if ($langBundle->get('Languages')->get($lang.'_'.$script)) {
  252. $langName = $langBundle->get('Languages')->get($lang.'_'.$script);
  253. // If the script is appended in braces, extract it
  254. // i.e. "zh_Hans" is translated as "Chinesisch (vereinfacht)"
  255. // in "de"
  256. if (strpos($langName, '(') !== false) {
  257. list($langName, $scriptName) = preg_split('/[\s()]/', $langName, null, PREG_SPLIT_NO_EMPTY);
  258. $extras[] = $scriptName;
  259. }
  260. } else {
  261. $scriptName = $langBundle->get('Scripts')->get($script);
  262. // Some scripts are not translated into every language
  263. if (!$scriptName) {
  264. continue;
  265. }
  266. $extras[] = $scriptName;
  267. }
  268. }
  269. // Discover the name of the region part of the locale
  270. // i.e. in de_AT, "AT" is the region
  271. if ($region) {
  272. // Some languages are translated together with their region,
  273. // i.e. "en_GB" is translated as "British English"
  274. // we don't include these languages though because they mess up
  275. // the locale sorting
  276. // if ($langBundle->get('Languages')->get($lang.'_'.$region)) {
  277. // $langName = $langBundle->get('Languages')->get($lang.'_'.$region);
  278. // } else {
  279. $regionName = $regionBundle->get('Countries')->get($region);
  280. // Some regions are not translated into every language
  281. if (!$regionName) {
  282. continue;
  283. }
  284. $extras[] = $regionName;
  285. // }
  286. }
  287. if (count($extras) > 0) {
  288. $langName .= ' (' . implode(', ', $extras) . ')';
  289. }
  290. $localeNames[$supportedLocale] = $langName;
  291. }
  292. // If no names could be generated for the current locale, skip it
  293. if (count($localeNames) === 0) {
  294. continue;
  295. }
  296. echo "Generating $translatedLocale...\n";
  297. $file = fopen($namesGeneratedDir.DIRECTORY_SEPARATOR.$translatedLocale.'.txt', 'w');
  298. fwrite($file, "$translatedLocale{\n");
  299. fwrite($file, " Locales{\n");
  300. foreach ($localeNames as $supportedLocale => $langName) {
  301. fwrite($file, " $supportedLocale{\"$langName\"}\n");
  302. }
  303. fwrite($file, " }\n");
  304. fwrite($file, "}\n");
  305. fclose($file);
  306. }
  307. // Convert generated files to binary format
  308. genrb($namesGeneratedDir, $namesDir);
  309. // Clean up
  310. clear_directory($namesGeneratedDir);
  311. rmdir($namesGeneratedDir);
  312. // Generate the data to the stubbed intl classes We only extract data for the 'en' locale
  313. // The extracted data is used only by the stub classes
  314. $defaultLocale = 'en';
  315. $currencies = array();
  316. $currenciesMeta = array();
  317. $defaultMeta = array();
  318. $bundle = load_resource_bundle('supplementaldata', __DIR__.'/curr');
  319. foreach ($bundle->get('CurrencyMeta') as $code => $data) {
  320. // The 'DEFAULT' key contains the fraction digits and the rounding increment that are common for a lot of currencies
  321. // Only currencies with different values are added to the icu-data (e.g: CHF and JPY)
  322. if ('DEFAULT' == $code) {
  323. $defaultMeta = array(
  324. 'fractionDigits' => $data[0],
  325. 'roundingIncrement' => $data[1],
  326. );
  327. continue;
  328. }
  329. $currenciesMeta[$code]['fractionDigits'] = $data[0];
  330. $currenciesMeta[$code]['roundingIncrement'] = $data[1];
  331. }
  332. $bundle = load_resource_bundle('en', __DIR__.'/curr');
  333. foreach ($bundle->get('Currencies') as $code => $data) {
  334. $currencies[$code]['symbol'] = $data[0];
  335. $currencies[$code]['name'] = $data[1];
  336. if (!isset($currenciesMeta[$code])) {
  337. $currencies[$code]['fractionDigits'] = $defaultMeta['fractionDigits'];
  338. $currencies[$code]['roundingIncrement'] = $defaultMeta['roundingIncrement'];
  339. continue;
  340. }
  341. $currencies[$code]['fractionDigits'] = $currenciesMeta[$code]['fractionDigits'];
  342. $currencies[$code]['roundingIncrement'] = $currenciesMeta[$code]['roundingIncrement'];
  343. }
  344. // Countries.
  345. $countriesConstraint = function($code)
  346. {
  347. // Global countries (f.i. "America") have numeric codes
  348. // Countries have alphabetic codes
  349. // "ZZ" is the code for unknown country
  350. if (ctype_alpha($code) && 'ZZ' !== $code) {
  351. return true;
  352. }
  353. return false;
  354. };
  355. $countries = get_data('Countries', 'region', $defaultLocale, $countriesConstraint);
  356. // Languages
  357. $languagesConstraint = function($code)
  358. {
  359. // "mul" is the code for multiple languages
  360. if ('mul' !== $code) {
  361. return true;
  362. }
  363. return false;
  364. };
  365. $languages = get_data('Languages', 'lang', $defaultLocale, $languagesConstraint);
  366. // Display locales
  367. $displayLocales = get_data('Locales', 'names', $defaultLocale);
  368. // Create the stubs datafiles
  369. $stubDir = $target.DIRECTORY_SEPARATOR.'stub';
  370. $stubCurrDir = $stubDir.DIRECTORY_SEPARATOR.'curr';
  371. $stubLangDir = $stubDir.DIRECTORY_SEPARATOR.'lang';
  372. $stubNamesDir = $stubDir.DIRECTORY_SEPARATOR.'names';
  373. $stubRegionDir = $stubDir.DIRECTORY_SEPARATOR.'region';
  374. // Create the directories
  375. make_directory($stubDir);
  376. make_directory($stubCurrDir);
  377. make_directory($stubLangDir);
  378. make_directory($stubNamesDir);
  379. make_directory($stubRegionDir);
  380. clear_directory($stubCurrDir);
  381. clear_directory($stubLangDir);
  382. clear_directory($stubNamesDir);
  383. clear_directory($stubRegionDir);
  384. create_stub_datafile($defaultLocale, $stubCurrDir, $currencies);
  385. create_stub_datafile($defaultLocale, $stubLangDir, $languages);
  386. create_stub_datafile($defaultLocale, $stubNamesDir, $displayLocales);
  387. create_stub_datafile($defaultLocale, $stubRegionDir, $countries);
  388. // Clean up
  389. clear_directory($currDir);
  390. rmdir($currDir);