Skip to content
  • About
    • What is Symfony?
    • Community
    • News
    • Contributing
    • Support
  • Documentation
    • Symfony Docs
    • Symfony Book
    • Screencasts
    • Symfony Bundles
    • Symfony Cloud
    • Training
  • Services
    • SensioLabs Professional services to help you with Symfony
    • Platform.sh for Symfony Best platform to deploy Symfony apps
    • SymfonyInsight Automatic quality checks for your apps
    • Symfony Certification Prove your knowledge and boost your career
    • Blackfire Profile and monitor performance of your apps
  • Other
  • Blog
  • Download
sponsored by SensioLabs
  1. Home
  2. Documentation
  3. Translation
  4. How to Translate Messages using the ICU MessageFormat
  • Documentation
  • Book
  • Reference
  • Bundles
  • Cloud

Table of Contents

  • Using the ICU Message Format
  • Message Placeholders
  • Selecting Different Messages Based on a Condition
  • Pluralization
  • Additional Placeholder Functions
    • Ordinal
    • Date and Time
    • Numbers

How to Translate Messages using the ICU MessageFormat

Edit this page

Warning: You are browsing the documentation for Symfony 4.2, which is no longer maintained.

Consider upgrading your projects to Symfony 6.2.

How to Translate Messages using the ICU MessageFormat

4.2

Support for ICU MessageFormat was introduced in Symfony 4.2.

Messages (i.e. strings) in applications are almost never completely static. They contain variables or other complex logic like pluralization. In order to handle this, the Translator component supports the ICU MessageFormat syntax.

Tip

You can test out examples of the ICU MessageFormatter in this online editor.

Using the ICU Message Format

In order to use the ICU Message Format, the message domain has to be suffixed with +intl-icu:

Normal file name ICU Message Format filename
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

All messages in this file will now be processed by the MessageFormatter during translation.

Message Placeholders

The basic usage of the MessageFormat allows you to use placeholders (called arguments in ICU MessageFormat) in your messages:

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

Everything within the curly braces ({...}) is processed by the formatter and replaced by its placeholder:

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']);

Selecting Different Messages Based on a Condition

The curly brace syntax allows to "modify" the output of the variable. One of these functions is the select function. It acts like PHP's switch statement and allows to use different strings based on the value of the variable. A typical usage of this is gender:

1
2
3
4
5
6
7
# translations/messages+intl-icu.en.yaml
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!}
    }
1
2
3
4
5
6
7
8
9
10
11
12
<!-- translations/messages+intl-icu.en.xlf -->
<?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="invitation_title">
                <source>invitation_title</source>
                <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>
1
2
3
4
5
6
7
8
// translations/messages+intl-icu.en.php
return [
    '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!}
    }',
];

This might look very complex. The basic syntax for all functions is {variable_name, function_name, function_statement} (where, as you see later, function_statement is optional for some functions). In this case, the function name is select and its statement contains the "cases" of this select. This function is applied over the organizer_gender variable:

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

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

The {...} syntax alternates between "literal" and "code" mode. This allows you to use literal text in the select statements:

  1. The first {organizer_gender, select, ...} block starts the "code" mode, which means organizer_gender is processed as a variable.
  2. The inner {... has invited you for her party!} block brings you back in "literal" mode, meaning the text is not processed.
  3. Inside this block, {organizer_name} starts "code" mode again, allowing organizer_name to be processed as variable.

Tip

While it might seem more logical to only put her, his or their in the switch statement, it is better to use "complex arguments" at the outermost structure of the message. The strings are in this way better readable for translators and, as you can see in the other case, other parts of the sentence might be influenced by the variables.

Pluralization

Another interesting function is plural. It allows you to handle pluralization in your messages (e.g. There are 3 apples vs There is one apple). The function looks very similar to the select function:

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!}
    }
1
2
3
4
5
6
7
8
9
10
11
12
<!-- translations/messages+intl-icu.en.xlf -->
<?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="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>
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!}
    }',
];

Pluralization rules are actually quite complex and differ for each language. For instance, Russian uses different plural forms for numbers ending with 1; numbers ending with 2, 3 or 4; numbers ending with 5, 6, 7, 8 or 9; and even some exceptions of this!

In order to properly translate this, the possible cases in the plural function are also different for each language. For instance, Russian has one, few, many and other, while English has only one and other. The full list of possible cases can be found in Unicode's Language Plural Rules document. By prefixing with =, you can match exact values (like 0 in the above example).

Usage of this string is the same as with variables and 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]);

Note

You can also set an offset variable to determine whether the pluralization should be offset (e.g. in sentences like You and # other people / You and # other person).

Tip

When combining the select and plural functions, try to still have select as outermost function:

{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.}} }

Additional Placeholder Functions

Besides these, the ICU MessageFormat comes with a couple other interesting functions.

Ordinal

Similar to plural, selectordinal allows you to use numbers as ordinal scale:

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
9
10
11
12
13
14
15
16
17
18
19
<!-- translations/messages+intl-icu.en.xlf -->
<?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="finish_place">
                <source>finish_place</source>
                <target>You finished {place, selectordinal, one {#st} two {#nd} few {#rd} other {#th}}!</target>
            </trans-unit>

            <!-- when only formatting the number as ordinal (like
                 above), you can also use the `ordinal` function: -->
            <trans-unit id="finish_place">
                <source>finish_place</source>
                <target>You finished {place, ordinal}!</target>
            </trans-unit>
        </body>
    </file>
</xliff>
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}
    }!',

    // 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]);

The possible cases for this are also shown in Unicode's Language Plural Rules document.

Date and Time

The date and time function allows you to format dates in the target locale using the IntlDateFormatter:

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

The "function statement" for the time and date functions can be one of short, medium, long or full, which correspond to the constants defined by the IntlDateFormatter class:

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')]);

Numbers

The number formatter allows you to format numbers using Intl's 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
10
11
12
13
14
15
16
17
<!-- translations/messages+intl-icu.en.xlf -->
<?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="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>
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
// 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]);
This work, including the code samples, is licensed under a Creative Commons BY-SA 3.0 license.
TOC
    Version
    We stand with Ukraine.
    Version:
    Put the code quality back at the heart of your project

    Put the code quality back at the heart of your project

    Measure & Improve Symfony Code Performance

    Measure & Improve Symfony Code Performance

    Symfony footer

    ↓ Our footer now uses the colors of the Ukrainian flag because Symfony stands with the people of Ukraine.

    Avatar of Patrick Landolt, a Symfony contributor

    Thanks Patrick Landolt (@scube) for being a Symfony contributor

    11 commits • 263 lines changed

    View all contributors that help us make Symfony

    Become a Symfony contributor

    Be an active part of the community and contribute ideas, code and bug fixes. Both experts and newcomers are welcome.

    Learn how to contribute

    Symfony™ is a trademark of Symfony SAS. All rights reserved.

    • What is Symfony?

      • Symfony at a Glance
      • Symfony Components
      • Case Studies
      • Symfony Releases
      • Security Policy
      • Logo & Screenshots
      • Trademark & Licenses
      • symfony1 Legacy
    • Learn Symfony

      • Symfony Docs
      • Symfony Book
      • Reference
      • Bundles
      • Best Practices
      • Training
      • eLearning Platform
      • Certification
    • Screencasts

      • Learn Symfony
      • Learn PHP
      • Learn JavaScript
      • Learn Drupal
      • Learn RESTful APIs
    • Community

      • SymfonyConnect
      • Support
      • How to be Involved
      • Code of Conduct
      • Events & Meetups
      • Projects using Symfony
      • Downloads Stats
      • Contributors
      • Backers
    • Blog

      • Events & Meetups
      • A week of symfony
      • Case studies
      • Cloud
      • Community
      • Conferences
      • Diversity
      • Documentation
      • Living on the edge
      • Releases
      • Security Advisories
      • SymfonyInsight
      • Twig
      • SensioLabs
    • Services

      • SensioLabs services
      • Train developers
      • Manage your project quality
      • Improve your project performance
      • Host Symfony projects

      Deployed on

    Follow Symfony