國(guó)際化(I18N)是指在設(shè)計(jì)軟件時(shí),使它可以無需做大的改變就能夠適應(yīng)不同的語言和地區(qū)的需要。對(duì)于 Web 應(yīng)用程序,這有著特別重要的意義,因?yàn)闈撛诘挠脩艨赡軙?huì)在全球范圍內(nèi)。Yii 提供的國(guó)際化功能支持全方位信息翻譯,視圖翻譯,日期和數(shù)字格式化。
區(qū)域設(shè)置是一組參數(shù)以定義用戶希望能在他們的用戶界面所看到用戶的語言,國(guó)家和任何特殊的偏好。它通常是由語言 ID 和區(qū)域 ID 組成。例如,ID “en-US” 代表英語和美國(guó)的語言環(huán)境。為了保持一致性,在 Yii 應(yīng)用程序中使用的所有區(qū)域 ID 應(yīng)該規(guī)范化為 ll-CC
,其中 ll
是根據(jù)兩個(gè)或三個(gè)字母的小寫字母語言代碼ISO-639 和 CC
是兩個(gè)字母的國(guó)別代碼ISO-3166。有關(guān)區(qū)域設(shè)置的更多細(xì)節(jié)可以看ICU 項(xiàng)目文檔。
在 Yii 中,我們經(jīng)常用 “l(fā)anguage” 來代表一個(gè)區(qū)域。
一個(gè) Yii 應(yīng)用使用兩種語言:[[yii\base\Application::$sourceLanguage|源語言]] 和[[yii\base\Application::$language|目標(biāo)語言]] 。前者指的是寫在代碼中的語言,后者是向最終用戶顯示內(nèi)容的語言。而信息翻譯服務(wù)主要是將文本消息從原語言翻譯到目標(biāo)語言。
可以用類似下面的應(yīng)用程序配置來配置應(yīng)用程序語言:
return [
// 設(shè)置目標(biāo)語言為俄語
'language' => 'ru-RU',
// 設(shè)置源語言為英語
'sourceLanguage' => 'en-US',
......
];
默認(rèn)的 [[yii\base\Application::$sourceLanguage|源語言]] 值是 en-US
,即美國(guó)英語。建議你保留此默認(rèn)值不變,因?yàn)橥ǔW屓藢⒂⒄Z翻譯成其它語言要比將其它語言翻譯成其它語言容易得多。
你經(jīng)常需要根據(jù)不同的因素來動(dòng)態(tài)地設(shè)置 [[yii\base\Application::$language|目標(biāo)語言]] ,如最終用戶的語言首選項(xiàng)。要在應(yīng)用程序配置中配置它,你可以使用下面的語句來更改目標(biāo)語言:
// 改變目標(biāo)語言為中文
\Yii::$app->language = 'zh-CN';
消息翻譯服務(wù)用于將一條文本信息從一種語言(通常是 [[yii\base\Application::$sourceLanguage|源語言]] )翻譯成另一種語言(通常是 [[yii\base\Application::$language|目標(biāo)語言]])。它的翻譯原理是通過在語言文件中查找要翻譯的信息以及翻譯的結(jié)果。如果要翻譯的信息可以在語言文件中找到,會(huì)返回相應(yīng)的翻譯結(jié)果;否則會(huì)返回原始未翻譯的信息。
為了使用消息翻譯服務(wù),需要做如下工作:
這個(gè) [[Yii::t()]] 方法的用法如下,
echo \Yii::t('app', 'This is a string to translate!');
第一個(gè)參數(shù)指儲(chǔ)存消息來源的類別名稱,第二個(gè)參數(shù)指需要被翻譯的消息。
這個(gè) [[Yii::t()]] 方法會(huì)調(diào)用 i18n
應(yīng)用組件 來實(shí)現(xiàn)翻譯工作。這個(gè)組件可以在應(yīng)用程序中按下面的代碼來配置,
'components' => [
// ...
'i18n' => [
'translations' => [
'app*' => [
'class' => 'yii\i18n\PhpMessageSource',
//'basePath' => '@app/messages',
//'sourceLanguage' => 'en-US',
'fileMap' => [
'app' => 'app.php',
'app/error' => 'error.php',
],
],
],
],
],
在上面的代碼中,配置了由 [[yii\i18n\PhpMessageSource]] 所支持的消息來源。模式 app*
表示所有以 app
開頭的消息類別名稱都使用這個(gè)翻譯的消息來源。該 [[yii\i18n\PhpMessageSource]] 類使用 PHP 文件來存儲(chǔ)消息翻譯。每 PHP 文件對(duì)應(yīng)單一類別的消息。默認(rèn)情況下,文件名應(yīng)該與類別名稱相同。但是,你可以配置[[yii\i18n\PhpMessageSource::fileMap|fileMap]] 來映射一個(gè)類別到不同名稱的 PHP 文件。在上面的例子中,類別 app/error
被映射到 PHP 文件 @app/messages/ru-RU/error.php
(假設(shè) ru-RU
為目標(biāo)語言)。如果沒有此配置,該類別將被映射到 @app/messages/ru-RU/app/error.php
。
除了在 PHP 文件中存儲(chǔ)消息來源,也可以使用下面的消息來源在不同的存儲(chǔ)來存儲(chǔ)翻譯的消息:
在要翻譯的消息里,你可以嵌入一些占位符,并讓它們通過動(dòng)態(tài)的參數(shù)值來代替。你甚至可以根據(jù)目標(biāo)語言格式的參數(shù)值來使用特殊的占位符。在本節(jié)中,我們將介紹如何用不同的方式來格式化消息。
在待翻譯的消息,可以嵌入一個(gè)或多個(gè)占位符,以便它們可以由給定的參數(shù)值取代。通過給不同的參數(shù)值,可以動(dòng)態(tài)地改變翻譯內(nèi)容的消息。在下面的例子中,占位符 {username}
在 “Hello, {username}!”
中將分別被 'Alexander'
和'Qiang'
所替換。
$username = 'Alexander';
// 輸出:“Hello, Alexander”
echo \Yii::t('app', 'Hello, {username}!', [
'username' => $username,
]);
$username = 'Qiang';
// 輸出:“Hello, Qiang”
echo \Yii::t('app', 'Hello, {username}!', [
'username' => $username,
]);
當(dāng)翻譯的消息包含占位符時(shí),應(yīng)該讓占位符保留原樣。這是因?yàn)檎{(diào)用 Yii::t()
時(shí),占位符將被實(shí)際參數(shù)值代替。
你可以使用 名稱占位符 或者 位置占位符,但不能兩者都用在同一個(gè)消息里。
前面的例子說明了如何使用名稱占位符。即每個(gè)占位符的格式為 {參數(shù)名稱}
,你所提供的參數(shù)作為關(guān)聯(lián)數(shù)組,其中數(shù)組的鍵是參數(shù)名稱(沒有大括號(hào)),數(shù)組的值是對(duì)應(yīng)的參數(shù)值。
位置占位符是使用基于零的整數(shù)序列,在調(diào)用 Yii::t()
時(shí)會(huì)參數(shù)值根據(jù)它們出現(xiàn)位置的順序分別進(jìn)行替換。在下面的例子中,位置占位符 {0}
,{1}
和 {2}
將分別被 $price
,$count
和 $subtotal
所替換。
$price = 100;
$count = 2;
$subtotal = 200;
echo \Yii::t('app', 'Price: {0}, Count: {1}, Subtotal: {2}', $price, $count, $subtotal);
提示:大多數(shù)情況下你應(yīng)該使用名稱占位符。這是因?yàn)閰?shù)名稱可以讓翻譯者更好的理解要被翻譯的消息。
你可以在消息的占位符指定附加格式的規(guī)則,這樣的參數(shù)值可在替換占位符之前格式化它們。在下面的例子中,價(jià)格參數(shù)值將視為一個(gè)數(shù)并格式化為貨幣值:
$price = 100;
echo \Yii::t('app', 'Price: {0, number, currency}', $price);
注意:參數(shù)的格式化需要安裝 intl PHP 擴(kuò)展。
可以使用縮寫的形式或完整的形式來格式化占位符:
short form: {PlaceholderName, ParameterType}
full form: {PlaceholderName, ParameterType, ParameterStyle}
請(qǐng)參閱 ICU 文檔關(guān)于如何指定這樣的占位符的說明。
接下來我們會(huì)展示一些常用的使用方法。
參數(shù)值應(yīng)該被格式化為一個(gè)數(shù)。例如,
$sum = 42;
echo \Yii::t('app', 'Balance: {0, number}', $sum);
你可以指定參數(shù)的格式為 integer
(整型),currency
(貨幣),或者 percent
(百分?jǐn)?shù)):
$sum = 42;
echo \Yii::t('app', 'Balance: {0, number, currency}', $sum);
你也可以指定一個(gè)自定義模式來格式化數(shù)字。 例如,
$sum = 42;
echo \Yii::t('app', 'Balance: {0, number, ,000,000000}', $sum);
該參數(shù)值應(yīng)該被格式化為一個(gè)日期。 例如,
echo \Yii::t('app', 'Today is {0, date}', time());
你可以指定一個(gè)可選的參數(shù)格式 short
,medium
,long
,或 full
:
echo \Yii::t('app', 'Today is {0, date, short}', time());
你還可以指定一個(gè)自定義模式來格式化日期:
echo \Yii::t('app', 'Today is {0, date, yyyy-MM-dd}', time());
參數(shù)值應(yīng)該被格式化為一個(gè)時(shí)間。 例如,
echo \Yii::t('app', 'It is {0, time}', time());
你可以指定一個(gè)可選的參數(shù)格式 short
,medium
,long
,或 full
:
echo \Yii::t('app', 'It is {0, time, short}', time());
你還可以指定一個(gè)自定義模式來格式化時(shí)間:
echo \Yii::t('app', 'It is {0, date, HH:mm}', time());
參數(shù)值為一個(gè)數(shù)并被格式化為它的字母拼寫形式。 例如,
// 輸出:"42 is spelled as forty-two"
echo \Yii::t('app', '{n,number} is spelled as {n, spellout}', ['n' => 42]);
參數(shù)值為一個(gè)數(shù)并被格式化為一個(gè)序數(shù)詞。 例如,
// 輸出:"You are the 42nd visitor here!"
echo \Yii::t('app', 'You are the {n, ordinal} visitor here!', ['n' => 42]);
參數(shù)值為秒數(shù)并被格式化為持續(xù)的時(shí)間段。 例如,
// 輸出:"You are here for 47 sec. already!"
echo \Yii::t('app', 'You are here for {n, duration} already!', ['n' => 47]);
不同的語言有不同的方式來表示復(fù)數(shù)。 Yii 提供一個(gè)便捷的途徑,即使是非常復(fù)雜的規(guī)則也使翻譯消息時(shí)不同的復(fù)數(shù)形式行之有效。取之以直接處理詞形變化規(guī)則,它是足以面對(duì)某些詞形變化語言的翻譯。 例如,
// 當(dāng) $n = 0 時(shí),輸出:"There are no cats!"
// 當(dāng) $n = 1 時(shí),輸出:"There is one cat!"
// 當(dāng) $n = 42 時(shí),輸出:"There are 42 cats!"
echo \Yii::t('app', 'There {n, plural, =0{are no cats} =1{is one cat} other{are # cats}}!', ['n' => $n]);
在上面的多個(gè)規(guī)則的參數(shù)中, =0
意味著 n
的值是 0 ,=1
意味著 n
的值是 1 , 而 other
則是對(duì)于其它值,#
會(huì)被 n
中的值給替代。
復(fù)數(shù)形式可以是某些非常復(fù)雜的語言。下面以俄羅斯為例,=1
完全匹配 n = 1
,而 one
匹配 21
或 101
:
Здесь {n, plural, =0{котов нет} =1{есть один кот} one{# кот} few{# кота} many{# котов} other{# кота}}!
注意,上述信息主要是作為一個(gè)翻譯的信息,而不是一個(gè)原始消息,除非設(shè)置應(yīng)用程序的[[yii\base\Application::$sourceLanguage|源語言]] 為 ru-RU
。
如果沒有找到一個(gè)翻譯的原始消息,復(fù)數(shù)規(guī)則 [[yii\base\Application::$sourceLanguage|源語言]] 將被應(yīng)用到原始消息。
要了解詞形變化形式,你應(yīng)該指定一個(gè)特定的語言,請(qǐng)參考rules reference at unicode.org。
可以使用 select
參數(shù)類型來選擇基于參數(shù)值的短語。例如,
// 輸出:"Snoopy is a dog and it loves Yii!"
echo \Yii::t('app', '{name} is a {gender} and {gender, select, female{she} male{he} other{it}} loves Yii!', [
'name' => 'Snoopy',
'gender' => 'dog',
]);
在上面的表達(dá)中, female
和 male
是可能的參數(shù)值,而 other
用于處理不與它們中任何一個(gè)相匹配的值。對(duì)于每一個(gè)可能的參數(shù)值,應(yīng)指定一個(gè)短語并把它放在在一對(duì)大括號(hào)中。
你可以指定使用默認(rèn)的翻譯,該翻譯將作為一個(gè)類別,用于不匹配任何其他翻譯的后備。這種翻譯應(yīng)標(biāo)有 *
。為了做到這一點(diǎn)以下內(nèi)容需要添加到應(yīng)用程序的配置:
//配置 i18n 組件
'i18n' => [
'translations' => [
'*' => [
'class' => 'yii\i18n\PhpMessageSource'
],
],
],
現(xiàn)在,你可以使用每一個(gè)還沒有配置的類別,這跟 Yii 1.1 的行為有點(diǎn)類似。該類別的消息將來自在默認(rèn)翻譯 basePath
中的一個(gè)文件,該文件在 @app/messages
:
echo Yii::t('not_specified_category', 'message from unspecified category');
該消息將來自 @app/messages/<LanguageCode>/not_specified_category.php
。
如果你想翻譯一個(gè)模塊的消息,并避免使用單一翻譯文件的所有信息,你可以按照下面的方式來翻譯:
<?php
namespace app\modules\users;
use Yii;
class Module extends \yii\base\Module
{
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',
'sourceLanguage' => 'en-US',
'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);
}
}
在上面的例子中,我們使用通配符匹配,然后過濾了所需的文件中的每個(gè)類別。取之使用 fileMap
,你可以簡(jiǎn)單地使用類映射的同名文件?,F(xiàn)在你可以直接使用 Module::t('validation', 'your custom validation message')
或 Module::t('form', 'some form label')
。
上述模塊的翻譯規(guī)則也同樣適用于小部件的翻譯規(guī)則,例如:
<?php
namespace app\widgets\menu;
use yii\base\Widget;
use Yii;
class Menu extends Widget
{
public function init()
{
parent::init();
$this->registerTranslations();
}
public function registerTranslations()
{
$i18n = Yii::$app->i18n;
$i18n->translations['widgets/menu/*'] = [
'class' => 'yii\i18n\PhpMessageSource',
'sourceLanguage' => 'en-US',
'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);
}
}
你可以簡(jiǎn)單地使用類映射的同名文件而不是使用 fileMap
?,F(xiàn)在你直接可以使用 Menu::t('messages', 'new messages {messages}', ['{messages}' => 10])
。
提示: 對(duì)于小部件也可以使用 i18n 視圖,并一樣以控制器的規(guī)則來應(yīng)用它們。
Yii 自帶了一些默認(rèn)的信息驗(yàn)證錯(cuò)誤和其他一些字符串的翻譯。這些信息都是在 yii
類別中。有時(shí)候你想糾正應(yīng)用程序的默認(rèn)信息翻譯。為了做到這一點(diǎn),需配置 i18n
應(yīng)用組件 如下:
'i18n' => [
'translations' => [
'yii' => [
'class' => 'yii\i18n\PhpMessageSource',
'sourceLanguage' => 'en-US',
'basePath' => '@app/messages'
],
],
],
現(xiàn)在可以把你修改過的翻譯放在 @app/messages/<language>/yii.php
。
如果翻譯的消息在消息源文件里找不到,Yii 將直接顯示該消息內(nèi)容。這樣一來當(dāng)你的原始消息是一個(gè)有效的冗長(zhǎng)的文字時(shí)會(huì)很方便。然而,有時(shí)它是不能實(shí)現(xiàn)我們的需求。你可能需要執(zhí)行一些自定義處理的情況,這時(shí)請(qǐng)求的翻譯可能在消息翻譯源文件找不到。這可通過使用 [[yii\i18n\MessageSource::EVENT_MISSING_TRANSLATION|missingTranslation]] - [[yii\i18n\MessageSource]] 的事件來完成。
例如,你可能想要將所有缺失的翻譯做一個(gè)明顯的標(biāo)記,這樣它們就可以很容易地在頁面中找到。為此,你需要先設(shè)置一個(gè)事件處理程序。這可以在應(yīng)用程序配置中進(jìn)行:
'components' => [
// ...
'i18n' => [
'translations' => [
'app*' => [
'class' => 'yii\i18n\PhpMessageSource',
'fileMap' => [
'app' => 'app.php',
'app/error' => 'error.php',
],
'on missingTranslation' => ['app\components\TranslationEventHandler', 'handleMissingTranslation']
],
],
],
],
現(xiàn)在,你需要實(shí)現(xiàn)自己的事件處理程序:
<?php
namespace app\components;
use yii\i18n\MissingTranslationEvent;
class TranslationEventHandler
{
public static function handleMissingTranslation(MissingTranslationEvent $event)
{
$event->translatedMessage = "@MISSING: {$event->category}.{$event->message} FOR LANGUAGE {$event->language} @";
}
}
如果 [[yii\i18n\MissingTranslationEvent::translatedMessage]] 是由事件處理程序設(shè)置,它將顯示翻譯結(jié)果。
注意:每個(gè)消息源會(huì)單獨(dú)處理它缺少的翻譯。如果是使用多個(gè)消息源,并希望他們把缺少的翻譯以同樣的方式來處理, 你應(yīng)該給它們每一個(gè)消息源指定相應(yīng)的事件處理程序。
message
命令 翻譯儲(chǔ)存在 [[yii\i18n\PhpMessageSource|php 文件]],[[yii\i18n\GettextMessageSource|.po 文件] 或者 [[yii\i18n\DbMessageSource|數(shù)據(jù)庫]]。具體見類的附加選項(xiàng)。
首先,你需要?jiǎng)?chuàng)建一個(gè)配置文件。確定應(yīng)該保存在哪里,然后執(zhí)行命令
./yii message/config path/to/config.php
打開創(chuàng)建的文件,并按照需求來調(diào)整參數(shù)。特別注意:
languages
: 代表你的應(yīng)用程序應(yīng)該被翻譯成什么語言的一個(gè)數(shù)組;messagePath
: 存儲(chǔ)消息文件的路徑,這應(yīng)與配置中 i18n
的 basePath
參數(shù)一致。注意,這里不支持路徑別名,它們必須是配置文件相對(duì)路徑的位置
一旦你做好了配置文件,你就可以使用命令提取消息
./yii message path/to/config.php
然后你會(huì)發(fā)現(xiàn)你的文件(如果你已經(jīng)選擇基于文件的翻譯)在 messagePath
目錄。
有時(shí)你可能想要翻譯一個(gè)完整的視圖文件,而不是翻譯單條文本消息。為了達(dá)到這一目的,只需簡(jiǎn)單的翻譯視圖并在它子目錄下保存一個(gè)名稱一樣的目標(biāo)語言文件。例如,如果你想要翻譯的視圖文件為 views/site/index.php
且目標(biāo)語言是 ru-RU
,你可以將視圖翻譯并保存為 views/site/ru-RU/index.php
?,F(xiàn)在每當(dāng)你調(diào)用 [[yii\base\View::renderFile()]] 或任何其它方法 (如 [[yii\base\Controller::render()]]) 來渲染 views/site/index.php
視圖,它最終會(huì)使用所翻譯的 views/site/ru-RU/index.php
。
注意:如果 [[yii\base\Application::$language|目標(biāo)語言]] 跟 [[yii\base\Application::$sourceLanguage|源語言]] 相同,在翻譯視圖的存在下,將呈現(xiàn)原始視圖。
在 格式化輸出數(shù)據(jù) 一節(jié)可獲取詳細(xì)信息。
Yii 使用 PHP intl 擴(kuò)展 來提供大多數(shù) I18N 的功能,如日期和數(shù)字格式的 [[yii\i18n\Formatter]] 類和消息格式的 [[yii\i18n\MessageFormatter]] 類。當(dāng) intl
擴(kuò)展沒有安裝時(shí),兩者會(huì)提供一個(gè)回調(diào)機(jī)制。然而,該回調(diào)機(jī)制只適用于目標(biāo)語言是英語的情況下。因此,當(dāng) I18N 對(duì)你來說必不可少時(shí),強(qiáng)烈建議你安裝 intl
。
PHP intl 擴(kuò)展 是基于對(duì)于所有不同的語言環(huán)境提供格式化規(guī)則的 ICU 庫。不同版本的 ICU 中可能會(huì)產(chǎn)生不同日期和數(shù)值格式的結(jié)果。為了確保你的網(wǎng)站在所有環(huán)境產(chǎn)生相同的結(jié)果,建議你安裝與 intl
擴(kuò)展相同的版本(和 ICU 同一版本)。
要找出所使用的 PHP 是哪個(gè)版本的 ICU ,你可以運(yùn)行下面的腳本,它會(huì)給出你所使用的 PHP 和 ICU 的版本。
<?php
echo "PHP: " . PHP_VERSION . "\n";
echo "ICU: " . INTL_ICU_VERSION . "\n";
此外,還建議你所使用的 ICU 版本應(yīng)等于或大于 49 的版本。這確保了可以使用本文檔描述的所有功能。例如,低于 49 版本的 ICU 不支持使用 #
占位符來實(shí)現(xiàn)復(fù)數(shù)規(guī)則。請(qǐng)參閱 http://site.icu-project.org/download 獲取可用 ICU 版本的完整列表。注意,版本編號(hào)在 4.8 之后發(fā)生了變化(如 ICU4.8,ICU49,50 ICU 等)。
另外,ICU 庫中時(shí)區(qū)數(shù)據(jù)庫的信息可能過時(shí)。要更新時(shí)區(qū)數(shù)據(jù)庫時(shí)詳情請(qǐng)參閱ICU 手冊(cè) 。而對(duì)于 ICU 輸出格式使用的時(shí)區(qū)數(shù)據(jù)庫,PHP 用的時(shí)區(qū)數(shù)據(jù)庫可能跟它有關(guān)。你可以通過安裝 pecl package timezonedb
的最新版本來更新它。