diff --git a/components/translation/usage.rst b/components/translation/usage.rst
index b1dfa9a1f34..656e84083b5 100644
--- a/components/translation/usage.rst
+++ b/components/translation/usage.rst
@@ -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
-
-
-
-
-
-
- Hello %name%
- Bonjour %name%
-
-
-
-
-
- .. 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`. 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
---------------------
@@ -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
-----------------------------
diff --git a/reference/twig_reference.rst b/reference/twig_reference.rst
index 27c208dc34c..083583727e7 100644
--- a/reference/twig_reference.rst
+++ b/reference/twig_reference.rst
@@ -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 ` with
+ the ``trans`` filter instead.
+
.. code-block:: twig
{{ message|transchoice(count, arguments = [], domain = null, locale = null) }}
@@ -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 ` with
+ the ``trans`` tag instead.
+
.. code-block:: twig
{% transchoice count with vars from domain into locale %}{% endtranschoice %}
diff --git a/translation.rst b/translation.rst
index ac859742c55..2e1bee7d8a0 100644
--- a/translation.rst
+++ b/translation.rst
@@ -12,13 +12,11 @@ wrapping each with a function capable of translating the text (or "message")
into the language of the user::
// text will *always* print out in English
- dump('Hello World');
- die();
+ echo 'Hello World';
// text can be translated into the end-user's language or
// default to English
- dump($translator->trans('Hello World'));
- die();
+ echo $translator->trans('Hello World');
.. note::
@@ -163,7 +161,7 @@ different formats, XLIFF being the recommended format:
// translations/messages.fr.php
return [
- 'Symfony is great' => 'J\'aime Symfony',
+ 'Symfony is great' => "J'aime Symfony",
];
For information on where these files should be located, see
@@ -171,12 +169,13 @@ For information on where these files should be located, see
Now, if the language of the user's locale is French (e.g. ``fr_FR`` or ``fr_BE``),
the message will be translated into ``J'aime Symfony``. You can also translate
-the message inside your :ref:`templates `.
+the message inside your `templates `.
The Translation Process
~~~~~~~~~~~~~~~~~~~~~~~
-To actually translate the message, Symfony uses the following process:
+To actually translate the message, Symfony uses the following process when
+using the ``trans()`` method:
* The ``locale`` of the current user, which is stored on the request is determined;
@@ -184,38 +183,27 @@ To actually translate the message, Symfony uses the following process:
resources defined for the ``locale`` (e.g. ``fr_FR``). Messages from the
:ref:`fallback locale ` are also loaded and
added to the catalog if they don't already exist. The end result is a large
- "dictionary" of translations.
+ "dictionary" of translations. This catalog is cached in production to
+ minimize performance impact.
* If the message is located in the catalog, the translation is returned. If
not, the translator returns the original message.
-When using the ``trans()`` method, Symfony looks for the exact string inside
-the appropriate message catalog and returns it (if it exists).
+.. _message-placeholders:
+.. _pluralization:
-Message Placeholders
---------------------
+Message Format
+--------------
Sometimes, a message containing a variable needs to be translated::
- use Symfony\Contracts\Translation\TranslatorInterface;
-
- public function index(TranslatorInterface $translator, $name)
- {
- $translated = $translator->trans('Hello '.$name);
-
- // ...
- }
+ // ...
+ $translated = $translator->trans('Hello '.$name);
-However, creating a translation for this string is impossible since the translator
-will try to look up the exact message, including the variable portions
+However, creating a translation for this string is impossible since the
+translator will try to look up the message including the variable portions
(e.g. *"Hello Ryan"* or *"Hello Fabien"*).
-For details on how to handle this situation, see :ref:`component-translation-placeholders`
-in the components documentation. For how to do this in templates, see :ref:`translation-tags`.
-
-Pluralization
--------------
-
Another complication is when you have translations that may or may not be
plural, based on some variable:
@@ -224,128 +212,28 @@ plural, based on some variable:
There is one apple.
There are 5 apples.
-To handle this, use the :method:`Symfony\\Component\\Translation\\Translator::transChoice`
-method or the ``transchoice`` tag/filter in your :ref:`template `.
-
-For much more information, see :ref:`component-translation-pluralization`
-in the Translation component documentation.
+To manage these situations, Symfony follows the `ICU MessageFormat`_ syntax by
+using PHP's :phpclass:`MessageFormatter` class. Read more about this in
+:doc:`/translation/message_format`.
-.. deprecated:: 4.2
+.. versionadded:: 4.2
- In Symfony 4.2 the ``Translator::transChoice()`` method was deprecated in
- favor of using ``Translator::trans()`` with ``%count%`` as the parameter
- driving plurals.
+ Support for ICU MessageFormat was introduced in Symfony 4.2. Prior to this,
+ pluralization was managed by the
+ :method:`Symfony\\Component\\Translation\\Translator::transChoice` method.
Translations in Templates
-------------------------
Most of the time, translation occurs in templates. Symfony provides native
-support for both Twig and PHP templates.
-
-.. _translation-tags:
-
-Twig Templates
-~~~~~~~~~~~~~~
-
-Symfony provides specialized Twig tags (``trans`` and ``transchoice``) to
-help with message translation of *static blocks of text*:
-
-.. code-block:: twig
-
- {% trans %}Hello %name%{% endtrans %}
-
- {% transchoice count %}
- {0} There are no apples|{1} There is one apple|]1,Inf[ There are %count% apples
- {% endtranschoice %}
-
-The ``transchoice`` tag automatically gets the ``%count%`` variable from
-the current context and passes it to the translator. This mechanism only
-works when you use a placeholder following the ``%var%`` pattern.
-
-.. caution::
-
- The ``%var%`` notation of placeholders is required when translating in
- Twig templates using the tag.
-
-.. tip::
-
- If you need to use the percent character (``%``) in a string, escape it by
- doubling it: ``{% trans %}Percent: %percent%%%{% endtrans %}``
-
-You can also specify the message domain and pass some additional variables:
-
-.. code-block:: twig
-
- {% trans with {'%name%': 'Fabien'} from 'app' %}Hello %name%{% endtrans %}
-
- {% trans with {'%name%': 'Fabien'} from 'app' into 'fr' %}Hello %name%{% endtrans %}
-
- {% transchoice count with {'%name%': 'Fabien'} from 'app' %}
- {0} %name%, there are no apples|{1} %name%, there is one apple|]1,Inf[ %name%, there are %count% apples
- {% endtranschoice %}
-
-.. _translation-filters:
-
-The ``trans`` and ``transchoice`` filters can be used to translate *variable
-texts* and complex expressions:
-
-.. code-block:: twig
+support for both Twig and PHP templates:
- {{ message|trans }}
+.. code-block:: html+twig
- {{ message|transchoice(5) }}
+
{% trans %}Symfony is great!{% endtrans %}
- {{ message|trans({'%name%': 'Fabien'}, 'app') }}
-
- {{ message|transchoice(5, {'%name%': 'Fabien'}, 'app') }}
-
-.. tip::
-
- Using the translation tags or filters have the same effect, but with
- one subtle difference: automatic output escaping is only applied to
- translations using a filter. In other words, if you need to be sure
- that your translated message is *not* output escaped, you must apply
- the ``raw`` filter after the translation filter:
-
- .. code-block:: html+twig
-
- {# text translated between tags is never escaped #}
- {% trans %}
-
foo
- {% endtrans %}
-
- {% set message = '
foo
' %}
-
- {# strings and variables translated via a filter are escaped by default #}
- {{ message|trans|raw }}
- {{ '
bar
'|trans|raw }}
-
-.. tip::
-
- You can set the translation domain for an entire Twig template with a single tag:
-
- .. code-block:: twig
-
- {% trans_default_domain 'app' %}
-
- Note that this only influences the current template, not any "included"
- template (in order to avoid side effects).
-
-PHP Templates
-~~~~~~~~~~~~~
-
-The translator service is accessible in PHP templates through the
-``translator`` helper:
-
-.. code-block:: html+php
-
- = $view['translator']->trans('Symfony is great') ?>
-
- = $view['translator']->transChoice(
- '{0} There are no apples|{1} There is one apple|]1,Inf[ There are %count% apples',
- 10,
- ['%count%' => 10]
- ) ?>
+Read :doc:`/translation/templates` for more information about the Twig tags and
+filters for translation.
Extracting Translation Contents and Updating Catalogs Automatically
-------------------------------------------------------------------
@@ -381,11 +269,9 @@ Translation Resource/File Names and Locations
Symfony looks for message files (i.e. translations) in the following default locations:
-* the ``translations/`` directory (at the root of the project);
-
-* the ``src/Resources//translations/`` directory;
-
-* the ``Resources/translations/`` directory inside of any bundle.
+#. the ``translations/`` directory (at the root of the project);
+#. the ``src/Resources//translations/`` directory;
+#. the ``Resources/translations/`` directory inside of any bundle.
.. deprecated:: 4.2
@@ -486,6 +372,12 @@ For more options, see :ref:`component-translator-message-catalogs`.
$ php bin/console cache:clear
+Handling the User's Locale
+--------------------------
+
+Translating happens based on the user's locale. Read :doc:`/translation/locale`
+to learn more about how to handle it.
+
.. _translation-fallback:
Fallback Translation Locales
@@ -510,12 +402,6 @@ checks translation resources for several locales:
add the missing translation to the log file. For details,
see :ref:`reference-framework-translator-logging`.
-Handling the User's Locale
---------------------------
-
-Translating happens based on the user's locale. Read :doc:`/translation/locale`
-to learn more about how to handle it.
-
Translating Database Content
----------------------------
@@ -537,10 +423,8 @@ Summary
With the Symfony Translation component, creating an internationalized application
no longer needs to be a painful process and boils down to these steps:
-* Abstract messages in your application by wrapping each in either the
- :method:`Symfony\\Component\\Translation\\Translator::trans` or
- :method:`Symfony\\Component\\Translation\\Translator::transChoice` methods
- (learn about this in :doc:`/components/translation/usage`);
+* Abstract messages in your application by wrapping each in the
+ :method:`Symfony\\Component\\Translation\\Translator::trans` method;
* Translate each message into multiple locales by creating translation message
files. Symfony discovers and processes each file because its name follows
@@ -555,11 +439,14 @@ Learn more
.. toctree::
:maxdepth: 1
+ translation/message_format
+ translation/templates
translation/locale
translation/debug
translation/lint
.. _`i18n`: https://en.wikipedia.org/wiki/Internationalization_and_localization
+.. _`ICU MessageFormat`: http://userguide.icu-project.org/formatparse/messages
.. _`ISO 3166-1 alpha-2`: https://en.wikipedia.org/wiki/ISO_3166-1#Current_codes
.. _`ISO 639-1`: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
.. _`Translatable Extension`: http://atlantic18.github.io/DoctrineExtensions/doc/translatable.html
diff --git a/translation/debug.rst b/translation/debug.rst
index cec010c9901..126c048def9 100644
--- a/translation/debug.rst
+++ b/translation/debug.rst
@@ -12,15 +12,11 @@ command helps you to find these missing or unused translation messages templates
.. code-block:: twig
- {# messages can be found when using the trans/transchoice filters and tags #}
+ {# messages can be found when using the trans filter and tag #}
{% trans %}Symfony is great{% endtrans %}
{{ 'Symfony is great'|trans }}
- {{ 'Symfony is great'|transchoice(1) }}
-
- {% transchoice 1 %}Symfony is great{% endtranschoice %}
-
.. caution::
The extractors can't find messages translated outside templates, like form
diff --git a/translation/message_format.rst b/translation/message_format.rst
new file mode 100644
index 00000000000..9879be895a9
--- /dev/null
+++ b/translation/message_format.rst
@@ -0,0 +1,443 @@
+.. index::
+ single: Translation; Message Format
+
+How to Translate Messages using the ICU MessageFormat
+=====================================================
+
+.. versionadded:: 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 :ref:`message domain
+` has to be suffixed with ``+intl-icu``:
+
+====================== ===============================
+Normal file name ICU Message Format filename
+====================== ===============================
+``messages.en.yml`` ``messages+intl-icu.en.yml``
+``messages.fr_FR.xlf`` ``messages+intl-icu.fr_FR.xlf``
+``admin.en.yml`` ``admin+intl-icu.en.yml``
+====================== ===============================
+
+All messages in this file will now be processed by the
+:phpclass:`MessageFormatter` during translation.
+
+.. _component-translation-placeholders:
+
+Message Placeholders
+--------------------
+
+The basic usage of the MessageFormat allows you to use placeholders (called
+*arguments* in ICU MessageFormat) in your messages:
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # translations/messages+intl-icu.en.yaml
+ say_hello: 'Hello {name}!'
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+ say_hello
+ Hello {name}!
+
+
+
+
+
+ .. code-block:: php
+
+ // 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::
+
+ // 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:
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # 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!}
+ }
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+ 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!}}
+
+
+
+
+
+ .. code-block:: php
+
+ // 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::
+
+ // prints "Ryan has invited you for his party!"
+ echo $translator->trans('invition_title', [
+ 'organizer_name' => 'Ryan',
+ 'organizer_gender' => 'male',
+ ]);
+
+ // prints "John & Jane have invited you for their party!"
+ echo $translator->trans('invition_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:
+
+#. The first ``{organizer_gender, select, ...}`` block starts the "code" mode,
+ which means ``organizer_gender`` is processed as a variable.
+#. The inner ``{... has invited you for her party!}`` block brings you back in
+ "literal" mode, meaning the text is not processed.
+#. 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:
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # 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!}
+ }
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+ num_of_apples
+ {apples, plural, =0 {There are no apples} one {There is one apple...} other {There are # apples!}}
+
+
+
+
+
+ .. code-block:: php
+
+ // 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::
+
+ // 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:
+
+ .. code-block:: text
+
+ {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:
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # 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}!
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+ finish_place
+ You finished {place, selectordinal, one {#st} two {#nd} few {#rd} other {#th}}!
+
+
+
+
+ finish_place
+ You finished {place, ordinal}!
+
+
+
+
+
+ .. code-block:: php
+
+ // 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}!',
+ ];
+
+.. code-block:: php
+
+ // 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 :phpclass:`IntlDateFormatter`:
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # translations/messages+intl-icu.en.yaml
+ published_at: 'Published at {publication_date, date} - {publication_date, time, short}'
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+ published_at
+ Published at {publication_date, date} - {publication_date, time, short}
+
+
+
+
+
+ .. code-block:: php
+
+ // 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, as documented on PHP.net.
+
+.. code-block:: php
+
+ // 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 :phpclass:`NumberFormatter`:
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # 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}'
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+ progress
+ {progress, number, percent} of the work is done
+
+
+
+ value_of_object
+ This artifact is worth {value, number, currency}
+
+
+
+
+
+ .. code-block:: php
+
+ // 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}',
+ ];
+
+.. code-block:: php
+
+ // 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]);
+
+.. _`online editor`: http://format-message.github.io/icu-message-format-for-translators/
+.. _`ICU MessageFormat`: http://userguide.icu-project.org/formatparse/messages
+.. _`switch statement`: https://php.net/control-structures.switch
+.. _`Language Plural Rules`: http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html
diff --git a/translation/templates.rst b/translation/templates.rst
new file mode 100644
index 00000000000..903f1934d92
--- /dev/null
+++ b/translation/templates.rst
@@ -0,0 +1,123 @@
+Using Translation in Templates
+==============================
+
+Twig Templates
+--------------
+
+.. _translation-tags:
+
+Using Twig Tags
+~~~~~~~~~~~~~~~
+
+Symfony provides specialized Twig tags (``trans`` and ``transchoice``) to
+help with message translation of *static blocks of text*:
+
+.. code-block:: twig
+
+ {% trans %}Hello %name%{% endtrans %}
+
+ {% transchoice count %}
+ {0} There are no apples|{1} There is one apple|]1,Inf[ There are %count% apples
+ {% endtranschoice %}
+
+The ``transchoice`` tag automatically gets the ``%count%`` variable from
+the current context and passes it to the translator. This mechanism only
+works when you use a placeholder following the ``%var%`` pattern.
+
+.. deprecated:: 4.2
+
+ The ``transchoice`` tag is deprecated since Symfony 4.2 and will be
+ removed in 5.0. Use the :doc:`ICU MessageFormat ` with
+ the ``trans`` tag instead.
+
+.. caution::
+
+ The ``%var%`` notation of placeholders is required when translating in
+ Twig templates using the tag.
+
+.. tip::
+
+ If you need to use the percent character (``%``) in a string, escape it by
+ doubling it: ``{% trans %}Percent: %percent%%%{% endtrans %}``
+
+You can also specify the message domain and pass some additional variables:
+
+.. code-block:: twig
+
+ {% trans with {'%name%': 'Fabien'} from 'app' %}Hello %name%{% endtrans %}
+
+ {% trans with {'%name%': 'Fabien'} from 'app' into 'fr' %}Hello %name%{% endtrans %}
+
+ {% transchoice count with {'%name%': 'Fabien'} from 'app' %}
+ {0} %name%, there are no apples|{1} %name%, there is one apple|]1,Inf[ %name%, there are %count% apples
+ {% endtranschoice %}
+
+.. _translation-filters:
+
+Using Twig Filters
+~~~~~~~~~~~~~~~~~~
+
+The ``trans`` and ``transchoice`` filters can be used to translate *variable
+texts* and complex expressions:
+
+.. code-block:: twig
+
+ {{ message|trans }}
+
+ {{ message|transchoice(5) }}
+
+ {{ message|trans({'%name%': 'Fabien'}, 'app') }}
+
+ {{ message|transchoice(5, {'%name%': 'Fabien'}, 'app') }}
+
+.. deprecated:: 4.2
+
+ The ``transchoice`` filter is deprecated since Symfony 4.2 and will be
+ removed in 5.0. Use the :doc:`ICU MessageFormat ` with
+ the ``trans`` filter instead.
+
+.. tip::
+
+ Using the translation tags or filters have the same effect, but with
+ one subtle difference: automatic output escaping is only applied to
+ translations using a filter. In other words, if you need to be sure
+ that your translated message is *not* output escaped, you must apply
+ the ``raw`` filter after the translation filter:
+
+ .. code-block:: html+twig
+
+ {# text translated between tags is never escaped #}
+ {% trans %}
+
foo
+ {% endtrans %}
+
+ {% set message = '
foo
' %}
+
+ {# strings and variables translated via a filter are escaped by default #}
+ {{ message|trans|raw }}
+ {{ '
bar
'|trans|raw }}
+
+.. tip::
+
+ You can set the translation domain for an entire Twig template with a single tag:
+
+ .. code-block:: twig
+
+ {% trans_default_domain 'app' %}
+
+ Note that this only influences the current template, not any "included"
+ template (in order to avoid side effects).
+
+PHP Templates
+-------------
+
+The translator service is accessible in PHP templates through the
+``translator`` helper::
+
+ = $view['translator']->trans('Symfony is great') ?>
+
+ = $view['translator']->transChoice(
+ '{0} There are no apples|{1} There is one apple|]1,Inf[ There are %count% apples',
+ 10,
+ ['%count%' => 10]
+ ) ?>