diff --git a/book/translation.rst b/book/translation.rst index 98e8a87e8f4..4dcbe5e2495 100644 --- a/book/translation.rst +++ b/book/translation.rst @@ -4,12 +4,12 @@ Translations ============ -The term "internationalization" (often abbreviated `i18n`_) refers to the process -of abstracting strings and other locale-specific pieces out of your application -and into a layer where they can be translated and converted based on the user's -locale (i.e. language and country). For text, this means wrapping each with a -function capable of translating the text (or "message") into the language of -the user:: +The term "internationalization" (often abbreviated `i18n`_) refers to the +process of abstracting strings and other locale-specific pieces out of your +application and into a layer where they can be translated and converted based +on the user's locale (i.e. language and country). For text, this means +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 echo 'Hello World'; @@ -21,34 +21,36 @@ the user:: .. note:: The term *locale* refers roughly to the user's language and country. It - can be any string that your application uses to manage translations - and other format differences (e.g. currency format). The - `ISO639-1`_ *language* code, an underscore (``_``), then the `ISO3166 Alpha-2`_ *country* - code (e.g. ``fr_FR`` for French/France) is recommended. + can be any string that your application uses to manage translations and + other format differences (e.g. currency format). The `ISO639-1`_ + *language* code, an underscore (``_``), then the `ISO3166 Alpha-2`_ + *country* code (e.g. ``fr_FR`` for French/France) is recommended. -In this chapter, you'll learn how to prepare an application to support multiple -locales and then how to create translations for multiple locales. Overall, -the process has several common steps: +In this chapter, you'll learn how to use the Translation component in the +Symfony2 framework. You can read the +:doc:`Translation component documentation ` +to learn even more. Overall, the process has several steps: -#. Enable and configure Symfony's ``Translation`` component; +#. :ref:`Enable and configure ` Symfony's + translation service; -#. Abstract strings (i.e. "messages") by wrapping them in calls to the ``Translator``; +#. Abstract strings (i.e. "messages") by wrapping them in calls to the + ``Translator`` (":ref:`book-translation-basic`"); -#. Create translation resources for each supported locale that translate - each message in the application; +#. :ref:`Create translation resources/files ` + for each supported locale that translate each message in the application; -#. Determine, set and manage the user's locale for the request and optionally - on the user's entire session. +#. Determine, :ref:`set and manage the user's locale ` + for the request and optionally on the user's entire session. -.. index:: - single: Translations; Configuration +.. _book-translation-configuration: Configuration ------------- -Translations are handled by a ``Translator`` :term:`service` that uses the +Translations are handled by a ``translator`` :term:`service` that uses the user's locale to lookup and return translated messages. Before using it, -enable the ``Translator`` in your configuration: +enable the ``translator`` in your configuration: .. configuration-block:: @@ -80,21 +82,13 @@ enable the ``Translator`` in your configuration: 'translator' => array('fallback' => 'en'), )); -The ``fallback`` option defines the fallback locale when a translation does -not exist in the user's locale. - -.. tip:: - - When a translation does not exist for a locale, the translator first tries - to find the translation for the language (``fr`` if the locale is - ``fr_FR`` for instance). If this also fails, it looks for a translation - using the fallback locale. +See :ref:`book-translation-fallback` for details on the ``fallback`` key +and what Symfony does when it doesn't find a translation. The locale used in translations is the one stored on the request. This is typically set via a ``_locale`` attribute on your routes (see :ref:`book-translation-locale-url`). -.. index:: - single: Translations; Basic translation +.. _book-translation-basic: Basic Translation ----------------- @@ -115,12 +109,14 @@ for example, that you're translating a simple message from inside a controller:: return new Response($translated); } +.. _book-translation-resources: + When this code is executed, Symfony2 will attempt to translate the message "Symfony2 is great" based on the ``locale`` of the user. For this to work, you need to tell Symfony2 how to translate the message via a "translation -resource", which is a collection of message translations for a given locale. -This "dictionary" of translations can be created in several different formats, -XLIFF being the recommended format: +resource", which is usually a file that contains a collection of translations +for a given locale. This "dictionary" of translations can be created in several +different formats, XLIFF being the recommended format: .. configuration-block:: @@ -151,22 +147,24 @@ XLIFF being the recommended format: # messages.fr.yml Symfony2 is great: J'aime Symfony2 +For information on where these files should be located, see :ref:`book-translation-resource-locations`. + 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 Symfony2``. +the message will be translated into ``J'aime Symfony2``. You can also translate +message inside your :ref:`templates `. The Translation Process ~~~~~~~~~~~~~~~~~~~~~~~ To actually translate the message, Symfony2 uses a simple process: -* The ``locale`` of the current user, which is stored on the request (or - stored as ``_locale`` on the session), is determined; +* The ``locale`` of the current user, which is stored on the request is determined; -* A catalog of translated messages is loaded from translation resources defined - for the ``locale`` (e.g. ``fr_FR``). Messages from the 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. See `Message Catalogues`_ - for more details; +* A catalog (e.g. big collection) of translated messages is loaded from translation + 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. * If the message is located in the catalog, the translation is returned. If not, the translator returns the original message. @@ -174,15 +172,11 @@ To actually translate the message, Symfony2 uses a simple process: When using the ``trans()`` method, Symfony2 looks for the exact string inside the appropriate message catalog and returns it (if it exists). -.. index:: - single: Translations; Message placeholders - Message Placeholders -~~~~~~~~~~~~~~~~~~~~ +-------------------- Sometimes, a message containing a variable needs to be translated:: - // ... use Symfony\Component\HttpFoundation\Response; public function indexAction($name) @@ -194,318 +188,220 @@ Sometimes, a message containing a variable needs to be 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":: +(e.g. *"Hello Ryan"* or *"Hello Fabien"*). - // ... - use Symfony\Component\HttpFoundation\Response; +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:`book-translation-tags`. - public function indexAction($name) - { - $translated = $this->get('translator')->trans( - 'Hello %name%', - array('%name%' => $name) - ); - - return new Response($translated); - } - -Symfony2 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% - - - - +Pluralization +------------- - .. code-block:: php +Another complication is when you have translations that may or may not be +plural, based on some variable: - // messages.fr.php - return array( - 'Hello %name%' => 'Bonjour %name%', - ); +.. code-block:: text - .. code-block:: yaml + There is one apple. + There are 5 apples. - # messages.fr.yml - 'Hello %name%': Bonjour %name% +To handle this, use the :method:`Symfony\\Component\\Translation\\Translator::transChoice` +method or the ``transchoice`` tag/filter in your :ref:`template `. -.. note:: +For much more information, see :ref:`component-translation-pluralization` +in the Translation component documentation. - The placeholders can take on any form as the full message is reconstructed - using the PHP `strtr function`_. However, the ``%var%`` notation is - required when translating in Twig templates, and is overall a sensible - convention to follow. +Translations in Templates +------------------------- -As you've seen, creating a translation is a two-step process: +Most of the time, translation occurs in templates. Symfony2 provides native +support for both Twig and PHP templates. -#. Abstract the message that needs to be translated by processing it through - the ``Translator``. +.. _book-translation-tags: -#. Create a translation for the message in each locale that you choose to - support. +Twig Templates +~~~~~~~~~~~~~~ -The second step is done by creating message catalogues that define the translations -for any number of different locales. +Symfony2 provides specialized Twig tags (``trans`` and ``transchoice``) to +help with message translation of *static blocks of text*: -.. index:: - single: Translations; Message catalogues +.. code-block:: jinja -Message Catalogues ------------------- + {% trans %}Hello %name%{% endtrans %} -When a message is translated, Symfony2 compiles a message catalogue for the -user's locale and looks in it for a translation of the message. A message -catalogue is like a dictionary of translations for a specific locale. For -example, the catalogue for the ``fr_FR`` locale might contain the following -translation: + {% transchoice count %} + {0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples + {% endtranschoice %} -.. code-block:: text +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. - Symfony2 is Great => J'aime Symfony2 +.. caution:: -It's the responsibility of the developer (or translator) of an internationalized -application to create these translations. Translations are stored on the -filesystem and discovered by Symfony, thanks to some conventions. + The ``%var%`` notation of placeholders is required when translating in + Twig templates using the tag. .. tip:: - Each time you create a *new* translation resource (or install a bundle - that includes a translation resource), be sure to clear your cache so - that Symfony can discover the new translation resource: - - .. code-block:: bash - - $ php app/console cache:clear - -.. index:: - single: Translations; Translation resource locations + If you need to use the percent character (``%``) in a string, escape it by + doubling it: ``{% trans %}Percent: %percent%%%{% endtrans %}`` -Translation Locations and Naming Conventions -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +You can also specify the message domain and pass some additional variables: -Symfony2 looks for message files (i.e. translations) in the following locations: +.. code-block:: jinja -* the ``/Resources/translations`` directory; + {% trans with {'%name%': 'Fabien'} from "app" %}Hello %name%{% endtrans %} -* the ``/Resources//translations`` directory; + {% trans with {'%name%': 'Fabien'} from "app" into "fr" %}Hello %name%{% endtrans %} -* the ``Resources/translations/`` directory of the bundle. + {% 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 %} -The locations are listed with the highest priority first. That is you can -override the translation messages of a bundle in any of the top 2 directories. +.. _book-translation-filters: -The override mechanism works at a key level: only the overridden keys need -to be listed in a higher priority message file. When a key is not found -in a message file, the translator will automatically fall back to the lower -priority message files. +The ``trans`` and ``transchoice`` filters can be used to translate *variable +texts* and complex expressions: -The filename of the translations is also important as Symfony2 uses a convention -to determine details about the translations. Each message file must be named -according to the following path: ``domain.locale.loader``: +.. code-block:: jinja -* **domain**: An optional way to organize messages into groups (e.g. ``admin``, - ``navigation`` or the default ``messages``) - see `Using Message Domains`_; + {{ message|trans }} -* **locale**: The locale that the translations are for (e.g. ``en_GB``, ``en``, etc); + {{ message|transchoice(5) }} -* **loader**: How Symfony2 should load and parse the file (e.g. ``xliff``, - ``php`` or ``yml``). + {{ message|trans({'%name%': 'Fabien'}, "app") }} -The loader can be the name of any registered loader. By default, Symfony -provides the following loaders: + {{ message|transchoice(5, {'%name%': 'Fabien'}, 'app') }} -* ``xliff``: XLIFF file; -* ``php``: PHP file; -* ``yml``: YAML file. +.. tip:: -The choice of which loader to use is entirely up to you and is a matter of -taste. + 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 is *not* output escaped, you must apply the + ``raw`` filter after the translation filter: -.. note:: + .. code-block:: jinja - You can also store translations in a database, or any other storage by - providing a custom class implementing the - :class:`Symfony\\Component\\Translation\\Loader\\LoaderInterface` interface. - See the :ref:`dic-tags-translation-loader` tag for more information. + {# text translated between tags is never escaped #} + {% trans %} +

foo

+ {% endtrans %} -.. index:: - single: Translations; Creating translation resources + {% set message = '

foo

' %} -Creating Translations -~~~~~~~~~~~~~~~~~~~~~ + {# strings and variables translated via a filter is escaped by default #} + {{ message|trans|raw }} + {{ '

bar

'|trans|raw }} -The act of creating translation files is an important part of "localization" -(often abbreviated `L10n`_). Translation files consist of a series of -id-translation pairs for the given domain and locale. The source is the identifier -for the individual translation, and can be the message in the main locale (e.g. -"Symfony is great") of your application or a unique identifier (e.g. -"symfony2.great" - see the sidebar below): +.. tip:: -.. configuration-block:: + You can set the translation domain for an entire Twig template with a single tag: - .. code-block:: xml + .. code-block:: jinja - - - - - - - Symfony2 is great - J'aime Symfony2 - - - symfony2.great - J'aime Symfony2 - - - - + {% trans_default_domain "app" %} - .. code-block:: php + Note that this only influences the current template, not any "included" + templates (in order to avoid side effects). - // src/Acme/DemoBundle/Resources/translations/messages.fr.php - return array( - 'Symfony2 is great' => 'J\'aime Symfony2', - 'symfony2.great' => 'J\'aime Symfony2', - ); +.. versionadded:: 2.1 + The ``trans_default_domain`` tag is new in Symfony2.1 - .. code-block:: yaml +PHP Templates +~~~~~~~~~~~~~ - # src/Acme/DemoBundle/Resources/translations/messages.fr.yml - Symfony2 is great: J'aime Symfony2 - symfony2.great: J'aime Symfony2 +The translator service is accessible in PHP templates through the +``translator`` helper: -Symfony2 will discover these files and use them when translating either -"Symfony2 is great" or "symfony2.great" into a French language locale (e.g. -``fr_FR`` or ``fr_BE``). +.. code-block:: html+php -.. sidebar:: Using Real or Keyword Messages + trans('Symfony2 is great') ?> - This example illustrates the two different philosophies when creating - messages to be translated:: + transChoice( + '{0} There is no apples|{1} There is one apple|]1,Inf[ There are %count% apples', + 10, + array('%count%' => 10) + ) ?> - $translated = $translator->trans('Symfony2 is great'); +.. _book-translation-resource-locations: - $translated = $translator->trans('symfony2.great'); +Translation Resource/File Names and Locations +--------------------------------------------- - In the first method, messages are written in the language of the default - locale (English in this case). That message is then used as the "id" - when creating translations. +Symfony2 looks for message files (i.e. translations) in the following locations: - In the second method, messages are actually "keywords" that convey the - idea of the message. The keyword message is then used as the "id" for - any translations. In this case, translations must be made for the default - locale (i.e. to translate ``symfony2.great`` to ``Symfony2 is great``). +* the ``app/Resources/translations`` directory; - The second method is handy because the message key won't need to be changed - in every translation file if you decide that the message should actually - read "Symfony2 is really great" in the default locale. +* the ``app/Resources//translations`` directory; - The choice of which method to use is entirely up to you, but the "keyword" - format is often recommended. +* the ``Resources/translations/`` directory inside of any bundle. - Additionally, the ``php`` and ``yaml`` file formats support nested ids to - avoid repeating yourself if you use keywords instead of real text for your - ids: +The locations are listed here with the highest priority first. That is, you can +override the translation messages of a bundle in any of the top 2 directories. - .. configuration-block:: +The override mechanism works at a key level: only the overridden keys need +to be listed in a higher priority message file. When a key is not found +in a message file, the translator will automatically fall back to the lower +priority message files. - .. code-block:: yaml +The filename of the translation files is also important: each message file +must be named according to the following path: ``domain.locale.loader``: - symfony2: - is: - great: Symfony2 is great - amazing: Symfony2 is amazing - has: - bundles: Symfony2 has bundles - user: - login: Login +* **domain**: An optional way to organize messages into groups (e.g. ``admin``, + ``navigation`` or the default ``messages``) - see ":ref:`using-message-domains`"; - .. code-block:: php +* **locale**: The locale that the translations are for (e.g. ``en_GB``, ``en``, etc); - return array( - 'symfony2' => array( - 'is' => array( - 'great' => 'Symfony2 is great', - 'amazing' => 'Symfony2 is amazing', - ), - 'has' => array( - 'bundles' => 'Symfony2 has bundles', - ), - ), - 'user' => array( - 'login' => 'Login', - ), - ); +* **loader**: How Symfony2 should load and parse the file (e.g. ``xliff``, + ``php``, ``yml``, etc). - The multiple levels are flattened into single id/translation pairs by - adding a dot (.) between every level, therefore the above examples are - equivalent to the following: +The loader can be the name of any registered loader. By default, Symfony +provides many loaders, including: - .. configuration-block:: +* ``xliff``: XLIFF file; +* ``php``: PHP file; +* ``yml``: YAML file. - .. code-block:: yaml +The choice of which loader to use is entirely up to you and is a matter of +taste. For more options, see :ref:`component-translator-message-catalogs`. - symfony2.is.great: Symfony2 is great - symfony2.is.amazing: Symfony2 is amazing - symfony2.has.bundles: Symfony2 has bundles - user.login: Login +.. note:: - .. code-block:: php + You can also store translations in a database, or any other storage by + providing a custom class implementing the + :class:`Symfony\\Component\\Translation\\Loader\\LoaderInterface` interface. + See the :ref:`dic-tags-translation-loader` tag for more information. - return array( - 'symfony2.is.great' => 'Symfony2 is great', - 'symfony2.is.amazing' => 'Symfony2 is amazing', - 'symfony2.has.bundles' => 'Symfony2 has bundles', - 'user.login' => 'Login', - ); +.. caution:: -.. _translation-domains: + Each time you create a *new* translation resource (or install a bundle + that includes a translation resource), be sure to clear your cache so + that Symfony can discover the new translation resource: + .. code-block:: bash -.. _using-message-domains: + $ php app/console cache:clear -Using Message Domains ---------------------- +.. _book-translation-fallback: -As you've seen, message files are organized into the different locales that -they translate. The message files can also be organized further into "domains". -When creating message files, the domain is the first portion of the filename. -The default domain is ``messages``. For example, suppose that, for organization, -translations were split into three different domains: ``messages``, ``admin`` -and ``navigation``. The French translation would have the following message -files: +Fallback Translation Locales +---------------------------- -* ``messages.fr.xliff`` -* ``admin.fr.xliff`` -* ``navigation.fr.xliff`` +Imagine that the user's locale is ``fr_FR`` and that you're translating the +key ``Symfony2 is great``. To find the French translation, Symfony actually +checks translation resources for several different locales: -When translating strings that are not in the default domain (``messages``), -you must specify the domain as the third argument of ``trans()``:: +1. First, Symfony looks for the translation in a ``fr_FR`` translation resource + (e.g. ``messages.fr_FR.xfliff``); - $this->get('translator')->trans('Symfony2 is great', array(), 'admin'); +2. If it wasn't found, Symfony looks for the translation in a ``fr`` translation + resource (e.g. ``messages.fr.xliff``); -Symfony2 will now look for the message in the ``admin`` domain of the user's -locale. +3. If the translation still isn't found, Symfony uses the ``fallback`` configuration + parameter, which defaults to ``en`` (see `Configuration`_). -.. index:: - single: Translations; User's locale +.. _book-translation-user-locale: Handling the User's Locale -------------------------- @@ -531,51 +427,6 @@ via the ``request`` object:: See the :ref:`book-translation-locale-url` section below about setting the locale via routing. -Fallback and Default Locale -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -If the locale hasn't been set explicitly in the session, the ``fallback`` -configuration parameter will be used by the ``Translator``. The parameter -defaults to ``en`` (see `Configuration`_). - -Alternatively, you can guarantee that a locale is set on each user's request -by defining a ``default_locale`` for the framework: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - framework: - default_locale: en - - .. code-block:: xml - - - - - - - en - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('framework', array( - 'default_locale' => 'en', - )); - -.. versionadded:: 2.1 - The ``default_locale`` parameter was defined under the session key - originally, however, as of 2.1 this has been moved. This is because the - locale is now set on the request instead of the session. - .. _book-translation-locale-url: The Locale and the URL @@ -632,7 +483,7 @@ by the routing system using the special ``_locale`` parameter: return $collection; -When using the special `_locale` parameter in a route, the matched locale +When using the special ``_locale`` parameter in a route, the matched locale will *automatically be set on the user's session*. In other words, if a user visits the URI ``/fr/contact``, the locale ``fr`` will automatically be set as the locale for the user's session. @@ -640,271 +491,51 @@ as the locale for the user's session. You can now use the user's locale to create routes to other translated pages in your application. -.. index:: - single: Translations; Pluralization - -Pluralization -------------- - -Message pluralization is a tough topic as the rules can be quite complex. For -instance, here is the mathematic 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:: - - $translated = $this->get('translator')->transChoice( - 'There is one apple|There are %count% apples', - 10, - array('%count%' => 10) - ); - -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:: - - '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:: - - '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 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Setting a Default Locale +~~~~~~~~~~~~~~~~~~~~~~~~ -The easiest way to pluralize a message is to let Symfony2 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:: +What if the user's locale hasn't been determined? You can guarantee that a +locale is set on each user's request by defining a ``default_locale`` for +the framework: - '{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:: - - '{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:: - - {1,2,3,4} - -Or numbers between two other numbers:: - - [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. - -.. index:: - single: Translations; In templates - -Translations in Templates -------------------------- - -Most of the time, translation occurs in templates. Symfony2 provides native -support for both Twig and PHP templates. - -.. _book-translation-tags: - -Twig Templates -~~~~~~~~~~~~~~ - -Symfony2 provides specialized Twig tags (``trans`` and ``transchoice``) to -help with message translation of *static blocks of text*: - -.. code-block:: jinja - - {% 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. - -.. 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:: jinja - - {% 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 %} - -.. _book-translation-filters: - -The ``trans`` and ``transchoice`` filters can be used to translate *variable -texts* and complex expressions: - -.. code-block:: jinja - - {{ message|trans }} - - {{ message|transchoice(5) }} - - {{ 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 is *not* output escaped, you must apply the - ``raw`` filter after the translation filter: - - .. code-block:: jinja - - {# text translated between tags is never escaped #} - {% trans %} -

foo

- {% endtrans %} - - {% set message = '

foo

' %} +.. configuration-block:: - {# strings and variables translated via a filter is escaped by default #} - {{ message|trans|raw }} - {{ '

bar

'|trans|raw }} + .. code-block:: yaml -.. tip:: + # app/config/config.yml + framework: + default_locale: en - You can set the translation domain for an entire Twig template with a single tag: + .. code-block:: xml - .. code-block:: jinja + + + en + - {% trans_default_domain "app" %} + .. code-block:: php - Note that this only influences the current template, not any "included" - templates (in order to avoid side effects). + // app/config/config.php + $container->loadFromExtension('framework', array( + 'default_locale' => 'en', + )); .. versionadded:: 2.1 - The ``trans_default_domain`` tag is new in Symfony2.1 - -PHP Templates -~~~~~~~~~~~~~ - -The translator service is accessible in PHP templates through the -``translator`` helper: - -.. code-block:: html+php - - trans('Symfony2 is great') ?> - - transChoice( - '{0} There is no apples|{1} There is one apple|]1,Inf[ There are %count% apples', - 10, - array('%count%' => 10) - ) ?> - -Forcing the Translator Locale ------------------------------ - -When translating a message, Symfony2 uses the locale from the current request -or the ``fallback`` locale if necessary. You can also manually specify the -locale to use for translation:: - - $this->get('translator')->trans( - 'Symfony2 is great', - array(), - 'messages', - 'fr_FR' - ); - - $this->get('translator')->transChoice( - '{0} There are no apples|{1} There is one apple|]1,Inf[ There are %count% apples', - 10, - array('%count%' => 10), - 'messages', - 'fr_FR' - ); - -Translating Database Content ----------------------------- - -The translation of database content should be handled by Doctrine through -the `Translatable Extension`_. For more information, see the documentation -for that library. + The ``default_locale`` parameter was defined under the session key + originally, however, as of 2.1 this has been moved. This is because the + locale is now set on the request instead of the session. .. _book-translation-constraint-messages: Translating Constraint Messages ------------------------------- -The best way to understand constraint translation is to see it in action. To start, -suppose you've created a plain-old-PHP object that you need to use somewhere in -your application:: +If you're using validation constraints with the form framework, then translating +the error messages is easy: simply create a translation resource for the +``validators`` :ref:`domain `. + +To start, suppose you've created a plain-old-PHP object that you need to +use somewhere in your application:: // src/Acme/BlogBundle/Entity/Author.php namespace Acme\BlogBundle\Entity; @@ -978,7 +609,9 @@ empty, add the following: } } -Create a translation file under the ``validators`` catalog for the constraint messages, typically in the ``Resources/translations/`` directory of the bundle. See `Message Catalogues`_ for more details. +Create a translation file under the ``validators`` catalog for the constraint +messages, typically in the ``Resources/translations/`` directory of the +bundle. .. configuration-block:: @@ -1009,6 +642,13 @@ Create a translation file under the ``validators`` catalog for the constraint me # validators.en.yml author.name.not_blank: Please enter an author name. +Translating Database Content +---------------------------- + +The translation of database content should be handled by Doctrine through +the `Translatable Extension`_. For more information, see the documentation +for that library. + Summary ------- @@ -1018,7 +658,8 @@ 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; + :method:`Symfony\\Component\\Translation\\Translator::transChoice` methods + (learn about this in ":doc:`/components/translation/usage`"); * Translate each message into multiple locales by creating translation message files. Symfony2 discovers and processes each file because its name follows @@ -1028,9 +669,6 @@ steps: be set on the user's session. .. _`i18n`: http://en.wikipedia.org/wiki/Internationalization_and_localization -.. _`L10n`: http://en.wikipedia.org/wiki/Internationalization_and_localization -.. _`strtr function`: http://www.php.net/manual/en/function.strtr.php -.. _`ISO 31-11`: http://en.wikipedia.org/wiki/Interval_(mathematics)#Notations_for_intervals -.. _`Translatable Extension`: https://github.com/l3pp4rd/DoctrineExtensions .. _`ISO3166 Alpha-2`: http://en.wikipedia.org/wiki/ISO_3166-1#Current_codes .. _`ISO639-1`: http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes +.. _`Translatable Extension`: https://github.com/l3pp4rd/DoctrineExtensions diff --git a/components/index.rst b/components/index.rst index 01d348e8d69..d8101f9b89a 100644 --- a/components/index.rst +++ b/components/index.rst @@ -26,6 +26,7 @@ The Components serializer stopwatch templating/index + translation/index yaml/index .. include:: /components/map.rst.inc diff --git a/components/map.rst.inc b/components/map.rst.inc index c363c469f05..5c7fbf251ed 100644 --- a/components/map.rst.inc +++ b/components/map.rst.inc @@ -115,6 +115,11 @@ * :doc:`/components/templating/introduction` +* :doc:`/components/translation/index` + + * :doc:`/components/translation/introduction` + * :doc:`/components/translation/usage` + * :doc:`/components/yaml/index` * :doc:`/components/yaml/introduction` diff --git a/components/translation/index.rst b/components/translation/index.rst new file mode 100644 index 00000000000..3f87cbc1425 --- /dev/null +++ b/components/translation/index.rst @@ -0,0 +1,8 @@ +Translation +=========== + +.. toctree:: + :maxdepth: 2 + + introduction + usage diff --git a/components/translation/introduction.rst b/components/translation/introduction.rst new file mode 100644 index 00000000000..31c3074680e --- /dev/null +++ b/components/translation/introduction.rst @@ -0,0 +1,210 @@ +.. index:: + single: Translation + single: Components; Translation + +The Translation Component +========================= + + The Translation component provides tools to internationalize your + application. + +Installation +------------ + +You can install the component in 2 different ways: + +* :doc:`Install it via Composer` (``symfony/translation`` on `Packagist`_). +* Use the official Git repository (https://github.com/symfony/Translation); + +Constructing the Translator +--------------------------- + +The main access point of the Translation Component is +:class:`Symfony\\Component\\Translation\\Translator`. Before you can use it, +you need to configure it and load the messages to translate (called *message +catalogs*). + +Configuration +~~~~~~~~~~~~~ + +The constructor of the ``Translator`` class needs one argument: The locale. + +.. code-block:: php + + use Symfony\Component\Translation\Translator; + use Symfony\Component\Translation\MessageSelector; + + $translator = new Translator('fr_FR', new MessageSelector()); + +.. note:: + + The locale set here is the default locale to use. You can override this + locale when translating strings. + +.. note:: + + The term *locale* refers roughly to the user's language and country. It + can be any string that your application uses to manage translations and + other format differences (e.g. currency format). The `ISO639-1`_ + *language* code, an underscore (``_``), then the `ISO3166 Alpha-2`_ + *country* code (e.g. ``fr_FR`` for French/France) is recommended. + +.. _component-translator-message-catalogs: + +Loading Message Catalogs +~~~~~~~~~~~~~~~~~~~~~~~~ + +The messages are stored in message catalogs inside the ``Translator`` +class. A message catalog is like a dictionary of translations for a specific +locale. + +The Translation component uses Loader classes to load catalogs. You can load +multiple resources for the same locale, it will be combined into one +catalog. + +The component comes with some default Loaders and you can create your own +Loader too. The default loaders are: + +* :class:`Symfony\\Component\\Translation\\Loader\\ArrayLoader` - to load + catalogs from PHP arrays. +* :class:`Symfony\\Component\\Translation\\Loader\\CsvFileLoader` - to load + catalogs from CSV files. +* :class:`Symfony\\Component\\Translation\\Loader\\IcuDatFileLoader` - to load + catalogs form resource bundles. +* :class:`Symfony\\Component\\Translation\\Loader\\IcuResFileLoader` - to load + catalogs form resource bundles. +* :class:`Symfony\\Component\\Translation\\Loader\\IniFileLoader` - to load + catalogs form ini files. +* :class:`Symfony\\Component\\Translation\\Loader\\MoFileLoader` - to load + catalogs form gettext files. +* :class:`Symfony\\Component\\Translation\\Loader\\PhpFileLoader` - to load + catalogs from PHP files. +* :class:`Symfony\\Component\\Translation\\Loader\\PoFileLoader` - to load + catalogs form gettext files. +* :class:`Symfony\\Component\\Translation\\Loader\\QtFileLoader` - to load + catalogs form QT XML files. +* :class:`Symfony\\Component\\Translation\\Loader\\XliffFileLoader` - to load + catalogs from Xliff files. +* :class:`Symfony\\Component\\Translation\\Loader\\YamlFileLoader` - to load + catalogs from Yaml files (requires the :doc:`Yaml component`). + +.. versionadded:: 2.1 + The ``IcuDatFileLoader``, ``IcuResFileLoader``, ``IniFileLoader``, + ``MofileLoader``, ``PoFileLoader`` and ``QtFileLoader`` are new in + Symfony 2.1 + +All file loaders require the :doc:`Config component`. + +At first, you should add one or more loaders to the ``Translator``:: + + // ... + $translator->addLoader('array', new ArrayLoader()); + +The first argument is the name to which you can refer the loader in the +translator and the second argument is an instance of the loader itself. After +this, you can add your resources using the correct loader. + +Loading Messages with the ``ArrayLoader`` +......................................... + +Loading messages can be done by calling +:method:`Symfony\\Component\\Translation\\Translator::addResource`. The first +argument is the loader name (this was the first argument of the ``addLoader`` +method), the second is the resource and the third argument is the locale:: + + // ... + $translator->addResource('array', array( + 'Hello World!' => 'Bonjour', + ), 'fr_FR'); + +Loading Messages with the File Loaders +...................................... + +If you use one of the file loaders, you should also use the ``addResource`` +method. The only difference is that you should put the file name to the resource +file as the second argument, instead of an array:: + + // ... + $translator->addLoader('yaml', new YamlFileLoader()); + $translator->addResource('yaml', 'path/to/messages.fr.yml', 'fr_FR'); + +The Translation Process +----------------------- + +To actually translate the message, the Translator uses a simple process: + +* A catalog of translated messages is loaded from translation resources defined + for the ``locale`` (e.g. ``fr_FR``). Messages from the + :ref:`components-fallback-locales` are also loaded and added to the + catalog, if they don't already exist. The end result is a large "dictionary" + of translations; + +* If the message is located in the catalog, the translation is returned. If + not, the translator returns the original message. + +You start this process by calling +:method:`Symfony\\Component\\Translation\\Translator::trans` or +:method:`Symfony\\Component\\Translation\\Translator::transChoice`. Then, the +Translator looks for the exact string inside the appropriate message catalog +and returns it (if it exists). + +.. _components-fallback-locales: + +Fallback Locales +~~~~~~~~~~~~~~~~ + +If the message is not located in the catalog of the specific locale, the +translator will look into the catalog of one or more fallback locales. For +example, assume you're trying to translate into the ``fr_FR`` locale: + +1. First, the translator looks for the translation in the ``fr_FR`` locale; + +2. If it wasn't found, the translator looks for the translation in the ``fr`` + locale; + +3. If the translation still isn't found, the translator uses the one or more + fallback locales set explicitly on the translator. + +For (3), the fallback locales can be set by calling +:method:`Symfony\\Component\\Translation\\Translator::setFallbackLocale`:: + + // ... + $translator->setFallbackLocale(array('en')); + +.. _using-message-domains: + +Using Message Domains +--------------------- + +As you've seen, message files are organized into the different locales that +they translate. The message files can also be organized further into "domains". + +The domain is specified in the fourth argument of the ``addResource()`` +method. The default domain is ``messages``. For example, suppose that, for +organization, translations were split into three different domains: +``messages``, ``admin`` and ``navigation``. The French translation would be +loaded like this:: + + // ... + $translator->addLoader('xliff', new XliffLoader()); + + $translator->addResource('xliff', 'messages.fr.xliff', 'fr_FR'); + $translator->addResource('xliff', 'admin.fr.xliff', 'fr_FR', 'admin'); + $translator->addResource('xliff', 'navigation.fr.xliff', 'fr_FR', 'navigation'); + +When translating strings that are not in the default domain (``messages``), +you must specify the domain as the third argument of ``trans()``:: + + $translator->trans('Symfony2 is great', array(), 'admin'); + +Symfony2 will now look for the message in the ``admin`` domain of the +specified locale. + +Usage +----- + +Read how to use the Translation component in ":doc:`/components/translation/usage`". + +.. _Packagist: https://packagist.org/packages/symfony/translation +.. _`ISO3166 Alpha-2`: http://en.wikipedia.org/wiki/ISO_3166-1#Current_codes +.. _`ISO639-1`: http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes diff --git a/components/translation/usage.rst b/components/translation/usage.rst new file mode 100644 index 00000000000..78adbadfb78 --- /dev/null +++ b/components/translation/usage.rst @@ -0,0 +1,373 @@ +.. index:: + single: Translation; Usage + +Using the Translator +==================== + +Imagine you want to translate the string *"Symfony2 is great"* into French:: + + use Symfony\Component\Translation\Translator; + use Symfony\Component\Translation\Loader\ArrayLoader; + + $translator = new Translator('fr_FR'); + $translator->addLoader('array', new ArrayLoader()); + $translator->addResource('array', array( + 'Symfony2 is great!' => 'J\'aime Symfony2!', + ), 'fr_FR'); + + echo $translator->trans('Symfony2 is great!'); + +In this example, the message *"Symfony2 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); + + echo $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%', + array('%name%' => $name) + ); + + echo $translated; + +Symfony2 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:: php + + return array( + 'Hello %name%' => 'Bonjour %name%', + ); + + .. code-block:: yaml + + '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 recommend, 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 +===================== + +The act of creating translation files is an important part of "localization" +(often abbreviated `L10n`_). Translation files consist of a series of +id-translation pairs for the given domain and locale. The source is the identifier +for the individual translation, and can be the message in the main locale (e.g. +*"Symfony is great"*) of your application or a unique identifier (e.g. +``symfony2.great`` - see the sidebar below). + +Translation files can be created in several different formats, XLIFF being the +recommended format. These files are parsed by one of the loader classes. + +.. configuration-block:: + + .. code-block:: xml + + + + + + + Symfony2 is great + J'aime Symfony2 + + + symfony2.great + J'aime Symfony2 + + + + + + .. code-block:: php + + return array( + 'Symfony2 is great' => 'J\'aime Symfony2', + 'symfony2.great' => 'J\'aime Symfony2', + ); + + .. code-block:: yaml + + Symfony2 is great: J'aime Symfony2 + symfony2.great: J'aime Symfony2 + +.. sidebar:: Using Real or Keyword Messages + + This example illustrates the two different philosophies when creating + messages to be translated:: + + $translator->trans('Symfony2 is great'); + + $translator->trans('symfony2.great'); + + In the first method, messages are written in the language of the default + locale (English in this case). That message is then used as the "id" + when creating translations. + + In the second method, messages are actually "keywords" that convey the + idea of the message. The keyword message is then used as the "id" for + any translations. In this case, translations must be made for the default + locale (i.e. to translate ``symfony2.great`` to ``Symfony2 is great``). + + The second method is handy because the message key won't need to be changed + in every translation file if you decide that the message should actually + read "Symfony2 is really great" in the default locale. + + The choice of which method to use is entirely up to you, but the "keyword" + format is often recommended. + + Additionally, the ``php`` and ``yaml`` file formats support nested ids to + avoid repeating yourself if you use keywords instead of real text for your + ids: + + .. configuration-block:: + + .. code-block:: yaml + + symfony2: + is: + great: Symfony2 is great + amazing: Symfony2 is amazing + has: + bundles: Symfony2 has bundles + user: + login: Login + + .. code-block:: php + + array( + 'symfony2' => array( + 'is' => array( + 'great' => 'Symfony2 is great', + 'amazing' => 'Symfony2 is amazing', + ), + 'has' => array( + 'bundles' => 'Symfony2 has bundles', + ), + ), + 'user' => array( + 'login' => 'Login', + ), + ); + + The multiple levels are flattened into single id/translation pairs by + adding a dot (``.``) between every level, therefore the above examples are + equivalent to the following: + + .. configuration-block:: + + .. code-block:: yaml + + symfony2.is.great: Symfony2 is great + symfony2.is.amazing: Symfony2 is amazing + symfony2.has.bundles: Symfony2 has bundles + user.login: Login + + .. code-block:: php + + return array( + 'symfony2.is.great' => 'Symfony2 is great', + 'symfony2.is.amazing' => 'Symfony2 is amazing', + 'symfony2.has.bundles' => 'Symfony2 has bundles', + '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 mathematic 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:: + + $translator->transChoice( + 'There is one apple|There are %count% apples', + 10, + array('%count%' => 10) + ); + +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 +----------------------------- + +When translating a message, the Translator uses the specified locale or the +``fallback`` locale if necessary. You can also manually specify the locale to +use for translation:: + + $translator->trans( + 'Symfony2 is great', + array(), + 'messages', + 'fr_FR' + ); + + $translator->transChoice( + '{0} There are no apples|{1} There is one apple|]1,Inf[ There are %count% apples', + 10, + array('%count%' => 10), + 'messages', + 'fr_FR' + ); + +.. _`L10n`: http://en.wikipedia.org/wiki/Internationalization_and_localization +.. _`ISO 31-11`: http://en.wikipedia.org/wiki/Interval_(mathematics)#Notations_for_intervals