From 72ec91499284795b5206c5b84e0d075ccf1308c6 Mon Sep 17 00:00:00 2001 From: Klimov Paul Date: Wed, 13 May 2015 15:49:00 +0300 Subject: [PATCH] `yii\console\controllers\MessageController` improved allowing extraction of nested translator calls --- framework/CHANGELOG.md | 1 + .../console/controllers/MessageController.php | 78 ++++++++++++++++------ .../controllers/BaseMessageControllerTest.php | 21 ++++++ 3 files changed, 78 insertions(+), 22 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 487c54c..168b440 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -5,6 +5,7 @@ Yii Framework 2 Change Log ----------------------- - Bug #8322: `yii\behaviors\TimestampBehavior::touch()` now throws an exception if owner is new record (klimov-paul) +- Enh #8286: `yii\console\controllers\MessageController` improved allowing extraction of nested translator calls (klimov-paul) 2.0.4 May 10, 2015 diff --git a/framework/console/controllers/MessageController.php b/framework/console/controllers/MessageController.php index 49a1299..1ecc9a5 100644 --- a/framework/console/controllers/MessageController.php +++ b/framework/console/controllers/MessageController.php @@ -248,33 +248,55 @@ class MessageController extends Controller { $coloredFileName = Console::ansiFormat($fileName, [Console::FG_CYAN]); $this->stdout("Extracting messages from $coloredFileName...\n"); + $subject = file_get_contents($fileName); $messages = []; foreach ((array)$translator as $currentTranslator) { $translatorTokens = token_get_all('extractMessagesFromTokens($tokens, $translatorTokens, $ignoreCategories)); + } - $translatorTokensCount = count($translatorTokens); - $matchedTokensCount = 0; - $buffer = []; + $this->stdout("\n"); - $tokens = token_get_all($subject); - foreach ($tokens as $token) { - // finding out translator call - if ($matchedTokensCount < $translatorTokensCount) { - if ($this->tokensEqual($token, $translatorTokens[$matchedTokensCount])) { - $matchedTokensCount++; - } else { - $matchedTokensCount = 0; - } - } elseif ($matchedTokensCount === $translatorTokensCount) { - // translator found + return $messages; + } + + /** + * Extracts messages from a parsed PHP tokens list. + * @param array $tokens tokens to be processed. + * @param array $translatorTokens translator tokens. + * @param array $ignoreCategories message categories to ignore. + * @return array messages. + */ + private function extractMessagesFromTokens(array $tokens, array $translatorTokens, array $ignoreCategories) + { + $messages = []; + $translatorTokensCount = count($translatorTokens); + $matchedTokensCount = 0; + $buffer = []; + $pendingParenthesisCount = 0; + + foreach ($tokens as $token) { + // finding out translator call + if ($matchedTokensCount < $translatorTokensCount) { + if ($this->tokensEqual($token, $translatorTokens[$matchedTokensCount])) { + $matchedTokensCount++; + } else { + $matchedTokensCount = 0; + } + } elseif ($matchedTokensCount === $translatorTokensCount) { + // translator found + + // end of function call + if ($this->tokensEqual(')', $token)) { + $pendingParenthesisCount--; - // end of translator call or end of something that we can't extract - if ($this->tokensEqual(')', $token)) { + if ($pendingParenthesisCount === 0) { + // end of translator call or end of something that we can't extract 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) { // is valid call we can extract - $category = stripcslashes($buffer[0][1]); $category = mb_substr($category, 1, mb_strlen($category) - 2); @@ -284,9 +306,14 @@ class MessageController extends Controller $messages[$category][] = $message; } + + $nestedTokens = array_slice($buffer, 3); + if (count($nestedTokens) > $translatorTokensCount) { + // search for possible nested translator calls + $messages = array_merge_recursive($messages, $this->extractMessagesFromTokens($nestedTokens, $translatorTokens, $ignoreCategories)); + } } else { // invalid call or dynamic call we can't extract - $line = Console::ansiFormat($this->getLine($buffer), [Console::FG_CYAN]); $skipping = Console::ansiFormat('Skipping line', [Console::FG_YELLOW]); $this->stdout("$skipping $line. Make sure both category and message are static strings.\n"); @@ -294,17 +321,24 @@ class MessageController extends Controller // prepare for the next match $matchedTokensCount = 0; + $pendingParenthesisCount = 0; $buffer = []; - } elseif ($token !== '(' && isset($token[0]) && !in_array($token[0], [T_WHITESPACE, T_COMMENT])) { - // ignore comments, whitespaces and beginning of function call + } else { $buffer[] = $token; } + } elseif ($this->tokensEqual('(', $token)) { + // count beginning of function call, skipping translator beginning + if ($pendingParenthesisCount > 0) { + $buffer[] = $token; + } + $pendingParenthesisCount++; + } elseif (isset($token[0]) && !in_array($token[0], [T_WHITESPACE, T_COMMENT])) { + // ignore comments and whitespaces + $buffer[] = $token; } } } - $this->stdout("\n"); - return $messages; } diff --git a/tests/framework/console/controllers/BaseMessageControllerTest.php b/tests/framework/console/controllers/BaseMessageControllerTest.php index f720837..802daab 100644 --- a/tests/framework/console/controllers/BaseMessageControllerTest.php +++ b/tests/framework/console/controllers/BaseMessageControllerTest.php @@ -377,6 +377,27 @@ abstract class BaseMessageControllerTest extends TestCase $this->assertArrayNotHasKey($message3, $messages2, "message3 not found in category2. Command output:\n\n" . $out); $this->assertArrayNotHasKey($message2, $messages2, "message2 found in category2. Command output:\n\n" . $out); } + + /** + * @depends testCreateTranslation + * + * @see https://github.com/yiisoft/yii2/issues/8286 + */ + public function testCreateTranslationFromNested() + { + $category = 'test.category1'; + $mainMessage = 'main message'; + $nestedMessage = 'nested message'; + $sourceFileContent = "Yii::t('{$category}', '{$mainMessage}', ['param' => Yii::t('{$category}', '{$nestedMessage}')]);"; + $this->createSourceFile($sourceFileContent); + + $this->saveConfigFile($this->getConfig()); + $out = $this->runMessageControllerAction('extract', [$this->configFileName]); + + $messages = $this->loadMessages($category); + $this->assertArrayHasKey($mainMessage, $messages, "\"$mainMessage\" is missing in translation file. Command output:\n\n" . $out); + $this->assertArrayHasKey($nestedMessage, $messages, "\"$nestedMessage\" is missing in translation file. Command output:\n\n" . $out); + } } class MessageControllerMock extends MessageController