使用Translator

3.4 版本
维护中的版本

设想你要把字符串 "Symfony is great" 翻译成法语:

1
2
3
4
5
6
7
8
9
10
use Symfony\Component\Translation\Translator;
use Symfony\Component\Translation\Loader\ArrayLoader;
 
$translator = new Translator('fr_FR');
$translator->addLoader('array', new ArrayLoader());
$translator->addResource('array', array(
    'Symfony is great!' => 'J\'aime Symfony!',
), 'fr_FR');
 
var_dump($translator->trans('Symfony is great!'));

本例中,信息(message) "Symfony is great!" 将被翻译为设置在构造器中的 (fr_FR) locale,如果该条信息存在于目录中的话。

信息占位符 

有时,一条信息包含着需要被翻译的变量:

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

然而,为这个字符串创建翻译却是可能的,因为translator会尝试寻找确切的信息,包括那个变量部分 (如 "Hello Ryan""Hello Fabien")。不必为 $name 变量编写每一种可能的翻译,你可以用 "占位符" 来替换此变量:

1
2
3
4
5
6
7
// ...
$translated = $translator->trans(
    'Hello %name%',
    array('%name%' => $name)
);
 
var_dump($translated);

Symfony将查找原始信息(Hello %name%)的翻译,然后用它们的值来替换占位符。创建翻译时仍和以前一样:

1
2
3
4
5
6
7
8
9
10
11
<?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="1">
                <source>Hello %name%</source>
                <target>Bonjour %name%</target>
            </trans-unit>
        </body>
    </file>
</xliff>
1
2
3
return array(
    'Hello %name%' => 'Bonjour %name%',
);
1
'Hello %name%': Bonjour %name%

Note

占位符可以是任何形式的,因为完整信息被PHP的 strtr function 重新构造了。但是推荐 %...% 形式,可以避免在Twig中出现问题。

如你所见,创建翻译是一个“两步”流程:

  1. 抽象出 “通过 Translator 来处理” 的待译信息。
  2. 在每一个你支持locale中,为这条信息创建一个翻译。

第二步在创建信息目录(message catalogs)时被完成,信息目录中可以定义任意数量的不同locale的翻译(内容)。

创建翻译源 

创建翻译文件的过程,是创建 "本地化(localization)" 的重要一环 (常被简称为 L10n)。翻译文件包含了 id-translation 键值对(键是翻译id,值是翻译内容)用于给定的domain和locale。source是每一条翻译信息的识别符,在你的程序的主力locale中它可以是message本身 (如 "Symfony is great") 或者是一个唯一的识别符 (如 symfony.great - 参考下面的灰色区域文字)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?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>
            <trans-unit id="symfony.great">
                <source>symfony.great</source>
                <target>J'aime Symfony</target>
            </trans-unit>
        </body>
    </file>
</xliff>
1
2
3
4
return array(
    'Symfony is great' => 'J\'aime Symfony',
    'symfony.great'    => 'J\'aime Symfony',
);
1
2
Symfony is great: J'aime Symfony
symfony.great:    J'aime Symfony

使用真实的或关键字的Messages

上例中描绘了在创建待译的messages时的两种不同的思想:

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

第一种方法,信息(message)被写成了默认locale(本例是英语)的语言。在创建翻译时,该条信息即可被当做“id”使用。

在第二种方法中,信息是真正的 "keywords"(关键字),用于传达信息(大体上的)含义。关键字message也会被当作翻译文件中的 "id" 部分。本例中,(message的)翻译必须使用默认的locale (比如,把 symfony.great 译为 Symfony is great)。

选择哪种方法来用,完全取决于你,但是“关键字”格式常被推荐。

此外,如果你使用关键字而不是真实文本作为id的话,phpyaml 文件格式支持id嵌套以避免令你重复操作:

1
2
3
4
5
6
7
8
symfony:
    is:
        great: Symfony is great
        amazing: Symfony is amazing
    has:
        bundles: Symfony has bundles
user:
    login: Login
1
2
3
4
5
6
7
8
9
10
11
12
13
14
array(
    'symfony' => array(
        'is' => array(
            'great'   => 'Symfony is great',
            'amazing' => 'Symfony is amazing',
        ),
        'has' => array(
            'bundles' => 'Symfony has bundles',
        ),
    ),
    'user' => array(
        'login' => 'Login',
    ),
);

通过在每级之间添加一个点(.),多级id被扁平化为单一的id/translation键值对,因此上例等同于如下代码:

1
2
3
4
symfony.is.great: Symfony is great
symfony.is.amazing: Symfony is amazing
symfony.has.bundles: Symfony has bundles
user.login: Login
1
2
3
4
5
6
return array(
    'symfony.is.great'    => 'Symfony is great',
    'symfony.is.amazing'  => 'Symfony is amazing',
    'symfony.has.bundles' => 'Symfony has bundles',
    'user.login'          => 'Login',
);

复数处理 

翻译信息(message)的复数处理,是个困难的话题,因为规则可能相当复杂。例如,这里有一个俄语复数规则的数学呈现:

1
2
3
4
5
6
7
8
9
(($number % 10 == 1) && ($number % 100 != 11))
    ? 0
    : ((($number % 10 >= 2)
        && ($number % 10 <= 4)
        && (($number % 100 < 10)
        || ($number % 100 >= 20)))
            ? 1
            : 2
);

你已看到,在俄语中,你可以有三种不同的复数形式,每一种的索引是0,1,2。对于每种形式,复数形态不同,所以翻译也是不同的。

当翻译因复数而有不同的形式时,你可以通过一个由pipe(|)分隔的字符串,来提供出全部的形式。

1
'There is one apple|There are %count% apples'

要翻译复数信息,使用 transChoice() 方法:

1
2
3
4
5
$translator->transChoice(
    'There is one apple|There are %count% apples',
    10,
    array('%count%' => 10)
);

第二个参数 (本例是 10) 是被描述对象的 number(数量),用于决定要使用哪种翻译,同时还要装载 %count% 占位符

根据给定的数字,translator 会选择正确的复数形式。在英文中,当仅有一个物体时,多数单词只有一个单数形式,而其复数形式可以是所有其他数字 (0, 2, 3...)。因此,如果 count1,translator 将使用第一个字符串 (There is one apple) 作为翻译。否则它就使用 There are %count% apples

这里有一个法语翻译:

1
'Il y a %count% pomme|Il y a %count% pommes'

就算字符串看起来(和英语的)很相似 (它由两个通过pipe分隔的子串构成),法语的规则却不同: 第一种形式 (没有复数) 用于当 count01。因此,当 count01 时,translator将自动使用第一个字符串 (Il y a %count% pomme)。

每一种locale有其自己的规则集,其中某些甚至有高达六种不同的复数形式,配合着背后的复杂规则,即哪个数字映射的是哪种复数形式。对于英语和法语来说,规则十分简单,但对于俄语,你可能不太想去搞清哪个规则对应哪个字符串。要帮助translators,你可以可选地对每个字符串“打标签”:

1
2
3
'one: There is one apple|some: There are %count% apples'
 
'none_or_one: Il y a %count% pomme|some: Il y a %count% pommes'

标签(tag)真的是只能对translator进行提示而不会影响到“用于决定使用哪种复数形式”的规则。标签可以是任何描述性的字符串,它以冒号(:)结尾。标签并不需要和原始的待译信息相同。

Note

由于tag是可选的,translator并不使用它们(translator只根据tag在字符串中的位置来获取字符串)

显式的区间复数 

对message(待译信息)进行复数处理的最简单方式就是让translator使用内部逻辑,基于给定的数字,来决定要使用哪个字符串。有时,你会需要更多的控制权,或者希望在个别情况下(例如对于0或负值的count)使用不同的翻译。对于这类场景,你可以使用显式的算术区间:

1
'{0} There are no apples|{1} There is one apple|]1,19] There are %count% apples|[20,Inf[ There are many apples'

区间(interval)遵循的是 ISO 31-11 注释。上面的字符串指定了四个区间: 确切的 0, 确切的 1, 2-19, 和 20 以及更高。

你也可以显式地把算术规则(math rules)和标准规则混合起来指定。本例中,如果count没有匹配到一个特定区间,标准规则(standard rules)将在移除显式规则之后生效:

1
'{0} There are no apples|[20,Inf[ There are many apples|There is one apple|a_few: There are %count% apples'

例如,对于 1 个苹果,标准规则 There is one apple 将被使用。对于 2-19 个苹果,第二个标准规则 There are %count% apples 将被使用。

一个 Interval 可以呈现出无限的数字组合:

1
{1,2,3,4}

或者是两个数字之外的数字:

1
2
[1, +Inf[
]-1,2[

左边的分隔符可以是 [ (inclusive/包括) 或 ] (exclusive/排除)。 右边的分隔符可以是 [ (exclusive/排除) 或 ] (inclusive/包括)。除去数字,你还可以使用 -Inf+Inf 来表达无限。

强制Translator的Locale 

当翻译一条信息(message)时,translator使用的是特定的locale或者在必要时使用 fallback locale。你可以手动指定翻译时所要用到的locale:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$translator->trans(
    'Symfony is great',
    array(),
    'messages',
    'fr_FR'
);
 
$translator->transChoice(
    '{0} There are no apples|{1} There is one apple|]1,Inf[ There are %count% apples',
    10,
    array('%count%' => 10),
    'messages',
    'fr_FR'
);

取出信息目录 

如果你想使用程序之外的相同翻译目录(比如使用客户端的翻译源),那么取出原生的(raw)翻译信息是可能的。只要指定所需的locale即可:

1
2
3
4
5
$catalogue = $translator->getCatalogue('fr_FR');
$messages = $catalogue->all();
while ($catalogue = $catalogue->getFallbackCatalogue()) {
    $messages = array_replace_recursive($catalogue->all(), $messages);
}

messages 变量将具备如下结构:

1
2
3
4
5
6
7
8
9
array(
    'messages' => array(
        'Hello world' => 'Bonjour tout le monde',
    ),
    'validators' => array(
        'Value should not be empty' => 'Valeur ne doit pas être vide',
        'Value is too long' => 'Valeur est trop long',
    ),
);

本文,包括例程代码在内,采用的是 Creative Commons BY-SA 3.0 创作共用授权。

登录symfonychina 发表评论或留下问题(我们会尽量回复)