MessageCommand.php 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. <?php
  2. /**
  3. * MessageCommand 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. * MessageCommand extracts messages to be translated from source files.
  12. * The extracted messages are saved as PHP message source files
  13. * under the specified directory.
  14. *
  15. * @author Qiang Xue <qiang.xue@gmail.com>
  16. * @package system.cli.commands
  17. * @since 1.0
  18. */
  19. class MessageCommand extends CConsoleCommand
  20. {
  21. public function getHelp()
  22. {
  23. return <<<EOD
  24. USAGE
  25. yiic message <config-file>
  26. DESCRIPTION
  27. This command searches for messages to be translated in the specified
  28. source files and compiles them into PHP arrays as message source.
  29. PARAMETERS
  30. * config-file: required, the path of the configuration file. You can find
  31. an example in framework/messages/config.php.
  32. The file can be placed anywhere and must be a valid PHP script which
  33. returns an array of name-value pairs. Each name-value pair represents
  34. a configuration option.
  35. The following options are available:
  36. - sourcePath: string, root directory of all source files.
  37. - messagePath: string, root directory containing message translations.
  38. - languages: array, list of language codes that the extracted messages
  39. should be translated to. For example, array('zh_cn','en_au').
  40. - fileTypes: array, a list of file extensions (e.g. 'php', 'xml').
  41. Only the files whose extension name can be found in this list
  42. will be processed. If empty, all files will be processed.
  43. - exclude: array, a list of directory and file exclusions. Each
  44. exclusion can be either a name or a path. If a file or directory name
  45. or path matches the exclusion, it will not be copied. For example,
  46. an exclusion of '.svn' will exclude all files and directories whose
  47. name is '.svn'. And an exclusion of '/a/b' will exclude file or
  48. directory 'sourcePath/a/b'.
  49. - translator: the name of the function for translating messages.
  50. Defaults to 'Yii::t'. This is used as a mark to find messages to be
  51. translated. Accepts both string for single function name or array for
  52. multiple function names.
  53. - overwrite: if message file must be overwritten with the merged messages.
  54. - removeOld: if message no longer needs translation it will be removed,
  55. instead of being enclosed between a pair of '@@' marks.
  56. - sort: sort messages by key when merging, regardless of their translation
  57. state (new, obsolete, translated.)
  58. - fileHeader: A boolean indicating whether the file should contain a default
  59. comment that explains the message file or a string representing
  60. some PHP code or comment to add before the return tag in the message file.
  61. EOD;
  62. }
  63. /**
  64. * Execute the action.
  65. * @param array $args command line parameters specific for this command
  66. */
  67. public function run($args)
  68. {
  69. if(!isset($args[0]))
  70. $this->usageError('the configuration file is not specified.');
  71. if(!is_file($args[0]))
  72. $this->usageError("the configuration file {$args[0]} does not exist.");
  73. $config=require($args[0]);
  74. $translator='Yii::t';
  75. extract($config);
  76. if(!isset($sourcePath,$messagePath,$languages))
  77. $this->usageError('The configuration file must specify "sourcePath", "messagePath" and "languages".');
  78. if(!is_dir($sourcePath))
  79. $this->usageError("The source path $sourcePath is not a valid directory.");
  80. if(!is_dir($messagePath))
  81. $this->usageError("The message path $messagePath is not a valid directory.");
  82. if(empty($languages))
  83. $this->usageError("Languages cannot be empty.");
  84. if(!isset($overwrite))
  85. $overwrite = false;
  86. if(!isset($removeOld))
  87. $removeOld = false;
  88. if(!isset($sort))
  89. $sort = false;
  90. if(!isset($fileHeader))
  91. $fileHeader = true;
  92. $options=array();
  93. if(isset($fileTypes))
  94. $options['fileTypes']=$fileTypes;
  95. if(isset($exclude))
  96. $options['exclude']=$exclude;
  97. $files=CFileHelper::findFiles(realpath($sourcePath),$options);
  98. $messages=array();
  99. foreach($files as $file)
  100. $messages=array_merge_recursive($messages,$this->extractMessages($file,$translator));
  101. foreach($languages as $language)
  102. {
  103. $dir=$messagePath.DIRECTORY_SEPARATOR.$language;
  104. if(!is_dir($dir))
  105. @mkdir($dir);
  106. foreach($messages as $category=>$msgs)
  107. {
  108. $msgs=array_values(array_unique($msgs));
  109. $this->generateMessageFile($msgs,$dir.DIRECTORY_SEPARATOR.$category.'.php',$overwrite,$removeOld,$sort,$fileHeader);
  110. }
  111. }
  112. }
  113. protected function extractMessages($fileName,$translator)
  114. {
  115. echo "Extracting messages from $fileName...\n";
  116. $subject=file_get_contents($fileName);
  117. $messages=array();
  118. if(!is_array($translator))
  119. $translator=array($translator);
  120. foreach ($translator as $currentTranslator)
  121. {
  122. $n=preg_match_all('/\b'.$currentTranslator.'\s*\(\s*(\'[\w.\/]*?(?<!\.)\'|"[\w.]*?(?<!\.)")\s*,\s*(\'.*?(?<!\\\\)\'|".*?(?<!\\\\)")\s*[,\)]/s',$subject,$matches,PREG_SET_ORDER);
  123. for($i=0;$i<$n;++$i)
  124. {
  125. if(($pos=strpos($matches[$i][1],'.'))!==false)
  126. $category=substr($matches[$i][1],$pos+1,-1);
  127. else
  128. $category=substr($matches[$i][1],1,-1);
  129. $message=$matches[$i][2];
  130. $messages[$category][]=eval("return $message;"); // use eval to eliminate quote escape
  131. }
  132. }
  133. return $messages;
  134. }
  135. protected function generateMessageFile($messages,$fileName,$overwrite,$removeOld,$sort,$fileHeader)
  136. {
  137. echo "Saving messages to $fileName...";
  138. if(is_file($fileName))
  139. {
  140. $translated=require($fileName);
  141. sort($messages);
  142. ksort($translated);
  143. if(array_keys($translated)==$messages)
  144. {
  145. echo "nothing new...skipped.\n";
  146. return;
  147. }
  148. $merged=array();
  149. $untranslated=array();
  150. foreach($messages as $message)
  151. {
  152. if(array_key_exists($message,$translated) && strlen($translated[$message])>0)
  153. $merged[$message]=$translated[$message];
  154. else
  155. $untranslated[]=$message;
  156. }
  157. ksort($merged);
  158. sort($untranslated);
  159. $todo=array();
  160. foreach($untranslated as $message)
  161. $todo[$message]='';
  162. ksort($translated);
  163. foreach($translated as $message=>$translation)
  164. {
  165. if(!isset($merged[$message]) && !isset($todo[$message]) && !$removeOld)
  166. {
  167. if(substr($translation,0,2)==='@@' && substr($translation,-2)==='@@')
  168. $todo[$message]=$translation;
  169. else
  170. $todo[$message]='@@'.$translation.'@@';
  171. }
  172. }
  173. $merged=array_merge($todo,$merged);
  174. if($sort)
  175. ksort($merged);
  176. if($overwrite === false)
  177. $fileName.='.merged';
  178. echo "translation merged.\n";
  179. }
  180. else
  181. {
  182. $merged=array();
  183. foreach($messages as $message)
  184. $merged[$message]='';
  185. ksort($merged);
  186. echo "saved.\n";
  187. }
  188. $array=str_replace("\r",'',var_export($merged,true));
  189. if($fileHeader===true)
  190. $fileHeader=<<<EOD
  191. /**
  192. * Message translations.
  193. *
  194. * This file is automatically generated by 'yiic message' command.
  195. * It contains the localizable messages extracted from source code.
  196. * You may modify this file by translating the extracted messages.
  197. *
  198. * Each array element represents the translation (value) of a message (key).
  199. * If the value is empty, the message is considered as not translated.
  200. * Messages that no longer need translation will have their translations
  201. * enclosed between a pair of '@@' marks.
  202. *
  203. * Message string can be used with plural forms format. Check i18n section
  204. * of the guide for details.
  205. *
  206. * NOTE, this file must be saved in UTF-8 encoding.
  207. */
  208. EOD;
  209. elseif($fileHeader===false)
  210. $fileHeader='';
  211. file_put_contents($fileName,<<<EOD
  212. <?php
  213. $fileHeader
  214. return $array;
  215. EOD
  216. );
  217. }
  218. }