翻譯


「國際化」(internationalization,常被簡寫為i18n),是指將字串和其他一些具有區域特徵的片段從你的程式中提取(abstract)出來並基於使用者所在區域(如語言、國家)而將其置於一個能被翻譯和轉化的層的過程。對於文字來說(text),這意味著用一個能夠把它(或「資訊」)翻譯成使用者所需語言的函數,來剝離文字的每一部分:

// 文本始终以英语输出echo 'Hello World'; // 文本将以用户指定语言或默认英语输出echo $translator->trans('Hello World');

locale的意思,單純來說就是使用者的語言和國家。在程式中它可以是任何一個字串,用來管理翻譯(translation)和其他格式資訊(如幣種)。推薦使用以ISO 639-1語言程式碼,加上一個底線(_),再跟一個ISO 3166-1 alpha-2國家代碼(例如fr_FR這個locale是指「法國法文」French/France)。

在本章,你將學習如何使用Symfony框架中的translation元件。你可以閱讀翻譯元件來了解更多。整體上,翻譯的過程有以下幾步:

  1. 開啟與設定Symfony的翻譯服務;

  2. 將字串抽象化(如“xxxx”),這是透過呼叫Translator去剝離它們來實現的;(參考翻譯基礎)

  3. 針對每個被支援的locale,建立翻譯資源/檔案,用於翻譯程式中每一個待譯字串;

  4. 針對request(請求)和可選的基於使用者整個session過程,來確定、設定和管理使用者的locale資訊

設定 

翻譯的過程是透過translator服務來處理的,該服務使用使用者指定的locale來尋找並傳回翻譯過的資訊。使用translator之前,在設定檔中開啟它:

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] }

關於 fallbacks關鍵字 以及Symfony在找不到翻譯語種時如何處理,請參考 翻譯時的Locales回滾 以了解細節。

翻譯時要用到的locale資訊會儲存在request物件中。一般在路由中被設為 _locale 屬性。請參考 The Locale and the URL

翻譯基礎 

translator 服務負責完成文字的翻譯。為了翻譯一個文字區塊(被稱為“message”,以下稱為“訊息”),使用trans()方法。例如,你要在controller中翻譯一個簡單的訊息:

// ...use Symfony\Component\HttpFoundation\Response; public function indexAction(){$translated = $this->get('translator')->trans('Symfony is great'); return new Response($translated);}

上述程式碼被執行後,Symfony就嘗試基於使用者的 locale 來翻譯 “Symfony is great”訊息。為了讓這個過程實現,你需要告訴Symfony如何透過一個「翻譯來源(translation resource)」來執行翻譯。一般來說翻譯來源是一個文件,包含有成組的翻譯訊息,對應某一指定的locale。它就像個翻譯時的“字典”,可被創建為多種格式,但推薦使用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>

關於這類檔案的存放位置等信息,參考翻譯來源/檔案名稱與位置

現在,如果使用者的locale是法語(例如fr_FRfr_BE ),那麼前面的訊息將會翻譯為 J'aime Symfony。你也可以在 模板(templates) 完成翻譯。

翻譯的處理過程 

為了能翻譯一則訊息,Symfony執行下列簡明流程:

  • 先確定request物件中所儲存的目前使用者的locale 資訊;

  • #某一目錄(一大組message)的已經翻譯好的訊息,將從由locale(例如fr_FR )所決定的翻譯來源載入。如果locale不存在,則由 fallback locale 所決定的翻譯訊息,也會被載入並且合併到目錄之中。最終結果就是產生了一個「翻譯大字典」;

  • 如果能夠從目錄中找到待翻訊息,翻譯結果將會被回傳。否則, translator會傳回原始資訊。

當使用trans()方法時,Symfony從對應的資訊目錄中,尋找準確的字串,然後傳回它(如果存在的話)。

譯註:message catalog

這裡的目錄,原文是catalog,是指Symfony在處理「翻譯」的過程中,從翻譯來源擷取出來的已經翻譯好的待用資訊集合,也就是一個包含了許多message的xliff檔。參考後面「翻譯來源/檔案的命名和位置」中的domain部分)

訊息佔位符 

有時,一則包含變數的訊息,需要被翻譯:

use Symfony\Component\HttpFoundation\Response; public function indexAction($name){$translated = $this->get('translator')->trans('Hello '.$name); return new Response($translated);}

可是,對於這樣一個字串,創建相應的翻譯是不可能的,因為translator始終在嘗試尋找“確定訊息”,包括變數值本身(如「Hello Ryan」和「Hello Fabien」在translator看來是兩條不同的訊息)。

對於這種情形,請參考元件文件中的 資訊佔位符/Message Placeholders。同樣情況在範本中的處理方法,參考 Twig Templates

複數處理 

另一個複雜場面,則是你在翻譯中要面對基於某些變數的「複數狀況」:

##

要處理這個,應使用transChoice() 方法,或用模板中的transchoice標籤/調節器,請參考這裡

更多信息,參考Translation組件文件的 複數處理章節

模板中的翻譯 

多數情況下,翻譯發生在模板中,對於Twig和PHP模板,Symfony提供了原生支援。

Twig模板 

Symfony提供了特殊的Twig標籤(transtranschoice),用於對“靜態文字區塊」資訊提供翻譯幫助。

{% trans %}Hello %name%{% endtrans %} {% transchoice count %}{0} There are no apples|{1} There is one apple|]1,Inf[ There are %count% apples{% endtranschoice %}

transchoice標籤,自動地從目前上下文關係中得到%count%變量,並將其傳給translator。這種機制,只在你使用%var%這種格式的佔位符時生效。

在Twig範本中使用 trans/transchoice 標籤進行翻譯時,%var% 佔位符註解是必須要提供的。

如果你需要在字串中使用百分號%,要寫兩次來為它轉義:

1
2
#
There is one apple.
There are 5 apples.
##
1
#
{% trans %}Percent: %percent%%%{% endtrans %}
{% 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 %}



你也可以指定訊息域(message domain),並傳遞一些附加的變數進來:

{{ message|trans }} {{ message|transchoice(5) }} {{ message|trans({'%name%': 'Fabien'}, "app") }} {{ message|transchoice(5, {'%name%': 'Fabien'}, 'app') }}


trans和transchoice調節器,可用於翻譯變數文字和複雜表達式:
{# 标签中被翻译的文本从不被转义#}{% trans %}<h3>foo</h3>{% endtrans %} {% set message = '<h3>foo</h3>' %} {# 变量调节器翻译的字符串和变量,默认将被转义 #}{{ message|trans|raw }}{{ '<h3>bar</h3>'|trans|raw }}

在翻譯時無論使用標籤或調節器,效果是相同的,但有一個微小區別:自動輸出轉義功能僅對調節器有效。換言之,如果你需要翻譯出來的訊息“不被轉義”,則必須在translation調節器後面再跟一個raw調節器:

1
##你可以透過單一標籤,為整個twig模板設定一個翻譯域(translation domain):
{% trans_default_domain "app" %}
####
<?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)) ?>
################

注意這時僅會影響到當前模板,而不包括任何「被包容(included)」的模板(為的是減少副作用)。

#

PHP模板 

translator也可以在PHP模板中透過translator helper來使用:

PHP:// app/config/config.php$container->loadFromExtension('framework', array('translator' => array('paths' => array('%kernel.root_dir%/../translations',),),));

翻譯來源/檔案的命名和位置 

Symfony在下列位置尋找資訊檔案(即是translations/翻譯資訊):

  • app/Resources/translations 目錄;

  • app/Resources/<bundle name>/translations 目錄;

  • 任何bundle下的Resources/translations/ 目錄

上面的位置是依照「高優先權在前」的順序排列的。這意味著,你可以用前面兩個目錄之一,來覆寫某個bundle中的翻譯訊息。

覆寫機制是基於鍵等級(key level)而執行:只有被覆寫的鍵需要被列在高優先級的資訊檔案中。當一個鍵沒有在資訊檔案中被找到時,translator會自動回滾到低優先順序的資訊檔案中。

資訊檔案的檔案名稱也很重要,每個資訊檔案必須按下列命名路徑來命名:domain.locale.loader:

  • domain: 這是一個可選項,用於組織資訊檔案成為群組(例如admin, navigation 或default messages)。參閱使用翻譯訊息的網域Using Message Domains;

  • #locale: 這是翻譯訊息的locale (例如en_GB, en, 等等);

  • loader: 這是Symfony如何來載入和解析資訊檔案(也就是xlf, php , yml 等檔案後綴).

載入器(loader)可以是任何已註冊載入器的名稱。 Symfony預設提供了許多載入器,包括:

  • xlf: 載入XLIFF檔;

  • ##php : 載入PHP檔;

  • yml: 載入YAML檔;

使用何種載入器的選擇權完全在你,隨你喜好。推薦使用xlf作為翻譯的資訊檔。更多選擇,參考

載入資訊目錄 Loading Message Catalogs

你也可以將翻譯資訊存在資料庫中,或任何其他介質,只要提供一個自訂的類別去實作

LoaderInterface 接口即可。參考 translation.loader 標籤,以了解更多。

#

你可以在設定檔中透過paths選項加入一個目錄:

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'
1

每次你建立一個新的翻譯來源(translation resource)或安裝了一個包含翻譯來源的bundle時,一定要清除緩存,這樣Symfony才能發現這個新的翻譯來源。

$  php app/console cache:clear
#rrreee

翻譯時Locale的回溯 

#假設一個使用者的locale資訊是fr_FR,而你正翻譯的鍵是Symfony is great。為了找到法文信息,Symfony切實地檢查若干locale的翻譯源:

  1. #首先,Symfony在一個fr_FR 的翻譯源(例如messages.fr_FR.xlf)尋找翻譯資訊;

  2. 如果沒找到,Symfony在一個fr 翻譯來源(例如messages.fr.xlf)繼續尋找翻譯資訊;

  3. #如果仍然找不到,Symfony使用fallbacks這個設定參數, 它被預設設為en (參考FrameworkBundle設定資訊)。

2.6版Symfony引進了將缺少的翻譯訊息寫入日誌的能力。

當Symfony無法找到給定locale的翻譯資訊時,它會把缺少的翻譯資訊給加入到日誌檔案中。參閱 logging

翻譯資料庫內容 

翻譯資料庫內容時要用到Doctrine擴充功能中的Translatable ExtensionTranslatable Behavior(PHP 5.4 )。更多資訊請參考對應文件。

翻譯:使用Doctrine擴充有兩種方式

一個是類別庫方式,一個是bundle方式。此處的Translatable Behavior,是指用bundle安裝之後,翻譯資料庫內容時所應參考的用法)

翻譯約束資訊 

參考 如何翻譯驗證約束訊息 以了解更多。

處理使用者的Locale 

翻譯的過程是取決於使用者的locale的。閱讀 如何操作使用者的Locale 以了解如何處理。

對翻譯進行偵錯 

debug:translation 指令列語句從Symfony 2.5開始引入。  Symfony 2.6之前,此指令是translation:debug。

當你在不同語言的大量翻譯訊息中操作時,要追蹤到遺失了哪一個訊息以及哪一個訊息沒有被使用,是很困難的。閱讀 如何找到遺失或未使用的翻譯資訊 以了解如何發現這一類翻譯資訊。

總結 

使用Symfony的翻譯元件創建一個國際化的應用程式將不再是一個“痛苦過程”,而是歸結於以下簡單步驟:

  • 從程式中抽像出待翻譯的訊息,把每一則訊息用trans()transChoice() 方法取代(透過使用Translator一文了解更多);

  • 透過建立訊息檔案(translation message file)將待譯訊息翻譯成多個locale語種。 Symfony能夠找到並處理每個文件,因為這些文件的名字遵循指定的命名約定;

  • #管理好用戶的locale,它可以存在request中,但是也可以存在用戶的session中。