跳到主要內容

翻譯

編輯此頁面

術語「國際化」(通常縮寫為 i18n)指的是將字串和其他特定於語系的部分從您的應用程式中抽象出來,放到一個層級,在該層級中,可以根據使用者的語系(即語言和國家/地區)來翻譯和轉換它們。對於文字,這表示將每個文字包裝在一個能夠將文字(或「訊息」)翻譯成使用者語言的函數中。

1
2
3
4
5
6
// text will *always* print out in English
echo 'Hello World';

// text can be translated into the end-user's language or
// default to English
echo $translator->trans('Hello World');

注意

術語「語系」大致指的是使用者的語言和國家/地區。它可以是您的應用程式用來管理翻譯和其他格式差異(例如貨幣格式)的任何字串。建議使用 ISO 639-1 語言代碼、底線 (_),然後是 ISO 3166-1 alpha-2 國家/地區代碼(例如 fr_FR 代表法語/法國)。

翻譯可以組織成群組,稱為「網域」。預設情況下,所有訊息都使用預設的 messages 網域。

1
echo $translator->trans('Hello World', domain: 'messages');

翻譯過程包含幾個步驟:

  1. 啟用和設定 Symfony 的翻譯服務;
  2. 透過將字串(即「訊息」)包裝在對 Translator 的呼叫中來抽象化它們;
  3. 為每個支援的語系建立翻譯資源/檔案,以翻譯應用程式中的每個訊息;
  4. 判斷、設定和管理請求的使用者語系,以及選擇性地管理使用者整個工作階段的語系

安裝

首先,執行此命令以在使用翻譯器之前安裝它

1
$ composer require symfony/translation

設定

前一個命令會建立一個初始設定檔,您可以在其中定義應用程式的預設語系以及翻譯檔案所在目錄。

1
2
3
4
5
# config/packages/translation.yaml
framework:
    default_locale: 'en'
    translator:
        default_path: '%kernel.project_dir%/translations'

提示

您也可以定義enabled_locales 選項來限制您的應用程式可用的語系。

基礎翻譯

文字翻譯是透過 translator 服務 (Translator) 完成的。若要翻譯一段文字(稱為「訊息」),請使用 trans() 方法。例如,假設您要從控制器內部翻譯靜態訊息

1
2
3
4
5
6
7
8
9
// ...
use Symfony\Contracts\Translation\TranslatorInterface;

public function index(TranslatorInterface $translator): Response
{
    $translated = $translator->trans('Symfony is great');

    // ...
}

當執行此程式碼時,Symfony 會嘗試根據使用者的 locale 翻譯訊息「Symfony is great」。為了使此功能正常運作,您需要透過「翻譯資源」告訴 Symfony 如何翻譯訊息,這通常是一個檔案,其中包含給定語系的翻譯集合。這個「翻譯字典」可以用幾種不同的格式建立

1
2
# translations/messages.fr.yaml
Symfony is great: Symfony est génial

您可以在這裡找到有關這些檔案應放置位置的更多資訊。

現在,如果使用者語系的語言是法語(例如 fr_FRfr_BE),則訊息將翻譯成 Symfony est génial。您也可以在您的模板中翻譯訊息。

使用真實訊息或關鍵字訊息

這個範例說明了建立要翻譯的訊息時的兩種不同理念:

1
2
3
$translator->trans('Symfony is great');

$translator->trans('symfony.great');

在第一種方法中,訊息是以預設語系的語言(在本例中為英語)撰寫的。然後,該訊息在建立翻譯時用作「id」。

在第二種方法中,訊息實際上是傳達訊息概念的「關鍵字」。然後,關鍵字訊息用作任何翻譯的「id」。在這種情況下,必須為預設語系進行翻譯(即將 symfony.great 翻譯為 Symfony is great)。

第二種方法很方便,因為如果您決定訊息實際上在預設語系中應讀為「Symfony is really great」,則無需在每個翻譯檔案中變更訊息金鑰。

選擇使用哪種方法完全取決於您,但對於多語言應用程式,通常建議使用「關鍵字」格式,而對於包含翻譯資源的共用套件,我們建議使用真實訊息,以便您的應用程式可以選擇停用翻譯器層,並且您將看到可讀的訊息。

此外,如果您使用關鍵字而不是真實文字作為您的 id,則 phpyaml 檔案格式支援巢狀 id 以避免重複自己。

1
2
3
4
5
6
7
8
9
10
11
12
symfony:
    is:
        # id is symfony.is.great
        great: Symfony is great
        # id is symfony.is.amazing
        amazing: Symfony is amazing
    has:
        # id is symfony.has.bundles
        bundles: Symfony has bundles
user:
    # id is user.login
    login: Login

翻譯流程

為了實際翻譯訊息,Symfony 在使用 trans() 方法時使用以下流程:

  1. 會判斷目前使用者的 locale,該語系儲存在請求中;這通常是透過路由上的 _locale 屬性設定的;
  2. 從為 locale(例如 fr_FR)定義的翻譯資源載入翻譯訊息的目錄。如果回退語系已啟用語系的訊息尚不存在,也會載入並新增到目錄中。最終結果是一個大型的「翻譯字典」。
  3. 如果訊息位於目錄中,則會傳回翻譯。如果沒有,翻譯器會傳回原始訊息。

訊息格式

有時,需要翻譯包含變數的訊息。

1
2
// ...
$translated = $translator->trans('Hello '.$name);

但是,建立此字串的翻譯是不可能的,因為翻譯器會嘗試查閱包含變數部分的訊息(例如「Hello Ryan」或「Hello Fabien」)。

另一個複雜情況是,當您擁有一些翻譯可能根據某些變數而有複數或單數形式時。

1
2
There is one apple.
There are 5 apples.

為了管理這些情況,Symfony 遵循 ICU MessageFormat 語法,方法是使用 PHP 的 MessageFormatter 類別。請在如何使用 ICU MessageFormat 翻譯訊息中閱讀更多相關資訊。

可翻譯物件

有時翻譯模板中的內容很麻煩,因為您需要每個內容的原始訊息、翻譯參數和翻譯網域。在控制器或服務中進行翻譯可以簡化您的模板,但需要在應用程式的不同部分注入翻譯器服務,並在測試中模擬它。

您可以使用「可翻譯物件」而不是在建立時翻譯字串,這是一個 TranslatableMessage 類別的實例。此物件儲存完整翻譯其內容所需的所有資訊,在需要時。

1
2
3
4
5
6
7
use Symfony\Component\Translation\TranslatableMessage;

// the first argument is required and it's the original message
$message = new TranslatableMessage('Symfony is great!');
// the optional second argument defines the translation parameters and
// the optional third argument is the translation domain
$status = new TranslatableMessage('order.status', ['%status%' => $order->getStatus()], 'store');

現在模板變得更簡單,因為您可以將可翻譯物件傳遞給 trans 過濾器。

1
2
<h1>{{ message|trans }}</h1>
<p>{{ status|trans }}</p>

提示

翻譯參數也可以是 TranslatableMessage

提示

還有一個名為 t() 的函數,在 Twig 和 PHP 中都可用,作為建立可翻譯物件的捷徑。

模板中的翻譯

大多數時候,翻譯發生在模板中。Symfony 為 Twig 和 PHP 模板都提供了原生支援。

使用 Twig 過濾器

trans 過濾器可用於翻譯變數字串和複雜的運算式。

1
2
3
{{ message|trans }}

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

提示

您可以使用單一標籤為整個 Twig 模板設定翻譯網域。

1
{% trans_default_domain 'app' %}

請注意,這只會影響目前的模板,而不會影響任何「包含」的模板(為了避免副作用)。

預設情況下,翻譯後的訊息會輸出逸出;在翻譯過濾器之後套用 raw 過濾器以避免自動逸出。

1
2
3
4
5
{% set message = '<h3>foo</h3>' %}

{# strings and variables translated via a filter are escaped by default #}
{{ message|trans|raw }}
{{ '<h3>bar</h3>'|trans|raw }}

使用 Twig 標籤

Symfony 提供了一個專門的 Twig 標籤 trans,以協助靜態文字區塊的訊息翻譯。

1
{% trans %}Hello %name%{% endtrans %}

警告

當在 Twig 模板中使用標籤進行翻譯時,佔位符的 %var% 表示法是必要的。

提示

如果您需要在字串中使用百分比字元 (%),請透過將其加倍來逸出它:{% trans %}百分比:%percent%%%{% endtrans %}

您也可以指定訊息網域並傳遞一些額外的變數。

1
2
3
{% trans with {'%name%': 'Fabien'} from 'app' %}Hello %name%{% endtrans %}

{% trans with {'%name%': 'Fabien'} from 'app' into 'fr' %}Hello %name%{% endtrans %}

警告

使用翻譯標籤與使用過濾器具有相同的效果,但有一個主要差異:自動輸出逸出不會套用至使用標籤的翻譯。

強制翻譯器語系

翻譯訊息時,翻譯器會使用指定的語系,或在必要時使用 fallback 語系。您也可以手動指定要用於翻譯的語系。

1
$translator->trans('Symfony is great', locale: 'fr_FR');

提取翻譯內容並自動更新目錄

翻譯應用程式時最耗時的任務是提取所有要翻譯的模板內容,並保持所有翻譯檔案同步。Symfony 包含一個名為 translation:extract 的命令,可協助您完成這些任務。

1
2
3
4
5
6
7
8
# shows all the messages that should be translated for the French language
$ php bin/console translation:extract --dump-messages fr

# updates the French translation files with the missing strings for that locale
$ php bin/console translation:extract --force fr

# check out the command help to see its options (prefix, output format, domain, sorting, etc.)
$ php bin/console translation:extract --help

translation:extract 命令會在以下位置尋找遺失的翻譯:

  • 儲存在 templates/ 目錄中的模板(或 twig.default_pathtwig.paths 設定選項中定義的任何其他目錄);
  • 任何注入或自動裝配翻譯器服務並呼叫 trans() 方法的 PHP 檔案/類別;
  • 儲存在 src/ 目錄中的任何 PHP 檔案/類別,這些檔案/類別使用建構函式或 t() 方法建立可翻譯物件,或呼叫 trans() 方法;
  • 儲存在 src/ 目錄中的任何 PHP 檔案/類別,這些檔案/類別使用帶有 *message 命名引數的 Constraints Attributes

提示

在您的專案中安裝 nikic/php-parser 套件,以改善 translation:extract 命令的結果。此套件啟用了一個 AST 解析器,可以找到更多可翻譯的項目。

1
$ composer require nikic/php-parser

預設情況下,當 translation:extract 命令在翻譯檔案中建立新條目時,它會使用與來源和待處理翻譯相同的內容。唯一的區別是待處理翻譯的前面會加上 __。您可以使用 --prefix 選項自訂此前綴。

1
$ php bin/console translation:extract --force --prefix="NEW_" fr

或者,您可以使用 --no-fill 選項在翻譯目錄中建立新條目時,將待處理翻譯完全留空。當使用外部翻譯工具時,這特別有用,因為它可以更輕鬆地發現未翻譯的字串。

1
2
# when using the --no-fill option, the --prefix option is ignored
$ php bin/console translation:extract --force --no-fill fr

7.2

--no-fill 選項是在 Symfony 7.2 中引入的。

翻譯資源/檔案名稱和位置

Symfony 會在以下預設位置尋找訊息檔案(即翻譯):

  • translations/ 目錄(位於專案的根目錄);
  • 任何套件內的 translations/ 目錄(以及它們的 Resources/translations/ 目錄,不再建議用於套件)。

此處列出的位置優先順序最高。也就是說,您可以在第一個目錄中覆寫套件的翻譯訊息。

覆寫機制在鍵層級運作:只有被覆寫的鍵需要列在較高優先順序的訊息檔案中。當在訊息檔案中找不到鍵時,翻譯器會自動回退到較低優先順序的訊息檔案。

翻譯檔的檔名也很重要:每個訊息檔案都必須根據以下路徑命名:domain.locale.loader

  • domain:翻譯網域;
  • locale:翻譯所針對的語系(例如 en_GBen 等);
  • loader:Symfony 應如何載入和解析檔案(例如 xlfphpyaml 等)。

載入器可以是任何已註冊載入器的名稱。預設情況下,Symfony 提供了許多載入器,這些載入器會根據以下副檔名選擇

  • .yaml:YAML 檔案(您也可以使用 .yml 副檔名);
  • .xlf:XLIFF 檔案(您也可以使用 .xliff 副檔名);
  • .php:PHP 檔案,傳回包含翻譯的陣列;
  • .csv:CSV 檔案;
  • .json:JSON 檔案;
  • .ini:INI 檔案;
  • .dat.resICU 資源包
  • .mo機器物件格式
  • .po可攜式物件格式
  • .qtQT 翻譯 TS XML 檔案;

要使用哪個載入器完全取決於您,並且是品味問題。建議的選項是簡單專案使用 YAML,如果您使用專門的程式或團隊產生翻譯,則使用 XLIFF。

警告

每次您建立新的訊息目錄(或安裝包含翻譯目錄的套件)時,請務必清除快取,以便 Symfony 可以發現新的翻譯資源

1
$ php bin/console cache:clear

注意

您可以使用組態中的 paths 選項新增其他目錄

1
2
3
4
5
# config/packages/translation.yaml
framework:
    translator:
        paths:
            - '%kernel.project_dir%/custom/path/to/translations'

Doctrine 實體的翻譯

與樣板內容不同,使用翻譯目錄來翻譯 Doctrine 實體中儲存的內容並不實際。請改為使用 Doctrine Translatable ExtensionTranslatable Behavior。如需更多資訊,請閱讀這些程式庫的文件。

自訂翻譯資源

如果您的翻譯使用 Symfony 不支援的格式,或您以特殊方式儲存它們(例如,不使用檔案或 Doctrine 實體),則您需要提供一個自訂類別,實作 LoaderInterface 介面。請參閱 內建 Symfony 服務標籤 標籤以取得更多資訊。

翻譯提供者

當使用外部翻譯人員翻譯您的應用程式時,您必須頻繁地將新的內容傳送給他們進行翻譯,並將結果合併回應用程式中。

Symfony 提供了與多個第三方翻譯服務的整合,而不是手動執行此操作。您可以將翻譯上傳和下載(稱為「推送」和「拉取」)到/從這些服務,並自動將結果合併到應用程式中。

安裝和設定第三方提供者

在將翻譯推送/拉取到第三方供應商之前,您必須安裝提供與該供應商整合的套件

供應商 使用以下命令安裝
Crowdin composer require symfony/crowdin-translation-provider
Loco (localise.biz) composer require symfony/loco-translation-provider
Lokalise composer require symfony/lokalise-translation-provider
Phrase composer require symfony/phrase-translation-provider

每個程式庫都包含一個 Symfony Flex recipe,它會將組態範例新增到您的 .env 檔案中。例如,假設您想要使用 Loco。首先,安裝它

1
$ composer require symfony/loco-translation-provider

您現在會在 .env 檔案中看到一個新的行,您可以取消註解

1
2
# .env
LOCO_DSN=loco://API_KEY@default

LOCO_DSN 不是一個真實的位址:它是一種方便的格式,可將大部分組態工作卸載到 Symfony。 loco 方案會啟動您剛剛安裝的 Loco 供應商,該供應商知道如何透過 Loco 推送和拉取翻譯。您唯一需要變更的部分是 API_KEY 預留位置。

下表顯示每個供應商可用的完整 DSN 格式清單

供應商 DSN
Crowdin crowdin://PROJECT_ID:API_TOKEN@ORGANIZATION_DOMAIN.default
Loco (localise.biz) loco://API_KEY@default
Lokalise lokalise://PROJECT_ID:API_KEY@default
Phrase phrase://PROJECT_ID:API_TOKEN@default?userAgent=myProject

若要啟用翻譯供應商,請自訂 .env 檔案中的 DSN 並組態 providers 選項

1
2
3
4
5
6
7
8
# config/packages/translation.yaml
framework:
    translator:
        providers:
            loco:
                dsn: '%env(LOCO_DSN)%'
                domains: ['messages']
                locales: ['en', 'fr']

重要事項

如果您使用 Phrase 作為供應商,則必須在您的 dsn 中組態使用者代理程式。請參閱 透過 User-Agent 識別 以了解原因和一些範例。

另請注意,Phrase 中的語系名稱應與 RFC4646 中定義的名稱相同(例如 pt-BR 而不是 pt_BR)。否則,Phrase 會為匯入的鍵建立新的語系。

提示

如果您使用 Crowdin 作為供應商,並且您的一些語系與 Crowdin 語言代碼 不同,則您必須在 Crowdin 專案中為每個語系設定 自訂語言代碼,以覆寫預設值。您需要選取「語系」預留位置,並在「自訂代碼」欄位中指定自訂代碼。

提示

如果您使用 Lokalise 作為供應商,並且語系格式遵循 ISO 639-1(例如「en」或「fr」),則您必須在 Lokalise 中為每個語系設定 自訂語言名稱設定,以覆寫預設值(預設值遵循 ISO 639-1,後跟一個大寫的子代碼,用於指定國家/地區變體(例如,根據 ISO 3166-1 alpha-2 的「GB」或「US」))。

提示

Phrase 供應商使用 Phrase 的標籤功能將翻譯對應到 Symfony 的翻譯網域。如果您在 Phrase 中組織標籤時需要一些協助,您可能需要考慮 Phrase Tag Bundle,它提供了一些指令來協助您完成該操作。

推送和拉取翻譯

在組態用於存取翻譯供應商的憑證之後,您現在可以使用以下指令來推送(上傳)和拉取(下載)翻譯

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# push all local translations to the Loco provider for the locales and domains
# configured in config/packages/translation.yaml file.
# it will update existing translations already on the provider.
$ php bin/console translation:push loco --force

# push new local translations to the Loco provider for the French locale
# and the validators domain.
# it will **not** update existing translations already on the provider.
$ php bin/console translation:push loco --locales fr --domains validators

# push new local translations and delete provider's translations that not
# exists anymore in local files for the French locale and the validators domain.
# it will **not** update existing translations already on the provider.
$ php bin/console translation:push loco --delete-missing --locales fr --domains validators

# check out the command help to see its options (format, domains, locales, etc.)
$ php bin/console translation:push --help
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# pull all provider's translations to local files for the locales and domains
# configured in config/packages/translation.yaml file.
# it will overwrite completely your local files.
$ php bin/console translation:pull loco --force

# pull new translations from the Loco provider to local files for the French
# locale and the validators domain.
# it will **not** overwrite your local files, only add new translations.
$ php bin/console translation:pull loco --locales fr --domains validators

# check out the command help to see its options (format, domains, locales, intl-icu, etc.)
$ php bin/console translation:pull --help

# the "--as-tree" option will write YAML messages as a tree-like structure instead
# of flat keys
$ php bin/console translation:pull loco --force --as-tree

建立自訂提供者

除了使用 Symfony 的內建翻譯供應商之外,您還可以建立自己的供應商。若要執行此操作,您需要建立兩個類別

  1. 第一個類別必須實作 ProviderInterface
  2. 第二個類別需要是一個工廠,它將建立第一個類別的實例。它必須實作

ProviderFactoryInterface(您可以擴充 AbstractProviderFactory 以簡化其建立)。

在建立這兩個類別之後,您需要將您的工廠註冊為服務,並使用 translation.provider_factory 標籤標記它。

處理使用者語系

翻譯是根據使用者的語系進行的。目前使用者的語系儲存在請求中,並且可以透過 Request 物件存取

1
2
3
4
5
6
use Symfony\Component\HttpFoundation\Request;

public function index(Request $request): void
{
    $locale = $request->getLocale();
}

若要設定使用者的語系,您可能需要建立一個自訂事件接聽器,以便在系統的任何其他部分(即翻譯器)需要它之前設定它

1
2
3
4
5
6
7
public function onKernelRequest(RequestEvent $event): void
{
    $request = $event->getRequest();

    // some logic to determine the $locale
    $request->setLocale($locale);
}

注意

自訂接聽器必須在 LocaleListener 之前呼叫,後者會根據目前的請求初始化語系。若要執行此操作,請將您的接聽器優先順序設定為高於 LocaleListener 優先順序的值(您可以透過執行 debug:event kernel.request 指令取得)。

請閱讀 工作階段 以取得關於使使用者的語系「黏著」到其工作階段的更多資訊。

注意

在控制器中使用 $request->setLocale() 設定語系對於影響翻譯器來說太晚了。您可以透過接聽器(如上所述)、URL(請參閱下一個)設定語系,或直接在 translator 服務上呼叫 setLocale()

請參閱下方關於透過路由設定語系的 翻譯 章節。

語系和 URL

由於您可以將使用者的語系儲存在工作階段中,因此可能會很想使用相同的 URL 以根據使用者的語系顯示不同語言的資源。例如,http://www.example.com/contact 可以為一位使用者顯示英文內容,為另一位使用者顯示法文內容。遺憾的是,這違反了網頁的基本規則:特定 URL 傳回相同的資源,而與使用者無關。更複雜的是,搜尋引擎會為哪個版本的內容建立索引?

更好的策略是在 URL 中包含語系,使用 特殊的 _locale 參數

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// src/Controller/ContactController.php
namespace App\Controller;

// ...
class ContactController extends AbstractController
{
    #[Route(
        path: '/{_locale}/contact',
        name: 'contact',
        requirements: [
            '_locale' => 'en|fr|de',
        ],
    )]
    public function contact(): Response
    {
        // ...
    }
}

當在路由中使用特殊的 _locale 參數時,相符的語系會自動設定在請求上,並且可以透過 getLocale() 方法擷取。換句話說,如果使用者造訪 URI /fr/contact,則語系 fr 將自動設定為目前請求的語系。

您現在可以使用語系來建立應用程式中其他翻譯頁面的路由。

提示

將語系需求定義為 容器參數,以避免在其所有路由中硬式編碼其值。

設定預設語系

如果尚未決定使用者的語系該怎麼辦?您可以透過為框架定義 default_locale 來保證在每個使用者的請求上設定語系

1
2
3
# config/packages/translation.yaml
framework:
    default_locale: en

default_locale 也與翻譯器相關,如下一節所示。

選擇使用者偏好的語言

如果您的應用程式支援多種語言,則使用者第一次造訪您的網站時,通常會將他們重新導向到根據其偏好設定的最佳語言。這是透過 Request 物件getPreferredLanguage() 方法實現的

1
2
3
4
5
// get the Request object somehow (e.g. as a controller argument)
$request = ...
// pass an array of the locales (their script and region parts are optional) supported
// by your application and the method returns the best locale for the current user
$locale = $request->getPreferredLanguage(['pt', 'fr_Latn_CH', 'en_US'] );

Symfony 會根據作為引數傳遞的語系和 Accept-Language HTTP 標頭的值,找到最佳的可能語言。如果它無法在它們之間找到完全相符的項目,Symfony 將嘗試根據語言尋找部分相符的項目(例如,fr_CA 將與 fr_Latn_CH 相符,因為它們的語言相同)。如果沒有完全或部分相符的項目,此方法會傳回作為引數傳遞的第一個語系(這就是傳遞語系的順序很重要的原因)。

7.1

部分比對語系的功能是在 Symfony 7.1 中引入的。

回退翻譯語系

假設使用者的語系是 es_AR,並且您正在翻譯鍵 Symfony is great。為了找到西班牙文翻譯,Symfony 實際上會檢查多個語系的翻譯資源

  1. 首先,Symfony 會在 es_AR(阿根廷西班牙文)翻譯資源中尋找翻譯(例如,messages.es_AR.yaml);
  2. 如果找不到,Symfony 會在父語系中尋找翻譯,父語系僅針對某些語系自動定義。在此範例中,父語系為 es_419(拉丁美洲西班牙文);
  3. 如果找不到,Symfony 會在 es(西班牙文)翻譯資源中尋找翻譯(例如,messages.es.yaml);
  4. 如果仍然找不到翻譯,Symfony 會使用 fallbacks 選項,該選項可以組態如下。當未定義此選項時,它預設為前一節中提及的 default_locale 設定。

    1
    2
    3
    4
    5
    # config/packages/translation.yaml
    framework:
        translator:
            fallbacks: ['en']
            # ...

注意

當 Symfony 無法在給定的語系中找到翻譯時,它會將遺失的翻譯新增到記錄檔中。如需詳細資訊,請參閱 框架組態參考 (FrameworkBundle)

以程式方式切換語系

有時您需要動態變更應用程式的語系,只是為了執行一些程式碼。想像一個主控台指令,以不同的語言呈現電子郵件的 Twig 樣板。您只需要變更語系即可呈現這些樣板。

LocaleSwitcher 類別允許您一次變更以下項目的語系

  • 所有標記為 kernel.locale_aware 的服務;
  • \Locale::setDefault();
  • 如果 RequestContext 服務可用,則 _locale 參數(因此 URL 會以新的語系產生)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    use Symfony\Component\Translation\LocaleSwitcher;
    
    class SomeService
    {
        public function __construct(
            private LocaleSwitcher $localeSwitcher,
        ) {
        }
    
        public function someMethod(): void
        {
            // you can get the current application locale like this:
            $currentLocale = $this->localeSwitcher->getLocale();
    
            // you can set the locale for the entire application like this:
            // (from now on, the application will use 'fr' (French) as the
            // locale; including the default locale used to translate Twig templates)
            $this->localeSwitcher->setLocale('fr');
    
            // reset the current locale of your application to the configured default locale
            // in config/packages/translation.yaml, by option 'default_locale'
            $this->localeSwitcher->reset();
    
            // you can also run some code with a certain locale, without
            // changing the locale for the rest of the application
            $this->localeSwitcher->runWithLocale('es', function() {
    
                // e.g. render here some Twig templates using 'es' (Spanish) locale
    
            });
    
            // you can optionally declare an argument in your callback to receive the
            // injected locale
            $this->localeSwitcher->runWithLocale('es', function(string $locale) {
    
                // here, the $locale argument will be set to 'es'
    
            });
    
            // ...
        }
    }

當使用 自動連線 時,請使用 LocaleSwitcher 類別類型提示任何控制器或服務引數,以注入語系切換器服務。否則,請手動組態您的服務並注入 translation.locale_switcher 服務。

如何尋找遺失或未使用的翻譯訊息

當您使用多種語言的許多翻譯訊息時,可能很難追蹤哪些翻譯遺失以及哪些翻譯不再使用。 debug:translation 指令可協助您找到這些遺失或未使用的翻譯訊息樣板

1
2
3
4
{# messages can be found when using the trans filter and tag #}
{% trans %}Symfony is great{% endtrans %}

{{ 'Symfony is great'|trans }}

警告

除非使用 可翻譯物件 或在翻譯器上呼叫 trans() 方法(自 Symfony 5.3 以來),否則擷取器無法找到在樣板外部翻譯的訊息(例如表單標籤或控制器)。也不會偵測到在樣板中使用變數或運算式的動態翻譯

1
2
3
{# this translation uses a Twig variable, so it won't be detected #}
{% set message = 'Symfony is great' %}
{{ message|trans }}

假設您的應用程式的 default_locale 是 fr,並且您已將 en 組態為回退語系(請參閱 組態回退 以了解如何組態這些)。並且假設您已為 fr 語系設定了一些翻譯

1
2
3
4
5
6
7
8
9
10
11
12
<!-- translations/messages.fr.xlf -->
<?xml version="1.0" encoding="UTF-8" ?>
<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="1">
                <source>Symfony is great</source>
                <target>Symfony est génial</target>
            </trans-unit>
        </body>
    </file>
</xliff>

以及 en 語系

1
2
3
4
5
6
7
8
9
10
11
12
<!-- translations/messages.en.xlf -->
<?xml version="1.0" encoding="UTF-8" ?>
<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="1">
                <source>Symfony is great</source>
                <target>Symfony is great</target>
            </trans-unit>
        </body>
    </file>
</xliff>

若要檢查應用程式中 fr 語系中的所有訊息,請執行

1
2
3
4
5
6
7
$ php bin/console debug:translation fr

---------  ------------------  ----------------------  -------------------------------
 State      Id                  Message Preview (fr)    Fallback Message Preview (en)
---------  ------------------  ----------------------  -------------------------------
 unused     Symfony is great    Symfony est génial      Symfony is great
---------  ------------------  ----------------------  -------------------------------

它會向您顯示一個表格,其中包含在 fr 語系中翻譯訊息時的結果,以及將使用回退語系 en 時的結果。最重要的是,它也會向您顯示翻譯何時與回退翻譯相同(這可能表示訊息未正確翻譯)。此外,它還指出訊息 Symfony is great 未使用,因為它已翻譯,但您尚未在任何地方使用它。

現在,如果您在您的其中一個樣板中翻譯訊息,您將獲得此輸出

1
2
3
4
5
6
7
$ php bin/console debug:translation fr

---------  ------------------  ----------------------  -------------------------------
 State      Id                  Message Preview (fr)    Fallback Message Preview (en)
---------  ------------------  ----------------------  -------------------------------
            Symfony is great    Symfony est génial      Symfony is great
---------  ------------------  ----------------------  -------------------------------

狀態為空,表示訊息已在 fr 語系中翻譯,並且在一個或多個樣板中使用。

如果您從 fr 語系的翻譯檔案中刪除訊息 Symfony is great 並執行指令,您將獲得

1
2
3
4
5
6
7
$ php bin/console debug:translation fr

---------  ------------------  ----------------------  -------------------------------
 State      Id                  Message Preview (fr)    Fallback Message Preview (en)
---------  ------------------  ----------------------  -------------------------------
 missing    Symfony is great    Symfony is great        Symfony is great
---------  ------------------  ----------------------  -------------------------------

狀態表示訊息遺失,因為它未在 fr 語系中翻譯,但仍在樣板中使用。此外,fr 語系中的訊息等於 en 語系中的訊息。這是一個特殊情況,因為未翻譯的訊息 ID 等於其在 en 語系中的翻譯。

如果您將 en 語系中的翻譯檔案內容複製到 fr 語系中的翻譯檔案並執行指令,您將獲得

1
2
3
4
5
6
7
$ php bin/console debug:translation fr

----------  ------------------  ----------------------  -------------------------------
 State       Id                  Message Preview (fr)    Fallback Message Preview (en)
----------  ------------------  ----------------------  -------------------------------
 fallback    Symfony is great    Symfony is great        Symfony is great
----------  ------------------  ----------------------  -------------------------------

您可以看到訊息的翻譯在 fren 語系中是相同的,這表示此訊息可能是從英文複製到法文,並且您可能忘記翻譯它。

預設情況下,會檢查所有網域,但可以指定單一網域

1
$ php bin/console debug:translation en --domain=messages

當應用程式有大量訊息時,使用 --only-unused--only-missing 選項,僅顯示未使用的訊息或僅顯示遺失的訊息會很有用

1
2
$ php bin/console debug:translation en --only-unused
$ php bin/console debug:translation en --only-missing

偵錯命令結束代碼

debug:translation 指令的結束代碼會根據翻譯的狀態而變更。使用以下公用常數來檢查它

1
2
3
4
5
6
7
8
9
10
11
12
13
use Symfony\Bundle\FrameworkBundle\Command\TranslationDebugCommand;

// generic failure (e.g. there are no translations)
TranslationDebugCommand::EXIT_CODE_GENERAL_ERROR;

// there are missing translations
TranslationDebugCommand::EXIT_CODE_MISSING;

// there are unused translations
TranslationDebugCommand::EXIT_CODE_UNUSED;

// some translations are using the fallback translation
TranslationDebugCommand::EXIT_CODE_FALLBACK;

這些常數定義為「位元遮罩」,因此您可以將它們組合如下

1
2
3
if (TranslationDebugCommand::EXIT_CODE_MISSING | TranslationDebugCommand::EXIT_CODE_UNUSED) {
    // ... there are missing and/or unused translations
}

如何尋找翻譯檔案中的錯誤

Symfony 會處理所有應用程式翻譯檔案,作為在執行應用程式程式碼之前編譯應用程式程式碼的過程的一部分。如果任何翻譯檔案中發生錯誤,您會看到說明問題的錯誤訊息。

如果您願意,您也可以使用 lint:yamllint:xliff 指令驗證任何 YAML 和 XLIFF 翻譯檔案的語法

1
2
3
4
5
6
7
8
9
10
11
# lint a single file
$ php bin/console lint:yaml translations/messages.en.yaml
$ php bin/console lint:xliff translations/messages.en.xlf

# lint a whole directory
$ php bin/console lint:yaml translations
$ php bin/console lint:xliff translations

# lint multiple files or directories
$ php bin/console lint:yaml translations path/to/trans
$ php bin/console lint:xliff translations/messages.en.xlf translations/messages.es.xlf

可以使用 --format 選項將 linter 結果匯出到 JSON

1
2
$ php bin/console lint:yaml translations/ --format=json
$ php bin/console lint:xliff translations/ --format=json

當在 GitHub Actions 內執行這些 linter 時,輸出會自動調整為 GitHub 所需的格式,但您也可以強制使用該格式

1
2
$ php bin/console lint:yaml translations/ --format=github
$ php bin/console lint:xliff translations/ --format=github

提示

Yaml 元件提供了一個獨立的 yaml-lint 二進位檔案,允許您在不必建立主控台應用程式的情況下檢查 YAML 檔案

1
$ php vendor/bin/yaml-lint translations/

lint:yamllint:xliff 指令會驗證翻譯檔案的 YAML 和 XML 語法,但不會驗證其內容。使用以下指令來檢查翻譯內容是否也正確

1
2
3
4
5
# checks the contents of all the translation catalogues in all locales
$ php bin/console lint:translations

# checks the contents of the translation catalogues for Italian (it) and Japanese (ja) locales
$ php bin/console lint:translations --locale=it --locale=ja

7.2

lint:translations 指令是在 Symfony 7.2 中引入的。

偽本地化翻譯器

注意

偽本地化翻譯器僅供開發使用。

下圖顯示網頁上的典型選單

A menu showing multiple items nicely aligned next to eachother.

另一張圖片顯示當使用者將語言切換為西班牙文時的相同選單。出乎意料的是,某些文字被截斷,而其他內容太長而溢位,您看不到它們

In Spanish, some menu items contain more letters which result in them being cut.

這類錯誤非常常見,因為不同的語言可能比原始應用程式語言長或短。另一個常見問題是僅檢查應用程式在使用基本重音字母時是否運作,而不是檢查更複雜的字元,例如在波蘭文、捷克文等中找到的字元。

這些問題可以使用 偽本地化 解決,偽本地化是一種用於測試國際化的軟體測試方法。在此方法中,應用程式的文字元素會被原始語言的變更版本取代,而不是將軟體的文字翻譯成外語。

例如,Account Settings翻譯[!!! Àççôûñţ Šéţţîñĝš !!!]。首先,原始文字會使用諸如 [!!! !!!] 之類的字元擴充長度,以在使用比原始語言更冗長的語言時測試應用程式。這解決了第一個問題。

此外,原始字元會被相似但帶有重音符號的字元取代。這使得文字高度可讀,同時允許使用各種重音符號和特殊字元來測試應用程式。這解決了第二個問題。

已新增對偽本地化的完整支援,以協助您偵錯應用程式中的國際化問題。您可以在翻譯器組態中啟用和組態它

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# config/packages/translation.yaml
framework:
    translator:
        pseudo_localization:
            # replace characters by their accented version
            accents: true
            # wrap strings with brackets
            brackets: true
            # controls how many extra characters are added to make text longer
            expansion_factor: 1.4
            # maintain the original HTML tags of the translated contents
            parse_html: true
            # also translate the contents of these HTML attributes
            localizable_html_attributes: ['title']

就這樣。應用程式現在將開始顯示那些奇怪但可讀的內容,以協助您將其國際化。例如,請參閱 Symfony Demo 應用程式中的差異。這是原始頁面

The Symfony demo login page.

這是啟用偽本地化後的相同頁面

The Symfony demo login page with pseudolocalization.

摘要

使用 Symfony Translation 元件,建立國際化的應用程式不再需要是一個痛苦的過程,而是歸結為以下步驟

  • 透過將每個訊息包裝在 trans() 方法中來抽象化應用程式中的訊息;
  • 透過建立翻譯訊息檔案,將每個訊息翻譯成多個語系。Symfony 會發現並處理每個檔案,因為其名稱遵循特定的慣例;
  • 管理使用者的語系,該語系儲存在請求中,但也可以在使用者的工作階段中設定。
本作品,包括程式碼範例,已根據 Creative Commons BY-SA 3.0 授權條款授權。
TOC
    版本