跳到内容

翻译

编辑此页

术语“国际化”(通常缩写为 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”,则无需在每个翻译文件中更改消息键。

选择使用哪种方法完全取决于您,但对于多语言应用程序,通常建议使用“关键词”格式,而对于包含翻译资源的共享程序包,我们建议使用真实消息,这样您的应用程序可以选择禁用翻译器层,您将看到可读的消息。

此外,phpyaml 文件格式支持嵌套 id,以避免在使用关键词而不是真实文本作为 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 存储在请求中;这通常通过路由上的 _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

提示

Twig 和 PHP 中还有一个 名为 t() 的函数,可用作创建可翻译对象的快捷方式。

模板中的翻译

大多数时候,翻译发生在模板中。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 配置选项中定义的任何其他目录);
  • 任何注入或 自动装配 translator 服务并调用 trans() 方法的 PHP 文件/类;
  • 存储在 src/ 目录中的任何 PHP 文件/类,这些文件/类使用构造函数或 t() 方法创建可翻译对象或调用 trans() 方法;
  • 存储在 src/ 目录中的任何 PHP 文件/类,这些文件/类使用带有 *message 命名参数的 约束属性

提示

在您的项目中安装 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

  • :翻译域;
  • 区域设置:翻译所用的区域设置(例如 en_GBen 等);
  • 加载器: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 provider,它知道如何通过 Loco 推送和拉取翻译。你唯一需要更改的部分是 API_KEY 占位符。

下表显示了每个 provider 的完整可用 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

要启用翻译 provider,请在你的 .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 作为 provider,你必须在你的 dsn 中配置 user agent。请参阅 通过 User-Agent 识别 以了解原因和一些示例。

另外,请确保 Phrase 中的语言环境 _names_ 应按照 RFC4646 定义(例如 pt-BR 而不是 pt_BR)。否则,Phrase 将为导入的键创建新的语言环境。

提示

如果你使用 Crowdin 作为 provider,并且你的一些语言环境与 Crowdin 语言代码 不同,你必须在 Crowdin 项目中为你的每个语言环境设置 自定义语言代码,以覆盖默认值。你需要选择 “locale” 占位符,并在 “Custom Code” 字段中指定自定义代码。

提示

如果你使用 Lokalise 作为 provider,并且语言环境格式遵循 ISO 639-1 (例如 “en” 或 “fr”),你必须在 Lokalise 中为你的每个语言环境设置 自定义语言名称设置,以覆盖默认值(默认值遵循 ISO 639-1,后跟一个大写子代码,用于指定国家/地区变体,例如根据 ISO 3166-1 alpha-2 的 “GB” 或 “US”)。

提示

Phrase provider 使用 Phrase 的标签功能将翻译映射到 Symfony 的翻译域。如果你在组织 Phrase 中的标签时需要一些帮助,你可能需要考虑 Phrase Tag Bundle,它提供了一些命令来帮助你完成该操作。

推送和拉取翻译

配置访问翻译 provider 的凭据后,你现在可以使用以下命令来推送(上传)和拉取(下载)翻译

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 的内置翻译 provider 之外,你还可以创建自己的 provider。为此,你需要创建两个类

  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 基于当前请求初始化语言环境。为此,请将你的监听器优先级设置为高于 LocaleListener 优先级的更高值(你可以通过运行 debug:event kernel.request 命令获得)。

阅读 会话 以获取有关使用户的语言环境 “粘滞” 到其会话的更多信息。

注意

在控制器中使用 $request->setLocale() 设置语言环境对于影响翻译器来说太晚了。要么通过监听器(如上所述)、URL(见下文)设置语言环境,要么直接在 translator 服务上调用 setLocale()

请参阅下面关于通过路由设置语言环境的 翻译 部分。

区域设置和 URL

由于你可以将用户的语言环境存储在会话中,因此可能会很想使用相同的 URL,根据用户的语言环境以不同的语言显示资源。例如,http://www.example.com/contact 可以为一个用户显示英文内容,为另一个用户显示法文内容。不幸的是,这违反了 Web 的基本规则:特定的 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 参数时,匹配的语言环境会自动设置在 Request 上,并且可以通过 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 二进制文件,允许你 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 许可。
目录
    版本