mirror of
https://github.com/yiisoft/yii2.git
synced 2025-11-20 00:20:44 +08:00
More i18n tests, docs, added check to skip fixes where possible
This commit is contained in:
@@ -5,44 +5,215 @@ Internationalization (I18N) refers to the process of designing a software applic
|
|||||||
various languages and regions without engineering changes. For Web applications, this is of particular importance
|
various languages and regions without engineering changes. For Web applications, this is of particular importance
|
||||||
because the potential users may be worldwide.
|
because the potential users may be worldwide.
|
||||||
|
|
||||||
When developing an application it's assumed that we're relying on
|
|
||||||
[PHP internationalization extension](http://www.php.net/manual/en/intro.intl.php). While extension covers a lot of aspects
|
|
||||||
Yii adds a bit more:
|
|
||||||
|
|
||||||
- It handles message translation.
|
|
||||||
|
|
||||||
|
|
||||||
Locale and Language
|
Locale and Language
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
Translation
|
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:
|
||||||
|
|
||||||
numeric arg \{\s*\d+\s*\}
|
```php
|
||||||
named arg \{\s*(\w|(\w|\d){2,})\s*\}
|
echo \Yii::t('app', 'I am a message!');
|
||||||
|
```
|
||||||
|
|
||||||
named placeholder can be unicode!!!
|
> **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
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
### Strings translation
|
||||||
|
|
||||||
|
Yii basic message translation that works without additional PHP extension and
|
||||||
|
|
||||||
|
|
||||||
argName [^[[:Pattern_Syntax:][:Pattern_White_Space:]]]+
|
### Named placeholders
|
||||||
|
|
||||||
message = messageText (argument messageText)*
|
```php
|
||||||
argument = noneArg | simpleArg | complexArg
|
$username = 'Alexander';
|
||||||
complexArg = choiceArg | pluralArg | selectArg | selectordinalArg
|
echo \Yii::t('app', 'Hello, {username}!', array(
|
||||||
noneArg = '{' argNameOrNumber '}'
|
'username' => $username,
|
||||||
simpleArg = '{' argNameOrNumber ',' argType [',' argStyle] '}'
|
));
|
||||||
choiceArg = '{' argNameOrNumber ',' "choice" ',' choiceStyle '}'
|
```
|
||||||
pluralArg = '{' argNameOrNumber ',' "plural" ',' pluralStyle '}'
|
|
||||||
selectArg = '{' argNameOrNumber ',' "select" ',' selectStyle '}'
|
### Positional placeholders
|
||||||
selectordinalArg = '{' argNameOrNumber ',' "selectordinal" ',' pluralStyle '}'
|
|
||||||
choiceStyle: see ChoiceFormat
|
```php
|
||||||
pluralStyle: see PluralFormat
|
$sum = 42;
|
||||||
selectStyle: see SelectFormat
|
echo \Yii::t('app', 'Balance: {0}', $sum);
|
||||||
argNameOrNumber = argName | argNumber
|
```
|
||||||
argName = [^[[:Pattern_Syntax:][:Pattern_White_Space:]]]+
|
|
||||||
argNumber = '0' | ('1'..'9' ('0'..'9')*)
|
> **Tip**: When messages are extracted and passed to translator, he sees strings only. For the code above extracted message will be
|
||||||
argType = "number" | "date" | "time" | "spellout" | "ordinal" | "duration"
|
> "Balance: {0}". It's not recommended to use positional placeholders except when there's only one and message context is
|
||||||
argStyle = "short" | "medium" | "long" | "full" | "integer" | "currency" | "percent" | argStyleText
|
> clear as above.
|
||||||
*/
|
|
||||||
|
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.
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ namespace yii\i18n;
|
|||||||
* - Issues no error when an insufficient number of arguments have been provided. Instead, the placeholders will not be
|
* - Issues no error when an insufficient number of arguments have been provided. Instead, the placeholders will not be
|
||||||
* substituted.
|
* substituted.
|
||||||
*
|
*
|
||||||
* @see http://php.net/manual/en/migration55.changed-functions.php
|
|
||||||
*
|
|
||||||
* @author Alexander Makarov <sam@rmcreative.ru>
|
* @author Alexander Makarov <sam@rmcreative.ru>
|
||||||
* @since 2.0
|
* @since 2.0
|
||||||
*/
|
*/
|
||||||
@@ -30,9 +28,12 @@ class MessageFormatter extends \MessageFormatter
|
|||||||
*/
|
*/
|
||||||
public function format($args)
|
public function format($args)
|
||||||
{
|
{
|
||||||
|
if (self::needFix()) {
|
||||||
$pattern = self::replaceNamedArguments($this->getPattern(), $args);
|
$pattern = self::replaceNamedArguments($this->getPattern(), $args);
|
||||||
$this->setPattern($pattern);
|
$this->setPattern($pattern);
|
||||||
return parent::format(array_values($args));
|
$args = array_values($args);
|
||||||
|
}
|
||||||
|
return parent::format($args);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -46,8 +47,11 @@ class MessageFormatter extends \MessageFormatter
|
|||||||
*/
|
*/
|
||||||
public static function formatMessage($locale, $pattern, $args)
|
public static function formatMessage($locale, $pattern, $args)
|
||||||
{
|
{
|
||||||
|
if (self::needFix()) {
|
||||||
$pattern = self::replaceNamedArguments($pattern, $args);
|
$pattern = self::replaceNamedArguments($pattern, $args);
|
||||||
return parent::formatMessage($locale, $pattern, array_values($args));
|
$args = array_values($args);
|
||||||
|
}
|
||||||
|
return parent::formatMessage($locale, $pattern, $args);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -66,9 +70,25 @@ class MessageFormatter extends \MessageFormatter
|
|||||||
return $input[1] . $map[$name] . $input[3];
|
return $input[1] . $map[$name] . $input[3];
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
//return $input[1] . $name . $input[3];
|
||||||
return "'" . $input[1] . $name . $input[3] . "'";
|
return "'" . $input[1] . $name . $input[3] . "'";
|
||||||
}
|
}
|
||||||
}, $pattern);
|
}, $pattern);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if fix should be applied
|
||||||
|
*
|
||||||
|
* @see http://php.net/manual/en/migration55.changed-functions.php
|
||||||
|
* @return boolean if fix should be applied
|
||||||
|
*/
|
||||||
|
private static function needFix()
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
!defined('INTL_ICU_VERSION') ||
|
||||||
|
version_compare(INTL_ICU_VERSION, '48.0.0', '<') ||
|
||||||
|
version_compare(PHP_VERSION, '5.5.0', '<')
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,6 +32,39 @@ class MessageFormatterTest extends TestCase
|
|||||||
));
|
));
|
||||||
|
|
||||||
$this->assertEquals($expected, $result);
|
$this->assertEquals($expected, $result);
|
||||||
|
|
||||||
|
$pattern = <<<_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_;
|
||||||
|
$result = MessageFormatter::formatMessage('en_US', $pattern, array(
|
||||||
|
'gender_of_host' => 'male',
|
||||||
|
'num_guests' => 4,
|
||||||
|
'host' => 'ralph',
|
||||||
|
'guest' => 'beep'
|
||||||
|
));
|
||||||
|
$this->assertEquals('ralph invites beep and 3 other people to his party.', $result);
|
||||||
|
|
||||||
|
$pattern = '{name} is {gender} and {gender, select, female{she} male{he} other{it}} loves Yii!';
|
||||||
|
$result = MessageFormatter::formatMessage('en_US', $pattern, array(
|
||||||
|
'name' => 'Alexander',
|
||||||
|
'gender' => 'male',
|
||||||
|
));
|
||||||
|
$this->assertEquals('Alexander is male and he loves Yii!', $result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testInsufficientArguments()
|
public function testInsufficientArguments()
|
||||||
|
|||||||
Reference in New Issue
Block a user