跳到內容

如何使用 ICU MessageFormat 翻譯訊息

編輯此頁面

應用程式中的訊息 (即字串) 幾乎從來都不是完全靜態的。它們包含變數或其他複雜邏輯,例如複數化。為了處理這個問題,Translator 组件 支援 ICU MessageFormat 語法。

提示

您可以在這個 線上編輯器 中測試 ICU MessageFormatter 的範例。

使用 ICU 訊息格式

為了使用 ICU 訊息格式,訊息網域必須加上後綴 +intl-icu

一般檔案名稱 ICU 訊息格式檔案名稱
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,其語句包含此選取的「案例」。此函數應用於 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 被當作變數處理。

提示

雖然只在 switch 語句中放入 herhistheir 可能看起來更符合邏輯,但在訊息的最外層結構中使用「複雜引數」會更好。這樣一來,字串對於翻譯人員來說更易於閱讀,而且如您在 multiple 案例中看到的,句子的其他部分可能會受到變數的影響。

提示

可以直接在程式碼中翻譯 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 applesThere 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 函數中可能的案例也因語言而異。例如,俄語有 onefewmanyother,而英語只有 oneother。可能的案例完整列表可以在 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]);

此功能的可能案例也顯示在 Unicode 的 語言複數規則 文件中。

日期和時間

日期和時間函數允許您使用 IntlDateFormatter 以目標地區設定格式化日期。

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

timedate 函數的「函數語句」可以是 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 授權條款授權。
目錄
    版本