Skip to content

[Translation] Document ICU MessageFormat #11523

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jun 1, 2019
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
208 changes: 0 additions & 208 deletions components/translation/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,79 +21,6 @@ In this example, the message *"Symfony is great!"* will be translated into
the locale set in the constructor (``fr_FR``) if the message exists in one of
the message catalogs.

.. _component-translation-placeholders:

Message Placeholders
--------------------

Sometimes, a message containing a variable needs to be translated::

// ...
$translated = $translator->trans('Hello '.$name);

var_dump($translated);

However, creating a translation for this string is impossible since the translator
will try to look up the exact message, including the variable portions
(e.g. *"Hello Ryan"* or *"Hello Fabien"*). Instead of writing a translation
for every possible iteration of the ``$name`` variable, you can replace the
variable with a "placeholder"::

// ...
$translated = $translator->trans(
'Hello %name%',
['%name%' => $name]
);

var_dump($translated);

Symfony will now look for a translation of the raw message (``Hello %name%``)
and *then* replace the placeholders with their values. Creating a translation
is done just as before:

.. configuration-block::

.. code-block:: xml

<?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>

.. code-block:: yaml

'Hello %name%': Bonjour %name%

.. code-block:: php

return [
'Hello %name%' => 'Bonjour %name%',
];

.. note::

The placeholders can take on any form as the full message is reconstructed
using the PHP :phpfunction:`strtr function<strtr>`. But the ``%...%`` form
is recommended, to avoid problems when using Twig.

As you've seen, creating a translation is a two-step process:

#. Abstract the message that needs to be translated by processing it through
the ``Translator``.

#. Create a translation for the message in each locale that you choose to
support.

The second step is done by creating message catalogs that define the translations
for any number of different locales.

Creating Translations
---------------------

Expand Down Expand Up @@ -222,141 +149,6 @@ recommended format. These files are parsed by one of the loader classes.
'user.login' => 'Login',
];

.. _component-translation-pluralization:

Pluralization
-------------

Message pluralization is a tough topic as the rules can be quite complex. For
instance, here is the mathematical representation of the Russian pluralization
rules::

(($number % 10 == 1) && ($number % 100 != 11))
? 0
: ((($number % 10 >= 2)
&& ($number % 10 <= 4)
&& (($number % 100 < 10)
|| ($number % 100 >= 20)))
? 1
: 2
);

As you can see, in Russian, you can have three different plural forms, each
given an index of 0, 1 or 2. For each form, the plural is different, and
so the translation is also different.

When a translation has different forms due to pluralization, you can provide
all the forms as a string separated by a pipe (``|``)::

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

To translate pluralized messages, use the
:method:`Symfony\\Component\\Translation\\Translator::transChoice` method::

// the %count% placeholder is assigned to the second argument...
$translator->transChoice(
'There is one apple|There are %count% apples',
10
);

// ...but you can define more placeholders if needed
$translator->transChoice(
'Hurry up %name%! There is one apple left.|There are %count% apples left.',
10,
// no need to include %count% here; Symfony does that for you
['%name%' => $user->getName()]
);

The second argument (``10`` in this example) is the *number* of objects being
described and is used to determine which translation to use and also to populate
the ``%count%`` placeholder.

Based on the given number, the translator chooses the right plural form.
In English, most words have a singular form when there is exactly one object
and a plural form for all other numbers (0, 2, 3...). So, if ``count`` is
``1``, the translator will use the first string (``There is one apple``)
as the translation. Otherwise it will use ``There are %count% apples``.

Here is the French translation:

.. code-block:: text

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

Even if the string looks similar (it is made of two sub-strings separated by a
pipe), the French rules are different: the first form (no plural) is used when
``count`` is ``0`` or ``1``. So, the translator will automatically use the
first string (``Il y a %count% pomme``) when ``count`` is ``0`` or ``1``.

Each locale has its own set of rules, with some having as many as six different
plural forms with complex rules behind which numbers map to which plural form.
The rules are quite simple for English and French, but for Russian, you'd
may want a hint to know which rule matches which string. To help translators,
you can optionally "tag" each string:

.. code-block:: text

'one: There is one apple|some: There are %count% apples'

'none_or_one: Il y a %count% pomme|some: Il y a %count% pommes'

The tags are really only hints for translators and don't affect the logic
used to determine which plural form to use. The tags can be any descriptive
string that ends with a colon (``:``). The tags also do not need to be the
same in the original message as in the translated one.

.. tip::

As tags are optional, the translator doesn't use them (the translator will
only get a string based on its position in the string).

Explicit Interval Pluralization
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The easiest way to pluralize a message is to let the Translator use internal
logic to choose which string to use based on a given number. Sometimes, you'll
need more control or want a different translation for specific cases (for
``0``, or when the count is negative, for example). For such cases, you can
use explicit math intervals:

.. code-block:: text

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

The intervals follow the `ISO 31-11`_ notation. The above string specifies
four different intervals: exactly ``0``, exactly ``1``, ``2-19``, and ``20``
and higher.

You can also mix explicit math rules and standard rules. In this case, if
the count is not matched by a specific interval, the standard rules take
effect after removing the explicit rules:

.. code-block:: text

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

For example, for ``1`` apple, the standard rule ``There is one apple`` will
be used. For ``2-19`` apples, the second standard rule
``There are %count% apples`` will be selected.

An :class:`Symfony\\Component\\Translation\\Interval` can represent a finite set
of numbers:

.. code-block:: text

{1,2,3,4}

Or numbers between two other numbers:

.. code-block:: text

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

The left delimiter can be ``[`` (inclusive) or ``]`` (exclusive). The right
delimiter can be ``[`` (exclusive) or ``]`` (inclusive). Beside numbers, you
can use ``-Inf`` and ``+Inf`` for the infinite.

Forcing the Translator Locale
-----------------------------

Expand Down
12 changes: 12 additions & 0 deletions reference/twig_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,12 @@ Translates the text into the current language. More information in
transchoice
~~~~~~~~~~~

.. deprecated:: 4.2

The ``transchoice`` filter is deprecated since Symfony 4.2 and will be
removed in 5.0. Use the :doc:`ICU MessageFormat </translation/message_format>` with
the ``trans`` filter instead.

.. code-block:: twig

{{ message|transchoice(count, arguments = [], domain = null, locale = null) }}
Expand Down Expand Up @@ -588,6 +594,12 @@ Renders the translation of the content. More information in :ref:`translation-ta
transchoice
~~~~~~~~~~~

.. deprecated:: 4.2

The ``transchoice`` tag is deprecated since Symfony 4.2 and will be
removed in 5.0. Use the :doc:`ICU MessageFormat </translation/message_format>` with
the ``trans`` tag instead.

.. code-block:: twig

{% transchoice count with vars from domain into locale %}{% endtranschoice %}
Expand Down
Loading