Дата обновления перевода 2021-09-30

Как переводить сообщения, используя ICU MessageFormat

Сообщения (т.е. строки) в приложениях практически никогда не бывают абсолютно статическими. Они содержат переменные или другую сложную логику вроде плюрализации. Для того, чтобы обработать это, компонент Переводчик поддерживает синтаксис ICU MessageFormat.

Tip

Вы можете протестировать примеры ICU MessageFormatter в этом онлайн-редакторе.

Использование формата сообщений ICU

Для того, чтобы использовать формат сообщений ICU, домен сообщения должен иметь суффикс +intl-icu:

Все сообщения в этом файле теперь будут обработаны MessageFormatter во время перевода.

Заполнители сообщений

Базовое использование MessageFormat позволяет вам использовать заполнители (которые называются аргументами в ICU MessageFormat) в ваших сообщениях:

  • YAML
    1
    2
    # translations/messages+intl-icu.en.yaml
    say_hello: 'Hello {name}!'
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    <!-- translations/messages+intl-icu.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="say_hello">
                    <source>say_hello</source>
                    <target>Hello {name}!</target>
                </trans-unit>
            </body>
        </file>
    </xliff>
    
  • PHP
    1
    2
    3
    4
    // translations/messages+intl-icu.en.php
    return [
        'say_hello' => "Hello {name}!",
    ];
    

Caution

В предыдущем формате перевода, заполнители часто были обернуты в % (например, %name%). Этот символ % больше не валиден с синтаксисом ICU MessageFormat, поэтому вам нужно переименовать ваши параметры, если вы обновляетесь с предыдущего формата.

Все, заключенное в фигурные скобки ({...}) обрабатывается форматироващиком и заменяется заполнителем:

// выводит "Hello Fabien!"
echo $translator->trans('say_hello', ['name' => 'Fabien']);

// выводит "Hello Symfony!"
echo $translator->trans('say_hello', ['name' => 'Symfony']);

Выбор разных сообщений, в зависимости от состояния

Синтаксис фигурных скобок позволяет “изменять” вывод переменной. Одной из таких функций является функция select. Она действует как PHP-функция switch statement, и позволяет использовать разные строки, в зависимости от значения переменной. Типичное использование этого гендера:

  • YAML
    1
    2
    3
    4
    5
    6
    7
    8
    9
    # translations/messages+intl-icu.en.yaml
    
    # требуется ключ 'other', который выбирается, если не подходит больше ни один случай
    invitation_title: >-
        {organizer_gender, select,
            female {{organizer_name} has invited you for her party!}
            male   {{organizer_name} has invited you for his party!}
            other  {{organizer_name} have invited you for their party!}
        }
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    <!-- translations/messages+intl-icu.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="invitation_title">
                    <source>invitation_title</source>
                    <!-- требуется ключ 'other', который выбирается, если не подходит больше ни один случай -->
                    <target>{organizer_gender, select,
                        female {{organizer_name} has invited you for her party!}
                        male {{organizer_name} has invited you for his party!}
                        other {{organizer_name} have invited you for their party!}
                    }</target>
                </trans-unit>
            </body>
        </file>
    </xliff>
    
  • PHP
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // translations/messages+intl-icu.en.php
    return [
        // требуется ключ 'other', который выбирается, если не подходит больше ни один случай
        'invitation_title' => '{organizer_gender, select,
            female {{organizer_name} has invited you for her party!}
            male   {{organizer_name} has invited you for his party!}
            other  {{organizer_name} have invited you for their party!}
        }',
    ];
    

Это может выглядеть очень сложно. Базовый синтаксис для всех функций - {variable_name, function_name, function_statement} (где, как вы увидите позже, function_statement является необязательным для некоторых функций). В данном случае, имя функции - select, а ее утверждение содержит “случаи” этого выбора. Эта функция применяется поверх переменной organizer_gender:

// выводит "Ryan has invited you for his party!"
echo $translator->trans('invitation_title', [
    'organizer_name' => 'Ryan',
    'organizer_gender' => 'male',
]);

// выводит "John & Jane have invited you for their party!"
echo $translator->trans('invitation_title', [
    'organizer_name' => 'John & Jane',
    'organizer_gender' => 'not_applicable',
]);

Синтаксис {...} варьируется между режимами “literal” (буквально) и “code” (код). Это позволяет вам использовать буквальный текст в выбранных утверждениях:

  1. Первый блок {organizer_gender, select, ...} запускает режим “code”, что означает, что``organizer_gender`` обрабатывается, как переменная.
  2. Внутренний болк {... has invited you for her party!} возвращает вас в режим “literal”, что означает, что текст не обрабатывается.
  3. Внутри этого блока, {organizer_name} снова запускает режим “code”, позволяя organizer_name быть обработанным, как переменная.

Tip

Хотя может казаться более логичным размещать только her, his или their в переменном утверждении, лучше использовать “сложные аргументы” на краю структуры сообщения. Строки таким образом более читаемы для переводчиков, как вы можете увидеть в случае other, другие части предложения могут быть подвержены влиянию переменных.

Tip

Возможно переводить сообщения ICU MessageFormat прямо в коде, не определяя их ни в каком файле:

$invitation = '{organizer_gender, select,
    female {{organizer_name} has invited you for her party!}
    male   {{organizer_name} has invited you for his party!}
    other  {{organizer_name} have invited you for their party!}
}';

// выводит "Ryan has invited you for his party!"
echo $translator->trans($invitation, [
    'organizer_name' => 'Ryan',
    'organizer_gender' => 'male',
]);

Плюрализация

Другая интересная функция - plural. Она позволяет вам работать с плюрализацией в ваших сообщениях (например, There are 3 apples и There is one apple). Функция выглядит очень похоже на функцию select:

  • YAML
    1
    2
    3
    4
    5
    6
    7
    # translations/messages+intl-icu.en.yaml
    num_of_apples: >-
        {apples, plural,
            =0    {There are no apples}
            one   {There is one apple...}
            other {There are # apples!}
        }
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    <!-- translations/messages+intl-icu.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="num_of_apples">
                    <source>num_of_apples</source>
                    <target>{apples, plural, =0 {There are no apples} one {There is one apple...} other {There are # apples!}}</target>
                </trans-unit>
            </body>
        </file>
    </xliff>
    
  • PHP
    1
    2
    3
    4
    5
    6
    7
    8
    // translations/messages+intl-icu.en.php
    return [
        'num_of_apples' => '{apples, plural,
            =0    {There are no apples}
            one   {There is one apple...}
            other {There are # apples!}
        }',
    ];
    

Правила плюрализации на самом деле достаточно сложные и отличваются в каждом языке. Например, русский использует разные множественные окончания для чисел, заканчивающихся на 1; чисел, заканчивающихся на 2, 3 или 4; числе, заканчивающихся на 5, 6, 7, 8 или 9; и кроме этого есть еще и исключения!

Для того, чтобы правильно перевести это, возможные случаи в функции plural, также будут отличаться для каждого языка. К примеру, русский язык будет иметь one, few, many и other, в то время как английский - только one и other. Полный список возможных случаев можно найти в документе Unicode Правила множественных чисел в разных языках. Добавив префикс =, вы можете соответствовать точным значениям (как 0 в примере выше).

Использование этой строки такое же, как с переменными и выбором:

// выводит "There is one apple..."
echo $translator->trans('num_of_apples', ['apples' => 1]);

// выводит "There are 23 apples!"
echo $translator->trans('num_of_apples', ['apples' => 23]);

Note

Вы также можете установить переменную offset, чтобы определить, должна ли плюрализация быть относительной (например, в предложениях вроде You and # other people / You and # other person).

Tip

При комбинировании функций select и plural, постарайтесь, чтобы функция 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.}
    }}
}

Дополнительные функции заполнителя

Кроме этого, ICU MessageFormat имеет несколько других интересных функций.

Ordinal

Схоже с plural, selectordinal позволяет вам использовать числа как порядковую шкалу:

  • YAML
     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}
        }!
    
    # при форматировании числа только как порядкового (как выше), вы также можете
    # использовать функцию `ordinal`:
    finish_place: You finished {place, ordinal}!
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <!-- translations/messages+intl-icu.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="finish_place">
                    <source>finish_place</source>
                    <target>You finished {place, selectordinal, one {#st} two {#nd} few {#rd} other {#th}}!</target>
                </trans-unit>
    
                <!-- при форматировании числа только как порядкового (как выше),
                     вы также можете использовать функцию `ordinal`: -->
                <trans-unit id="finish_place">
                    <source>finish_place</source>
                    <target>You finished {place, ordinal}!</target>
                </trans-unit>
            </body>
        </file>
    </xliff>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    // translations/messages+intl-icu.en.php
    return [
        'finish_place' => 'You finished {place, selectordinal,
            one {#st}
            two {#nd}
            few {#rd}
            other {#th}
        }!',
    
        // при форматировании числа только как порядкового (как выше), вы также можете
        // использовать функцию `ordinal`:
        'finish_place' => 'You finished {place, ordinal}!',
    ];
    
1
2
3
4
5
6
7
8
// выводит "You finished 1st!"
echo $translator->trans('finish_place', ['place' => 1]);

// выводит "You finished 9th!"
echo $translator->trans('finish_place', ['place' => 9]);

// выводит "You finished 23rd!"
echo $translator->trans('finish_place', ['place' => 23]);

Возможные случаи этого также показаны в документе Unicode Правила множественных чисел в разных языках.

Date and Time

Функция дата и время позволяет вам форматировать даты в целевой локали, используя IntlDateFormatter:

  • YAML
    1
    2
    # translations/messages+intl-icu.en.yaml
    published_at: 'Published at {publication_date, date} - {publication_date, time, short}'
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    <!-- translations/messages+intl-icu.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="published_at">
                    <source>published_at</source>
                    <target>Published at {publication_date, date} - {publication_date, time, short}</target>
                </trans-unit>
            </body>
        </file>
    </xliff>
    
  • PHP
    1
    2
    3
    4
    // translations/messages+intl-icu.en.php
    return [
        'published_at' => 'Published at {publication_date, date} - {publication_date, time, short}',
    ];
    

“Утверждение функции” для функций time и date может быть short, medium, long или full, что соответствует константам, определенным классом the IntlDateFormatter:

// выводит "Published at Jan 25, 2019 - 11:30 AM"
echo $translator->trans('published_at', ['publication_date' => new \DateTime('2019-01-25 11:30:00')]);

Numbers

Форматировщик number позволяет вам форматировать числа, используя NumberFormatter Intl:

  • YAML
    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}'
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    <!-- translations/messages+intl-icu.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="progress">
                    <source>progress</source>
                    <target>{progress, number, percent} of the work is done</target>
                </trans-unit>
    
                <trans-unit id="value_of_object">
                    <source>value_of_object</source>
                    <target>This artifact is worth {value, number, currency}</target>
                </trans-unit>
            </body>
        </file>
    </xliff>
    
  • PHP
    1
    2
    3
    4
    5
    // translations/messages+intl-icu.en.php
    return [
        '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
// выводит "82% of the work is done"
echo $translator->trans('progress', ['progress' => 0.82]);
// выводит "100% of the work is done"
echo $translator->trans('progress', ['progress' => 1]);

// выводит "This artifact is worth $9,988,776.65"
// если бы мы перевели это, к примеру, на французский, значение отображалось бы как
// "9 988 776,65 €"
echo $translator->trans('value_of_object', ['value' => 9988776.65]);

Эта документация является переводом официальной документации Symfony и предоставляется по свободной лицензии CC BY-SA 3.0.