mirror of
https://github.com/yiisoft/yii2.git
synced 2025-11-19 16:01:57 +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
|
||||
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
|
||||
-------------------
|
||||
|
||||
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*\}
|
||||
named arg \{\s*(\w|(\w|\d){2,})\s*\}
|
||||
```php
|
||||
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)*
|
||||
argument = noneArg | simpleArg | complexArg
|
||||
complexArg = choiceArg | pluralArg | selectArg | selectordinalArg
|
||||
noneArg = '{' argNameOrNumber '}'
|
||||
simpleArg = '{' argNameOrNumber ',' argType [',' argStyle] '}'
|
||||
choiceArg = '{' argNameOrNumber ',' "choice" ',' choiceStyle '}'
|
||||
pluralArg = '{' argNameOrNumber ',' "plural" ',' pluralStyle '}'
|
||||
selectArg = '{' argNameOrNumber ',' "select" ',' selectStyle '}'
|
||||
selectordinalArg = '{' argNameOrNumber ',' "selectordinal" ',' pluralStyle '}'
|
||||
choiceStyle: see ChoiceFormat
|
||||
pluralStyle: see PluralFormat
|
||||
selectStyle: see SelectFormat
|
||||
argNameOrNumber = argName | argNumber
|
||||
argName = [^[[:Pattern_Syntax:][:Pattern_White_Space:]]]+
|
||||
argNumber = '0' | ('1'..'9' ('0'..'9')*)
|
||||
argType = "number" | "date" | "time" | "spellout" | "ordinal" | "duration"
|
||||
argStyle = "short" | "medium" | "long" | "full" | "integer" | "currency" | "percent" | argStyleText
|
||||
*/
|
||||
```php
|
||||
$username = 'Alexander';
|
||||
echo \Yii::t('app', 'Hello, {username}!', array(
|
||||
'username' => $username,
|
||||
));
|
||||
```
|
||||
|
||||
### Positional placeholders
|
||||
|
||||
```php
|
||||
$sum = 42;
|
||||
echo \Yii::t('app', 'Balance: {0}', $sum);
|
||||
```
|
||||
|
||||
> **Tip**: When messages are extracted and passed to translator, he sees strings only. For the code above extracted message will be
|
||||
> "Balance: {0}". It's not recommended to use positional placeholders except when there's only one and message context is
|
||||
> 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
|
||||
* substituted.
|
||||
*
|
||||
* @see http://php.net/manual/en/migration55.changed-functions.php
|
||||
*
|
||||
* @author Alexander Makarov <sam@rmcreative.ru>
|
||||
* @since 2.0
|
||||
*/
|
||||
@@ -30,9 +28,12 @@ class MessageFormatter extends \MessageFormatter
|
||||
*/
|
||||
public function format($args)
|
||||
{
|
||||
$pattern = self::replaceNamedArguments($this->getPattern(), $args);
|
||||
$this->setPattern($pattern);
|
||||
return parent::format(array_values($args));
|
||||
if (self::needFix()) {
|
||||
$pattern = self::replaceNamedArguments($this->getPattern(), $args);
|
||||
$this->setPattern($pattern);
|
||||
$args = array_values($args);
|
||||
}
|
||||
return parent::format($args);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -46,8 +47,11 @@ class MessageFormatter extends \MessageFormatter
|
||||
*/
|
||||
public static function formatMessage($locale, $pattern, $args)
|
||||
{
|
||||
$pattern = self::replaceNamedArguments($pattern, $args);
|
||||
return parent::formatMessage($locale, $pattern, array_values($args));
|
||||
if (self::needFix()) {
|
||||
$pattern = self::replaceNamedArguments($pattern, $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];
|
||||
}
|
||||
else {
|
||||
//return $input[1] . $name . $input[3];
|
||||
return "'" . $input[1] . $name . $input[3] . "'";
|
||||
}
|
||||
}, $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);
|
||||
|
||||
$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()
|
||||
|
||||
Reference in New Issue
Block a user