MessageController.php 7.37 KB
Newer Older
Qiang Xue committed
1 2 3 4
<?php
/**
 * @author Qiang Xue <qiang.xue@gmail.com>
 * @link http://www.yiiframework.com/
Qiang Xue committed
5
 * @copyright Copyright (c) 2008 Yii Software LLC
Qiang Xue committed
6 7 8
 * @license http://www.yiiframework.com/license/
 */

9 10 11 12
namespace yii\console\controllers;

use yii\console\Controller;

Qiang Xue committed
13
/**
14
 * This command extracts messages to be translated from source files.
Qiang Xue committed
15 16 17 18
 * The extracted messages are saved as PHP message source files
 * under the specified directory.
 *
 * @author Qiang Xue <qiang.xue@gmail.com>
19
 * @since 2.0
Qiang Xue committed
20
 */
21
class MessageController extends Controller
Qiang Xue committed
22 23
{
	/**
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
	 * Searches for messages to be translated in the specified
	 * source files and compiles them into PHP arrays as message source.
	 *
	 * @param string $config the path of the configuration file. You can find
	 * an example in framework/messages/config.php.
	 *
	 * The file can be placed anywhere and must be a valid PHP script which
	 * returns an array of name-value pairs. Each name-value pair represents
	 * a configuration option.
	 *
	 * The following options are available:
	 *
	 *  - sourcePath: string, root directory of all source files.
	 *  - messagePath: string, root directory containing message translations.
	 *  - languages: array, list of language codes that the extracted messages
resurtm committed
39
	 *    should be translated to. For example, array('zh_cn', 'en_au').
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
	 *  - fileTypes: array, a list of file extensions (e.g. 'php', 'xml').
	 *    Only the files whose extension name can be found in this list
	 *    will be processed. If empty, all files will be processed.
	 *  - exclude: array, a list of directory and file exclusions. Each
	 *    exclusion can be either a name or a path. If a file or directory name
	 *    or path matches the exclusion, it will not be copied. For example,
	 *    an exclusion of '.svn' will exclude all files and directories whose
	 *    name is '.svn'. And an exclusion of '/a/b' will exclude file or
	 *    directory 'sourcePath/a/b'.
	 *  - translator: the name of the function for translating messages.
	 *    Defaults to 'Yii::t'. This is used as a mark to find messages to be
	 *    translated.
	 *  - overwrite: if message file must be overwritten with the merged messages.
	 *  - removeOld: if message no longer needs translation it will be removed,
	 *    instead of being enclosed between a pair of '@@' marks.
	 *  - sort: sort messages by key when merging, regardless of their translation
	 *    state (new, obsolete, translated.)
Qiang Xue committed
57
	 */
58
	public function actionIndex($config)
Qiang Xue committed
59
	{
resurtm committed
60
		if (!is_file($config)) {
61
			$this->usageError("the configuration file {$config} does not exist.");
resurtm committed
62
		}
63

resurtm committed
64
		$config = require_once($config);
Qiang Xue committed
65 66 67 68

		$translator='Yii::t';
		extract($config);

resurtm committed
69
		if (!isset($sourcePath, $messagePath, $languages)) {
Qiang Xue committed
70
			$this->usageError('The configuration file must specify "sourcePath", "messagePath" and "languages".');
resurtm committed
71 72
		}
		if (!is_dir($sourcePath)) {
Qiang Xue committed
73
			$this->usageError("The source path $sourcePath is not a valid directory.");
resurtm committed
74 75
		}
		if (!is_dir($messagePath)) {
Qiang Xue committed
76
			$this->usageError("The message path $messagePath is not a valid directory.");
resurtm committed
77 78
		}
		if (empty($languages)) {
Qiang Xue committed
79
			$this->usageError("Languages cannot be empty.");
resurtm committed
80
		}
Qiang Xue committed
81

resurtm committed
82
		if (!isset($overwrite)) {
Qiang Xue committed
83
			$overwrite = false;
resurtm committed
84 85
		}
		if (!isset($removeOld)) {
Qiang Xue committed
86
			$removeOld = false;
resurtm committed
87 88
		}
		if (!isset($sort)) {
Qiang Xue committed
89
			$sort = false;
resurtm committed
90
		}
91

resurtm committed
92 93 94 95 96 97 98 99
		$options = array();
		if (isset($fileTypes)) {
			$options['fileTypes'] = $fileTypes;
		}
		if (isset($exclude)) {
			$options['exclude'] = $exclude;
		}
		$files = CFileHelper::findFiles(realpath($sourcePath), $options);
Qiang Xue committed
100

resurtm committed
101 102 103 104
		$messages = array();
		foreach ($files as $file) {
			$messages = array_merge_recursive($messages, $this->extractMessages($file, $translator));
		}
Qiang Xue committed
105

resurtm committed
106 107 108
		foreach ($languages as $language) {
			$dir = $messagePath . DIRECTORY_SEPARATOR . $language;
			if (!is_dir($dir)) {
Qiang Xue committed
109
				@mkdir($dir);
resurtm committed
110 111 112 113
			}
			foreach ($messages as $category => $msgs) {
				$msgs = array_values(array_unique($msgs));
				$this->generateMessageFile($msgs, $dir . DIRECTORY_SEPARATOR . $category . '.php', $overwrite, $removeOld, $sort);
Qiang Xue committed
114 115 116 117
			}
		}
	}

Alexander Makarov committed
118 119 120 121 122 123 124
	/**
	 * Extracts messages from a file
	 *
	 * @param string $fileName name of the file to extract messages from
	 * @param string $translator name of the function used to translate messages
	 * @return array
	 */
resurtm committed
125
	protected function extractMessages($fileName, $translator)
Qiang Xue committed
126 127
	{
		echo "Extracting messages from $fileName...\n";
resurtm committed
128 129 130 131 132 133 134 135 136 137 138 139 140
		$subject = file_get_contents($fileName);
		$n = preg_match_all(
			'/\b' . $translator . '\s*\(\s*(\'.*?(?<!\\\\)\'|".*?(?<!\\\\)")\s*,\s*(\'.*?(?<!\\\\)\'|".*?(?<!\\\\)")\s*[,\)]/s',
			$subject, $matches, PREG_SET_ORDER);
		$messages = array();
		for ($i = 0; $i < $n; ++$i) {
			if (($pos = strpos($matches[$i][1], '.')) !== false) {
				$category=substr($matches[$i][1], $pos + 1, -1);
			} else {
				$category=substr($matches[$i][1], 1, -1);
			}
			$message = $matches[$i][2];
			$messages[$category][] = eval("return $message;"); // use eval to eliminate quote escape
Qiang Xue committed
141 142 143 144
		}
		return $messages;
	}

Alexander Makarov committed
145 146 147 148 149 150 151 152 153
	/**
	 * Writes messages into file
	 *
	 * @param array $messages
	 * @param string $fileName name of the file to write to
	 * @param boolean $overwrite if existing file should be overwritten without backup
	 * @param boolean $removeOld if obsolete translations should be removed
	 * @param boolean $sort if translations should be sorted
	 */
resurtm committed
154
	protected function generateMessageFile($messages, $fileName, $overwrite, $removeOld, $sort)
Qiang Xue committed
155 156
	{
		echo "Saving messages to $fileName...";
resurtm committed
157 158
		if (is_file($fileName)) {
			$translated = require($fileName);
Qiang Xue committed
159 160
			sort($messages);
			ksort($translated);
resurtm committed
161
			if (array_keys($translated) == $messages) {
Qiang Xue committed
162 163 164
				echo "nothing new...skipped.\n";
				return;
			}
resurtm committed
165 166 167 168 169 170 171 172
			$merged = array();
			$untranslated = array();
			foreach ($messages as $message) {
				if (!empty($translated[$message])) {
					$merged[$message] = $translated[$message];
				} else {
					$untranslated[] = $message;
				}
Qiang Xue committed
173 174 175
			}
			ksort($merged);
			sort($untranslated);
resurtm committed
176 177 178 179
			$todo = array();
			foreach ($untranslated as $message) {
				$todo[$message] = '';
			}
Qiang Xue committed
180
			ksort($translated);
resurtm committed
181 182
			foreach ($translated as $message => $translation) {
				if (!isset($merged[$message]) && !isset($todo[$message]) && !$removeOld)
Qiang Xue committed
183
				{
resurtm committed
184
					if (substr($translation, 0, 2) === '@@' && substr($translation, -2) === '@@') {
Qiang Xue committed
185
						$todo[$message]=$translation;
resurtm committed
186 187 188
					} else {
						$todo[$message] = '@@' . $translation . '@@';
					}
Qiang Xue committed
189 190
				}
			}
resurtm committed
191 192
			$merged = array_merge($todo, $merged);
			if ($sort) {
Qiang Xue committed
193
				ksort($merged);
resurtm committed
194 195 196 197
			}
			if (false === $overwrite) {
				$fileName .= '.merged';
			}
Qiang Xue committed
198
			echo "translation merged.\n";
resurtm committed
199 200 201 202 203
		} else {
			$merged = array();
			foreach ($messages as $message) {
				$merged[$message] = '';
			}
Qiang Xue committed
204 205 206
			ksort($merged);
			echo "saved.\n";
		}
resurtm committed
207 208
		$array = str_replace("\r", '', var_export($merged, true));
		$content = <<<EOD
Qiang Xue committed
209 210 211 212
<?php
/**
 * Message translations.
 *
213
 * This file is automatically generated by 'yii message' command.
Qiang Xue committed
214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232
 * It contains the localizable messages extracted from source code.
 * You may modify this file by translating the extracted messages.
 *
 * Each array element represents the translation (value) of a message (key).
 * If the value is empty, the message is considered as not translated.
 * Messages that no longer need translation will have their translations
 * enclosed between a pair of '@@' marks.
 *
 * Message string can be used with plural forms format. Check i18n section
 * of the guide for details.
 *
 * NOTE, this file must be saved in UTF-8 encoding.
 */
return $array;

EOD;
		file_put_contents($fileName, $content);
	}
}