跳到内容

如何使用 ICU MessageFormat 翻译消息

编辑此页

应用中的消息(即字符串)几乎从不是完全静态的。它们包含变量或其他复杂的逻辑,例如复数化。为了处理这种情况,Translator 组件 支持 ICU MessageFormat 语法。

提示

你可以在这个 在线编辑器 中测试 ICU MessageFormatter 的示例。

使用 ICU MessageFormat

为了使用 ICU Message Format,消息域必须以后缀 +intl-icu 结尾

普通文件名 ICU Message Format 文件名
messages.en.yaml messages+intl-icu.en.yaml
messages.fr_FR.xlf messages+intl-icu.fr_FR.xlf
admin.en.yaml admin+intl-icu.en.yaml

此文件中的所有消息现在都将由 MessageFormatter 在翻译期间处理。

消息占位符

MessageFormat 的基本用法允许你在消息中使用占位符(在 ICU MessageFormat 中称为参数

1
2
# translations/messages+intl-icu.en.yaml
say_hello: 'Hello {name}!'

警告

在之前的翻译格式中,占位符通常用 % 包裹(例如 %name%)。% 字符在 ICU MessageFormat 语法中不再有效,因此如果你从之前的格式升级,则必须重命名你的参数。

花括号 ({...}) 内的所有内容都由格式化程序处理并替换为其占位符

1
2
3
4
5
// prints "Hello Fabien!"
echo $translator->trans('say_hello', ['name' => 'Fabien']);

// prints "Hello Symfony!"
echo $translator->trans('say_hello', ['name' => 'Symfony']);

根据条件选择不同的消息

花括号语法允许“修改”变量的输出。其中一个功能是 select 函数。它的作用类似于 PHP 的 switch 语句,并允许你根据变量的值使用不同的字符串。一个典型的用法是性别

1
2
3
4
5
6
7
8
9
10
# translations/messages+intl-icu.en.yaml

# the 'other' key is required, and is selected if no other case matches
invitation_title: >-
    {organizer_gender, select,
        female   {{organizer_name} has invited you to her party!}
        male     {{organizer_name} has invited you to his party!}
        multiple {{organizer_name} have invited you to their party!}
        other    {{organizer_name} has invited you to their party!}
    }

这可能看起来非常复杂。所有函数的基本语法是 {variable_name, function_name, function_statement}(正如你稍后会看到的,对于某些函数,function_statement 是可选的)。在这种情况下,函数名称是 select,其语句包含此选择的“cases”。此函数应用于 organizer_gender 变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// prints "Ryan has invited you to his party!"
echo $translator->trans('invitation_title', [
    'organizer_name' => 'Ryan',
    'organizer_gender' => 'male',
]);

// prints "John & Jane have invited you to their party!"
echo $translator->trans('invitation_title', [
    'organizer_name' => 'John & Jane',
    'organizer_gender' => 'multiple',
]);

// prints "ACME Company has invited you to their party!"
echo $translator->trans('invitation_title', [
    'organizer_name' => 'ACME Company',
    'organizer_gender' => 'not_applicable',
]);

{...} 语法在“字面量”和“代码”模式之间交替。这允许你在 select 语句中使用字面文本

  1. 第一个 {organizer_gender, select, ...} 块启动“代码”模式,这意味着 organizer_gender 被处理为变量。
  2. 内部 {... has invited you to her party!} 块将你带回“字面量”模式,这意味着文本不会被处理。
  3. 在此块内,{organizer_name} 再次启动“代码”模式,允许将 organizer_name 处理为变量。

提示

虽然只将 herhistheir 放在 switch 语句中可能看起来更合乎逻辑,但最好在消息的最外层结构中使用“复杂参数”。这样,字符串对于翻译人员来说更易读,正如你在 multiple case 中看到的那样,句子的其他部分可能会受到变量的影响。

提示

可以直接在代码中翻译 ICU MessageFormat 消息,而无需在任何文件中定义它们

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$invitation = '{organizer_gender, select,
    female   {{organizer_name} has invited you to her party!}
    male     {{organizer_name} has invited you to his party!}
    multiple {{organizer_name} have invited you to their party!}
    other    {{organizer_name} has invited you to their party!}
}';

// prints "Ryan has invited you to his party!"
echo $translator->trans(
    $invitation,
    [
        'organizer_name' => 'Ryan',
        'organizer_gender' => 'male',
    ],
    // if you prefer, the required "+intl-icu" suffix is also defined as a constant:
    // Symfony\Component\Translation\MessageCatalogueInterface::INTL_DOMAIN_SUFFIX
    'messages+intl-icu'
);

复数化

另一个有趣的函数是 plural。它允许你处理消息中的复数化(例如 There are 3 apples vs There is one apple)。该函数看起来与 select 函数非常相似

1
2
3
4
5
6
7
# translations/messages+intl-icu.en.yaml
num_of_apples: >-
    {apples, plural,
        =0    {There are no apples}
        =1    {There is one apple...}
        other {There are # apples!}
    }

复数化规则实际上非常复杂,并且每种语言都不同。例如,俄语对以 1 结尾的数字;以 2、3 或 4 结尾的数字;以 5、6、7、8 或 9 结尾的数字;甚至对此有一些例外情况,使用不同的复数形式!

为了正确翻译这一点,plural 函数中可能的 cases 对于每种语言也不同。例如,俄语有 onefewmanyother,而英语只有 oneother。可能的 cases 的完整列表可以在 Unicode 的 语言复数规则 文档中找到。通过以 = 为前缀,你可以匹配确切的值(如上面示例中的 0)。

此字符串的用法与变量和 select 相同

1
2
3
4
5
// prints "There is one apple..."
echo $translator->trans('num_of_apples', ['apples' => 1]);

// prints "There are 23 apples!"
echo $translator->trans('num_of_apples', ['apples' => 23]);

注意

你还可以设置一个 offset 变量来确定复数化是否应该偏移(例如,在像 You and # other people / You and # other person 这样的句子中)。

提示

当组合 selectplural 函数时,尝试仍然将 select 作为最外层函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{gender_of_host, select,
    female {{num_guests, plural, offset:1
        =0    {{host} does not give a party.}
        =1    {{host} invites {guest} to her party.}
        =2    {{host} invites {guest} and one other person to her party.}
        other {{host} invites {guest} and # other people to her party.}
    }}
    male {{num_guests, plural, offset:1
        =0    {{host} does not give a party.}
        =1    {{host} invites {guest} to his party.}
        =2    {{host} invites {guest} and one other person to his party.}
        other {{host} invites {guest} and # other people to his party.}
    }}
    other {{num_guests, plural, offset:1
        =0    {{host} does not give a party.}
        =1    {{host} invites {guest} to their party.}
        =2    {{host} invites {guest} and one other person to their party.}
        other {{host} invites {guest} and # other people to their party.}
    }}
}

旧版 Symfony 语法中的复数化可以与自定义范围一起使用(例如,对于 0-12、12-40 和 40+ 具有不同的消息)。ICU 消息格式没有此功能。相反,此逻辑应移至 PHP 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
// Instead of
$message = $translator->trans('balance_message', $balance);
// with a message like:
// ]-Inf,0]Oops! I'm down|]0,1000]I still have money|]1000,Inf]I have lots of money

// use three different messages for each range:
if ($balance < 0) {
    $message = $translator->trans('no_money_message');
} elseif ($balance < 1000) {
    $message = $translator->trans('some_money_message');
} else {
    $message = $translator->trans('lots_of_money_message');
}

额外的占位符功能

除此之外,ICU MessageFormat 还附带了一些其他有趣的功能。

序数

plural 类似,selectordinal 允许你使用数字作为序数标度

1
2
3
4
5
6
7
8
9
10
11
12
# translations/messages+intl-icu.en.yaml
finish_place: >-
    You finished {place, selectordinal,
        one   {#st}
        two   {#nd}
        few   {#rd}
        other {#th}
    }!

# when only formatting the number as ordinal (like above), you can also
# use the `ordinal` function:
finish_place: You finished {place, ordinal}!
1
2
3
4
5
6
7
8
// prints "You finished 1st!"
echo $translator->trans('finish_place', ['place' => 1]);

// prints "You finished 9th!"
echo $translator->trans('finish_place', ['place' => 9]);

// prints "You finished 23rd!"
echo $translator->trans('finish_place', ['place' => 23]);

此功能的可能 cases 也显示在 Unicode 的 语言复数规则 文档中。

日期和时间

日期和时间函数允许你使用 IntlDateFormatter 以目标区域设置格式化日期

1
2
# translations/messages+intl-icu.en.yaml
published_at: 'Published at {publication_date, date} - {publication_date, time, short}'

timedate 函数的“function statement”可以是 shortmediumlongfull 之一,它们对应于 IntlDateFormatter 类定义的常量

1
2
// prints "Published at Jan 25, 2019 - 11:30 AM"
echo $translator->trans('published_at', ['publication_date' => new \DateTime('2019-01-25 11:30:00')]);

数字

number 格式化程序允许你使用 Intl 的 NumberFormatter 格式化数字

1
2
3
# translations/messages+intl-icu.en.yaml
progress: '{progress, number, percent} of the work is done'
value_of_object: 'This artifact is worth {value, number, currency}'
1
2
3
4
5
6
7
8
9
// prints "82% of the work is done"
echo $translator->trans('progress', ['progress' => 0.82]);
// prints "100% of the work is done"
echo $translator->trans('progress', ['progress' => 1]);

// prints "This artifact is worth $9,988,776.65"
// if we would translate this to i.e. French, the value would be shown as
// "9 988 776,65 €"
echo $translator->trans('value_of_object', ['value' => 9988776.65]);
此作品,包括代码示例,根据 Creative Commons BY-SA 3.0 许可获得许可。
目录
    版本