1 | <?php |
||||
2 | /** |
||||
3 | * @link https://www.yiiframework.com/ |
||||
4 | * @copyright Copyright (c) 2008 Yii Software LLC |
||||
5 | * @license https://www.yiiframework.com/license/ |
||||
6 | */ |
||||
7 | |||||
8 | namespace yii\console\controllers; |
||||
9 | |||||
10 | use Yii; |
||||
11 | use yii\console\Exception; |
||||
12 | use yii\console\ExitCode; |
||||
13 | use yii\db\Connection; |
||||
14 | use yii\db\Query; |
||||
15 | use yii\di\Instance; |
||||
16 | use yii\helpers\Console; |
||||
17 | use yii\helpers\FileHelper; |
||||
18 | use yii\helpers\VarDumper; |
||||
19 | use yii\i18n\GettextPoFile; |
||||
20 | |||||
21 | /** |
||||
22 | * Extracts messages to be translated from source files. |
||||
23 | * |
||||
24 | * The extracted messages can be saved the following depending on `format` |
||||
25 | * setting in config file: |
||||
26 | * |
||||
27 | * - PHP message source files. |
||||
28 | * - ".po" files. |
||||
29 | * - Database. |
||||
30 | * |
||||
31 | * Usage: |
||||
32 | * 1. Create a configuration file using the 'message/config' command: |
||||
33 | * yii message/config /path/to/myapp/messages/config.php |
||||
34 | * 2. Edit the created config file, adjusting it for your web application needs. |
||||
35 | * 3. Run the 'message/extract' command, using created config: |
||||
36 | * yii message /path/to/myapp/messages/config.php |
||||
37 | * |
||||
38 | * @author Qiang Xue <[email protected]> |
||||
39 | * @since 2.0 |
||||
40 | */ |
||||
41 | class MessageController extends \yii\console\Controller |
||||
42 | { |
||||
43 | /** |
||||
44 | * @var string controller default action ID. |
||||
45 | */ |
||||
46 | public $defaultAction = 'extract'; |
||||
47 | /** |
||||
48 | * @var string required, root directory of all source files. |
||||
49 | */ |
||||
50 | public $sourcePath = '@yii'; |
||||
51 | /** |
||||
52 | * @var string required, root directory containing message translations. |
||||
53 | */ |
||||
54 | public $messagePath = '@yii/messages'; |
||||
55 | /** |
||||
56 | * @var array required, list of language codes that the extracted messages |
||||
57 | * should be translated to. For example, ['zh-CN', 'de']. |
||||
58 | */ |
||||
59 | public $languages = []; |
||||
60 | /** |
||||
61 | * @var string|string[] the name of the function for translating messages. |
||||
62 | * This is used as a mark to find the messages to be translated. |
||||
63 | * You may use a string for single function name or an array for multiple function names. |
||||
64 | */ |
||||
65 | public $translator = ['Yii::t', '\Yii::t']; |
||||
66 | /** |
||||
67 | * @var bool whether to sort messages by keys when merging new messages |
||||
68 | * with the existing ones. Defaults to false, which means the new (untranslated) |
||||
69 | * messages will be separated from the old (translated) ones. |
||||
70 | */ |
||||
71 | public $sort = false; |
||||
72 | /** |
||||
73 | * @var bool whether the message file should be overwritten with the merged messages |
||||
74 | */ |
||||
75 | public $overwrite = true; |
||||
76 | /** |
||||
77 | * @var bool whether to remove messages that no longer appear in the source code. |
||||
78 | * Defaults to false, which means these messages will NOT be removed. |
||||
79 | */ |
||||
80 | public $removeUnused = false; |
||||
81 | /** |
||||
82 | * @var bool whether to mark messages that no longer appear in the source code. |
||||
83 | * Defaults to true, which means each of these messages will be enclosed with a pair of '@@' marks. |
||||
84 | */ |
||||
85 | public $markUnused = true; |
||||
86 | /** |
||||
87 | * @var array|null list of patterns that specify which files/directories should NOT be processed. |
||||
88 | * If empty or not set, all files/directories will be processed. |
||||
89 | * See helpers/FileHelper::findFiles() description for pattern matching rules. |
||||
90 | * If a file/directory matches both a pattern in "only" and "except", it will NOT be processed. |
||||
91 | */ |
||||
92 | public $except = [ |
||||
93 | '.*', |
||||
94 | '/.*', |
||||
95 | '/messages', |
||||
96 | '/tests', |
||||
97 | '/runtime', |
||||
98 | '/vendor', |
||||
99 | '/BaseYii.php', // contains examples about Yii::t() |
||||
100 | ]; |
||||
101 | /** |
||||
102 | * @var array|null list of patterns that specify which files (not directories) should be processed. |
||||
103 | * If empty or not set, all files will be processed. |
||||
104 | * See helpers/FileHelper::findFiles() description for pattern matching rules. |
||||
105 | * If a file/directory matches both a pattern in "only" and "except", it will NOT be processed. |
||||
106 | */ |
||||
107 | public $only = ['*.php']; |
||||
108 | /** |
||||
109 | * @var string generated file format. Can be "php", "db", "po" or "pot". |
||||
110 | */ |
||||
111 | public $format = 'php'; |
||||
112 | /** |
||||
113 | * @var string connection component ID for "db" format. |
||||
114 | */ |
||||
115 | public $db = 'db'; |
||||
116 | /** |
||||
117 | * @var string custom name for source message table for "db" format. |
||||
118 | */ |
||||
119 | public $sourceMessageTable = '{{%source_message}}'; |
||||
120 | /** |
||||
121 | * @var string custom name for translation message table for "db" format. |
||||
122 | */ |
||||
123 | public $messageTable = '{{%message}}'; |
||||
124 | /** |
||||
125 | * @var string name of the file that will be used for translations for "po" format. |
||||
126 | */ |
||||
127 | public $catalog = 'messages'; |
||||
128 | /** |
||||
129 | * @var array message categories to ignore. For example, 'yii', 'app*', 'widgets/menu', etc. |
||||
130 | * @see isCategoryIgnored |
||||
131 | */ |
||||
132 | public $ignoreCategories = []; |
||||
133 | /** |
||||
134 | * @var string File header in generated PHP file with messages. This property is used only if [[$format]] is "php". |
||||
135 | * @since 2.0.13 |
||||
136 | */ |
||||
137 | public $phpFileHeader = ''; |
||||
138 | /** |
||||
139 | * @var string|null DocBlock used for messages array in generated PHP file. If `null`, default DocBlock will be used. |
||||
140 | * This property is used only if [[$format]] is "php". |
||||
141 | * @since 2.0.13 |
||||
142 | */ |
||||
143 | public $phpDocBlock; |
||||
144 | |||||
145 | /** |
||||
146 | * @var array Config for messages extraction. |
||||
147 | * @see actionExtract() |
||||
148 | * @see initConfig() |
||||
149 | * @since 2.0.13 |
||||
150 | */ |
||||
151 | protected $config; |
||||
152 | |||||
153 | |||||
154 | /** |
||||
155 | * {@inheritdoc} |
||||
156 | */ |
||||
157 | 69 | public function options($actionID) |
|||
158 | { |
||||
159 | 69 | return array_merge(parent::options($actionID), [ |
|||
160 | 69 | 'sourcePath', |
|||
161 | 69 | 'messagePath', |
|||
162 | 69 | 'languages', |
|||
163 | 69 | 'translator', |
|||
164 | 69 | 'sort', |
|||
165 | 69 | 'overwrite', |
|||
166 | 69 | 'removeUnused', |
|||
167 | 69 | 'markUnused', |
|||
168 | 69 | 'except', |
|||
169 | 69 | 'only', |
|||
170 | 69 | 'format', |
|||
171 | 69 | 'db', |
|||
172 | 69 | 'sourceMessageTable', |
|||
173 | 69 | 'messageTable', |
|||
174 | 69 | 'catalog', |
|||
175 | 69 | 'ignoreCategories', |
|||
176 | 69 | 'phpFileHeader', |
|||
177 | 69 | 'phpDocBlock', |
|||
178 | 69 | ]); |
|||
179 | } |
||||
180 | |||||
181 | /** |
||||
182 | * {@inheritdoc} |
||||
183 | * @since 2.0.8 |
||||
184 | */ |
||||
185 | public function optionAliases() |
||||
186 | { |
||||
187 | return array_merge(parent::optionAliases(), [ |
||||
188 | 'c' => 'catalog', |
||||
189 | 'e' => 'except', |
||||
190 | 'f' => 'format', |
||||
191 | 'i' => 'ignoreCategories', |
||||
192 | 'l' => 'languages', |
||||
193 | 'u' => 'markUnused', |
||||
194 | 'p' => 'messagePath', |
||||
195 | 'o' => 'only', |
||||
196 | 'w' => 'overwrite', |
||||
197 | 'S' => 'sort', |
||||
198 | 't' => 'translator', |
||||
199 | 'm' => 'sourceMessageTable', |
||||
200 | 's' => 'sourcePath', |
||||
201 | 'r' => 'removeUnused', |
||||
202 | ]); |
||||
203 | } |
||||
204 | |||||
205 | /** |
||||
206 | * Creates a configuration file for the "extract" command using command line options specified. |
||||
207 | * |
||||
208 | * The generated configuration file contains parameters required |
||||
209 | * for source code messages extraction. |
||||
210 | * You may use this configuration file with the "extract" command. |
||||
211 | * |
||||
212 | * @param string $filePath output file name or alias. |
||||
213 | * @return int CLI exit code |
||||
214 | * @throws Exception on failure. |
||||
215 | */ |
||||
216 | 6 | public function actionConfig($filePath) |
|||
217 | { |
||||
218 | 6 | $filePath = Yii::getAlias($filePath); |
|||
219 | 6 | $dir = dirname($filePath); |
|||
220 | |||||
221 | 6 | if (file_exists($filePath)) { |
|||
222 | if (!$this->confirm("File '{$filePath}' already exists. Do you wish to overwrite it?")) { |
||||
223 | return ExitCode::OK; |
||||
224 | } |
||||
225 | } |
||||
226 | |||||
227 | 6 | $array = VarDumper::export($this->getOptionValues($this->action->id)); |
|||
228 | 6 | $content = <<<EOD |
|||
229 | 6 | <?php |
|||
230 | /** |
||||
231 | 6 | * Configuration file for 'yii {$this->id}/{$this->defaultAction}' command. |
|||
232 | * |
||||
233 | 6 | * This file is automatically generated by 'yii {$this->id}/{$this->action->id}' command. |
|||
234 | * It contains parameters for source code messages extraction. |
||||
235 | * You may modify this file to suit your needs. |
||||
236 | * |
||||
237 | 6 | * You can use 'yii {$this->id}/{$this->action->id}-template' command to create |
|||
238 | * template configuration file with detailed description for each parameter. |
||||
239 | */ |
||||
240 | 6 | return $array; |
|||
241 | |||||
242 | 6 | EOD; |
|||
243 | |||||
244 | 6 | if (FileHelper::createDirectory($dir) === false || file_put_contents($filePath, $content, LOCK_EX) === false) { |
|||
245 | $this->stdout("Configuration file was NOT created: '{$filePath}'.\n\n", Console::FG_RED); |
||||
246 | return ExitCode::UNSPECIFIED_ERROR; |
||||
247 | } |
||||
248 | |||||
249 | 6 | $this->stdout("Configuration file created: '{$filePath}'.\n\n", Console::FG_GREEN); |
|||
250 | 6 | return ExitCode::OK; |
|||
251 | } |
||||
252 | |||||
253 | /** |
||||
254 | * Creates a configuration file template for the "extract" command. |
||||
255 | * |
||||
256 | * The created configuration file contains detailed instructions on |
||||
257 | * how to customize it to fit for your needs. After customization, |
||||
258 | * you may use this configuration file with the "extract" command. |
||||
259 | * |
||||
260 | * @param string $filePath output file name or alias. |
||||
261 | * @return int CLI exit code |
||||
262 | * @throws Exception on failure. |
||||
263 | */ |
||||
264 | public function actionConfigTemplate($filePath) |
||||
265 | { |
||||
266 | $filePath = Yii::getAlias($filePath); |
||||
267 | |||||
268 | if (file_exists($filePath)) { |
||||
269 | if (!$this->confirm("File '{$filePath}' already exists. Do you wish to overwrite it?")) { |
||||
270 | return ExitCode::OK; |
||||
271 | } |
||||
272 | } |
||||
273 | |||||
274 | if (!copy(Yii::getAlias('@yii/views/messageConfig.php'), $filePath)) { |
||||
0 ignored issues
–
show
Bug
introduced
by
Loading history...
It seems like
Yii::getAlias('@yii/views/messageConfig.php') can also be of type false ; however, parameter $from of copy() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
275 | $this->stdout("Configuration file template was NOT created at '{$filePath}'.\n\n", Console::FG_RED); |
||||
276 | return ExitCode::UNSPECIFIED_ERROR; |
||||
277 | } |
||||
278 | |||||
279 | $this->stdout("Configuration file template created at '{$filePath}'.\n\n", Console::FG_GREEN); |
||||
280 | return ExitCode::OK; |
||||
281 | } |
||||
282 | |||||
283 | /** |
||||
284 | * Extracts messages to be translated from source code. |
||||
285 | * |
||||
286 | * This command will search through source code files and extract |
||||
287 | * messages that need to be translated in different languages. |
||||
288 | * |
||||
289 | * @param string|null $configFile the path or alias of the configuration file. |
||||
290 | * You may use the "yii message/config" command to generate |
||||
291 | * this file and then customize it for your needs. |
||||
292 | * @throws Exception on failure. |
||||
293 | */ |
||||
294 | 63 | public function actionExtract($configFile = null) |
|||
295 | { |
||||
296 | 63 | $this->initConfig($configFile); |
|||
297 | |||||
298 | 60 | $files = FileHelper::findFiles(realpath($this->config['sourcePath']), $this->config); |
|||
299 | |||||
300 | 60 | $messages = []; |
|||
301 | 60 | foreach ($files as $file) { |
|||
302 | 54 | $messages = array_merge_recursive($messages, $this->extractMessages($file, $this->config['translator'], $this->config['ignoreCategories'])); |
|||
303 | } |
||||
304 | |||||
305 | 60 | $catalog = isset($this->config['catalog']) ? $this->config['catalog'] : 'messages'; |
|||
306 | |||||
307 | 60 | if (in_array($this->config['format'], ['php', 'po'])) { |
|||
308 | 45 | foreach ($this->config['languages'] as $language) { |
|||
309 | 45 | $dir = $this->config['messagePath'] . DIRECTORY_SEPARATOR . $language; |
|||
310 | 45 | if (!is_dir($dir) && !@mkdir($dir)) { |
|||
311 | throw new Exception("Directory '{$dir}' can not be created."); |
||||
312 | } |
||||
313 | 45 | if ($this->config['format'] === 'po') { |
|||
314 | 16 | $this->saveMessagesToPO($messages, $dir, $this->config['overwrite'], $this->config['removeUnused'], $this->config['sort'], $catalog, $this->config['markUnused']); |
|||
315 | } else { |
||||
316 | 29 | $this->saveMessagesToPHP($messages, $dir, $this->config['overwrite'], $this->config['removeUnused'], $this->config['sort'], $this->config['markUnused']); |
|||
317 | } |
||||
318 | } |
||||
319 | 15 | } elseif ($this->config['format'] === 'db') { |
|||
320 | /** @var Connection $db */ |
||||
321 | 15 | $db = Instance::ensure($this->config['db'], Connection::className()); |
|||
322 | 15 | $sourceMessageTable = isset($this->config['sourceMessageTable']) ? $this->config['sourceMessageTable'] : '{{%source_message}}'; |
|||
323 | 15 | $messageTable = isset($this->config['messageTable']) ? $this->config['messageTable'] : '{{%message}}'; |
|||
324 | 15 | $this->saveMessagesToDb( |
|||
325 | 15 | $messages, |
|||
326 | 15 | $db, |
|||
327 | 15 | $sourceMessageTable, |
|||
328 | 15 | $messageTable, |
|||
329 | 15 | $this->config['removeUnused'], |
|||
330 | 15 | $this->config['languages'], |
|||
331 | 15 | $this->config['markUnused'] |
|||
332 | 15 | ); |
|||
333 | } elseif ($this->config['format'] === 'pot') { |
||||
334 | $this->saveMessagesToPOT($messages, $this->config['messagePath'], $catalog); |
||||
335 | } |
||||
336 | } |
||||
337 | |||||
338 | /** |
||||
339 | * Saves messages to database. |
||||
340 | * |
||||
341 | * @param array $messages |
||||
342 | * @param Connection $db |
||||
343 | * @param string $sourceMessageTable |
||||
344 | * @param string $messageTable |
||||
345 | * @param bool $removeUnused |
||||
346 | * @param array $languages |
||||
347 | * @param bool $markUnused |
||||
348 | */ |
||||
349 | 15 | protected function saveMessagesToDb($messages, $db, $sourceMessageTable, $messageTable, $removeUnused, $languages, $markUnused) |
|||
350 | { |
||||
351 | 15 | $currentMessages = []; |
|||
352 | 15 | $rows = (new Query())->select(['id', 'category', 'message'])->from($sourceMessageTable)->all($db); |
|||
353 | 15 | foreach ($rows as $row) { |
|||
354 | 14 | $currentMessages[$row['category']][$row['id']] = $row['message']; |
|||
355 | } |
||||
356 | |||||
357 | 15 | $new = []; |
|||
358 | 15 | $obsolete = []; |
|||
359 | |||||
360 | 15 | foreach ($messages as $category => $msgs) { |
|||
361 | 15 | $msgs = array_unique($msgs); |
|||
362 | |||||
363 | 15 | if (isset($currentMessages[$category])) { |
|||
364 | 10 | $new[$category] = array_diff($msgs, $currentMessages[$category]); |
|||
365 | // obsolete messages per category |
||||
366 | 10 | $obsolete += array_diff($currentMessages[$category], $msgs); |
|||
367 | } else { |
||||
368 | 8 | $new[$category] = $msgs; |
|||
369 | } |
||||
370 | } |
||||
371 | |||||
372 | // obsolete categories |
||||
373 | 15 | foreach (array_diff(array_keys($currentMessages), array_keys($messages)) as $category) { |
|||
374 | 9 | $obsolete += $currentMessages[$category]; |
|||
375 | } |
||||
376 | |||||
377 | 15 | if (!$removeUnused) { |
|||
378 | 14 | foreach ($obsolete as $pk => $msg) { |
|||
379 | // skip already marked unused |
||||
380 | 11 | if (strncmp($msg, '@@', 2) === 0 && substr($msg, -2) === '@@') { |
|||
381 | 6 | unset($obsolete[$pk]); |
|||
382 | } |
||||
383 | } |
||||
384 | } |
||||
385 | |||||
386 | 15 | $this->stdout('Inserting new messages...'); |
|||
387 | 15 | $insertCount = 0; |
|||
388 | |||||
389 | 15 | foreach ($new as $category => $msgs) { |
|||
390 | 15 | foreach ($msgs as $msg) { |
|||
391 | 13 | $insertCount++; |
|||
392 | 13 | $db->schema->insert($sourceMessageTable, ['category' => $category, 'message' => $msg]); |
|||
393 | } |
||||
394 | } |
||||
395 | |||||
396 | 15 | $this->stdout($insertCount ? "{$insertCount} saved.\n" : "Nothing to save.\n"); |
|||
397 | |||||
398 | 15 | $this->stdout($removeUnused ? 'Deleting obsoleted messages...' : 'Updating obsoleted messages...'); |
|||
399 | |||||
400 | 15 | if (empty($obsolete)) { |
|||
401 | 6 | $this->stdout("Nothing obsoleted...skipped.\n"); |
|||
402 | } |
||||
403 | |||||
404 | 15 | if ($obsolete) { |
|||
405 | 12 | if ($removeUnused) { |
|||
406 | 1 | $affected = $db->createCommand() |
|||
407 | 1 | ->delete($sourceMessageTable, ['in', 'id', array_keys($obsolete)]) |
|||
408 | 1 | ->execute(); |
|||
409 | 1 | $this->stdout("{$affected} deleted.\n"); |
|||
410 | 11 | } elseif ($markUnused) { |
|||
411 | 9 | $marked = 0; |
|||
412 | 9 | $rows = (new Query()) |
|||
413 | 9 | ->select(['id', 'message']) |
|||
414 | 9 | ->from($sourceMessageTable) |
|||
415 | 9 | ->where(['in', 'id', array_keys($obsolete)]) |
|||
416 | 9 | ->all($db); |
|||
417 | |||||
418 | 9 | foreach ($rows as $row) { |
|||
419 | 9 | $marked++; |
|||
420 | 9 | $db->createCommand()->update( |
|||
421 | 9 | $sourceMessageTable, |
|||
422 | 9 | ['message' => '@@' . $row['message'] . '@@'], |
|||
423 | 9 | ['id' => $row['id']] |
|||
424 | 9 | )->execute(); |
|||
425 | } |
||||
426 | 9 | $this->stdout("{$marked} updated.\n"); |
|||
427 | } else { |
||||
428 | 2 | $this->stdout("kept untouched.\n"); |
|||
429 | } |
||||
430 | } |
||||
431 | |||||
432 | // get fresh message id list |
||||
433 | 15 | $freshMessagesIds = []; |
|||
434 | 15 | $rows = (new Query())->select(['id'])->from($sourceMessageTable)->all($db); |
|||
435 | 15 | foreach ($rows as $row) { |
|||
436 | 15 | $freshMessagesIds[] = $row['id']; |
|||
437 | } |
||||
438 | |||||
439 | 15 | $this->stdout('Generating missing rows...'); |
|||
440 | 15 | $generatedMissingRows = []; |
|||
441 | |||||
442 | 15 | foreach ($languages as $language) { |
|||
443 | 15 | $count = 0; |
|||
444 | |||||
445 | // get list of ids of translations for this language |
||||
446 | 15 | $msgRowsIds = []; |
|||
447 | 15 | $msgRows = (new Query())->select(['id'])->from($messageTable)->where([ |
|||
448 | 15 | 'language' => $language, |
|||
449 | 15 | ])->all($db); |
|||
450 | 15 | foreach ($msgRows as $row) { |
|||
451 | 14 | $msgRowsIds[] = $row['id']; |
|||
452 | } |
||||
453 | |||||
454 | // insert missing |
||||
455 | 15 | foreach ($freshMessagesIds as $id) { |
|||
456 | 15 | if (!in_array($id, $msgRowsIds)) { |
|||
457 | 13 | $db->createCommand() |
|||
458 | 13 | ->insert($messageTable, ['id' => $id, 'language' => $language]) |
|||
459 | 13 | ->execute(); |
|||
460 | 13 | $count++; |
|||
461 | } |
||||
462 | } |
||||
463 | 15 | if ($count) { |
|||
464 | 13 | $generatedMissingRows[] = "{$count} for {$language}"; |
|||
465 | } |
||||
466 | } |
||||
467 | |||||
468 | 15 | $this->stdout($generatedMissingRows ? implode(', ', $generatedMissingRows) . ".\n" : "Nothing to do.\n"); |
|||
469 | |||||
470 | 15 | $this->stdout('Dropping unused languages...'); |
|||
471 | 15 | $droppedLanguages = []; |
|||
472 | |||||
473 | 15 | $currentLanguages = []; |
|||
474 | 15 | $rows = (new Query())->select(['language'])->from($messageTable)->groupBy('language')->all($db); |
|||
475 | 15 | foreach ($rows as $row) { |
|||
476 | 15 | $currentLanguages[] = $row['language']; |
|||
477 | } |
||||
478 | |||||
479 | 15 | foreach ($currentLanguages as $currentLanguage) { |
|||
480 | 15 | if (!in_array($currentLanguage, $languages)) { |
|||
481 | 1 | $deleted = $db->createCommand()->delete($messageTable, 'language=:language', [ |
|||
482 | 1 | 'language' => $currentLanguage, |
|||
483 | 1 | ])->execute(); |
|||
484 | 1 | $droppedLanguages[] = "removed {$deleted} rows for $currentLanguage"; |
|||
485 | } |
||||
486 | } |
||||
487 | |||||
488 | 15 | $this->stdout($droppedLanguages ? implode(', ', $droppedLanguages) . ".\n" : "Nothing to do.\n"); |
|||
489 | } |
||||
490 | |||||
491 | /** |
||||
492 | * Extracts messages from a file. |
||||
493 | * |
||||
494 | * @param string $fileName name of the file to extract messages from |
||||
495 | * @param string $translator name of the function used to translate messages |
||||
496 | * @param array $ignoreCategories message categories to ignore. |
||||
497 | * This parameter is available since version 2.0.4. |
||||
498 | * @return array |
||||
499 | */ |
||||
500 | 54 | protected function extractMessages($fileName, $translator, $ignoreCategories = []) |
|||
501 | { |
||||
502 | 54 | $this->stdout('Extracting messages from '); |
|||
503 | 54 | $this->stdout($fileName, Console::FG_CYAN); |
|||
504 | 54 | $this->stdout("...\n"); |
|||
505 | |||||
506 | 54 | $subject = file_get_contents($fileName); |
|||
507 | 54 | $messages = []; |
|||
508 | 54 | $tokens = token_get_all($subject); |
|||
509 | 54 | foreach ((array) $translator as $currentTranslator) { |
|||
510 | 54 | $translatorTokens = token_get_all('<?php ' . $currentTranslator); |
|||
511 | 54 | array_shift($translatorTokens); |
|||
512 | 54 | $messages = array_merge_recursive($messages, $this->extractMessagesFromTokens($tokens, $translatorTokens, $ignoreCategories)); |
|||
513 | } |
||||
514 | |||||
515 | 54 | $this->stdout("\n"); |
|||
516 | |||||
517 | 54 | return $messages; |
|||
518 | } |
||||
519 | |||||
520 | /** |
||||
521 | * Extracts messages from a parsed PHP tokens list. |
||||
522 | * @param array $tokens tokens to be processed. |
||||
523 | * @param array $translatorTokens translator tokens. |
||||
524 | * @param array $ignoreCategories message categories to ignore. |
||||
525 | * @return array messages. |
||||
526 | */ |
||||
527 | 54 | protected function extractMessagesFromTokens(array $tokens, array $translatorTokens, array $ignoreCategories) |
|||
528 | { |
||||
529 | 54 | $messages = []; |
|||
530 | 54 | $translatorTokensCount = count($translatorTokens); |
|||
531 | 54 | $matchedTokensCount = 0; |
|||
532 | 54 | $buffer = []; |
|||
533 | 54 | $pendingParenthesisCount = 0; |
|||
534 | |||||
535 | 54 | foreach ($tokens as $tokenIndex => $token) { |
|||
536 | // finding out translator call |
||||
537 | 54 | if ($matchedTokensCount < $translatorTokensCount) { |
|||
538 | 54 | if ($this->tokensEqual($token, $translatorTokens[$matchedTokensCount])) { |
|||
539 | 54 | $matchedTokensCount++; |
|||
540 | } else { |
||||
541 | 54 | $matchedTokensCount = 0; |
|||
542 | } |
||||
543 | 54 | } elseif ($matchedTokensCount === $translatorTokensCount) { |
|||
544 | // translator found |
||||
545 | |||||
546 | // end of function call |
||||
547 | 54 | if ($this->tokensEqual(')', $token)) { |
|||
548 | 54 | $pendingParenthesisCount--; |
|||
549 | |||||
550 | 54 | if ($pendingParenthesisCount === 0) { |
|||
551 | // end of translator call or end of something that we can't extract |
||||
552 | 54 | if (isset($buffer[0][0], $buffer[1], $buffer[2][0]) && $buffer[0][0] === T_CONSTANT_ENCAPSED_STRING && $buffer[1] === ',' && $buffer[2][0] === T_CONSTANT_ENCAPSED_STRING) { |
|||
553 | // is valid call we can extract |
||||
554 | 54 | $category = stripcslashes($buffer[0][1]); |
|||
555 | 54 | $category = mb_substr($category, 1, -1); |
|||
556 | |||||
557 | 54 | if (!$this->isCategoryIgnored($category, $ignoreCategories)) { |
|||
558 | 54 | $fullMessage = mb_substr($buffer[2][1], 1, -1); |
|||
559 | 54 | $i = 3; |
|||
560 | 54 | while ($i < count($buffer) - 1 && !is_array($buffer[$i]) && $buffer[$i] === '.') { |
|||
561 | 3 | $fullMessage .= mb_substr($buffer[$i + 1][1], 1, -1); |
|||
562 | 3 | $i += 2; |
|||
563 | } |
||||
564 | |||||
565 | 54 | $message = stripcslashes($fullMessage); |
|||
566 | 54 | $messages[$category][] = $message; |
|||
567 | } |
||||
568 | |||||
569 | 54 | $nestedTokens = array_slice($buffer, 3); |
|||
570 | 54 | if (count($nestedTokens) > $translatorTokensCount) { |
|||
571 | // search for possible nested translator calls |
||||
572 | 54 | $messages = array_merge_recursive($messages, $this->extractMessagesFromTokens($nestedTokens, $translatorTokens, $ignoreCategories)); |
|||
573 | } |
||||
574 | } else { |
||||
575 | // invalid call or dynamic call we can't extract |
||||
576 | $line = Console::ansiFormat($this->getLine($buffer), [Console::FG_CYAN]); |
||||
577 | $skipping = Console::ansiFormat('Skipping line', [Console::FG_YELLOW]); |
||||
578 | $this->stdout("$skipping $line. Make sure both category and message are static strings.\n"); |
||||
579 | } |
||||
580 | |||||
581 | // prepare for the next match |
||||
582 | 54 | $matchedTokensCount = 0; |
|||
583 | 54 | $pendingParenthesisCount = 0; |
|||
584 | 54 | $buffer = []; |
|||
585 | } else { |
||||
586 | 54 | $buffer[] = $token; |
|||
587 | } |
||||
588 | 54 | } elseif ($this->tokensEqual('(', $token)) { |
|||
589 | // count beginning of function call, skipping translator beginning |
||||
590 | |||||
591 | // If we are not yet inside the translator, make sure that it's beginning of the real translator. |
||||
592 | // See https://github.com/yiisoft/yii2/issues/16828 |
||||
593 | 54 | if ($pendingParenthesisCount === 0) { |
|||
594 | 54 | $previousTokenIndex = $tokenIndex - $matchedTokensCount - 1; |
|||
595 | 54 | if (is_array($tokens[$previousTokenIndex])) { |
|||
596 | 54 | $previousToken = $tokens[$previousTokenIndex][0]; |
|||
597 | 54 | if (in_array($previousToken, [T_OBJECT_OPERATOR, T_PAAMAYIM_NEKUDOTAYIM], true)) { |
|||
598 | 3 | $matchedTokensCount = 0; |
|||
599 | 3 | continue; |
|||
600 | } |
||||
601 | } |
||||
602 | } |
||||
603 | |||||
604 | 54 | if ($pendingParenthesisCount > 0) { |
|||
605 | 6 | $buffer[] = $token; |
|||
606 | } |
||||
607 | 54 | $pendingParenthesisCount++; |
|||
608 | 54 | } elseif (isset($token[0]) && !in_array($token[0], [T_WHITESPACE, T_COMMENT])) { |
|||
609 | // ignore comments and whitespaces |
||||
610 | 54 | $buffer[] = $token; |
|||
611 | } |
||||
612 | } |
||||
613 | } |
||||
614 | |||||
615 | 54 | return $messages; |
|||
616 | } |
||||
617 | |||||
618 | /** |
||||
619 | * The method checks, whether the $category is ignored according to $ignoreCategories array. |
||||
620 | * |
||||
621 | * Examples: |
||||
622 | * |
||||
623 | * - `myapp` - will be ignored only `myapp` category; |
||||
624 | * - `myapp*` - will be ignored by all categories beginning with `myapp` (`myapp`, `myapplication`, `myapprove`, `myapp/widgets`, `myapp.widgets`, etc). |
||||
625 | * |
||||
626 | * @param string $category category that is checked |
||||
627 | * @param array $ignoreCategories message categories to ignore. |
||||
628 | * @return bool |
||||
629 | * @since 2.0.7 |
||||
630 | */ |
||||
631 | 54 | protected function isCategoryIgnored($category, array $ignoreCategories) |
|||
632 | { |
||||
633 | 54 | if (!empty($ignoreCategories)) { |
|||
634 | 3 | if (in_array($category, $ignoreCategories, true)) { |
|||
635 | 3 | return true; |
|||
636 | } |
||||
637 | 3 | foreach ($ignoreCategories as $pattern) { |
|||
638 | 3 | if (strpos($pattern, '*') > 0 && strpos($category, rtrim($pattern, '*')) === 0) { |
|||
639 | 3 | return true; |
|||
640 | } |
||||
641 | } |
||||
642 | } |
||||
643 | |||||
644 | 54 | return false; |
|||
645 | } |
||||
646 | |||||
647 | /** |
||||
648 | * Finds out if two PHP tokens are equal. |
||||
649 | * |
||||
650 | * @param array|string $a |
||||
651 | * @param array|string $b |
||||
652 | * @return bool |
||||
653 | * @since 2.0.1 |
||||
654 | */ |
||||
655 | 54 | protected function tokensEqual($a, $b) |
|||
656 | { |
||||
657 | 54 | if (is_string($a) && is_string($b)) { |
|||
658 | 54 | return $a === $b; |
|||
659 | } |
||||
660 | 54 | if (isset($a[0], $a[1], $b[0], $b[1])) { |
|||
661 | 54 | return $a[0] === $b[0] && $a[1] == $b[1]; |
|||
662 | } |
||||
663 | |||||
664 | 54 | return false; |
|||
665 | } |
||||
666 | |||||
667 | /** |
||||
668 | * Finds out a line of the first non-char PHP token found. |
||||
669 | * |
||||
670 | * @param array $tokens |
||||
671 | * @return int|string |
||||
672 | * @since 2.0.1 |
||||
673 | */ |
||||
674 | protected function getLine($tokens) |
||||
675 | { |
||||
676 | foreach ($tokens as $token) { |
||||
677 | if (isset($token[2])) { |
||||
678 | return $token[2]; |
||||
679 | } |
||||
680 | } |
||||
681 | |||||
682 | return 'unknown'; |
||||
683 | } |
||||
684 | |||||
685 | /** |
||||
686 | * Writes messages into PHP files. |
||||
687 | * |
||||
688 | * @param array $messages |
||||
689 | * @param string $dirName name of the directory to write to |
||||
690 | * @param bool $overwrite if existing file should be overwritten without backup |
||||
691 | * @param bool $removeUnused if obsolete translations should be removed |
||||
692 | * @param bool $sort if translations should be sorted |
||||
693 | * @param bool $markUnused if obsolete translations should be marked |
||||
694 | */ |
||||
695 | 29 | protected function saveMessagesToPHP($messages, $dirName, $overwrite, $removeUnused, $sort, $markUnused) |
|||
696 | { |
||||
697 | 29 | foreach ($messages as $category => $msgs) { |
|||
698 | 23 | $file = str_replace('\\', '/', "$dirName/$category.php"); |
|||
699 | 23 | $path = dirname($file); |
|||
700 | 23 | FileHelper::createDirectory($path); |
|||
701 | 23 | $msgs = array_values(array_unique($msgs)); |
|||
702 | 23 | $coloredFileName = Console::ansiFormat($file, [Console::FG_CYAN]); |
|||
703 | 23 | $this->stdout("Saving messages to $coloredFileName...\n"); |
|||
704 | 23 | $this->saveMessagesCategoryToPHP($msgs, $file, $overwrite, $removeUnused, $sort, $category, $markUnused); |
|||
705 | } |
||||
706 | |||||
707 | 29 | if ($removeUnused) { |
|||
708 | 7 | $this->deleteUnusedPhpMessageFiles(array_keys($messages), $dirName); |
|||
709 | } |
||||
710 | } |
||||
711 | |||||
712 | /** |
||||
713 | * Writes category messages into PHP file. |
||||
714 | * |
||||
715 | * @param array $messages |
||||
716 | * @param string $fileName name of the file to write to |
||||
717 | * @param bool $overwrite if existing file should be overwritten without backup |
||||
718 | * @param bool $removeUnused if obsolete translations should be removed |
||||
719 | * @param bool $sort if translations should be sorted |
||||
720 | * @param string $category message category |
||||
721 | * @param bool $markUnused if obsolete translations should be marked |
||||
722 | * @return int exit code |
||||
723 | */ |
||||
724 | 23 | protected function saveMessagesCategoryToPHP($messages, $fileName, $overwrite, $removeUnused, $sort, $category, $markUnused) |
|||
725 | { |
||||
726 | 23 | if (is_file($fileName)) { |
|||
727 | 16 | $rawExistingMessages = require $fileName; |
|||
728 | 16 | $existingMessages = $rawExistingMessages; |
|||
729 | 16 | sort($messages); |
|||
730 | 16 | ksort($existingMessages); |
|||
731 | 16 | if (array_keys($existingMessages) === $messages && (!$sort || array_keys($rawExistingMessages) === $messages)) { |
|||
732 | 10 | $this->stdout("Nothing new in \"$category\" category... Nothing to save.\n\n", Console::FG_GREEN); |
|||
733 | 10 | return ExitCode::OK; |
|||
734 | } |
||||
735 | 7 | unset($rawExistingMessages); |
|||
736 | 7 | $merged = []; |
|||
737 | 7 | $untranslated = []; |
|||
738 | 7 | foreach ($messages as $message) { |
|||
739 | 7 | if (array_key_exists($message, $existingMessages) && $existingMessages[$message] !== '') { |
|||
740 | 4 | $merged[$message] = $existingMessages[$message]; |
|||
741 | } else { |
||||
742 | 6 | $untranslated[] = $message; |
|||
743 | } |
||||
744 | } |
||||
745 | 7 | ksort($merged); |
|||
746 | 7 | sort($untranslated); |
|||
747 | 7 | $todo = []; |
|||
748 | 7 | foreach ($untranslated as $message) { |
|||
749 | 6 | $todo[$message] = ''; |
|||
750 | } |
||||
751 | 7 | ksort($existingMessages); |
|||
752 | 7 | foreach ($existingMessages as $message => $translation) { |
|||
753 | 7 | if (!$removeUnused && !isset($merged[$message]) && !isset($todo[$message])) { |
|||
754 | 3 | if (!$markUnused || (!empty($translation) && (strncmp($translation, '@@', 2) === 0 && substr_compare($translation, '@@', -2, 2) === 0))) { |
|||
755 | 2 | $todo[$message] = $translation; |
|||
756 | } else { |
||||
757 | 1 | $todo[$message] = '@@' . $translation . '@@'; |
|||
758 | } |
||||
759 | } |
||||
760 | } |
||||
761 | 7 | $merged = array_merge($merged, $todo); |
|||
762 | 7 | if ($sort) { |
|||
763 | 1 | ksort($merged); |
|||
764 | } |
||||
765 | 7 | if (false === $overwrite) { |
|||
766 | $fileName .= '.merged'; |
||||
767 | } |
||||
768 | 7 | $this->stdout("Translation merged.\n"); |
|||
769 | } else { |
||||
770 | 11 | $merged = []; |
|||
771 | 11 | foreach ($messages as $message) { |
|||
772 | 11 | $merged[$message] = ''; |
|||
773 | } |
||||
774 | 11 | ksort($merged); |
|||
775 | } |
||||
776 | |||||
777 | 17 | $array = VarDumper::export($merged); |
|||
778 | 17 | $content = <<<EOD |
|||
779 | 17 | <?php |
|||
780 | 17 | {$this->config['phpFileHeader']}{$this->config['phpDocBlock']} |
|||
781 | 17 | return $array; |
|||
782 | |||||
783 | 17 | EOD; |
|||
784 | |||||
785 | 17 | if (file_put_contents($fileName, $content, LOCK_EX) === false) { |
|||
786 | $this->stdout("Translation was NOT saved.\n\n", Console::FG_RED); |
||||
787 | return ExitCode::UNSPECIFIED_ERROR; |
||||
788 | } |
||||
789 | |||||
790 | 17 | $this->stdout("Translation saved.\n\n", Console::FG_GREEN); |
|||
791 | 17 | return ExitCode::OK; |
|||
792 | } |
||||
793 | |||||
794 | /** |
||||
795 | * Writes messages into PO file. |
||||
796 | * |
||||
797 | * @param array $messages |
||||
798 | * @param string $dirName name of the directory to write to |
||||
799 | * @param bool $overwrite if existing file should be overwritten without backup |
||||
800 | * @param bool $removeUnused if obsolete translations should be removed |
||||
801 | * @param bool $sort if translations should be sorted |
||||
802 | * @param string $catalog message catalog |
||||
803 | * @param bool $markUnused if obsolete translations should be marked |
||||
804 | */ |
||||
805 | 16 | protected function saveMessagesToPO($messages, $dirName, $overwrite, $removeUnused, $sort, $catalog, $markUnused) |
|||
806 | { |
||||
807 | 16 | $file = str_replace('\\', '/', "$dirName/$catalog.po"); |
|||
808 | 16 | FileHelper::createDirectory(dirname($file)); |
|||
809 | 16 | $this->stdout("Saving messages to $file...\n"); |
|||
810 | |||||
811 | 16 | $poFile = new GettextPoFile(); |
|||
812 | |||||
813 | 16 | $merged = []; |
|||
814 | 16 | $todos = []; |
|||
815 | |||||
816 | 16 | $hasSomethingToWrite = false; |
|||
817 | 16 | foreach ($messages as $category => $msgs) { |
|||
818 | 16 | $notTranslatedYet = []; |
|||
819 | 16 | $msgs = array_values(array_unique($msgs)); |
|||
820 | |||||
821 | 16 | if (is_file($file)) { |
|||
822 | 10 | $existingMessages = $poFile->load($file, $category); |
|||
823 | |||||
824 | 10 | sort($msgs); |
|||
825 | 10 | ksort($existingMessages); |
|||
826 | 10 | if (array_keys($existingMessages) == $msgs) { |
|||
827 | 4 | $this->stdout("Nothing new in \"$category\" category...\n"); |
|||
828 | |||||
829 | 4 | sort($msgs); |
|||
830 | 4 | foreach ($msgs as $message) { |
|||
831 | 4 | $merged[$category . chr(4) . $message] = $existingMessages[$message]; |
|||
832 | } |
||||
833 | 4 | ksort($merged); |
|||
834 | 4 | continue; |
|||
835 | } |
||||
836 | |||||
837 | // merge existing message translations with new message translations |
||||
838 | 7 | foreach ($msgs as $message) { |
|||
839 | 7 | if (array_key_exists($message, $existingMessages) && $existingMessages[$message] !== '') { |
|||
840 | 4 | $merged[$category . chr(4) . $message] = $existingMessages[$message]; |
|||
841 | } else { |
||||
842 | 6 | $notTranslatedYet[] = $message; |
|||
843 | } |
||||
844 | } |
||||
845 | 7 | ksort($merged); |
|||
846 | 7 | sort($notTranslatedYet); |
|||
847 | |||||
848 | // collect not yet translated messages |
||||
849 | 7 | foreach ($notTranslatedYet as $message) { |
|||
850 | 6 | $todos[$category . chr(4) . $message] = ''; |
|||
851 | } |
||||
852 | |||||
853 | // add obsolete unused messages |
||||
854 | 7 | foreach ($existingMessages as $message => $translation) { |
|||
855 | 7 | if (!$removeUnused && !isset($merged[$category . chr(4) . $message]) && !isset($todos[$category . chr(4) . $message])) { |
|||
856 | 3 | if (!$markUnused || (!empty($translation) && (substr($translation, 0, 2) === '@@' && substr($translation, -2) === '@@'))) { |
|||
857 | 2 | $todos[$category . chr(4) . $message] = $translation; |
|||
858 | } else { |
||||
859 | 1 | $todos[$category . chr(4) . $message] = '@@' . $translation . '@@'; |
|||
860 | } |
||||
861 | } |
||||
862 | } |
||||
863 | |||||
864 | 7 | $merged = array_merge($merged, $todos); |
|||
865 | 7 | if ($sort) { |
|||
866 | 1 | ksort($merged); |
|||
867 | } |
||||
868 | |||||
869 | 7 | if ($overwrite === false) { |
|||
870 | 7 | $file .= '.merged'; |
|||
871 | } |
||||
872 | } else { |
||||
873 | 10 | sort($msgs); |
|||
874 | 10 | foreach ($msgs as $message) { |
|||
875 | 10 | $merged[$category . chr(4) . $message] = ''; |
|||
876 | } |
||||
877 | 10 | ksort($merged); |
|||
878 | } |
||||
879 | 16 | $this->stdout("Category \"$category\" merged.\n"); |
|||
880 | 16 | $hasSomethingToWrite = true; |
|||
881 | } |
||||
882 | 16 | if ($hasSomethingToWrite) { |
|||
883 | 16 | $poFile->save($file, $merged); |
|||
884 | 16 | $this->stdout("Translation saved.\n", Console::FG_GREEN); |
|||
885 | } else { |
||||
886 | 3 | $this->stdout("Nothing to save.\n", Console::FG_GREEN); |
|||
887 | } |
||||
888 | } |
||||
889 | |||||
890 | /** |
||||
891 | * Writes messages into POT file. |
||||
892 | * |
||||
893 | * @param array $messages |
||||
894 | * @param string $dirName name of the directory to write to |
||||
895 | * @param string $catalog message catalog |
||||
896 | * @since 2.0.6 |
||||
897 | */ |
||||
898 | protected function saveMessagesToPOT($messages, $dirName, $catalog) |
||||
899 | { |
||||
900 | $file = str_replace('\\', '/', "$dirName/$catalog.pot"); |
||||
901 | FileHelper::createDirectory(dirname($file)); |
||||
902 | $this->stdout("Saving messages to $file...\n"); |
||||
903 | |||||
904 | $poFile = new GettextPoFile(); |
||||
905 | |||||
906 | $merged = []; |
||||
907 | |||||
908 | $hasSomethingToWrite = false; |
||||
909 | foreach ($messages as $category => $msgs) { |
||||
910 | $msgs = array_values(array_unique($msgs)); |
||||
911 | |||||
912 | sort($msgs); |
||||
913 | foreach ($msgs as $message) { |
||||
914 | $merged[$category . chr(4) . $message] = ''; |
||||
915 | } |
||||
916 | $this->stdout("Category \"$category\" merged.\n"); |
||||
917 | $hasSomethingToWrite = true; |
||||
918 | } |
||||
919 | if ($hasSomethingToWrite) { |
||||
920 | ksort($merged); |
||||
921 | $poFile->save($file, $merged); |
||||
922 | $this->stdout("Translation saved.\n", Console::FG_GREEN); |
||||
923 | } else { |
||||
924 | $this->stdout("Nothing to save.\n", Console::FG_GREEN); |
||||
925 | } |
||||
926 | } |
||||
927 | |||||
928 | 7 | private function deleteUnusedPhpMessageFiles($existingCategories, $dirName) |
|||
929 | { |
||||
930 | 7 | $messageFiles = FileHelper::findFiles($dirName); |
|||
931 | 7 | foreach ($messageFiles as $messageFile) { |
|||
932 | 7 | $categoryFileName = str_replace($dirName, '', $messageFile); |
|||
933 | 7 | $categoryFileName = ltrim($categoryFileName, DIRECTORY_SEPARATOR); |
|||
934 | 7 | $category = preg_replace('#\.php$#', '', $categoryFileName); |
|||
935 | 7 | $category = str_replace(DIRECTORY_SEPARATOR, '/', $category); |
|||
936 | |||||
937 | 7 | if (!in_array($category, $existingCategories, true)) { |
|||
938 | 3 | unlink($messageFile); |
|||
939 | } |
||||
940 | } |
||||
941 | } |
||||
942 | |||||
943 | /** |
||||
944 | * @param string $configFile |
||||
945 | * @throws Exception If configuration file does not exists. |
||||
946 | * @since 2.0.13 |
||||
947 | */ |
||||
948 | 63 | protected function initConfig($configFile) |
|||
949 | { |
||||
950 | 63 | $configFileContent = []; |
|||
951 | 63 | if ($configFile !== null) { |
|||
952 | 63 | $configFile = Yii::getAlias($configFile); |
|||
953 | 63 | if (!is_file($configFile)) { |
|||
954 | 3 | throw new Exception("The configuration file does not exist: $configFile"); |
|||
955 | } |
||||
956 | 60 | $configFileContent = require $configFile; |
|||
957 | } |
||||
958 | |||||
959 | 60 | $this->config = array_merge( |
|||
960 | 60 | $this->getOptionValues($this->action->id), |
|||
961 | 60 | $configFileContent, |
|||
962 | 60 | $this->getPassedOptionValues() |
|||
963 | 60 | ); |
|||
964 | 60 | $this->config['sourcePath'] = Yii::getAlias($this->config['sourcePath']); |
|||
965 | 60 | $this->config['messagePath'] = Yii::getAlias($this->config['messagePath']); |
|||
966 | |||||
967 | 60 | if (!isset($this->config['sourcePath'], $this->config['languages'])) { |
|||
968 | throw new Exception('The configuration file must specify "sourcePath" and "languages".'); |
||||
969 | } |
||||
970 | 60 | if (!is_dir($this->config['sourcePath'])) { |
|||
971 | throw new Exception("The source path {$this->config['sourcePath']} is not a valid directory."); |
||||
972 | } |
||||
973 | 60 | if (empty($this->config['format']) || !in_array($this->config['format'], ['php', 'po', 'pot', 'db'])) { |
|||
974 | throw new Exception('Format should be either "php", "po", "pot" or "db".'); |
||||
975 | } |
||||
976 | 60 | if (in_array($this->config['format'], ['php', 'po', 'pot'])) { |
|||
977 | 45 | if (!isset($this->config['messagePath'])) { |
|||
978 | throw new Exception('The configuration file must specify "messagePath".'); |
||||
979 | } |
||||
980 | 45 | if (!is_dir($this->config['messagePath'])) { |
|||
981 | throw new Exception("The message path {$this->config['messagePath']} is not a valid directory."); |
||||
982 | } |
||||
983 | } |
||||
984 | 60 | if (empty($this->config['languages'])) { |
|||
985 | throw new Exception('Languages cannot be empty.'); |
||||
986 | } |
||||
987 | |||||
988 | 60 | if ($this->config['format'] === 'php' && $this->config['phpDocBlock'] === null) { |
|||
989 | $this->config['phpDocBlock'] = <<<DOCBLOCK |
||||
990 | /** |
||||
991 | * Message translations. |
||||
992 | * |
||||
993 | * This file is automatically generated by 'yii {$this->id}/{$this->action->id}' command. |
||||
994 | * It contains the localizable messages extracted from source code. |
||||
995 | * You may modify this file by translating the extracted messages. |
||||
996 | * |
||||
997 | * Each array element represents the translation (value) of a message (key). |
||||
998 | * If the value is empty, the message is considered as not translated. |
||||
999 | * Messages that no longer need translation will have their translations |
||||
1000 | * enclosed between a pair of '@@' marks. |
||||
1001 | * |
||||
1002 | * Message string can be used with plural forms format. Check i18n section |
||||
1003 | * of the guide for details. |
||||
1004 | * |
||||
1005 | * NOTE: this file must be saved in UTF-8 encoding. |
||||
1006 | */ |
||||
1007 | DOCBLOCK; |
||||
1008 | } |
||||
1009 | } |
||||
1010 | } |
||||
1011 |