I18nFile.php 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. <?php
  2. require_once __DIR__ . '/I18nValue.php';
  3. class I18nFile {
  4. public function load() {
  5. $i18n = array();
  6. $dirs = new DirectoryIterator(I18N_PATH);
  7. foreach ($dirs as $dir) {
  8. if ($dir->isDot()) {
  9. continue;
  10. }
  11. $files = new DirectoryIterator($dir->getPathname());
  12. foreach ($files as $file) {
  13. if (!$file->isFile()) {
  14. continue;
  15. }
  16. $i18n[$dir->getFilename()][$file->getFilename()] = $this->flatten($this->process($file->getPathname()), $file->getBasename('.php'));
  17. }
  18. }
  19. return $i18n;
  20. }
  21. public function dump(array $i18n) {
  22. foreach ($i18n as $language => $file) {
  23. $dir = I18N_PATH . DIRECTORY_SEPARATOR . $language;
  24. if (!file_exists($dir)) {
  25. mkdir($dir, 0770, true);
  26. }
  27. foreach ($file as $name => $content) {
  28. $filename = $dir . DIRECTORY_SEPARATOR . $name;
  29. file_put_contents($filename, $this->format($content));
  30. }
  31. }
  32. }
  33. /**
  34. * Process the content of an i18n file
  35. *
  36. * @param string $filename
  37. * @return array
  38. */
  39. private function process(string $filename) {
  40. $content = file_get_contents($filename);
  41. $content = str_replace('<?php', '', $content);
  42. $content = preg_replace([
  43. "#',\s*//\s*TODO.*#i",
  44. "#',\s*//\s*DIRTY.*#i",
  45. "#',\s*//\s*IGNORE.*#i",
  46. ], [
  47. ' -> todo\',',
  48. ' -> dirty\',',
  49. ' -> ignore\',',
  50. ], $content);
  51. try {
  52. $content = eval($content);
  53. } catch (ParseError $ex) {
  54. if (defined('STDERR')) {
  55. fwrite(STDERR, "Error while processing: $filename\n");
  56. fwrite(STDERR, $ex);
  57. }
  58. die(1);
  59. }
  60. if (is_array($content)) {
  61. return $content;
  62. }
  63. return [];
  64. }
  65. /**
  66. * Flatten an array of translation
  67. *
  68. * @param array $translation
  69. * @param string $prefix
  70. * @return array
  71. */
  72. private function flatten(array $translation, string $prefix = '') {
  73. $a = array();
  74. if ('' !== $prefix) {
  75. $prefix .= '.';
  76. }
  77. foreach ($translation as $key => $value) {
  78. if (is_array($value)) {
  79. $a += $this->flatten($value, $prefix . $key);
  80. } else {
  81. $a[$prefix . $key] = new I18nValue($value);
  82. }
  83. }
  84. return $a;
  85. }
  86. /**
  87. * Unflatten an array of translation
  88. *
  89. * The first key is dropped since it represents the filename and we have
  90. * no use of it.
  91. *
  92. * @param array $translation
  93. * @return array
  94. */
  95. private function unflatten(array $translation) {
  96. $a = array();
  97. ksort($translation, SORT_NATURAL);
  98. foreach ($translation as $compoundKey => $value) {
  99. $keys = explode('.', $compoundKey);
  100. array_shift($keys);
  101. eval("\$a['" . implode("']['", $keys) . "'] = '" . addcslashes($value, "'") . "';");
  102. }
  103. return $a;
  104. }
  105. /**
  106. * Format an array of translation
  107. *
  108. * It takes an array of translation and format it to be dumped in a
  109. * translation file. The array is first converted to a string then some
  110. * formatting regexes are applied to match the original content.
  111. *
  112. * @param array $translation
  113. * @return string
  114. */
  115. private function format(array $translation) {
  116. $translation = var_export($this->unflatten($translation), true);
  117. $patterns = array(
  118. '/ -> todo\',/',
  119. '/ -> dirty\',/',
  120. '/ -> ignore\',/',
  121. '/array \(/',
  122. '/=>\s*array/',
  123. '/(\w) {2}/',
  124. '/ {2}/',
  125. );
  126. $replacements = array(
  127. "',\t// TODO", // Double quoting is mandatory to have a tab instead of the \t string
  128. "',\t// DIRTY", // Double quoting is mandatory to have a tab instead of the \t string
  129. "',\t// IGNORE", // Double quoting is mandatory to have a tab instead of the \t string
  130. 'array(',
  131. '=> array',
  132. '$1 ',
  133. "\t", // Double quoting is mandatory to have a tab instead of the \t string
  134. );
  135. $translation = preg_replace($patterns, $replacements, $translation);
  136. return <<<OUTPUT
  137. <?php
  138. /******************************************************************************/
  139. /* Each entry of that file can be associated with a comment to indicate its */
  140. /* state. When there is no comment, it means the entry is fully translated. */
  141. /* The recognized comments are (comment matching is case-insensitive): */
  142. /* + TODO: the entry has never been translated. */
  143. /* + DIRTY: the entry has been translated but needs to be updated. */
  144. /* + IGNORE: the entry does not need to be translated. */
  145. /* When a comment is not recognized, it is discarded. */
  146. /******************************************************************************/
  147. return {$translation};
  148. OUTPUT;
  149. }
  150. }