diff --git a/framework/yii/i18n/MessageFormatter.php b/framework/yii/i18n/MessageFormatter.php index 8fcf4bf..a038be1 100644 --- a/framework/yii/i18n/MessageFormatter.php +++ b/framework/yii/i18n/MessageFormatter.php @@ -116,7 +116,7 @@ class MessageFormatter extends Component * Parses an input string according to an [ICU message format](http://userguide.icu-project.org/formatparse/messages) pattern. * * It uses the PHP intl extension's [MessageFormatter::parse()](http://www.php.net/manual/en/messageformatter.parsemessage.php) - * and works around some issues. + * and adds support for named arguments. * Usage of this method requires PHP intl extension to be installed. * * @param string $pattern The pattern to use for parsing the message. @@ -134,11 +134,24 @@ class MessageFormatter extends Component throw new NotSupportedException('You have to install PHP intl extension to use this feature.'); } - // TODO try to support named args -// if (version_compare(PHP_VERSION, '5.5.0', '<')) { -// $message = $this->replaceNamedArguments($message, $params); -// $params = array_values($params); -// } + // replace named arguments + if (($tokens = $this->tokenizePattern($pattern)) === false) { + return false; + } + $map = []; + foreach($tokens as $i => $token) { + if (is_array($token)) { + $param = trim($token[0]); + if (!isset($map[$param])) { + $map[$param] = count($map); + } + $token[0] = $map[$param]; + $tokens[$i] = '{' . implode(',', $token) . '}'; + } + } + $pattern = implode('', $tokens); + $map = array_flip($map); + $formatter = new \MessageFormatter($language, $pattern); if ($formatter === null) { $this->_errorCode = -1; @@ -151,9 +164,12 @@ class MessageFormatter extends Component $this->_errorMessage = $formatter->getErrorMessage(); return false; } else { - return $result; + $values = []; + foreach($result as $key => $value) { + $values[$map[$key]] = $value; + } + return $values; } - } /** diff --git a/tests/unit/framework/i18n/FallbackMessageFormatterTest.php b/tests/unit/framework/i18n/FallbackMessageFormatterTest.php index 910c433..6aa8a22 100644 --- a/tests/unit/framework/i18n/FallbackMessageFormatterTest.php +++ b/tests/unit/framework/i18n/FallbackMessageFormatterTest.php @@ -34,6 +34,24 @@ class FallbackMessageFormatterTest extends TestCase ] ], + [ + '{'.self::SUBJECT.'} is {'.self::N.', number}', // pattern + self::SUBJECT_VALUE.' is '.self::N_VALUE, // expected + [ // params + self::N => self::N_VALUE, + self::SUBJECT => self::SUBJECT_VALUE, + ] + ], + + [ + '{'.self::SUBJECT.'} is {'.self::N.', number, integer}', // pattern + self::SUBJECT_VALUE.' is '.self::N_VALUE, // expected + [ // params + self::N => self::N_VALUE, + self::SUBJECT => self::SUBJECT_VALUE, + ] + ], + // This one was provided by Aura.Intl. Thanks! [<<<_MSG_ {gender_of_host, select, diff --git a/tests/unit/framework/i18n/MessageFormatterTest.php b/tests/unit/framework/i18n/MessageFormatterTest.php index 9030d87..7b20d43 100644 --- a/tests/unit/framework/i18n/MessageFormatterTest.php +++ b/tests/unit/framework/i18n/MessageFormatterTest.php @@ -41,6 +41,15 @@ class MessageFormatterTest extends TestCase ] ], + [ + '{'.self::SUBJECT.'} is {'.self::N.', number, integer}', // pattern + self::SUBJECT_VALUE.' is '.self::N_VALUE, // expected + [ // params + self::N => self::N_VALUE, + self::SUBJECT => self::SUBJECT_VALUE, + ] + ], + // This one was provided by Aura.Intl. Thanks! [<<<_MSG_ {gender_of_host, select, @@ -119,6 +128,79 @@ _MSG_ ]; } + public function parsePatterns() + { + return [ + [ + self::SUBJECT_VALUE.' is {0, number}', // pattern + self::SUBJECT_VALUE.' is '.self::N_VALUE, // expected + [ // params + 0 => self::N_VALUE, + ] + ], + + [ + self::SUBJECT_VALUE.' is {'.self::N.', number}', // pattern + self::SUBJECT_VALUE.' is '.self::N_VALUE, // expected + [ // params + self::N => self::N_VALUE, + ] + ], + + [ + self::SUBJECT_VALUE.' is {'.self::N.', number, integer}', // pattern + self::SUBJECT_VALUE.' is '.self::N_VALUE, // expected + [ // params + self::N => self::N_VALUE, + ] + ], + + [ + "{0,number,integer} monkeys on {1,number,integer} trees make {2,number} monkeys per tree", + "4,560 monkeys on 123 trees make 37.073 monkeys per tree", + [ + 0 => 4560, + 1 => 123, + 2 => 37.073 + ], + 'en_US' + ], + + [ + "{0,number,integer} Affen auf {1,number,integer} Bäumen sind {2,number} Affen pro Baum", + "4.560 Affen auf 123 Bäumen sind 37,073 Affen pro Baum", + [ + 0 => 4560, + 1 => 123, + 2 => 37.073 + ], + 'de', + ], + + [ + "{monkeyCount,number,integer} monkeys on {trees,number,integer} trees make {monkeysPerTree,number} monkeys per tree", + "4,560 monkeys on 123 trees make 37.073 monkeys per tree", + [ + 'monkeyCount' => 4560, + 'trees' => 123, + 'monkeysPerTree' => 37.073 + ], + 'en_US' + ], + + [ + "{monkeyCount,number,integer} Affen auf {trees,number,integer} Bäumen sind {monkeysPerTree,number} Affen pro Baum", + "4.560 Affen auf 123 Bäumen sind 37,073 Affen pro Baum", + [ + 'monkeyCount' => 4560, + 'trees' => 123, + 'monkeysPerTree' => 37.073 + ], + 'de', + ], + ]; + } + /** * @dataProvider patterns */ @@ -129,6 +211,16 @@ _MSG_ $this->assertEquals($expected, $result, $formatter->getErrorMessage()); } + /** + * @dataProvider parsePatterns + */ + public function testParseNamedArguments($pattern, $expected, $args, $locale = 'en_US') + { + $formatter = new MessageFormatter(); + $result = $formatter->parse($pattern, $expected, $locale); + $this->assertEquals($args, $result, $formatter->getErrorMessage() . ' Pattern: ' . $pattern); + } + public function testInsufficientArguments() { $expected = '{'.self::SUBJECT.'} is '.self::N_VALUE;