Skip to content

Commit 31175cf

Browse files
committed
feature #11523 [Translation] Document ICU MessageFormat (wouterj)
This PR was squashed before being merged into the 4.2 branch (closes #11523). Discussion ---------- [Translation] Document ICU MessageFormat Fixes #10604 and #11002 Thanks to the amazing work by @arjenm in the issue above, I was able to update the documentation with the new ICU MessageFormat and also create a guide explaining the format. Besides that, I did some small changes in the translation doc (including making the main guide smaller and removing duplicate parts from the component docs). While the format is a standard, ICU documentation is very difficult to read (as with all standards tbh). The JavaScript library [messageformat](https://messageformat.github.io/messageformat/page-guide) has documented the format in a simpler way, but I don't want to rely on documentation of another library (as they might suddently decide to break with the ICU standard). Thus, I decided to write our own article explaining the format in depth. I've also used Yaml as first in the configuration blocks in the message format article. This is not because I'm a huge supporter of not recommending Xliff for everyone (ref #5026, symfony/symfony#22566), but simply because it's easier to read. MessageFormat is quite complex, especially when used on a single line within lots of XML around it. Using Yaml makes examples focus on the MessageFormat, instead of the file format. Commits ------- d2736bc Yet another great review! eed64a8 Add link to usefull online editor 6ec443f Fixes after thorough reviews! 2bd7f86 Removed further usage of transchoice in the docs 0d29e3a Added documentation on the ICU MessageFormat a6dabc1 Some general fixes/improvements for translation guide
2 parents 1a7766f + d2736bc commit 31175cf

File tree

6 files changed

+621
-368
lines changed

6 files changed

+621
-368
lines changed

components/translation/usage.rst

Lines changed: 0 additions & 208 deletions
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

reference/twig_reference.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,12 @@ Translates the text into the current language. More information in
359359
transchoice
360360
~~~~~~~~~~~
361361

362+
.. deprecated:: 4.2
363+
364+
The ``transchoice`` filter is deprecated since Symfony 4.2 and will be
365+
removed in 5.0. Use the :doc:`ICU MessageFormat </translation/message_format>` with
366+
the ``trans`` filter instead.
367+
362368
.. code-block:: twig
363369
364370
{{ message|transchoice(count, arguments = [], domain = null, locale = null) }}
@@ -588,6 +594,12 @@ Renders the translation of the content. More information in :ref:`translation-ta
588594
transchoice
589595
~~~~~~~~~~~
590596

597+
.. deprecated:: 4.2
598+
599+
The ``transchoice`` tag is deprecated since Symfony 4.2 and will be
600+
removed in 5.0. Use the :doc:`ICU MessageFormat </translation/message_format>` with
601+
the ``trans`` tag instead.
602+
591603
.. code-block:: twig
592604
593605
{% transchoice count with vars from domain into locale %}{% endtranschoice %}

0 commit comments

Comments
 (0)