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 - - trans('Symfony is great') ?> - - 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:: + + trans('Symfony is great') ?> + + transChoice( + '{0} There are no apples|{1} There is one apple|]1,Inf[ There are %count% apples', + 10, + ['%count%' => 10] + ) ?>