Skip to content

Commit 0d29e3a

Browse files
committed
Added documentation on the ICU MessageFormat
1 parent a6dabc1 commit 0d29e3a

File tree

3 files changed

+457
-242
lines changed

3 files changed

+457
-242
lines changed

components/translation/usage.rst

-208
Original file line numberDiff line numberDiff line change
@@ -21,79 +21,6 @@ In this example, the message *"Symfony is great!"* will be translated into
2121
the locale set in the constructor (``fr_FR``) if the message exists in one of
2222
the message catalogs.
2323

24-
.. _component-translation-placeholders:
25-
26-
Message Placeholders
27-
--------------------
28-
29-
Sometimes, a message containing a variable needs to be translated::
30-
31-
// ...
32-
$translated = $translator->trans('Hello '.$name);
33-
34-
var_dump($translated);
35-
36-
However, creating a translation for this string is impossible since the translator
37-
will try to look up the exact message, including the variable portions
38-
(e.g. *"Hello Ryan"* or *"Hello Fabien"*). Instead of writing a translation
39-
for every possible iteration of the ``$name`` variable, you can replace the
40-
variable with a "placeholder"::
41-
42-
// ...
43-
$translated = $translator->trans(
44-
'Hello %name%',
45-
['%name%' => $name]
46-
);
47-
48-
var_dump($translated);
49-
50-
Symfony will now look for a translation of the raw message (``Hello %name%``)
51-
and *then* replace the placeholders with their values. Creating a translation
52-
is done just as before:
53-
54-
.. configuration-block::
55-
56-
.. code-block:: xml
57-
58-
<?xml version="1.0"?>
59-
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
60-
<file source-language="en" datatype="plaintext" original="file.ext">
61-
<body>
62-
<trans-unit id="1">
63-
<source>Hello %name%</source>
64-
<target>Bonjour %name%</target>
65-
</trans-unit>
66-
</body>
67-
</file>
68-
</xliff>
69-
70-
.. code-block:: yaml
71-
72-
'Hello %name%': Bonjour %name%
73-
74-
.. code-block:: php
75-
76-
return [
77-
'Hello %name%' => 'Bonjour %name%',
78-
];
79-
80-
.. note::
81-
82-
The placeholders can take on any form as the full message is reconstructed
83-
using the PHP :phpfunction:`strtr function<strtr>`. But the ``%...%`` form
84-
is recommended, to avoid problems when using Twig.
85-
86-
As you've seen, creating a translation is a two-step process:
87-
88-
#. Abstract the message that needs to be translated by processing it through
89-
the ``Translator``.
90-
91-
#. Create a translation for the message in each locale that you choose to
92-
support.
93-
94-
The second step is done by creating message catalogs that define the translations
95-
for any number of different locales.
96-
9724
Creating Translations
9825
---------------------
9926

@@ -222,141 +149,6 @@ recommended format. These files are parsed by one of the loader classes.
222149
'user.login' => 'Login',
223150
];
224151
225-
.. _component-translation-pluralization:
226-
227-
Pluralization
228-
-------------
229-
230-
Message pluralization is a tough topic as the rules can be quite complex. For
231-
instance, here is the mathematical representation of the Russian pluralization
232-
rules::
233-
234-
(($number % 10 == 1) && ($number % 100 != 11))
235-
? 0
236-
: ((($number % 10 >= 2)
237-
&& ($number % 10 <= 4)
238-
&& (($number % 100 < 10)
239-
|| ($number % 100 >= 20)))
240-
? 1
241-
: 2
242-
);
243-
244-
As you can see, in Russian, you can have three different plural forms, each
245-
given an index of 0, 1 or 2. For each form, the plural is different, and
246-
so the translation is also different.
247-
248-
When a translation has different forms due to pluralization, you can provide
249-
all the forms as a string separated by a pipe (``|``)::
250-
251-
'There is one apple|There are %count% apples'
252-
253-
To translate pluralized messages, use the
254-
:method:`Symfony\\Component\\Translation\\Translator::transChoice` method::
255-
256-
// the %count% placeholder is assigned to the second argument...
257-
$translator->transChoice(
258-
'There is one apple|There are %count% apples',
259-
10
260-
);
261-
262-
// ...but you can define more placeholders if needed
263-
$translator->transChoice(
264-
'Hurry up %name%! There is one apple left.|There are %count% apples left.',
265-
10,
266-
// no need to include %count% here; Symfony does that for you
267-
['%name%' => $user->getName()]
268-
);
269-
270-
The second argument (``10`` in this example) is the *number* of objects being
271-
described and is used to determine which translation to use and also to populate
272-
the ``%count%`` placeholder.
273-
274-
Based on the given number, the translator chooses the right plural form.
275-
In English, most words have a singular form when there is exactly one object
276-
and a plural form for all other numbers (0, 2, 3...). So, if ``count`` is
277-
``1``, the translator will use the first string (``There is one apple``)
278-
as the translation. Otherwise it will use ``There are %count% apples``.
279-
280-
Here is the French translation:
281-
282-
.. code-block:: text
283-
284-
'Il y a %count% pomme|Il y a %count% pommes'
285-
286-
Even if the string looks similar (it is made of two sub-strings separated by a
287-
pipe), the French rules are different: the first form (no plural) is used when
288-
``count`` is ``0`` or ``1``. So, the translator will automatically use the
289-
first string (``Il y a %count% pomme``) when ``count`` is ``0`` or ``1``.
290-
291-
Each locale has its own set of rules, with some having as many as six different
292-
plural forms with complex rules behind which numbers map to which plural form.
293-
The rules are quite simple for English and French, but for Russian, you'd
294-
may want a hint to know which rule matches which string. To help translators,
295-
you can optionally "tag" each string:
296-
297-
.. code-block:: text
298-
299-
'one: There is one apple|some: There are %count% apples'
300-
301-
'none_or_one: Il y a %count% pomme|some: Il y a %count% pommes'
302-
303-
The tags are really only hints for translators and don't affect the logic
304-
used to determine which plural form to use. The tags can be any descriptive
305-
string that ends with a colon (``:``). The tags also do not need to be the
306-
same in the original message as in the translated one.
307-
308-
.. tip::
309-
310-
As tags are optional, the translator doesn't use them (the translator will
311-
only get a string based on its position in the string).
312-
313-
Explicit Interval Pluralization
314-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
315-
316-
The easiest way to pluralize a message is to let the Translator use internal
317-
logic to choose which string to use based on a given number. Sometimes, you'll
318-
need more control or want a different translation for specific cases (for
319-
``0``, or when the count is negative, for example). For such cases, you can
320-
use explicit math intervals:
321-
322-
.. code-block:: text
323-
324-
'{0} There are no apples|{1} There is one apple|]1,19] There are %count% apples|[20,Inf[ There are many apples'
325-
326-
The intervals follow the `ISO 31-11`_ notation. The above string specifies
327-
four different intervals: exactly ``0``, exactly ``1``, ``2-19``, and ``20``
328-
and higher.
329-
330-
You can also mix explicit math rules and standard rules. In this case, if
331-
the count is not matched by a specific interval, the standard rules take
332-
effect after removing the explicit rules:
333-
334-
.. code-block:: text
335-
336-
'{0} There are no apples|[20,Inf[ There are many apples|There is one apple|a_few: There are %count% apples'
337-
338-
For example, for ``1`` apple, the standard rule ``There is one apple`` will
339-
be used. For ``2-19`` apples, the second standard rule
340-
``There are %count% apples`` will be selected.
341-
342-
An :class:`Symfony\\Component\\Translation\\Interval` can represent a finite set
343-
of numbers:
344-
345-
.. code-block:: text
346-
347-
{1,2,3,4}
348-
349-
Or numbers between two other numbers:
350-
351-
.. code-block:: text
352-
353-
[1, +Inf[
354-
]-1,2[
355-
356-
The left delimiter can be ``[`` (inclusive) or ``]`` (exclusive). The right
357-
delimiter can be ``[`` (exclusive) or ``]`` (inclusive). Beside numbers, you
358-
can use ``-Inf`` and ``+Inf`` for the infinite.
359-
360152
Forcing the Translator Locale
361153
-----------------------------
362154

translation.rst

+21-34
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,8 @@ the message inside your `templates <Translations in Templates>`.
174174
The Translation Process
175175
~~~~~~~~~~~~~~~~~~~~~~~
176176

177-
To actually translate the message, Symfony uses the following process:
177+
To actually translate the message, Symfony uses the following process when
178+
using the ``trans()`` method:
178179

179180
* The ``locale`` of the current user, which is stored on the request is determined;
180181

@@ -188,33 +189,21 @@ To actually translate the message, Symfony uses the following process:
188189
* If the message is located in the catalog, the translation is returned. If
189190
not, the translator returns the original message.
190191

191-
When using the ``trans()`` method, Symfony looks for the exact string inside
192-
the appropriate message catalog and returns it (if it exists).
192+
.. _message-placeholders:
193+
.. _pluralization:
193194

194-
Message Placeholders
195-
--------------------
195+
Message Format
196+
--------------
196197

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

199-
use Symfony\Contracts\Translation\TranslatorInterface;
200-
201-
public function index(TranslatorInterface $translator, $name)
202-
{
203-
$translated = $translator->trans('Hello '.$name);
204-
205-
// ...
206-
}
200+
// ...
201+
$translated = $translator->trans('Hello '.$name);
207202

208-
However, creating a translation for this string is impossible since the translator
209-
will try to look up the exact message, including the variable portions
203+
However, creating a translation for this string is impossible since the
204+
translator will try to look up the message including the variable portions
210205
(e.g. *"Hello Ryan"* or *"Hello Fabien"*).
211206

212-
For details on how to handle this situation, see :ref:`component-translation-placeholders`
213-
in the components documentation. For how to do this in templates, see :ref:`translation-tags`.
214-
215-
Pluralization
216-
-------------
217-
218207
Another complication is when you have translations that may or may not be
219208
plural, based on some variable:
220209

@@ -223,17 +212,15 @@ plural, based on some variable:
223212
There is one apple.
224213
There are 5 apples.
225214
226-
To handle this, use the :method:`Symfony\\Component\\Translation\\Translator::transChoice`
227-
method or the ``transchoice`` tag/filter in your :ref:`template <translation-tags>`.
228-
229-
For much more information, see :ref:`component-translation-pluralization`
230-
in the Translation component documentation.
215+
To manage these situations, Symfony follows the `ICU MessageFormat`_ syntax by
216+
using PHP's :phpclass:`MessageFormatter` class. Read more about this in
217+
:doc:`/translation/message_format`.
231218

232-
.. deprecated:: 4.2
219+
.. versionadded:: 4.2
233220

234-
In Symfony 4.2 the ``Translator::transChoice()`` method was deprecated in
235-
favor of using ``Translator::trans()`` with ``%count%`` as the parameter
236-
driving plurals.
221+
Support for ICU MessageFormat was introduced in Symfony 4.2. Prior to this,
222+
pluralization was managed by the
223+
:method:`Symfony\\Component\\Translation\\Translator::transChoice` method.
237224

238225
Translations in Templates
239226
-------------------------
@@ -436,10 +423,8 @@ Summary
436423
With the Symfony Translation component, creating an internationalized application
437424
no longer needs to be a painful process and boils down to these steps:
438425

439-
* Abstract messages in your application by wrapping each in either the
440-
:method:`Symfony\\Component\\Translation\\Translator::trans` or
441-
:method:`Symfony\\Component\\Translation\\Translator::transChoice` methods
442-
(learn about this in :doc:`/components/translation/usage`);
426+
* Abstract messages in your application by wrapping each in the
427+
:method:`Symfony\\Component\\Translation\\Translator::trans` method;
443428

444429
* Translate each message into multiple locales by creating translation message
445430
files. Symfony discovers and processes each file because its name follows
@@ -454,12 +439,14 @@ Learn more
454439
.. toctree::
455440
:maxdepth: 1
456441

442+
translation/message_format
457443
translation/templates
458444
translation/locale
459445
translation/debug
460446
translation/lint
461447

462448
.. _`i18n`: https://en.wikipedia.org/wiki/Internationalization_and_localization
449+
.. _`ICU MessageFormat`: http://userguide.icu-project.org/formatparse/messages
463450
.. _`ISO 3166-1 alpha-2`: https://en.wikipedia.org/wiki/ISO_3166-1#Current_codes
464451
.. _`ISO 639-1`: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
465452
.. _`Translatable Extension`: http://atlantic18.github.io/DoctrineExtensions/doc/translatable.html

0 commit comments

Comments
 (0)