翻譯
“國(guó)際化”(internationalization,常被簡(jiǎn)寫(xiě)為i18n),是指將字符串和其他一些具有區(qū)域特征的片段從你的程序中提?。╝bstract)出來(lái)并基于用戶所在區(qū)域(比如語(yǔ)言、國(guó)家)而將其置于一個(gè)能被翻譯和轉(zhuǎn)化的層的過(guò)程。對(duì)于文本來(lái)說(shuō)(text),這意味著用一個(gè)能夠把它(或“信息”)翻譯成用戶所需語(yǔ)言的函數(shù),來(lái)剝離文本的每一部分:
// 文本始終以英語(yǔ)輸出echo 'Hello World'; // 文本將以用戶指定語(yǔ)言或默認(rèn)英語(yǔ)輸出echo $translator->trans('Hello World');
locale的意思,單純來(lái)說(shuō)就是用戶的語(yǔ)言和國(guó)家。在程序中它可以是任何一個(gè)字符串,用來(lái)管理翻譯(translation)和其他格式信息(比如幣種)。推薦使用以ISO 639-1語(yǔ)言代碼,加一個(gè)下劃線(_
),再跟一個(gè)ISO 3166-1 alpha-2國(guó)家代碼(比如fr_FR
這個(gè)locale是指“法國(guó)法語(yǔ)”French/France)。
在本章,你將學(xué)習(xí)如何使用Symfony框架中的translation組件。你可以閱讀翻譯組件來(lái)了解更多。整體上,翻譯的過(guò)程有如下幾步:
開(kāi)啟和配置Symfony的翻譯服務(wù);
將字符串抽象出來(lái)(如“xxxx”),這是通過(guò)調(diào)用Translator去剝離它們來(lái)實(shí)現(xiàn)的 ;(參考 翻譯基礎(chǔ))
針對(duì)每個(gè)被支持的locale,創(chuàng)建翻譯資源/文件,用于翻譯程序中每一個(gè)待譯字串;
針對(duì)request(請(qǐng)求)和可選的 基于用戶整個(gè)session過(guò)程,來(lái) 確定、設(shè)置和管理用戶的locale信息。
配置 ?
翻譯的過(guò)程是通過(guò)translator
服務(wù)來(lái)處理的,該服務(wù)使用用戶指定的locale來(lái)查找并返回翻譯過(guò)的信息。使用translator之前,在配置文件中開(kāi)啟它:
PHP:// app/config/config.php$container->loadFromExtension('framework', array('translator' => array('fallbacks' => array('en')),));
XML:<!-- app/config/config.xml --><?xml version="1.0" encoding="UTF-8" ?><container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:framework="http://symfony.com/schema/dic/symfony" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd http://symfony.com/schema/dic/symfony http://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> <framework:config> <framework:translator> <framework:fallback>en</framework:fallback> </framework:translator> </framework:config></container>
YAML:# app/config/config.ymlframework:translator: { fallbacks: [en] }
關(guān)于 fallbacks關(guān)鍵字 以及Symfony在找不到翻譯語(yǔ)種時(shí)如何處理,請(qǐng)參考 翻譯時(shí)的Locales回滾 以了解細(xì)節(jié)。
翻譯時(shí)要用到的locale信息被存于request對(duì)象中。一般在路由中被設(shè)為 _locale
屬性。請(qǐng)參考 The Locale and the URL。
翻譯基礎(chǔ) ?
translator
服務(wù)負(fù)責(zé)完成對(duì)文本的翻譯。為了翻譯一個(gè)文本塊(被稱為“message”,以下稱作“信息”),使用trans()方法。例如,你要在controller中翻譯一個(gè)簡(jiǎn)單的信息:
// ...use Symfony\Component\HttpFoundation\Response; public function indexAction(){$translated = $this->get('translator')->trans('Symfony is great'); return new Response($translated);}
上述代碼被執(zhí)行后,Symfony就嘗試基于用戶的 locale
來(lái)翻譯 “Symfony is great”信息。為了讓這個(gè)過(guò)程實(shí)現(xiàn),你需要告訴Symfony如何通過(guò)一個(gè)“翻譯源(translation resource)”來(lái)執(zhí)行翻譯。一般來(lái)說(shuō)翻譯源是一個(gè)文件,包含有成組的翻譯信息,對(duì)應(yīng)某一指定的locale。它就像個(gè)翻譯時(shí)的“字典”,可被創(chuàng)建為多種格式,但推薦使用XLIFF(譯注:就是后綴不同的xml格式):
PHP:// messages.fr.phpreturn array('Symfony is great' => 'J\'aime Symfony',);
YAML:# messages.fr.ymlSymfony is great: J'aime Symfony
XML:<!-- messages.fr.xlf --><?xml version="1.0"?><xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2"> <file source-language="en" datatype="plaintext" original="file.ext"> <body> <trans-unit id="symfony_is_great"> <source>Symfony is great</source> <target>J'aime Symfony</target> </trans-unit> </body> </file></xliff>
關(guān)于這類文件的存放位置等信息,參考 翻譯源/文件名與位置
現(xiàn)在,如果用戶的locale是法語(yǔ)(比如 fr_FR
或 fr_BE
),那么前面的信息將被翻譯為 J'aime Symfony
。你也可以在 模板(templates) 中完成翻譯。
翻譯的處理過(guò)程 ?
為了能翻譯一條信息,Symfony執(zhí)行以下簡(jiǎn)明流程:
先確定request對(duì)象中所存儲(chǔ)的當(dāng)前用戶的
locale
信息;某一目錄(一大組message)的已經(jīng)翻譯好的信息,將從由
locale
(比如fr_FR
)所決定的翻譯源加載。如果locale不存在,則由 fallback locale 所決定的翻譯信息,也將被加載并且合并到目錄之中。最終結(jié)果就是生成了一個(gè)“翻譯大詞典”;如果能夠從目錄中找到待翻信息,翻譯結(jié)果將被返回。否則, translator返回原始信息。
當(dāng)使用trans()方法時(shí),Symfony從對(duì)應(yīng)的信息目錄中,尋找準(zhǔn)確的字符串,然后返回它(如果存在的話)。
信息占位符 ?
有時(shí),一條包含變量的信息,需要被翻譯:
use Symfony\Component\HttpFoundation\Response; public function indexAction($name){$translated = $this->get('translator')->trans('Hello '.$name); return new Response($translated);}
可是,對(duì)于這樣一個(gè)字符串,創(chuàng)建相應(yīng)的翻譯是不可能的,因?yàn)閠ranslator始終在嘗試尋找“確定信息”,包括變量值本身(比如“Hello Ryan”和“Hello Fabien”在translator看來(lái)是兩條不同的信息)。
對(duì)于這種情形,請(qǐng)參考組件文檔中的 信息占位符/Message Placeholders。同樣情況在模板中的處理方法,參考 Twig Templates。
復(fù)數(shù)處理 ?
另一個(gè)復(fù)雜場(chǎng)面,則是你在翻譯中要面對(duì)基于某些變量的“復(fù)數(shù)狀況”:
1 2 | There is one apple.
There are 5 apples. |
要處理這個(gè),應(yīng)使用 transChoice()
方法,或者用模板中的transchoice
標(biāo)簽/調(diào)節(jié)器,請(qǐng) 參考這里。
更多信息,參考Translation組件文檔的 復(fù)數(shù)處理章節(jié)。
模板中的翻譯 ?
多數(shù)情況下,翻譯發(fā)生在模板中,對(duì)于Twig和PHP模板,Symfony提供了原生支持。
Twig模板 ?
Symfony提供了特殊的Twig標(biāo)簽(trans
和 transchoice
),用于對(duì)“靜態(tài)文本塊”信息提供翻譯幫助。
{% trans %}Hello %name%{% endtrans %} {% transchoice count %}{0} There are no apples|{1} There is one apple|]1,Inf[ There are %count% apples{% endtranschoice %}
transchoice
標(biāo)簽,自動(dòng)地從當(dāng)前上下文關(guān)系中得到%count%
變量,并將其傳給translator。這種機(jī)制,只在你使用%var%
這種格式的占位符時(shí)生效。
在Twig模板中使用 trans/transchoice 標(biāo)簽進(jìn)行翻譯時(shí),%var%
占位符注釋是必須要提供的。
如果你需要在字符串中使用百分號(hào)%,要寫(xiě)兩次來(lái)為它轉(zhuǎn)義:
1 | {% trans %}Percent: %percent%%%{% endtrans %} |
你也可以指定信息域(message domain),并傳遞一些附加的變量進(jìn)來(lái):
{% trans with {'%name%': 'Fabien'} from "app" %}Hello %name%{% endtrans %} {% trans with {'%name%': 'Fabien'} from "app" into "fr" %}Hello %name%{% endtrans %} {% transchoice count with {'%name%': 'Fabien'} from "app" %}{0} %name%, there are no apples|{1} %name%, there is one apple|]1,Inf[ %name%, there are %count% apples{% endtranschoice %}
trans和transchoice調(diào)節(jié)器,可用于翻譯變量文本和復(fù)雜表達(dá)式:
{{ message|trans }} {{ message|transchoice(5) }} {{ message|trans({'%name%': 'Fabien'}, "app") }} {{ message|transchoice(5, {'%name%': 'Fabien'}, 'app') }}
在翻譯時(shí)無(wú)論使用標(biāo)簽還是調(diào)節(jié)器,效果是相同的,但有一個(gè)微小區(qū)別:自動(dòng)輸出轉(zhuǎn)義功能僅對(duì)調(diào)節(jié)器有效。換言之,如果你需要翻譯出來(lái)的信息“不被轉(zhuǎn)義”,則必須在translation調(diào)節(jié)器后面再跟一個(gè)raw調(diào)節(jié)器:
{# 標(biāo)簽中被翻譯的文本從不被轉(zhuǎn)義#}{% trans %}<h3>foo</h3>{% endtrans %} {% set message = '<h3>foo</h3>' %} {# 變量調(diào)節(jié)器翻譯的字符串和變量,默認(rèn)將被轉(zhuǎn)義 #}{{ message|trans|raw }}{{ '<h3>bar</h3>'|trans|raw }}
你可以通過(guò)單一標(biāo)簽,為整個(gè)twig模板設(shè)置一個(gè)翻譯域(translation domain):
1 | {% trans_default_domain "app" %} |
注意這時(shí)僅會(huì)影響到當(dāng)前模板,而不包括任何“被包容(included)”的模板(為的是減少副作用)。
PHP模板 ?
translator也可以在PHP模板中通過(guò)translator helper來(lái)使用:
<?php echo $view['translator']->trans('Symfony is great') ?> <?php echo $view['translator']->transChoice('{0} There are no apples|{1} There is one apple|]1,Inf[ There are %count% apples',10,array('%count%' => 10)) ?>
翻譯源/文件的命名和位置 ?
Symfony在以下位置尋找信息文件(即是translations/翻譯信息):
app/Resources/translations
目錄;app/Resources/<bundle name>/translations
目錄;任何bundle下的
Resources/translations/
目錄
上面的位置是按照“高優(yōu)先權(quán)在前”的順序排列的。這意味著,你可以用前面兩個(gè)目錄之一,來(lái)覆寫(xiě)某個(gè)bundle中的翻譯信息。
覆寫(xiě)機(jī)制基于鍵等級(jí)(key level)而執(zhí)行:只有被覆寫(xiě)的鍵需要被列在高優(yōu)先級(jí)的信息文件中。當(dāng)一個(gè)鍵沒(méi)有在信息文件中被找到時(shí),translator將自動(dòng)回滾到低優(yōu)先級(jí)的信息文件中。
信息文件的文件名也很重要,每一個(gè)信息文件必須按下列命名路徑來(lái)命名:domain.locale.loader
:
domain: 這是一個(gè)可選項(xiàng),用于組織信息文件成為群組(例如admin, navigation 或default messages)。參閱 使用翻譯信息的域 Using Message Domains;
locale: 這是翻譯信息的locale (例如 en_GB, en, 等等);
loader: 這是Symfony如何來(lái)加載和解析信息文件 (也就是
xlf
,php
,yml
等文件后綴).
加載器(loader)可以是任何已注冊(cè)加載器的名稱。Symfony默認(rèn)提供了許多加載器,包括:
xlf
: 加載XLIFF文件;php
: 加載PHP文件;yml
: 加載YAML文件;
使用何種加載器的選擇權(quán)完全在你,隨你喜好。推薦使用xlf作為翻譯的信息文件。更多選擇,參考 加載信息目錄 Loading Message Catalogs。
你也可以將翻譯信息存在數(shù)據(jù)庫(kù)中,或任何其他介質(zhì),只要提供一個(gè)自定義的類去實(shí)現(xiàn) LoaderInterface
接口即可。參考 translation.loader
標(biāo)簽,以了解更多。
你可以在配置文件中通過(guò)paths
選項(xiàng)添加一個(gè)目錄:
PHP:// app/config/config.php$container->loadFromExtension('framework', array('translator' => array('paths' => array('%kernel.root_dir%/../translations',),),));
XML:<!-- app/config/config.xml --><?xml version="1.0" encoding="UTF-8" ?><container xmlns="http://symfony.com/schema/dic/services" xmlns:framework="http://symfony.com/schema/dic/symfony" xmlns:xsi="http://www.w3.org/2001/XMLSchema-Instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd http://symfony.com/schema/dic/symfony http://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> <framework:config> <framework:translator> <framework:path>%kernel.root_dir%/../translations</framework:path> </framework:translator> </framework:config></container>
YAML:# app/config/config.ymlframework:translator:paths:- '%kernel.root_dir%/../translations'
每次你創(chuàng)建一個(gè)新的翻譯源(translation resource)或安裝了一個(gè)包含翻譯源的bundle時(shí),一定要清除緩存,這樣Symfony才能發(fā)現(xiàn)這個(gè)新的翻譯源。
1 | $ php app/console cache:clear |
翻譯時(shí)Locale的回滾 ?
假設(shè)一名用戶的locale信息是 fr_FR
,而你正翻譯的鍵是 Symfony is great
。為了找到法語(yǔ)信息,Symfony切實(shí)地檢查若干locale的翻譯源:
首先,Symfony在一個(gè)
fr_FR
的翻譯源(例如messages.fr_FR.xlf)尋找翻譯信息;如果沒(méi)找到,Symfony在一個(gè)
fr
翻譯源(比如messages.fr.xlf)繼續(xù)尋找翻譯信息;如果仍然沒(méi)找到,Symfony使用fallbacks這個(gè)配置參數(shù), 它被默認(rèn)設(shè)為
en
(參閱 FrameworkBundle配置信息)。
2.6版Symfony引入了將缺少的翻譯信息寫(xiě)入日志的能力。
當(dāng)Symfony無(wú)法找到給定locale的翻譯信息時(shí),它會(huì)把缺少的翻譯信息給添加到日志文件中。參閱 logging。
翻譯數(shù)據(jù)庫(kù)內(nèi)容 ?
翻譯數(shù)據(jù)庫(kù)內(nèi)容時(shí)要用到Doctrine擴(kuò)展中的 Translatable Extension 或 Translatable Behavior(PHP 5.4+)。更多信息請(qǐng)參考相應(yīng)文檔。
翻譯約束信息 ?
參考 如何翻譯驗(yàn)證約束消息 以了解更多。
處理用戶的Locale ?
翻譯的過(guò)程是取決于用戶的locale的。閱讀 如何操作用戶的Locale 以了解如何處理。
對(duì)翻譯進(jìn)行調(diào)試 ?
debug:translation 命令行語(yǔ)句從Symfony 2.5起引入。 Symfony 2.6之前,這一命令是translation:debug。
當(dāng)你在不同語(yǔ)言的大量翻譯信息中操作時(shí),要跟蹤到丟失了哪條信息以及哪條信息沒(méi)有被使用,是很困難的。閱讀 如何找到丟失或未使用的翻譯信息 以了解如何發(fā)現(xiàn)這一類翻譯信息。
總結(jié) ?
使用Symfony的翻譯組件創(chuàng)建一個(gè)國(guó)際化的應(yīng)用程序?qū)⒉辉偈且粋€(gè)“痛苦過(guò)程”,而是歸結(jié)于以下簡(jiǎn)單步驟:
從程序中抽象出待翻譯的信息,把每一條信息用
trans()
或transChoice()
方法替換(通過(guò) 使用Translator一文了解更多);通過(guò)創(chuàng)建信息文件(translation message file)將待譯信息翻譯成多個(gè)locale語(yǔ)種。Symfony能夠找到并處理每一個(gè)文件,因?yàn)檫@些文件的名字遵循指定的命名約定;
管理好用戶的locale,它可以存在request中,但是也可以存在用戶的session中。