tutorial-i18n.md 19.6 KB
Newer Older
1 2 3
Internationalization
====================

4
> Note: This section is under development.
Qiang Xue committed
5

6 7 8 9
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.

10 11
Yii offers several tools that help with internationalisation of a website such as message translation and
number- and date-formatting.
12

13 14
Locale and Language
-------------------
15

16
There are two languages defined in the Yii application: [[yii\base\Application::$sourceLanguage|source language]] and
17
[[yii\base\Application::$language|target language]].
18

19
Source language is the language original application messages are written in directly in the code such as:
20

21 22 23 24
```php
echo \Yii::t('app', 'I am a message!');
```

25 26
The target language is the language that should be used to display the current page i.e. the language that original messages need
to be translated to. It is defined in the application configuration like the following:
27 28

```php
Alexander Makarov committed
29
return [
30 31
    'id' => 'applicationID',
    'basePath' => dirname(__DIR__),
32 33
    // ...
    'language' => 'ru-RU', // <- here!
34 35
    // ...
]
36 37
```

38 39 40 41 42 43 44
> **Tip**: The default value for the [[yii\base\Application::$sourceLanguage|source language]] is English and it is
> recommended to keep this value. The reason is that it's easier to find people translating from
> English to any language than from non-English to non-English.

You may set the application language at runtime to set it to a language the user has chosen.
This has to be done at a point before any output is generated so that it affects all the output correctly.
Therefor just change the application property to the desired value:
45 46

```php
ff committed
47
\Yii::$app->language = 'zh-CN';
48 49
```

50 51
The format for the language/locale is `ll-CC` where `ll` is a two- or three-letter lowercase code for a language according to
[ISO-639](http://www.loc.gov/standards/iso639-2/) and `CC` is the country code according to
52 53
[ISO-3166](http://www.iso.org/iso/en/prods-services/iso3166ma/02iso-3166-code-lists/list-en1.html).

54 55
> **Note**: For more information on the concept and syntax of locales, check the
> [documentation of the ICU project](http://userguide.icu-project.org/locale#TOC-The-Locale-Concept).
56

57 58 59
Message translation
-------------------

munawer committed
60
Message translation is used to translate messages that are output by an application to different languages
61
so that users from different countries can use the application in their native language.
62

63 64 65
The message translation feature in Yii works simply as finding a
translation of the message from a source language into a target language.
To use the message translation feature you wrap your original message strings with a call to the [[Yii::t()]] method.
munawer committed
66
The first parameter of this method takes a category which helps to distinguish the source of messages in different parts
67
of the application and the second parameter is the message itself.
Alexander Makarov committed
68 69 70 71

```php
echo \Yii::t('app', 'This is a string to translate!');
```
72

73
Yii tries to load an appropriate translation according to the current [[yii\base\Application::$language|application language]]
74
from one of the message sources defined in the `i18n` [application component](structure-application-components.md).
75 76
A message source is a set of files or a database that provides translation messages.
The following configuration example defines a messages source that takes the messages from PHP files:
77

78
```php
Alexander Makarov committed
79
'components' => [
80 81 82 83 84 85
    // ...
    'i18n' => [
        'translations' => [
            'app*' => [
                'class' => 'yii\i18n\PhpMessageSource',
                //'basePath' => '@app/messages',
86
                //'sourceLanguage' => 'en-US',
87 88 89 90 91 92 93
                'fileMap' => [
                    'app' => 'app.php',
                    'app/error' => 'error.php',
                ],
            ],
        ],
    ],
Alexander Makarov committed
94
],
95 96 97
```

In the above `app*` is a pattern that specifies which categories are handled by the message source. In this case we're
98 99 100 101 102
handling everything that begins with `app`. Message files are located in `@app/messages`, the `messages` directory
in your application directory. The [[yii\i18n\PhpMessageSource::fileMap|fileMap]] array
defines which file is to be used for which category.
Instead of configuring `fileMap` you can rely on convention which is to use the category name as the file name
(e.g. category `app/error` will result in the file name `app/error.php` under the [[yii\i18n\PhpMessageSource::basePath|basePath]].
103

104 105 106
When translating the message for `\Yii::t('app', 'This is a string to translate!')` and an application language `ru-RU`, Yii
will first look for a file `@app/messages/ru-RU/app.php` to retrieve the list of available translations.
If there is file `ru-RU` it will try `ru` as well before failing.
107

108 109
Beside storing messages in PHP files (using [[yii\i18n\PhpMessageSource|PhpMessageSource]]) Yii provides two other
classes:
110

111 112
- [[yii\i18n\GettextMessageSource]] that uses GNU Gettext MO or PO files.
- [[yii\i18n\DbMessageSource]] that uses a database.
113 114


115
### Named placeholders
116

117 118 119
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:

120 121
```php
$username = 'Alexander';
Alexander Makarov committed
122
echo \Yii::t('app', 'Hello, {username}!', [
123
    'username' => $username,
Alexander Makarov committed
124
]);
125 126
```

127 128
Note that the parameter assignment is without the brackets.

129
### Positional placeholders
130 131 132 133 134 135

```php
$sum = 42;
echo \Yii::t('app', 'Balance: {0}', $sum);
```

Alexander Makarov committed
136 137
> **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.
138

139 140
### Advanced placeholder formatting

141 142
In order to use advanced features you need to install and enable the [intl PHP extension](http://www.php.net/manual/en/intro.intl.php).
After installing and enabling it you will be able to use extended syntax for placeholders. Either short form
143 144 145
`{placeholderName, argumentType}` that means default setting or full form `{placeholderName, argumentType, argumentStyle}`
that allows you to specify formatting style.

146
A complete reference is available at the [ICU website](http://icu-project.org/apiref/icu4c/classMessageFormat.html) but we will show some examples in the following.
147

148
#### Numbers
149 150 151 152 153 154 155 156 157 158 159 160 161

```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);
```

162
Or specify a custom pattern:
163 164 165 166 167 168 169 170

```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).

171
#### Dates
172 173 174 175 176

```php
echo \Yii::t('app', 'Today is {0, date}', time());
```

177
Built in formats are `short`, `medium`, `long`, and `full`:
178 179 180 181 182

```php
echo \Yii::t('app', 'Today is {0, date, short}', time());
```

183
You may also specify a custom pattern:
184 185

```php
186
echo \Yii::t('app', 'Today is {0, date, yyyy-MM-dd}', time());
187 188 189 190
```

[Formatting reference](http://icu-project.org/apiref/icu4c/classicu_1_1SimpleDateFormat.html).

191
#### Time
192 193 194 195 196

```php
echo \Yii::t('app', 'It is {0, time}', time());
```

197
Built in formats are `short`, `medium`, `long`, and `full`:
198 199 200 201 202

```php
echo \Yii::t('app', 'It is {0, time, short}', time());
```

203
You may also specify a custom pattern:
204 205 206 207 208 209 210 211

```php
echo \Yii::t('app', 'It is {0, date, HH:mm}', time());
```

[Formatting reference](http://icu-project.org/apiref/icu4c/classicu_1_1SimpleDateFormat.html).


212
#### Spellout
213 214

```php
Alexander Makarov committed
215
echo \Yii::t('app', '{n,number} is spelled as {n, spellout}', ['n' => 42]);
216 217
```

218
#### Ordinal
219 220

```php
Alexander Makarov committed
221
echo \Yii::t('app', 'You are {n, ordinal} visitor here!', ['n' => 42]);
222 223 224 225
```

Will produce "You are 42nd visitor here!".

226
#### Duration
227 228

```php
Alexander Makarov committed
229
echo \Yii::t('app', 'You are here for {n, duration} already!', ['n' => 47]);
230 231 232 233
```

Will produce "You are here for 47 sec. already!".

234
#### Plurals
235

236 237 238
Different languages have different ways to inflect plurals. Yii provides a convenient way for translating messages in
different plural forms that works well even for very complex rules. Instead of dealing with the inflection rules directly
it is sufficient to provide the translation of inflected words in certain situations only.
239 240

```php
241
echo \Yii::t('app', 'There {n, plural, =0{are no cats} =1{is one cat} other{are # cats}}!', ['n' => $n]);
242 243
```

244
Will give us
245

246 247 248 249 250 251
- "There are no cats!" for `$n = 0`,
- "There is one cat!" for `$n = 1`,
- and "There are 42 cats!" for `$n = 42`.

In the plural rule arguments above `=0` means exactly zero, `=1` stands for exactly one, and `other` is for any other number.
`#` is replaced with the value of `n`. It's not that simple for languages other than English. Here's an example
252 253 254 255 256 257
for Russian:

```
Здесь {n, plural, =0{котов нет} =1{есть один кот} one{# кот} few{# кота} many{# котов} other{# кота}}!
```

258
In the above it's worth mentioning that `=1` matches exactly `n = 1` while `one` matches `21` or `101`.
259

260
Note, that you can not use the Russian example in `Yii::t()` directly if your
261 262 263
[[yii\base\Application::$sourceLanguage|source language]] isn't set to `ru_RU`. This however is not recommended, instead such
strings should go into message files or message database (in case DB source is used). Yii uses plural rules of the
translated language strings and is falling back to plural rules of source language if translation isn't available.
264

265
To learn which inflection forms you should specify for your language you can referrer to the
266 267
[rules reference at unicode.org](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html).

268
#### Selections
269 270 271 272 273

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
Alexander Makarov committed
274
echo \Yii::t('app', '{name} is {gender} and {gender, select, female{she} male{he} other{it}} loves Yii!', [
275 276
    'name' => 'Snoopy',
    'gender' => 'dog',
Alexander Makarov committed
277
]);
278 279 280 281
```

Will produce "Snoopy is dog and it loves Yii!".

282
In the expression `female` and `male` are possible values. `other` handles values that do not match. Strings inside
283 284
brackets are sub-expressions so could be just a string or a string with more placeholders.

285
### Specifying default translation
286

287 288
You can specify default translations that will be used as a fallback for categories that don't match any other translation.
This translation should be marked with `*`. In order to do it add the following to the application config:
289 290 291 292 293

```php
//configure i18n component

'i18n' => [
294 295 296 297 298
    'translations' => [
        '*' => [
            'class' => 'yii\i18n\PhpMessageSource'
        ],
    ],
299 300 301
],
```

302 303
Now you can use categories without configuring each one, which is similar to Yii 1.1 behavior.
Messages for the category will be loaded from a file under the default translation `basePath` that is `@app/messages`:
Mark committed
304

305
```php
Mark committed
306
echo Yii::t('not_specified_category', 'message from unspecified category');
307 308
```

Mark committed
309
Message will be loaded from `@app/messages/<LanguageCode>/not_specified_category.php`.
310

311
### Translating module messages
Mark committed
312

313
If you want to translate messages for a module and avoid using a single translation file for all messages, you can do it like the following:
Mark committed
314 315 316 317 318 319 320 321 322 323

```php
<?php

namespace app\modules\users;

use Yii;

class Module extends \yii\base\Module
{
324 325 326 327 328 329 330 331 332 333 334 335
    public $controllerNamespace = 'app\modules\users\controllers';

    public function init()
    {
        parent::init();
        $this->registerTranslations();
    }

    public function registerTranslations()
    {
        Yii::$app->i18n->translations['modules/users/*'] = [
            'class' => 'yii\i18n\PhpMessageSource',
336
            'sourceLanguage' => 'en-US',
337 338 339 340 341 342 343 344 345 346 347 348 349
            'basePath' => '@app/modules/users/messages',
            'fileMap' => [
                'modules/users/validation' => 'validation.php',
                'modules/users/form' => 'form.php',
                ...
            ],
        ];
    }

    public static function t($category, $message, $params = [], $language = null)
    {
        return Yii::t('modules/users/' . $category, $message, $params, $language);
    }
Mark committed
350 351 352 353

}
```

Mark committed
354
In the example above we are using wildcard for matching and then filtering each category per needed file. Instead of using `fileMap` you can simply
Mark committed
355
use convention of category mapping to the same named file and use `Module::t('validation', 'your custom validation message')` or `Module::t('form', 'some form label')` directly.
Mark committed
356

357
### Translating widgets messages
Mark committed
358

359
The same rule as applied for Modules above can be applied for widgets too, for example:
Mark committed
360 361 362 363 364 365 366 367 368 369 370 371

```php
<?php

namespace app\widgets\menu;

use yii\base\Widget;
use Yii;

class Menu extends Widget
{

372 373 374 375 376 377 378 379 380 381 382
    public function init()
    {
        parent::init();
        $this->registerTranslations();
    }

    public function registerTranslations()
    {
        $i18n = Yii::$app->i18n;
        $i18n->translations['widgets/menu/*'] = [
            'class' => 'yii\i18n\PhpMessageSource',
383
            'sourceLanguage' => 'en-US',
384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399
            'basePath' => '@app/widgets/menu/messages',
            'fileMap' => [
                'widgets/menu/messages' => 'messages.php',
            ],
        ];
    }

    public function run()
    {
        echo $this->render('index');
    }

    public static function t($category, $message, $params = [], $language = null)
    {
        return Yii::t('widgets/menu/' . $category, $message, $params, $language);
    }
Mark committed
400 401 402 403

}
```

Mark committed
404
Instead of using `fileMap` you can simply use convention of category mapping to the same named file and use `Menu::t('messages', 'new messages {messages}', ['{messages}' => 10])` directly.
Mark committed
405

Mark committed
406 407
> **Note**: For widgets you also can use i18n views, same rules as for controllers are applied to them too.

408 409 410

### Translating framework messages

411 412
Yii comes with default translation messages for validation errors and some other strings. These messages are all
in the category `yii`. Sometimes you want to correct default framework message translation for your application.
413
In order to do so configure the `i18n` [application component](structure-application-components.md) like the following:
414 415

```php
416 417 418 419 420 421
'i18n' => [
    'translations' => [
        'yii' => [
            'class' => 'yii\i18n\PhpMessageSource',
            'sourceLanguage' => 'en-US',
            'basePath' => '@app/messages'
422 423 424 425 426
        ],
    ],
],
```

427
Now you can place your adjusted translations to `@app/messages/<language>/yii.php`.
428

429 430
### Handling missing translations

431
If the translation is missing at the source, Yii displays the requested message content. Such behavior is very convenient
432 433
in case your raw message is a valid verbose text. However, sometimes it is not enough.
You may need to perform some custom processing of the situation, when requested translation is missing at the source.
434
This can be achieved using the [[yii\i18n\MessageSource::EVENT_MISSING_TRANSLATION|missingTranslation]]-event of [[yii\i18n\MessageSource]].
435

436 437
For example to mark all missing translations with something notable, so they can be easily found at the page we
first we need to setup event handler. This can be done in the application configuration:
438 439 440 441 442 443 444 445 446 447 448 449

```php
'components' => [
    // ...
    'i18n' => [
        'translations' => [
            'app*' => [
                'class' => 'yii\i18n\PhpMessageSource',
                'fileMap' => [
                    'app' => 'app.php',
                    'app/error' => 'error.php',
                ],
450
                'on missingTranslation' => ['app\components\TranslationEventHandler', 'handleMissingTranslation']
451 452 453 454 455 456
            ],
        ],
    ],
],
```

457
Now we need to implement our own event handler:
458 459

```php
460 461 462 463
<?php

namespace app\components;

464 465 466 467
use yii\i18n\MissingTranslationEvent;

class TranslationEventHandler
{
468
    public static function handleMissingTranslation(MissingTranslationEvent $event) {
469 470 471 472 473
        $event->translatedMessage = "@MISSING: {$event->category}.{$event->message} FOR LANGUAGE {$event->language} @";
    }
}
```

474
If [[yii\i18n\MissingTranslationEvent::translatedMessage]] is set by the event handler it will be displayed as the translation result.
475 476

> Attention: each message source handles its missing translations separately. If you are using several message sources
477 478
> and wish them treat missing translation in the same way, you should assign corresponding event handler to each of them.

479

480 481 482
Views
-----

483 484 485 486 487
Instead of translating messages as described in the last section,
you can also use `i18n` in your views to provide support for different languages. For example, if you have a view `views/site/index.php` and
you want to create a special version for russian language of it, you create a `ru-RU` folder under the view path of the current controller/widget and
put the file for russian language as follows `views/site/ru-RU/index.php`. Yii will then load the file for the current language if it exists
and fall back to the original view file if none was found.
488 489 490 491

> **Note**: If language is specified as `en-US` and there are no corresponding views, Yii will try views under `en`
> before using original ones.

492 493 494

Formatting Number and Date values
---------------------------------
495

Carsten Brandt committed
496
See the [data formatter section](output-formatter.md) for details.
497 498 499 500 501


Setting up your PHP environment <a name="setup-environment"></a>
-------------------------------

502 503 504 505
Yii uses the [PHP intl extension](http://php.net/manual/en/book.intl.php) to provide most of its internationalization features
such as the number and date formatting of the [[yii\i18n\Formatter]] class and the message formatting using [[yii\i18n\MessageFormatter]].
Both classes provides a fallback implementation that provides basic functionality in case intl is not installed.
This fallback implementation however only works well for sites in english language and even there can not provide the
munawer committed
506
rich set of features that is available with the PHP intl extension, so its installation is highly recommended.
507

508 509 510 511 512 513 514 515 516 517
The [PHP intl extension](http://php.net/manual/en/book.intl.php) is based on the [ICU library](http://site.icu-project.org/) which
provides the knowledge and formatting rules for all the different locales. According to this fact the formatting of dates and numbers
and also the supported syntax available for message formatting differs between different versions of the ICU library that is compiled with
you PHP binary.

To ensure your website works with the same output in all environments it is recommended to install the PHP intl extension
in all environments and verify that the version of the ICU library compiled with PHP is the same.

To find out which version of ICU is used by PHP you can run the following script, which will give you the PHP and ICU version used.

Carsten Brandt committed
518
```php
519 520 521 522 523 524 525 526 527
<?php
echo "PHP: " . PHP_VERSION . "\n";
echo "ICU: " . INTL_ICU_VERSION . "\n";
```

We recommend an ICU version greater or equal to version ICU 49 to be able to use all the features described in this document.
One major feature that is missing in Versions below 49 is the `#` placeholder in plural rules.
See <http://site.icu-project.org/download> for a list of available ICU versions. Note that the version numbering has changed after the
4.8 release so that the first digits are now merged: the sequence is ICU 4.8, ICU 49, ICU 50.
528 529 530 531 532

Additionally the information in the time zone database shipped with the ICU library may be outdated. Please refer
to the [ICU manual](http://userguide.icu-project.org/datetime/timezone#TOC-Updating-the-Time-Zone-Data) for details
on updating the time zone database. While for output formatting the ICU timezone database is used, the time zone database
used by PHP may be relevant too. You can update it by installing the latest version of the [pecl package `timezonedb`](http://pecl.php.net/package/timezonedb).