Browse Source

Merge branch 'master' of github.com:yiisoft/yii2

* 'master' of github.com:yiisoft/yii2: (27 commits)
  Fixed typos
  fixes #995
  Fixed option merging in Console::prompt()
  changed default button style
  typo fix.
  Refactored getModule and hasModule.
  Added intl check to tests, better error reporting, credited Aura.Intl for the most complex test pattern
  Better MessageFormatter tests
  GII sub-modules support - see #871
  Module::getModule and Module::hasModule support for sub-modules - see #983
  removed needFix method
  Removed intl version check since it's very inconsistent and it seems bundled versions are always the same ones for the same PHP versions
  Fixes PHP 5.5 weird placeholder replacememt in case no arguments are provided at all (https://bugs.php.net/bug.php?id=65920).
  changed plural format for BaseListView
  applied new style of Yii::t params to all occurences
  fixed I18N handling of special param values and broken message tags
  adjusted I18N to be consistent with intl message formatting
  improved unit tests for ICU message formatter
  fixed regex for multiline patterns
  intl message parser now handles too many or too less args
  ...
tags/2.0.0-beta
Carsten Brandt 11 years ago
parent
commit
f273e0542c
  1. 114
      build/controllers/LocaleController.php
  2. 230
      docs/guide/i18n.md
  3. 3
      docs/guide/view.md
  4. 6
      framework/yii/base/Model.php
  5. 23
      framework/yii/base/Module.php
  6. 3
      framework/yii/console/Application.php
  7. 4
      framework/yii/console/Controller.php
  8. 4
      framework/yii/console/controllers/HelpController.php
  9. 2
      framework/yii/data/ActiveDataProvider.php
  10. 2
      framework/yii/gii/generators/controller/Generator.php
  11. 2
      framework/yii/gii/generators/crud/templates/views/index.php
  12. 2
      framework/yii/gii/generators/crud/templates/views/view.php
  13. 7
      framework/yii/gii/generators/module/Generator.php
  14. 4
      framework/yii/helpers/BaseConsole.php
  15. 103
      framework/yii/i18n/I18N.php
  16. 117
      framework/yii/i18n/MessageFormatter.php
  17. 627
      framework/yii/i18n/data/plurals.php
  18. 109
      framework/yii/i18n/data/plurals.xml
  19. 4
      framework/yii/requirements/requirements.php
  20. 2
      framework/yii/web/Controller.php
  21. 4
      framework/yii/widgets/BaseListView.php
  22. 9
      tests/unit/data/i18n/messages/de_DE/test.php
  23. 7
      tests/unit/data/i18n/messages/en_US/test.php
  24. 4
      tests/unit/framework/BaseYiiTest.php
  25. 2
      tests/unit/framework/i18n/GettextMessageSourceTest.php
  26. 85
      tests/unit/framework/i18n/I18NTest.php
  27. 177
      tests/unit/framework/i18n/MessageFormatterTest.php

114
build/controllers/LocaleController.php

@ -1,114 +0,0 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\build\controllers;
use yii\console\Exception;
use yii\console\Controller;
/**
* http://www.unicode.org/cldr/charts/supplemental/language_plural_rules.html
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class LocaleController extends Controller
{
public $defaultAction = 'plural';
/**
* Generates the plural rules data.
*
* This command will parse the plural rule XML file from CLDR and convert them
* into appropriate PHP representation to support Yii message translation feature.
* @param string $xmlFile the original plural rule XML file (from CLDR). This file may be found in
* http://www.unicode.org/Public/cldr/latest/core.zip
* Extract the zip file and locate the file "common/supplemental/plurals.xml".
* @throws Exception
*/
public function actionPlural($xmlFile)
{
if (!is_file($xmlFile)) {
throw new Exception("The source plural rule file does not exist: $xmlFile");
}
$xml = simplexml_load_file($xmlFile);
$allRules = array();
$patterns = array(
'/n in 0..1/' => '(n==0||n==1)',
'/\s+is\s+not\s+/i' => '!=', //is not
'/\s+is\s+/i' => '==', //is
'/n\s+mod\s+(\d+)/i' => 'fmod(n,$1)', //mod (CLDR's "mod" is "fmod()", not "%")
'/^(.*?)\s+not\s+in\s+(\d+)\.\.(\d+)/i' => '!in_array($1,range($2,$3))', //not in
'/^(.*?)\s+in\s+(\d+)\.\.(\d+)/i' => 'in_array($1,range($2,$3))', //in
'/^(.*?)\s+not\s+within\s+(\d+)\.\.(\d+)/i' => '($1<$2||$1>$3)', //not within
'/^(.*?)\s+within\s+(\d+)\.\.(\d+)/i' => '($1>=$2&&$1<=$3)', //within
);
foreach ($xml->plurals->pluralRules as $node) {
$attributes = $node->attributes();
$locales = explode(' ', $attributes['locales']);
$rules = array();
if (!empty($node->pluralRule)) {
foreach ($node->pluralRule as $rule) {
$expr_or = preg_split('/\s+or\s+/i', $rule);
foreach ($expr_or as $key_or => $val_or) {
$expr_and = preg_split('/\s+and\s+/i', $val_or);
$expr_and = preg_replace(array_keys($patterns), array_values($patterns), $expr_and);
$expr_or[$key_or] = implode('&&', $expr_and);
}
$expr = preg_replace('/\\bn\\b/', '$n', implode('||', $expr_or));
$rules[] = preg_replace_callback('/range\((\d+),(\d+)\)/', function ($matches) {
if ($matches[2] - $matches[1] <= 5) {
return 'array(' . implode(',', range($matches[1], $matches[2])) . ')';
} else {
return $matches[0];
}
}, $expr);
}
foreach ($locales as $locale) {
$allRules[$locale] = $rules;
}
}
}
// hard fix for "br": the rule is too complex
$allRules['br'] = array(
0 => 'fmod($n,10)==1&&!in_array(fmod($n,100),array(11,71,91))',
1 => 'fmod($n,10)==2&&!in_array(fmod($n,100),array(12,72,92))',
2 => 'in_array(fmod($n,10),array(3,4,9))&&!in_array(fmod($n,100),array_merge(range(10,19),range(70,79),range(90,99)))',
3 => 'fmod($n,1000000)==0&&$n!=0',
);
if (preg_match('/\d+/', $xml->version['number'], $matches)) {
$revision = $matches[0];
} else {
$revision = -1;
}
echo "<?php\n";
echo <<<EOD
/**
* Plural rules.
*
* This file is automatically generated by the "yii locale/plural" command under the "build" folder.
* Do not modify it directly.
*
* The original plural rule data used for generating this file has the following copyright terms:
*
* Copyright © 1991-2007 Unicode, Inc. All rights reserved.
* Distributed under the Terms of Use in http://www.unicode.org/copyright.html.
*
* @revision $revision (of the original plural file)
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
EOD;
echo "\nreturn " . var_export($allRules, true) . ';';
}
}

230
docs/guide/i18n.md

@ -0,0 +1,230 @@
Internationalization
====================
Internationalization (I18N) refers to the process of designing a software application so that it can be adapted to
various languages and regions without engineering changes. For Web applications, this is of particular importance
because the potential users may be worldwide.
Locale and Language
-------------------
There are two languages defined in Yii application: [[\yii\base\Application::$sourceLanguage|source language]] and
[[\yii\base\Application::$language|target language]].
Source language is the language original application messages are written in such as:
```php
echo \Yii::t('app', 'I am a message!');
```
> **Tip**: Default is English and it's not recommended to change it. The reason is that it's easier to find people translating from
> English to any language than from non-English to non-English.
Target language is what's currently used. It's defined in application configuration like the following:
```php
// ...
return array(
'id' => 'applicationID',
'basePath' => dirname(__DIR__),
'language' => 'ru_RU' // ← here!
```
Later you can easily change it in runtime:
```php
\Yii::$app->language = 'zh_CN';
```
Basic message translation
-------------------------
Yii basic message translation in its basic variant works without additional PHP extension. What it does is finding a
translation of the message from source language into target language. Message itself is specified as the second
`\Yii::t` method parameter:
```php
echo \Yii::t('app', 'This is a string to translate!');
```
Yii tries to load approprite translation from one of the message sources defined via `i18n` component configuration.
TBD: https://github.com/yiisoft/yii2/issues/930
### Named placeholders
You can add parameters to a translation message that will be substituted with the corresponding value after translation.
The format for this is to use curly brackets around the parameter name as you can see in the following example:
```php
$username = 'Alexander';
echo \Yii::t('app', 'Hello, {username}!', array(
'username' => $username,
));
```
Note that the parameter assignment is without the brackets.
### Positional placeholders
```php
$sum = 42;
echo \Yii::t('app', 'Balance: {0}', $sum);
```
> **Tip**: Try keep message strings meaningful and avoid using too many positional parameters. Remember that
> translator has source string only so it should be obvious about what will replace each placeholder.
Advanced placeholder formatting
-------------------------------
In order to use advanced features you need to install and enable [intl](http://www.php.net/manual/en/intro.intl.php) PHP
extension. After installing and enabling it you will be able to use extended syntax for placeholders. Either short form
`{placeholderName, argumentType}` that means default setting or full form `{placeholderName, argumentType, argumentStyle}`
that allows you to specify formatting style.
Full reference is [available at ICU website](http://icu-project.org/apiref/icu4c/classMessageFormat.html) but since it's
a bit crypric we have our own reference below.
### Numbers
```php
$sum = 42;
echo \Yii::t('app', 'Balance: {0, number}', $sum);
```
You can specify one of the built-in styles (`integer`, `currency`, `percent`):
```php
$sum = 42;
echo \Yii::t('app', 'Balance: {0, number, currency}', $sum);
```
Or specify custom pattern:
```php
$sum = 42;
echo \Yii::t('app', 'Balance: {0, number, ,000,000000}', $sum);
```
[Formatting reference](http://icu-project.org/apiref/icu4c/classicu_1_1DecimalFormat.html).
### Dates
```php
echo \Yii::t('app', 'Today is {0, date}', time());
```
Built in formats (`short`, `medium`, `long`, `full`):
```php
echo \Yii::t('app', 'Today is {0, date, short}', time());
```
Custom pattern:
```php
echo \Yii::t('app', 'Today is {0, date, YYYY-MM-dd}', time());
```
[Formatting reference](http://icu-project.org/apiref/icu4c/classicu_1_1SimpleDateFormat.html).
### Time
```php
echo \Yii::t('app', 'It is {0, time}', time());
```
Built in formats (`short`, `medium`, `long`, `full`):
```php
echo \Yii::t('app', 'It is {0, time, short}', time());
```
Custom pattern:
```php
echo \Yii::t('app', 'It is {0, date, HH:mm}', time());
```
[Formatting reference](http://icu-project.org/apiref/icu4c/classicu_1_1SimpleDateFormat.html).
### Spellout
```php
echo \Yii::t('app', '{n,number} is spelled as {n, spellout}', array(
'n' => 42,
));
```
### Ordinal
```php
echo \Yii::t('app', 'You are {n, ordinal} visitor here!', array(
'n' => 42,
));
```
Will produce "You are 42nd visitor here!".
### Duration
```php
echo \Yii::t('app', 'You are here for {n, duration} already!', array(
'n' => 42,
));
```
Will produce "You are here for 47 sec. already!".
### Plurals
Different languages have different ways to inflect plurals. Some rules are very complex so it's very handy that this
functionality is provided without the need to specify inflection rule. Instead it only requires your input of inflected
word in certain situations.
```php
echo \Yii::t('app', 'There {n, plural, =0{are no cats} =1{is one cat} other{are # cats}}!', array(
'n' => 0,
));
```
Will give us "There are no cats!".
In the plural rule arguments above `=0` means exactly zero, `=1` stands for exactly one `other` is for any other number.
`#` is replaced with the `n` argument value. It's not that simple for languages other than English. Here's an example
for Russian:
```
Здесь {n, plural, =0{котов нет} =1{есть один кот} one{# кот} few{# кота} many{# котов} other{# кота}}!
```
In the above it worth mentioning that `=1` matches exactly `n = 1` while `one` matches `21` or `101`.
To learn which inflection forms you should specify for your language you can referer to
[rules reference at unicode.org](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html).
### Selections
You can select phrases based on keywords. The pattern in this case specifies how to map keywords to phrases and
provides a default phrase.
```php
echo \Yii::t('app', '{name} is {gender} and {gender, select, female{she} male{he} other{it}} loves Yii!', array(
'name' => 'Snoopy',
'gender' => 'dog',
));
```
Will produce "Snoopy is dog and it loves Yii!".
In the expression `female` and `male` are possible values. `other` handler values that do not match. Strings inside
brackets are sub-expressions so could be just a string or a string with more placeholders.
Formatters
----------
In order to use formatters you need to install and enable [intl](http://www.php.net/manual/en/intro.intl.php) PHP
extension.

3
docs/guide/view.md

@ -1,7 +1,8 @@
View View
==== ====
View is an important part of MVC and is responsible for how data is presented to the end user. View is an important part of MVC and is responsible for presenting data to end users.
Basics Basics
------ ------

6
framework/yii/base/Model.php

@ -117,7 +117,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess
* ~~~ * ~~~
* *
* In the above `$attribute` refers to currently validated attribute name while `$params` contains an array of * In the above `$attribute` refers to currently validated attribute name while `$params` contains an array of
* validator configuration options such as `max` in case of `length` validator. Currently validate attribute value * validator configuration options such as `max` in case of `string` validator. Currently validate attribute value
* can be accessed as `$this->[$attribute]`. * can be accessed as `$this->[$attribute]`.
* *
* Yii also provides a set of [[Validator::builtInValidators|built-in validators]]. * Yii also provides a set of [[Validator::builtInValidators|built-in validators]].
@ -129,8 +129,8 @@ class Model extends Component implements IteratorAggregate, ArrayAccess
* array( * array(
* // built-in "required" validator * // built-in "required" validator
* array('username', 'required'), * array('username', 'required'),
* // built-in "length" validator customized with "min" and "max" properties * // built-in "string" validator customized with "min" and "max" properties
* array('username', 'length', 'min' => 3, 'max' => 12), * array('username', 'string', 'min' => 3, 'max' => 12),
* // built-in "compare" validator that is used in "register" scenario only * // built-in "compare" validator that is used in "register" scenario only
* array('password', 'compare', 'compareAttribute' => 'password2', 'on' => 'register'), * array('password', 'compare', 'compareAttribute' => 'password2', 'on' => 'register'),
* // an inline validator defined via the "authenticate()" method in the model class * // an inline validator defined via the "authenticate()" method in the model class

23
framework/yii/base/Module.php

@ -326,25 +326,40 @@ abstract class Module extends Component
} }
/** /**
* Checks whether the named module exists. * Checks whether the child module of the specified ID exists.
* @param string $id module ID * This method supports checking the existence of both child and grand child modules.
* @param string $id module ID. For grand child modules, use ID path relative to this module (e.g. `admin/content`).
* @return boolean whether the named module exists. Both loaded and unloaded modules * @return boolean whether the named module exists. Both loaded and unloaded modules
* are considered. * are considered.
*/ */
public function hasModule($id) public function hasModule($id)
{ {
if (($pos = strpos($id, '/')) !== false) {
// sub-module
$module = $this->getModule(substr($id, 0, $pos));
return $module === null ? false : $module->hasModule(substr($id, $pos + 1));
} else {
return isset($this->_modules[$id]); return isset($this->_modules[$id]);
} }
}
/** /**
* Retrieves the named module. * Retrieves the child module of the specified ID.
* @param string $id module ID (case-sensitive). * This method supports retrieving both child modules and grand child modules.
* @param string $id module ID (case-sensitive). To retrieve grand child modules,
* use ID path relative to this module (e.g. `admin/content`).
* @param boolean $load whether to load the module if it is not yet loaded. * @param boolean $load whether to load the module if it is not yet loaded.
* @return Module|null the module instance, null if the module does not exist. * @return Module|null the module instance, null if the module does not exist.
* @see hasModule() * @see hasModule()
*/ */
public function getModule($id, $load = true) public function getModule($id, $load = true)
{ {
if (($pos = strpos($id, '/')) !== false) {
// sub-module
$module = $this->getModule(substr($id, 0, $pos));
return $module === null ? null : $module->getModule(substr($id, $pos + 1), $load);
}
if (isset($this->_modules[$id])) { if (isset($this->_modules[$id])) {
if ($this->_modules[$id] instanceof Module) { if ($this->_modules[$id] instanceof Module) {
return $this->_modules[$id]; return $this->_modules[$id];

3
framework/yii/console/Application.php

@ -9,6 +9,7 @@
namespace yii\console; namespace yii\console;
use Yii;
use yii\base\InvalidRouteException; use yii\base\InvalidRouteException;
/** /**
@ -129,7 +130,7 @@ class Application extends \yii\base\Application
try { try {
return parent::runAction($route, $params); return parent::runAction($route, $params);
} catch (InvalidRouteException $e) { } catch (InvalidRouteException $e) {
throw new Exception(\Yii::t('yii', 'Unknown command "{command}".', array('{command}' => $route)), 0, $e); throw new Exception(Yii::t('yii', 'Unknown command "{command}".', array('command' => $route)), 0, $e);
} }
} }

4
framework/yii/console/Controller.php

@ -99,7 +99,7 @@ class Controller extends \yii\base\Controller
$args[] = $value; $args[] = $value;
} else { } else {
throw new Exception(Yii::t('yii', 'Unknown option: --{name}', array( throw new Exception(Yii::t('yii', 'Unknown option: --{name}', array(
'{name}' => $name, 'name' => $name,
))); )));
} }
} }
@ -125,7 +125,7 @@ class Controller extends \yii\base\Controller
if (!empty($missing)) { if (!empty($missing)) {
throw new Exception(Yii::t('yii', 'Missing required arguments: {params}', array( throw new Exception(Yii::t('yii', 'Missing required arguments: {params}', array(
'{params}' => implode(', ', $missing), 'params' => implode(', ', $missing),
))); )));
} }

4
framework/yii/console/controllers/HelpController.php

@ -58,7 +58,7 @@ class HelpController extends Controller
$result = Yii::$app->createController($command); $result = Yii::$app->createController($command);
if ($result === false) { if ($result === false) {
throw new Exception(Yii::t('yii', 'No help for unknown command "{command}".', array( throw new Exception(Yii::t('yii', 'No help for unknown command "{command}".', array(
'{command}' => $this->ansiFormat($command, Console::FG_YELLOW), 'command' => $this->ansiFormat($command, Console::FG_YELLOW),
))); )));
} }
@ -243,7 +243,7 @@ class HelpController extends Controller
$action = $controller->createAction($actionID); $action = $controller->createAction($actionID);
if ($action === null) { if ($action === null) {
throw new Exception(Yii::t('yii', 'No help for unknown sub-command "{command}".', array( throw new Exception(Yii::t('yii', 'No help for unknown sub-command "{command}".', array(
'{command}' => rtrim($controller->getUniqueId() . '/' . $actionID, '/'), 'command' => rtrim($controller->getUniqueId() . '/' . $actionID, '/'),
))); )));
} }
if ($action instanceof InlineAction) { if ($action instanceof InlineAction) {

2
framework/yii/data/ActiveDataProvider.php

@ -77,7 +77,7 @@ class ActiveDataProvider extends BaseDataProvider
public $db; public $db;
/** /**
* Initializes the DbCache component. * Initializes the DB connection component.
* This method will initialize the [[db]] property to make sure it refers to a valid DB connection. * This method will initialize the [[db]] property to make sure it refers to a valid DB connection.
* @throws InvalidConfigException if [[db]] is invalid. * @throws InvalidConfigException if [[db]] is invalid.
*/ */

2
framework/yii/gii/generators/controller/Generator.php

@ -197,7 +197,7 @@ class Generator extends \yii\gii\Generator
*/ */
public function getModule() public function getModule()
{ {
if (($pos = strpos($this->controller, '/')) !== false) { if (($pos = strrpos($this->controller, '/')) !== false) {
$id = substr($this->controller, 0, $pos); $id = substr($this->controller, 0, $pos);
if (($module = Yii::$app->getModule($id)) !== null) { if (($module = Yii::$app->getModule($id)) !== null) {
return $module; return $module;

2
framework/yii/gii/generators/crud/templates/views/index.php

@ -33,7 +33,7 @@ $this->params['breadcrumbs'][] = $this->title;
<?php echo '<?php' . ($generator->indexWidgetType === 'grid' ? ' //' : ''); ?> echo $this->render('_search', array('model' => $searchModel)); ?> <?php echo '<?php' . ($generator->indexWidgetType === 'grid' ? ' //' : ''); ?> echo $this->render('_search', array('model' => $searchModel)); ?>
<p> <p>
<?php echo '<?php'; ?> echo Html::a('Create <?php echo StringHelper::basename($generator->modelClass); ?>', array('create'), array('class' => 'btn btn-danger')); ?> <?php echo '<?php'; ?> echo Html::a('Create <?php echo StringHelper::basename($generator->modelClass); ?>', array('create'), array('class' => 'btn btn-success')); ?>
</p> </p>
<?php if ($generator->indexWidgetType === 'grid'): ?> <?php if ($generator->indexWidgetType === 'grid'): ?>

2
framework/yii/gii/generators/crud/templates/views/view.php

@ -30,7 +30,7 @@ $this->params['breadcrumbs'][] = $this->title;
<h1><?php echo "<?php"; ?> echo Html::encode($this->title); ?></h1> <h1><?php echo "<?php"; ?> echo Html::encode($this->title); ?></h1>
<p> <p>
<?php echo '<?php'; ?> echo Html::a('Update', array('update', <?php echo $urlParams; ?>), array('class' => 'btn btn-danger')); ?> <?php echo '<?php'; ?> echo Html::a('Update', array('update', <?php echo $urlParams; ?>), array('class' => 'btn btn-primary')); ?>
<?php echo '<?php'; ?> echo Html::a('Delete', array('delete', <?php echo $urlParams; ?>), array( <?php echo '<?php'; ?> echo Html::a('Delete', array('delete', <?php echo $urlParams; ?>), array(
'class' => 'btn btn-danger', 'class' => 'btn btn-danger',
'data-confirm' => Yii::t('app', 'Are you sure to delete this item?'), 'data-confirm' => Yii::t('app', 'Are you sure to delete this item?'),

7
framework/yii/gii/generators/module/Generator.php

@ -87,18 +87,17 @@ class Generator extends \yii\gii\Generator
$output = <<<EOD $output = <<<EOD
<p>The module has been generated successfully.</p> <p>The module has been generated successfully.</p>
<p>To access the module, you need to modify the application configuration as follows:</p> <p>To access the module, you need to add this to your application configuration:</p>
EOD; EOD;
$code = <<<EOD $code = <<<EOD
<?php <?php
return array( ......
'modules'=>array( 'modules' => array(
'{$this->moduleID}' => array( '{$this->moduleID}' => array(
'class' => '{$this->moduleClass}', 'class' => '{$this->moduleClass}',
), ),
), ),
...... ......
);
EOD; EOD;
return $output . '<pre>' . highlight_string($code, true) . '</pre>'; return $output . '<pre>' . highlight_string($code, true) . '</pre>';

4
framework/yii/helpers/BaseConsole.php

@ -700,14 +700,14 @@ class BaseConsole
public static function prompt($text, $options = array()) public static function prompt($text, $options = array())
{ {
$options = ArrayHelper::merge( $options = ArrayHelper::merge(
$options,
array( array(
'required' => false, 'required' => false,
'default' => null, 'default' => null,
'pattern' => null, 'pattern' => null,
'validator' => null, 'validator' => null,
'error' => 'Invalid input.', 'error' => 'Invalid input.',
) ),
$options
); );
$error = null; $error = null;

103
framework/yii/i18n/I18N.php

@ -10,7 +10,7 @@ namespace yii\i18n;
use Yii; use Yii;
use yii\base\Component; use yii\base\Component;
use yii\base\InvalidConfigException; use yii\base\InvalidConfigException;
use yii\base\InvalidParamException; use yii\log\Logger;
/** /**
* I18N provides features related with internationalization (I18N) and localization (L10N). * I18N provides features related with internationalization (I18N) and localization (L10N).
@ -37,17 +37,6 @@ class I18N extends Component
* You may override the configuration of both categories. * You may override the configuration of both categories.
*/ */
public $translations; public $translations;
/**
* @var string the path or path alias of the file that contains the plural rules.
* By default, this refers to a file shipped with the Yii distribution. The file is obtained
* by converting from the data file in the CLDR project.
*
* If the default rule file does not contain the expected rules, you may copy and modify it
* for your application, and then configure this property to point to your modified copy.
*
* @see http://www.unicode.org/cldr/charts/supplemental/language_plural_rules.html
*/
public $pluralRuleFile = '@yii/i18n/data/plurals.php';
/** /**
* Initializes the component by configuring the default message categories. * Initializes the component by configuring the default message categories.
@ -73,8 +62,7 @@ class I18N extends Component
/** /**
* Translates a message to the specified language. * Translates a message to the specified language.
* If the first parameter in `$params` is a number and it is indexed by 0, appropriate plural rules *
* will be applied to the translated message.
* @param string $category the message category. * @param string $category the message category.
* @param string $message the message to be translated. * @param string $message the message to be translated.
* @param array $params the parameters that will be used to replace the corresponding placeholders in the message. * @param array $params the parameters that will be used to replace the corresponding placeholders in the message.
@ -85,19 +73,32 @@ class I18N extends Component
{ {
$message = $this->getMessageSource($category)->translate($category, $message, $language); $message = $this->getMessageSource($category)->translate($category, $message, $language);
if (!is_array($params)) { $params = (array)$params;
$params = array($params); if ($params === array()) {
return $message;
} }
if (isset($params[0])) { if (class_exists('MessageFormatter', false) && preg_match('~{\s*[\d\w]+\s*,~u', $message)) {
$message = $this->applyPluralRules($message, $params[0], $language); $formatter = new MessageFormatter($language, $message);
if (!isset($params['{n}'])) { if ($formatter === null) {
$params['{n}'] = $params[0]; Yii::warning("$language message from category $category is invalid. Message is: $message.");
return $message;
}
$result = $formatter->format($params);
if ($result === false) {
$errorMessage = $formatter->getErrorMessage();
Yii::warning("$language message from category $category failed with error: $errorMessage. Message is: $message.");
return $message;
} else {
return $result;
} }
unset($params[0]);
} }
return empty($params) ? $message : strtr($message, $params); $p = array();
foreach($params as $name => $value) {
$p['{' . $name . '}'] = $value;
}
return strtr($message, $p);
} }
/** /**
@ -125,62 +126,4 @@ class I18N extends Component
throw new InvalidConfigException("Unable to locate message source for category '$category'."); throw new InvalidConfigException("Unable to locate message source for category '$category'.");
} }
} }
/**
* Applies appropriate plural rules to the given message.
* @param string $message the message to be applied with plural rules
* @param mixed $number the number by which plural rules will be applied
* @param string $language the language code that determines which set of plural rules to be applied.
* @return string the message that has applied plural rules
*/
protected function applyPluralRules($message, $number, $language)
{
if (strpos($message, '|') === false) {
return $message;
}
$chunks = explode('|', $message);
$rules = $this->getPluralRules($language);
foreach ($rules as $i => $rule) {
if (isset($chunks[$i]) && $this->evaluate($rule, $number)) {
return $chunks[$i];
}
}
$n = count($rules);
return isset($chunks[$n]) ? $chunks[$n] : $chunks[0];
}
private $_pluralRules = array(); // language => rule set
/**
* Returns the plural rules for the given language code.
* @param string $language the language code (e.g. `en_US`, `en`).
* @return array the plural rules
* @throws InvalidParamException if the language code is invalid.
*/
protected function getPluralRules($language)
{
if (isset($this->_pluralRules[$language])) {
return $this->_pluralRules[$language];
}
$allRules = require(Yii::getAlias($this->pluralRuleFile));
if (isset($allRules[$language])) {
return $this->_pluralRules[$language] = $allRules[$language];
} elseif (preg_match('/^[a-z]+/', strtolower($language), $matches)) {
return $this->_pluralRules[$language] = isset($allRules[$matches[0]]) ? $allRules[$matches[0]] : array();
} else {
throw new InvalidParamException("Invalid language code: $language");
}
}
/**
* Evaluates a PHP expression with the given number value.
* @param string $expression the PHP expression
* @param mixed $n the number value
* @return boolean the expression result
*/
protected function evaluate($expression, $n)
{
return eval("return $expression;");
}
} }

117
framework/yii/i18n/MessageFormatter.php

@ -0,0 +1,117 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\i18n;
/**
* MessageFormatter is an enhanced version of PHP intl class that no matter which PHP and ICU versions are used:
*
* - Accepts named arguments and mixed numeric and named arguments.
* - Issues no error when an insufficient number of arguments have been provided. Instead, the placeholders will not be
* substituted.
* - Fixes PHP 5.5 weird placeholder replacement in case no arguments are provided at all (https://bugs.php.net/bug.php?id=65920).
*
* @author Alexander Makarov <sam@rmcreative.ru>
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0
*/
class MessageFormatter extends \MessageFormatter
{
/**
* Format the message.
*
* @link http://php.net/manual/en/messageformatter.format.php
* @param array $args Arguments to insert into the format string.
* @return string|boolean The formatted string, or false if an error occurred.
*/
public function format($args)
{
if ($args === array()) {
return $this->getPattern();
}
if (version_compare(PHP_VERSION, '5.5.0', '<')) {
$pattern = self::replaceNamedArguments($this->getPattern(), $args);
$this->setPattern($pattern);
$args = array_values($args);
}
return parent::format($args);
}
/**
* Quick format message.
*
* @link http://php.net/manual/en/messageformatter.formatmessage.php
* @param string $locale The locale to use for formatting locale-dependent parts.
* @param string $pattern The pattern string to insert things into.
* @param array $args The array of values to insert into the format string.
* @return string|boolean The formatted pattern string or false if an error occurred.
*/
public static function formatMessage($locale, $pattern, $args)
{
if ($args === array()) {
return $pattern;
}
if (version_compare(PHP_VERSION, '5.5.0', '<')) {
$pattern = self::replaceNamedArguments($pattern, $args);
$args = array_values($args);
}
return parent::formatMessage($locale, $pattern, $args);
}
/**
* Replace named placeholders with numeric placeholders and quote unused.
*
* @param string $pattern The pattern string to replace things into.
* @param array $args The array of values to insert into the format string.
* @return string The pattern string with placeholders replaced.
*/
private static function replaceNamedArguments($pattern, $args)
{
$map = array_flip(array_keys($args));
// parsing pattern based on ICU grammar:
// http://icu-project.org/apiref/icu4c/classMessageFormat.html#details
$parts = explode('{', $pattern);
$c = count($parts);
$pattern = $parts[0];
$d = 0;
$stack = array();
for($i = 1; $i < $c; $i++) {
if (preg_match('~^(\s*)([\d\w]+)(\s*)([},])(\s*)(.*)$~us', $parts[$i], $matches)) {
// if we are not inside a plural or select this is a message
if (!isset($stack[$d]) || $stack[$d] != 'plural' && $stack[$d] != 'select') {
$d++;
// replace normal arg if it is available
if (isset($map[$matches[2]])) {
$q = '';
$pattern .= '{' . $matches[1] . $map[$matches[2]] . $matches[3];
} else {
// quote unused args
$q = ($matches[4] == '}') ? "'" : "";
$pattern .= "$q{" . $matches[1] . $matches[2] . $matches[3];
}
$pattern .= ($term = $matches[4] . $q . $matches[5] . $matches[6]);
// store type of current level
$stack[$d] = ($matches[4] == ',') ? substr($matches[6], 0, 6) : 'none';
// if it's plural or select, the next bracket is NOT begin of a message then!
if ($stack[$d] == 'plural' || $stack[$d] == 'select') {
$i++;
$d -= substr_count($term, '}');
} else {
$d -= substr_count($term, '}');
continue;
}
}
}
$pattern .= '{' . $parts[$i];
$d += 1 - substr_count($parts[$i], '}');
}
return $pattern;
}
}

627
framework/yii/i18n/data/plurals.php

@ -1,627 +0,0 @@
<?php
/**
* Plural rules.
*
* This file is automatically generated by the "yii locale/plural" command under the "build" folder.
* Do not modify it directly.
*
* The original plural rule data used for generating this file has the following copyright terms:
*
* Copyright © 1991-2007 Unicode, Inc. All rights reserved.
* Distributed under the Terms of Use in http://www.unicode.org/copyright.html.
*
* @revision 6008 (of the original plural file)
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
return array (
'ar' =>
array (
0 => '$n==0',
1 => '$n==1',
2 => '$n==2',
3 => 'in_array(fmod($n,100),range(3,10))',
4 => 'in_array(fmod($n,100),range(11,99))',
),
'asa' =>
array (
0 => '$n==1',
),
'af' =>
array (
0 => '$n==1',
),
'bem' =>
array (
0 => '$n==1',
),
'bez' =>
array (
0 => '$n==1',
),
'bg' =>
array (
0 => '$n==1',
),
'bn' =>
array (
0 => '$n==1',
),
'brx' =>
array (
0 => '$n==1',
),
'ca' =>
array (
0 => '$n==1',
),
'cgg' =>
array (
0 => '$n==1',
),
'chr' =>
array (
0 => '$n==1',
),
'da' =>
array (
0 => '$n==1',
),
'de' =>
array (
0 => '$n==1',
),
'dv' =>
array (
0 => '$n==1',
),
'ee' =>
array (
0 => '$n==1',
),
'el' =>
array (
0 => '$n==1',
),
'en' =>
array (
0 => '$n==1',
),
'eo' =>
array (
0 => '$n==1',
),
'es' =>
array (
0 => '$n==1',
),
'et' =>
array (
0 => '$n==1',
),
'eu' =>
array (
0 => '$n==1',
),
'fi' =>
array (
0 => '$n==1',
),
'fo' =>
array (
0 => '$n==1',
),
'fur' =>
array (
0 => '$n==1',
),
'fy' =>
array (
0 => '$n==1',
),
'gl' =>
array (
0 => '$n==1',
),
'gsw' =>
array (
0 => '$n==1',
),
'gu' =>
array (
0 => '$n==1',
),
'ha' =>
array (
0 => '$n==1',
),
'haw' =>
array (
0 => '$n==1',
),
'he' =>
array (
0 => '$n==1',
),
'is' =>
array (
0 => '$n==1',
),
'it' =>
array (
0 => '$n==1',
),
'jmc' =>
array (
0 => '$n==1',
),
'kaj' =>
array (
0 => '$n==1',
),
'kcg' =>
array (
0 => '$n==1',
),
'kk' =>
array (
0 => '$n==1',
),
'kl' =>
array (
0 => '$n==1',
),
'ksb' =>
array (
0 => '$n==1',
),
'ku' =>
array (
0 => '$n==1',
),
'lb' =>
array (
0 => '$n==1',
),
'lg' =>
array (
0 => '$n==1',
),
'mas' =>
array (
0 => '$n==1',
),
'ml' =>
array (
0 => '$n==1',
),
'mn' =>
array (
0 => '$n==1',
),
'mr' =>
array (
0 => '$n==1',
),
'nah' =>
array (
0 => '$n==1',
),
'nb' =>
array (
0 => '$n==1',
),
'nd' =>
array (
0 => '$n==1',
),
'ne' =>
array (
0 => '$n==1',
),
'nl' =>
array (
0 => '$n==1',
),
'nn' =>
array (
0 => '$n==1',
),
'no' =>
array (
0 => '$n==1',
),
'nr' =>
array (
0 => '$n==1',
),
'ny' =>
array (
0 => '$n==1',
),
'nyn' =>
array (
0 => '$n==1',
),
'om' =>
array (
0 => '$n==1',
),
'or' =>
array (
0 => '$n==1',
),
'pa' =>
array (
0 => '$n==1',
),
'pap' =>
array (
0 => '$n==1',
),
'ps' =>
array (
0 => '$n==1',
),
'pt' =>
array (
0 => '$n==1',
),
'rof' =>
array (
0 => '$n==1',
),
'rm' =>
array (
0 => '$n==1',
),
'rwk' =>
array (
0 => '$n==1',
),
'saq' =>
array (
0 => '$n==1',
),
'seh' =>
array (
0 => '$n==1',
),
'sn' =>
array (
0 => '$n==1',
),
'so' =>
array (
0 => '$n==1',
),
'sq' =>
array (
0 => '$n==1',
),
'ss' =>
array (
0 => '$n==1',
),
'ssy' =>
array (
0 => '$n==1',
),
'st' =>
array (
0 => '$n==1',
),
'sv' =>
array (
0 => '$n==1',
),
'sw' =>
array (
0 => '$n==1',
),
'syr' =>
array (
0 => '$n==1',
),
'ta' =>
array (
0 => '$n==1',
),
'te' =>
array (
0 => '$n==1',
),
'teo' =>
array (
0 => '$n==1',
),
'tig' =>
array (
0 => '$n==1',
),
'tk' =>
array (
0 => '$n==1',
),
'tn' =>
array (
0 => '$n==1',
),
'ts' =>
array (
0 => '$n==1',
),
'ur' =>
array (
0 => '$n==1',
),
'wae' =>
array (
0 => '$n==1',
),
've' =>
array (
0 => '$n==1',
),
'vun' =>
array (
0 => '$n==1',
),
'xh' =>
array (
0 => '$n==1',
),
'xog' =>
array (
0 => '$n==1',
),
'zu' =>
array (
0 => '$n==1',
),
'ak' =>
array (
0 => '($n==0||$n==1)',
),
'am' =>
array (
0 => '($n==0||$n==1)',
),
'bh' =>
array (
0 => '($n==0||$n==1)',
),
'fil' =>
array (
0 => '($n==0||$n==1)',
),
'tl' =>
array (
0 => '($n==0||$n==1)',
),
'guw' =>
array (
0 => '($n==0||$n==1)',
),
'hi' =>
array (
0 => '($n==0||$n==1)',
),
'ln' =>
array (
0 => '($n==0||$n==1)',
),
'mg' =>
array (
0 => '($n==0||$n==1)',
),
'nso' =>
array (
0 => '($n==0||$n==1)',
),
'ti' =>
array (
0 => '($n==0||$n==1)',
),
'wa' =>
array (
0 => '($n==0||$n==1)',
),
'ff' =>
array (
0 => '($n>=0&&$n<=2)&&$n!=2',
),
'fr' =>
array (
0 => '($n>=0&&$n<=2)&&$n!=2',
),
'kab' =>
array (
0 => '($n>=0&&$n<=2)&&$n!=2',
),
'lv' =>
array (
0 => '$n==0',
1 => 'fmod($n,10)==1&&fmod($n,100)!=11',
),
'iu' =>
array (
0 => '$n==1',
1 => '$n==2',
),
'kw' =>
array (
0 => '$n==1',
1 => '$n==2',
),
'naq' =>
array (
0 => '$n==1',
1 => '$n==2',
),
'se' =>
array (
0 => '$n==1',
1 => '$n==2',
),
'sma' =>
array (
0 => '$n==1',
1 => '$n==2',
),
'smi' =>
array (
0 => '$n==1',
1 => '$n==2',
),
'smj' =>
array (
0 => '$n==1',
1 => '$n==2',
),
'smn' =>
array (
0 => '$n==1',
1 => '$n==2',
),
'sms' =>
array (
0 => '$n==1',
1 => '$n==2',
),
'ga' =>
array (
0 => '$n==1',
1 => '$n==2',
2 => 'in_array($n,array(3,4,5,6))',
3 => 'in_array($n,array(7,8,9,10))',
),
'ro' =>
array (
0 => '$n==1',
1 => '$n==0||$n!=1&&in_array(fmod($n,100),range(1,19))',
),
'mo' =>
array (
0 => '$n==1',
1 => '$n==0||$n!=1&&in_array(fmod($n,100),range(1,19))',
),
'lt' =>
array (
0 => 'fmod($n,10)==1&&!in_array(fmod($n,100),range(11,19))',
1 => 'in_array(fmod($n,10),range(2,9))&&!in_array(fmod($n,100),range(11,19))',
),
'be' =>
array (
0 => 'fmod($n,10)==1&&fmod($n,100)!=11',
1 => 'in_array(fmod($n,10),array(2,3,4))&&!in_array(fmod($n,100),array(12,13,14))',
2 => 'fmod($n,10)==0||in_array(fmod($n,10),array(5,6,7,8,9))||in_array(fmod($n,100),array(11,12,13,14))',
),
'bs' =>
array (
0 => 'fmod($n,10)==1&&fmod($n,100)!=11',
1 => 'in_array(fmod($n,10),array(2,3,4))&&!in_array(fmod($n,100),array(12,13,14))',
2 => 'fmod($n,10)==0||in_array(fmod($n,10),array(5,6,7,8,9))||in_array(fmod($n,100),array(11,12,13,14))',
),
'hr' =>
array (
0 => 'fmod($n,10)==1&&fmod($n,100)!=11',
1 => 'in_array(fmod($n,10),array(2,3,4))&&!in_array(fmod($n,100),array(12,13,14))',
2 => 'fmod($n,10)==0||in_array(fmod($n,10),array(5,6,7,8,9))||in_array(fmod($n,100),array(11,12,13,14))',
),
'ru' =>
array (
0 => 'fmod($n,10)==1&&fmod($n,100)!=11',
1 => 'in_array(fmod($n,10),array(2,3,4))&&!in_array(fmod($n,100),array(12,13,14))',
2 => 'fmod($n,10)==0||in_array(fmod($n,10),array(5,6,7,8,9))||in_array(fmod($n,100),array(11,12,13,14))',
),
'sh' =>
array (
0 => 'fmod($n,10)==1&&fmod($n,100)!=11',
1 => 'in_array(fmod($n,10),array(2,3,4))&&!in_array(fmod($n,100),array(12,13,14))',
2 => 'fmod($n,10)==0||in_array(fmod($n,10),array(5,6,7,8,9))||in_array(fmod($n,100),array(11,12,13,14))',
),
'sr' =>
array (
0 => 'fmod($n,10)==1&&fmod($n,100)!=11',
1 => 'in_array(fmod($n,10),array(2,3,4))&&!in_array(fmod($n,100),array(12,13,14))',
2 => 'fmod($n,10)==0||in_array(fmod($n,10),array(5,6,7,8,9))||in_array(fmod($n,100),array(11,12,13,14))',
),
'uk' =>
array (
0 => 'fmod($n,10)==1&&fmod($n,100)!=11',
1 => 'in_array(fmod($n,10),array(2,3,4))&&!in_array(fmod($n,100),array(12,13,14))',
2 => 'fmod($n,10)==0||in_array(fmod($n,10),array(5,6,7,8,9))||in_array(fmod($n,100),array(11,12,13,14))',
),
'cs' =>
array (
0 => '$n==1',
1 => 'in_array($n,array(2,3,4))',
),
'sk' =>
array (
0 => '$n==1',
1 => 'in_array($n,array(2,3,4))',
),
'pl' =>
array (
0 => '$n==1',
1 => 'in_array(fmod($n,10),array(2,3,4))&&!in_array(fmod($n,100),array(12,13,14))',
2 => '$n!=1&&in_array(fmod($n,10),array(0,1))||in_array(fmod($n,10),array(5,6,7,8,9))||in_array(fmod($n,100),array(12,13,14))',
),
'sl' =>
array (
0 => 'fmod($n,100)==1',
1 => 'fmod($n,100)==2',
2 => 'in_array(fmod($n,100),array(3,4))',
),
'mt' =>
array (
0 => '$n==1',
1 => '$n==0||in_array(fmod($n,100),range(2,10))',
2 => 'in_array(fmod($n,100),range(11,19))',
),
'mk' =>
array (
0 => 'fmod($n,10)==1&&$n!=11',
),
'cy' =>
array (
0 => '$n==0',
1 => '$n==1',
2 => '$n==2',
3 => '$n==3',
4 => '$n==6',
),
'lag' =>
array (
0 => '$n==0',
1 => '($n>=0&&$n<=2)&&$n!=0&&$n!=2',
),
'shi' =>
array (
0 => '($n>=0&&$n<=1)',
1 => 'in_array($n,range(2,10))',
),
'br' =>
array (
0 => 'fmod($n,10)==1&&!in_array(fmod($n,100),array(11,71,91))',
1 => 'fmod($n,10)==2&&!in_array(fmod($n,100),array(12,72,92))',
2 => 'in_array(fmod($n,10),array(3,4,9))&&!in_array(fmod($n,100),array_merge(range(10,19),range(70,79),range(90,99)))',
3 => 'fmod($n,1000000)==0&&$n!=0',
),
'ksh' =>
array (
0 => '$n==0',
1 => '$n==1',
),
'tzm' =>
array (
0 => '($n==0||$n==1)||in_array($n,range(11,99))',
),
'gv' =>
array (
0 => 'in_array(fmod($n,10),array(1,2))||fmod($n,20)==0',
),
);

109
framework/yii/i18n/data/plurals.xml

@ -1,109 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE supplementalData SYSTEM "../../common/dtd/ldmlSupplemental.dtd">
<supplementalData>
<version number="$Revision: 6008 $"/>
<generation date="$Date: 2011-07-12 13:18:01 -0500 (Tue, 12 Jul 2011) $"/>
<plurals>
<!-- if locale is known to have no plurals, there are no rules -->
<pluralRules locales="az bm bo dz fa id ig ii hu ja jv ka kde kea km kn ko lo ms my sah ses sg th to tr vi wo yo zh"/>
<pluralRules locales="ar">
<pluralRule count="zero">n is 0</pluralRule>
<pluralRule count="one">n is 1</pluralRule>
<pluralRule count="two">n is 2</pluralRule>
<pluralRule count="few">n mod 100 in 3..10</pluralRule>
<pluralRule count="many">n mod 100 in 11..99</pluralRule>
</pluralRules>
<pluralRules locales="asa af bem bez bg bn brx ca cgg chr da de dv ee el en eo es et eu fi fo fur fy gl gsw gu ha haw he is it jmc kaj kcg kk kl ksb ku lb lg mas ml mn mr nah nb nd ne nl nn no nr ny nyn om or pa pap ps pt rof rm rwk saq seh sn so sq ss ssy st sv sw syr ta te teo tig tk tn ts ur wae ve vun xh xog zu">
<pluralRule count="one">n is 1</pluralRule>
</pluralRules>
<pluralRules locales="ak am bh fil tl guw hi ln mg nso ti wa">
<pluralRule count="one">n in 0..1</pluralRule>
</pluralRules>
<pluralRules locales="ff fr kab">
<pluralRule count="one">n within 0..2 and n is not 2</pluralRule>
</pluralRules>
<pluralRules locales="lv">
<pluralRule count="zero">n is 0</pluralRule>
<pluralRule count="one">n mod 10 is 1 and n mod 100 is not 11</pluralRule>
</pluralRules>
<pluralRules locales="iu kw naq se sma smi smj smn sms">
<pluralRule count="one">n is 1</pluralRule>
<pluralRule count="two">n is 2</pluralRule>
</pluralRules>
<pluralRules locales="ga"> <!-- http://unicode.org/cldr/trac/ticket/3915 -->
<pluralRule count="one">n is 1</pluralRule>
<pluralRule count="two">n is 2</pluralRule>
<pluralRule count="few">n in 3..6</pluralRule>
<pluralRule count="many">n in 7..10</pluralRule>
</pluralRules>
<pluralRules locales="ro mo">
<pluralRule count="one">n is 1</pluralRule>
<pluralRule count="few">n is 0 OR n is not 1 AND n mod 100 in 1..19</pluralRule>
</pluralRules>
<pluralRules locales="lt">
<pluralRule count="one">n mod 10 is 1 and n mod 100 not in 11..19</pluralRule>
<pluralRule count="few">n mod 10 in 2..9 and n mod 100 not in 11..19</pluralRule>
</pluralRules>
<pluralRules locales="be bs hr ru sh sr uk">
<pluralRule count="one">n mod 10 is 1 and n mod 100 is not 11</pluralRule>
<pluralRule count="few">n mod 10 in 2..4 and n mod 100 not in 12..14</pluralRule>
<pluralRule count="many">n mod 10 is 0 or n mod 10 in 5..9 or n mod 100 in 11..14</pluralRule>
<!-- others are fractions -->
</pluralRules>
<pluralRules locales="cs sk">
<pluralRule count="one">n is 1</pluralRule>
<pluralRule count="few">n in 2..4</pluralRule>
</pluralRules>
<pluralRules locales="pl">
<pluralRule count="one">n is 1</pluralRule>
<pluralRule count="few">n mod 10 in 2..4 and n mod 100 not in 12..14</pluralRule>
<pluralRule count="many">n is not 1 and n mod 10 in 0..1 or n mod 10 in 5..9 or n mod 100 in 12..14</pluralRule>
<!-- others are fractions -->
<!-- and n mod 100 not in 22..24 from Tamplin -->
</pluralRules>
<pluralRules locales="sl">
<pluralRule count="one">n mod 100 is 1</pluralRule>
<pluralRule count="two">n mod 100 is 2</pluralRule>
<pluralRule count="few">n mod 100 in 3..4</pluralRule>
</pluralRules>
<pluralRules locales="mt"> <!-- from Tamplin's data -->
<pluralRule count="one">n is 1</pluralRule>
<pluralRule count="few">n is 0 or n mod 100 in 2..10</pluralRule>
<pluralRule count="many">n mod 100 in 11..19</pluralRule>
</pluralRules>
<pluralRules locales="mk"> <!-- from Tamplin's data -->
<pluralRule count="one">n mod 10 is 1 and n is not 11</pluralRule>
</pluralRules>
<pluralRules locales="cy"> <!-- from http://www.saltcymru.org/wordpress/?p=99&lang=en -->
<pluralRule count="zero">n is 0</pluralRule>
<pluralRule count="one">n is 1</pluralRule>
<pluralRule count="two">n is 2</pluralRule>
<pluralRule count="few">n is 3</pluralRule>
<pluralRule count="many">n is 6</pluralRule>
</pluralRules>
<pluralRules locales="lag">
<pluralRule count="zero">n is 0</pluralRule>
<pluralRule count="one">n within 0..2 and n is not 0 and n is not 2</pluralRule>
</pluralRules>
<pluralRules locales="shi">
<pluralRule count="one">n within 0..1</pluralRule>
<pluralRule count="few">n in 2..10</pluralRule>
</pluralRules>
<pluralRules locales="br"> <!-- from http://unicode.org/cldr/trac/ticket/2886 -->
<pluralRule count="one">n mod 10 is 1 and n mod 100 not in 11,71,91</pluralRule>
<pluralRule count="two">n mod 10 is 2 and n mod 100 not in 12,72,92</pluralRule>
<pluralRule count="few">n mod 10 in 3..4,9 and n mod 100 not in 10..19,70..79,90..99</pluralRule>
<pluralRule count="many">n mod 1000000 is 0 and n is not 0</pluralRule>
</pluralRules>
<pluralRules locales="ksh">
<pluralRule count="zero">n is 0</pluralRule>
<pluralRule count="one">n is 1</pluralRule>
</pluralRules>
<pluralRules locales="tzm">
<pluralRule count="one">n in 0..1 or n in 11..99</pluralRule>
</pluralRules>
<pluralRules locales="gv">
<pluralRule count="one">n mod 10 in 1..2 or n mod 20 is 0</pluralRule>
</pluralRules>
</plurals>
</supplementalData>

4
framework/yii/requirements/requirements.php

@ -43,6 +43,8 @@ return array(
'mandatory' => false, 'mandatory' => false,
'condition' => $this->checkPhpExtensionVersion('intl', '1.0.2', '>='), 'condition' => $this->checkPhpExtensionVersion('intl', '1.0.2', '>='),
'by' => '<a href="http://www.php.net/manual/en/book.intl.php">Internationalization</a> support', 'by' => '<a href="http://www.php.net/manual/en/book.intl.php">Internationalization</a> support',
'memo' => 'PHP Intl extension 1.0.2 or higher is required when you want to use <abbr title="Internationalized domain names">IDN</abbr>-feature of EmailValidator or UrlValidator or the <code>yii\i18n\Formatter</code> class.' 'memo' => 'PHP Intl extension 1.0.2 or higher is required when you want to use advanced parameters formatting
in <code>\Yii::t()</code>, <abbr title="Internationalized domain names">IDN</abbr>-feature of
<code>EmailValidator</code> or <code>UrlValidator</code> or the <code>yii\i18n\Formatter</code> class.'
), ),
); );

2
framework/yii/web/Controller.php

@ -60,7 +60,7 @@ class Controller extends \yii\base\Controller
if (!empty($missing)) { if (!empty($missing)) {
throw new HttpException(400, Yii::t('yii', 'Missing required parameters: {params}', array( throw new HttpException(400, Yii::t('yii', 'Missing required parameters: {params}', array(
'{params}' => implode(', ', $missing), 'params' => implode(', ', $missing),
))); )));
} }

4
framework/yii/widgets/BaseListView.php

@ -139,13 +139,13 @@ abstract class BaseListView extends Widget
$page = $pagination->getPage() + 1; $page = $pagination->getPage() + 1;
$pageCount = $pagination->pageCount; $pageCount = $pagination->pageCount;
if (($summaryContent = $this->summary) === null) { if (($summaryContent = $this->summary) === null) {
$summaryContent = '<div class="summary">' . Yii::t('yii', 'Showing <b>{begin}-{end}</b> of <b>{totalCount}</b> item.|Showing <b>{begin}-{end}</b> of <b>{totalCount}</b> items.', $totalCount) . '</div>'; $summaryContent = '<div class="summary">' . Yii::t('yii', 'Showing <b>{begin}-{end}</b> of <b>{totalCount}</b> {0, plural, =1{item} other{items}}.', $totalCount) . '</div>';
} }
} else { } else {
$begin = $page = $pageCount = 1; $begin = $page = $pageCount = 1;
$end = $totalCount = $count; $end = $totalCount = $count;
if (($summaryContent = $this->summary) === null) { if (($summaryContent = $this->summary) === null) {
$summaryContent = '<div class="summary">' . Yii::t('yii', 'Total <b>1</b> item.|Total <b>{count}</b> items.', $count) . '</div>'; $summaryContent = '<div class="summary">' . Yii::t('yii', 'Total <b>{count}</b> {0, plural, =1{item} other{items}}.', $count) . '</div>';
} }
} }
return strtr($summaryContent, array( return strtr($summaryContent, array(

9
tests/unit/data/i18n/messages/de_DE/test.php

@ -0,0 +1,9 @@
<?php
/**
*
*/
return array(
'The dog runs fast.' => 'Der Hund rennt schnell.',
'His speed is about {n} km/h.' => 'Seine Geschwindigkeit beträgt {n} km/h.',
'His name is {name} and his speed is about {n, number} km/h.' => 'Er heißt {name} und ist {n, number} km/h schnell.',
);

7
tests/unit/data/i18n/messages/en_US/test.php

@ -0,0 +1,7 @@
<?php
/**
*
*/
return array(
'The dog runs fast.' => 'Der Hund rennt schell.',
);

4
tests/unit/framework/YiiBaseTest.php → tests/unit/framework/BaseYiiTest.php

@ -5,10 +5,10 @@ use Yii;
use yiiunit\TestCase; use yiiunit\TestCase;
/** /**
* YiiBaseTest * BaseYiiTest
* @group base * @group base
*/ */
class YiiBaseTest extends TestCase class BaseYiiTest extends TestCase
{ {
public $aliases; public $aliases;

2
tests/unit/framework/i18n/GettextMessageSourceTest.php

@ -12,6 +12,6 @@ class GettextMessageSourceTest extends TestCase
{ {
public function testLoadMessages() public function testLoadMessages()
{ {
$this->markTestSkipped(); $this->markTestIncomplete();
} }
} }

85
tests/unit/framework/i18n/I18NTest.php

@ -0,0 +1,85 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yiiunit\framework\i18n;
use yii\base\Model;
use yii\i18n\I18N;
use yii\i18n\MessageFormatter;
use yii\i18n\PhpMessageSource;
use yiiunit\TestCase;
/**
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0
* @group i18n
*/
class I18NTest extends TestCase
{
/**
* @var I18N
*/
public $i18n;
protected function setUp()
{
parent::setUp();
$this->mockApplication();
$this->i18n = new I18N(array(
'translations' => array(
'test' => new PhpMessageSource(array(
'basePath' => '@yiiunit/data/i18n/messages',
))
)
));
}
public function testTranslate()
{
$msg = 'The dog runs fast.';
$this->assertEquals('The dog runs fast.', $this->i18n->translate('test', $msg, array(), 'en_US'));
$this->assertEquals('Der Hund rennt schnell.', $this->i18n->translate('test', $msg, array(), 'de_DE'));
}
public function testTranslateParams()
{
$msg = 'His speed is about {n} km/h.';
$params = array(
'n' => 42,
);
$this->assertEquals('His speed is about 42 km/h.', $this->i18n->translate('test', $msg, $params, 'en_US'));
$this->assertEquals('Seine Geschwindigkeit beträgt 42 km/h.', $this->i18n->translate('test', $msg, $params, 'de_DE'));
$msg = 'His name is {name} and his speed is about {n, number} km/h.';
$params = array(
'n' => 42,
'name' => 'DA VINCI', // http://petrix.com/dognames/d.html
);
$this->assertEquals('His name is DA VINCI and his speed is about 42 km/h.', $this->i18n->translate('test', $msg, $params, 'en_US'));
$this->assertEquals('Er heißt DA VINCI und ist 42 km/h schnell.', $this->i18n->translate('test', $msg, $params, 'de_DE'));
}
public function testSpecialParams()
{
$msg = 'His speed is about {0} km/h.';
$this->assertEquals('His speed is about 0 km/h.', $this->i18n->translate('test', $msg, 0, 'en_US'));
$this->assertEquals('His speed is about 42 km/h.', $this->i18n->translate('test', $msg, 42, 'en_US'));
$this->assertEquals('His speed is about {0} km/h.', $this->i18n->translate('test', $msg, null, 'en_US'));
$this->assertEquals('His speed is about {0} km/h.', $this->i18n->translate('test', $msg, array(), 'en_US'));
$msg = 'His name is {name} and he is {age} years old.';
$model = new ParamModel();
$this->assertEquals('His name is peer and he is 5 years old.', $this->i18n->translate('test', $msg, $model, 'en_US'));
}
}
class ParamModel extends Model
{
public $name = 'peer';
public $age = 5;
}

177
tests/unit/framework/i18n/MessageFormatterTest.php

@ -0,0 +1,177 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yiiunit\framework\i18n;
use yii\i18n\MessageFormatter;
use yiiunit\TestCase;
/**
* @author Alexander Makarov <sam@rmcreative.ru>
* @since 2.0
* @group i18n
*/
class MessageFormatterTest extends TestCase
{
const N = 'n';
const N_VALUE = 42;
const SUBJECT = 'сабж';
const SUBJECT_VALUE = 'Answer to the Ultimate Question of Life, the Universe, and Everything';
protected function setUp()
{
if (!extension_loaded("intl")) {
$this->markTestSkipped("intl not installed. Skipping.");
}
}
public function patterns()
{
return array(
array(
'{'.self::SUBJECT.'} is {'.self::N.', number}', // pattern
self::SUBJECT_VALUE.' is '.self::N_VALUE, // expected
array( // params
self::N => self::N_VALUE,
self::SUBJECT => self::SUBJECT_VALUE,
)
),
// This one was provided by Aura.Intl. Thanks!
array(<<<_MSG_
{gender_of_host, select,
female {{num_guests, plural, offset:1
=0 {{host} does not give a party.}
=1 {{host} invites {guest} to her party.}
=2 {{host} invites {guest} and one other person to her party.}
other {{host} invites {guest} and # other people to her party.}}}
male {{num_guests, plural, offset:1
=0 {{host} does not give a party.}
=1 {{host} invites {guest} to his party.}
=2 {{host} invites {guest} and one other person to his party.}
other {{host} invites {guest} and # other people to his party.}}}
other {{num_guests, plural, offset:1
=0 {{host} does not give a party.}
=1 {{host} invites {guest} to their party.}
=2 {{host} invites {guest} and one other person to their party.}
other {{host} invites {guest} and # other people to their party.}}}}
_MSG_
,
'ralph invites beep and 3 other people to his party.',
array(
'gender_of_host' => 'male',
'num_guests' => 4,
'host' => 'ralph',
'guest' => 'beep'
)
),
array(
'{name} is {gender} and {gender, select, female{she} male{he} other{it}} loves Yii!',
'Alexander is male and he loves Yii!',
array(
'name' => 'Alexander',
'gender' => 'male',
),
),
// verify pattern in select does not get replaced
array(
'{name} is {gender} and {gender, select, female{she} male{he} other{it}} loves Yii!',
'Alexander is male and he loves Yii!',
array(
'name' => 'Alexander',
'gender' => 'male',
// following should not be replaced
'he' => 'wtf',
'she' => 'wtf',
'it' => 'wtf',
)
),
// verify pattern in select message gets replaced
array(
'{name} is {gender} and {gender, select, female{she} male{{he}} other{it}} loves Yii!',
'Alexander is male and wtf loves Yii!',
array(
'name' => 'Alexander',
'gender' => 'male',
'he' => 'wtf',
'she' => 'wtf',
),
),
// some parser specific verifications
array(
'{gender} and {gender, select, female{she} male{{he}} other{it}} loves {nr, number} is {gender}!',
'male and wtf loves 42 is male!',
array(
'nr' => 42,
'gender' => 'male',
'he' => 'wtf',
'she' => 'wtf',
),
),
);
}
/**
* @dataProvider patterns
*/
public function testNamedArgumentsStatic($pattern, $expected, $args)
{
$result = MessageFormatter::formatMessage('en_US', $pattern, $args);
$this->assertEquals($expected, $result, intl_get_error_message());
}
/**
* @dataProvider patterns
*/
public function testNamedArgumentsObject($pattern, $expected, $args)
{
$formatter = new MessageFormatter('en_US', $pattern);
$result = $formatter->format($args);
$this->assertEquals($expected, $result, $formatter->getErrorMessage());
}
public function testInsufficientArguments()
{
$expected = '{'.self::SUBJECT.'} is '.self::N_VALUE;
$result = MessageFormatter::formatMessage('en_US', '{'.self::SUBJECT.'} is {'.self::N.', number}', array(
self::N => self::N_VALUE,
));
$this->assertEquals($expected, $result, intl_get_error_message());
}
/**
* When instantiating a MessageFormatter with invalid pattern it should be null with default settings.
* It will be IntlException if intl.use_exceptions=1 and PHP 5.5 or newer or an error if intl.error_level is not 0.
*/
public function testNullConstructor()
{
if(ini_get('intl.use_exceptions')) {
$this->setExpectedException('IntlException');
}
if (!ini_get('intl.error_level') || ini_get('intl.use_exceptions')) {
$this->assertNull(new MessageFormatter('en_US', ''));
}
}
public function testNoParams()
{
$pattern = '{'.self::SUBJECT.'} is '.self::N;
$result = MessageFormatter::formatMessage('en_US', $pattern, array());
$this->assertEquals($pattern, $result, intl_get_error_message());
$formatter = new MessageFormatter('en_US', $pattern);
$result = $formatter->format(array());
$this->assertEquals($pattern, $result, $formatter->getErrorMessage());
}
}
Loading…
Cancel
Save