not-gettexted.php 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. <?php
  2. /**
  3. * Console application, which extracts or replaces strings for
  4. * translation, which cannot be gettexted
  5. *
  6. * @package wordpress-i18n
  7. * @subpackage tools
  8. */
  9. // see: http://php.net/tokenizer
  10. if ( ! defined( 'T_ML_COMMENT' ) )
  11. define( 'T_ML_COMMENT', T_COMMENT );
  12. else
  13. define( 'T_DOC_COMMENT', T_ML_COMMENT );
  14. $pomo = dirname( dirname( dirname( __FILE__ ) ) ) . '/src/wp-includes/pomo';
  15. require_once "$pomo/po.php";
  16. require_once "$pomo/mo.php";
  17. class NotGettexted {
  18. var $enable_logging = false;
  19. var $STAGE_OUTSIDE = 0;
  20. var $STAGE_START_COMMENT = 1;
  21. var $STAGE_WHITESPACE_BEFORE = 2;
  22. var $STAGE_STRING = 3;
  23. var $STAGE_WHITESPACE_AFTER = 4;
  24. var $STAGE_END_COMMENT = 4;
  25. var $commands = array('extract' => 'command_extract', 'replace' => 'command_replace' );
  26. function logmsg() {
  27. $args = func_get_args();
  28. if ($this->enable_logging) error_log(implode(' ', $args));
  29. }
  30. function stderr($msg, $nl=true) {
  31. fwrite(STDERR, $msg.($nl? "\n" : ""));
  32. }
  33. function cli_die($msg) {
  34. $this->stderr($msg);
  35. exit(1);
  36. }
  37. function unchanged_token($token, $s='') {
  38. return is_array($token)? $token[1] : $token;
  39. }
  40. function ignore_token($token, $s='') {
  41. return '';
  42. }
  43. function list_php_files($dir) {
  44. $files = array();
  45. $items = scandir( $dir );
  46. foreach ( (array) $items as $item ) {
  47. $full_item = $dir . '/' . $item;
  48. if ('.' == $item || '..' == $item)
  49. continue;
  50. if ('.php' == substr($item, -4))
  51. $files[] = $full_item;
  52. if (is_dir($full_item))
  53. $files += array_merge($files, NotGettexted::list_php_files($full_item, $files));
  54. }
  55. return $files;
  56. }
  57. function make_string_aggregator($global_array_name, $filename) {
  58. $a = $global_array_name;
  59. return create_function('$string, $comment_id, $line_number', 'global $'.$a.'; $'.$a.'[] = array($string, $comment_id, '.var_export($filename, true).', $line_number);');
  60. }
  61. function make_mo_replacer($global_mo_name) {
  62. $m = $global_mo_name;
  63. return create_function('$token, $string', 'global $'.$m.'; return var_export($'.$m.'->translate($string), true);');
  64. }
  65. function walk_tokens(&$tokens, $string_action, $other_action, $register_action=null) {
  66. $current_comment_id = '';
  67. $current_string = '';
  68. $current_string_line = 0;
  69. $result = '';
  70. $line = 1;
  71. foreach($tokens as $token) {
  72. if (is_array($token)) {
  73. list($id, $text) = $token;
  74. $line += substr_count($text, "\n");
  75. if ((T_ML_COMMENT == $id || T_COMMENT == $id) && preg_match('|/\*\s*(/?WP_I18N_[a-z_]+)\s*\*/|i', $text, $matches)) {
  76. if ($this->STAGE_OUTSIDE == $stage) {
  77. $stage = $this->STAGE_START_COMMENT;
  78. $current_comment_id = $matches[1];
  79. $this->logmsg('start comment', $current_comment_id);
  80. $result .= call_user_func($other_action, $token);
  81. continue;
  82. }
  83. if ($this->STAGE_START_COMMENT <= $stage && $stage <= $this->STAGE_WHITESPACE_AFTER && '/'.$current_comment_id == $matches[1]) {
  84. $stage = $this->STAGE_END_COMMENT;
  85. $this->logmsg('end comment', $current_comment_id);
  86. $result .= call_user_func($other_action, $token);
  87. if (!is_null($register_action)) call_user_func($register_action, $current_string, $current_comment_id, $current_string_line);
  88. continue;
  89. }
  90. } else if (T_CONSTANT_ENCAPSED_STRING == $id) {
  91. if ($this->STAGE_START_COMMENT <= $stage && $stage < $this->STAGE_WHITESPACE_AFTER) {
  92. eval('$current_string='.$text.';');
  93. $this->logmsg('string', $current_string);
  94. $current_string_line = $line;
  95. $result .= call_user_func($string_action, $token, $current_string);
  96. continue;
  97. }
  98. } else if (T_WHITESPACE == $id) {
  99. if ($this->STAGE_START_COMMENT <= $stage && $stage < $this->STAGE_STRING) {
  100. $stage = $this->STAGE_WHITESPACE_BEFORE;
  101. $this->logmsg('whitespace before');
  102. $result .= call_user_func($other_action, $token);
  103. continue;
  104. }
  105. if ($this->STAGE_STRING < $stage && $stage < $this->STAGE_END_COMMENT) {
  106. $stage = $this->STAGE_WHITESPACE_AFTER;
  107. $this->logmsg('whitespace after');
  108. $result .= call_user_func($other_action, $token);
  109. continue;
  110. }
  111. }
  112. }
  113. $result .= call_user_func($other_action, $token);
  114. $stage = $this->STAGE_OUTSIDE;
  115. $current_comment_id = '';
  116. $current_string = '';
  117. $current_string_line = 0;
  118. }
  119. return $result;
  120. }
  121. function command_extract() {
  122. $args = func_get_args();
  123. $pot_filename = $args[0];
  124. if (isset($args[1]) && is_array($args[1]))
  125. $filenames = $args[1];
  126. else
  127. $filenames = array_slice($args, 1);
  128. $global_name = '__entries_'.mt_rand(1, 1000);
  129. $GLOBALS[$global_name] = array();
  130. foreach($filenames as $filename) {
  131. $tokens = token_get_all(file_get_contents($filename));
  132. $aggregator = $this->make_string_aggregator($global_name, $filename);
  133. $this->walk_tokens($tokens, array($this, 'ignore_token'), array($this, 'ignore_token'), $aggregator);
  134. }
  135. $potf = '-' == $pot_filename? STDOUT : @fopen($pot_filename, 'a');
  136. if (false === $potf) {
  137. $this->cli_die("Couldn't open pot file: $pot_filename");
  138. }
  139. foreach($GLOBALS[$global_name] as $item) {
  140. @list($string, $comment_id, $filename, $line_number) = $item;
  141. $filename = isset($filename)? preg_replace('|^\./|', '', $filename) : '';
  142. $ref_line_number = isset($line_number)? ":$line_number" : '';
  143. $args = array(
  144. 'singular' => $string,
  145. 'extracted_comments' => "Not gettexted string $comment_id",
  146. 'references' => array("$filename$ref_line_number"),
  147. );
  148. $entry = new Translation_Entry($args);
  149. fwrite($potf, "\n".PO::export_entry($entry)."\n");
  150. }
  151. if ('-' != $pot_filename) fclose($potf);
  152. return true;
  153. }
  154. function command_replace() {
  155. $args = func_get_args();
  156. $mo_filename = $args[0];
  157. if (isset($args[1]) && is_array($args[1]))
  158. $filenames = $args[1];
  159. else
  160. $filenames = array_slice($args, 1);
  161. $global_name = '__mo_'.mt_rand(1, 1000);
  162. $GLOBALS[$global_name] = new MO();
  163. $replacer = $this->make_mo_replacer($global_name);
  164. $res = $GLOBALS[$global_name]->import_from_file($mo_filename);
  165. if (false === $res) {
  166. $this->cli_die("Couldn't read MO file '$mo_filename'!");
  167. }
  168. foreach($filenames as $filename) {
  169. $source = file_get_contents($filename);
  170. if ( strlen($source) > 150000 ) continue;
  171. $tokens = token_get_all($source);
  172. $new_file = $this->walk_tokens($tokens, $replacer, array($this, 'unchanged_token'));
  173. $f = fopen($filename, 'w');
  174. fwrite($f, $new_file);
  175. fclose($f);
  176. }
  177. return true;
  178. }
  179. function usage() {
  180. $this->stderr('php i18n-comments.php COMMAND OUTPUTFILE INPUTFILES');
  181. $this->stderr('Extracts and replaces strings, which cannot be gettexted');
  182. $this->stderr('Commands:');
  183. $this->stderr(' extract POTFILE PHPFILES appends the strings to POTFILE');
  184. $this->stderr(' replace MOFILE PHPFILES replaces strings in PHPFILES with translations from MOFILE');
  185. }
  186. function cli() {
  187. global $argv, $commands;
  188. if (count($argv) < 4 || !in_array($argv[1], array_keys($this->commands))) {
  189. $this->usage();
  190. exit(1);
  191. }
  192. call_user_func_array(array($this, $this->commands[$argv[1]]), array_slice($argv, 2));
  193. }
  194. }
  195. // run the CLI only if the file
  196. // wasn't included
  197. $included_files = get_included_files();
  198. if ($included_files[0] == __FILE__) {
  199. error_reporting(E_ALL);
  200. $not_gettexted = new NotGettexted;
  201. $not_gettexted->cli();
  202. }
  203. ?>