diff --git a/book/translation.rst b/book/translation.rst index b99720f5169..aea1fc6e3d3 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,31 +21,30 @@ 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. Read the +:doc:`components documentation ` to learn how to use +the Translator. Overall, the process has several common steps: -#. Enable and configure Symfony's ``Translation`` component; +#. Enable and configure Symfony's Translation component; -#. 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`` (learn about this in ":doc:`/components/translation/usage`"); #. Create translation resources for each supported locale that translate each message in the application; #. Determine, set and manage the user's locale in the session. -.. index:: - single: Translations; 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: @@ -74,218 +73,66 @@ enable the ``Translator`` in your configuration: 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. - The locale used in translations is the one stored in the user session. -.. index:: - single: Translations; Basic translation - -Basic Translation ------------------ - -Translation of text is done through the ``translator`` service -(:class:`Symfony\\Component\\Translation\\Translator`). To translate a block -of text (called a *message*), use the -:method:`Symfony\\Component\\Translation\\Translator::trans` method. Suppose, -for example, that you're translating a simple message from inside a controller:: - - // ... - use Symfony\Component\HttpFoundation\Response; - - public function indexAction() - { - $t = $this->get('translator')->trans('Symfony2 is great'); +Fallback and Default Locale +~~~~~~~~~~~~~~~~~~~~~~~~~~~ - return new Response($t); - } +If the locale hasn't been set, the ``fallback`` configuration parameter will +be used by the ``Translator``. The parameter defaults to ``en`` (see +`Configuration`_). -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: +Alternatively, you can guarantee that a locale is set on the user's session +by defining a ``default_locale`` for the session service: .. configuration-block:: - .. code-block:: xml - - - - - - - - Symfony2 is great - J'aime Symfony2 - - - - - - .. code-block:: php - - // messages.fr.php - return array( - 'Symfony2 is great' => 'J\'aime Symfony2', - ); - .. code-block:: yaml - # messages.fr.yml - Symfony2 is great: J'aime Symfony2 - -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 Translation Process -~~~~~~~~~~~~~~~~~~~~~~~ + # app/config/config.yml + framework: + session: { default_locale: en } -To actually translate the message, Symfony2 uses a simple process: + .. code-block:: xml -* The ``locale`` of the current user, which is stored in the session, 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; + .. code-block:: php -* If the message is located in the catalog, the translation is returned. If - not, the translator returns the original message. + // app/config/config.php + $container->loadFromExtension('framework', array( + 'session' => array('default_locale' => 'en'), + )); -When using the ``trans()`` method, Symfony2 looks for the exact string inside -the appropriate message catalog and returns it (if it exists). +Using the Translation inside Controllers +---------------------------------------- -.. index:: - single: Translations; Message placeholders +When you want to use translation inside controllers, you need to get the +``translator`` service and use +:method:`Symfony\\Component\\Translation\\Translator::trans` or +:method:`Symfony\\Component\\Translation\\Translator::transChoice`:: -Message Placeholders -~~~~~~~~~~~~~~~~~~~~ - -Sometimes, a message containing a variable needs to be translated:: + // src/Acme/DemoBundle/Controller/DemoController.php + namespace Amce\DemoBundle\Controller; // ... - use Symfony\Component\HttpFoundation\Response; - - public function indexAction($name) + class DemoController extends Controller { - $t = $this->get('translator')->trans('Hello '.$name); - - return new Response($t); - } - -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":: - - // ... - use Symfony\Component\HttpFoundation\Response; + public function indexAction() + { + $translator = $this->get('translator'); - public function indexAction($name) - { - $t = $this->get('translator')->trans( - 'Hello %name%', - array('%name%' => $name) - ); + $translated = $translator->trans('Symfony2 is great!'); - return new Response($t); + 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% - - - - - - .. code-block:: php - - // messages.fr.php - return array( - 'Hello %name%' => 'Bonjour %name%', - ); - - .. code-block:: yaml - - # messages.fr.yml - 'Hello %name%': Bonjour %name% - -.. note:: - - 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. - -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 catalogues that define the translations -for any number of different locales. - -.. index:: - single: Translations; Message catalogues - -Message Catalogues ------------------- - -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: - -.. code-block:: text - - Symfony2 is Great => J'aime Symfony2 - -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. - -.. 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 - Translation Locations and Naming Conventions -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +-------------------------------------------- Symfony2 looks for message files (i.e. translations) in two locations: @@ -300,7 +147,7 @@ to determine details about the translations. Each message file must be named according to the following pattern: ``domain.locale.loader``: * **domain**: An optional way to organize messages into groups (e.g. ``admin``, - ``navigation`` or the default ``messages``) - see `Using Message Domains`_; + ``navigation`` or the default ``messages``) - see ":ref:`using-message-domains`"; * **locale**: The locale that the translations are for (e.g. ``en_GB``, ``en``, etc); @@ -311,8 +158,8 @@ The loader can be the name of any registered loader. By default, Symfony provides the following loaders: * ``xliff``: XLIFF file; -* ``php``: PHP file; -* ``yml``: YAML file. +* ``php``: PHP file; +* ``yml``: YAML file. The choice of which loader to use is entirely up to you and is a matter of taste. @@ -323,167 +170,15 @@ taste. providing a custom class implementing the :class:`Symfony\\Component\\Translation\\Loader\\LoaderInterface` interface. -.. index:: - single: Translations; Creating translation resources - -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): - -.. configuration-block:: - - .. code-block:: xml - - - - - - - - Symfony2 is great - J'aime Symfony2 - - - symfony2.great - J'aime Symfony2 - - - - - - .. code-block:: php - - // src/Acme/DemoBundle/Resources/translations/messages.fr.php - return array( - 'Symfony2 is great' => 'J\'aime Symfony2', - 'symfony2.great' => 'J\'aime Symfony2', - ); - - .. code-block:: yaml - - # src/Acme/DemoBundle/Resources/translations/messages.fr.yml - Symfony2 is great: J'aime Symfony2 - symfony2.great: J'aime Symfony2 - -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``). - -.. sidebar:: Using Real or Keyword Messages - - This example illustrates the two different philosophies when creating - messages to be translated:: - - $t = $translator->trans('Symfony2 is great'); - - $t = $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 +.. caution:: - symfony2: - is: - great: Symfony2 is great - amazing: Symfony2 is amazing - has: - bundles: Symfony2 has bundles - user: - login: Login - - .. code-block:: php - - return 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', - ); - -.. index:: - single: Translations; 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". -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: - -* ``messages.fr.xliff`` -* ``admin.fr.xliff`` -* ``navigation.fr.xliff`` - -When translating strings that are not in the default domain (``messages``), -you must specify the domain as the third argument of ``trans()``:: - - $this->get('translator')->trans('Symfony2 is great', array(), 'admin'); + 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: -Symfony2 will now look for the message in the ``admin`` domain of the user's -locale. + .. code-block:: bash -.. index:: - single: Translations; User's locale + $ php app/console cache:clear Handling the User's Locale -------------------------- @@ -495,41 +190,6 @@ via the ``session`` service:: $this->get('session')->setLocale('en_US'); -.. index:: - single: Translations; Fallback and default locale - -Fallback and Default Locale -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -If the locale hasn't been set explicitly in the session, the ``fallback_locale`` -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 the user's session -by defining a ``default_locale`` for the session service: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - framework: - session: { default_locale: en } - - .. code-block:: xml - - - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('framework', array( - 'session' => array('default_locale' => 'en'), - )); - .. _book-translation-locale-url: The Locale and the URL @@ -587,125 +247,6 @@ 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:: - - $t = $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 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -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:: - - '{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 ------------------------- @@ -732,6 +273,11 @@ 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 @@ -799,35 +345,6 @@ The translator service is accessible in PHP templates through the array('%count%' => 10) ) ?> -Forcing the Translator Locale ------------------------------ - -When translating a message, Symfony2 uses the locale from the user's session -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. - .. _book-translation-constraint-messages: Translating Constraint Messages @@ -909,7 +426,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:: @@ -940,6 +459,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 ------- @@ -949,7 +475,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 @@ -958,9 +485,6 @@ steps: * Manage the user's locale, which is stored in the 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 14e73dddf1a..d3437cb5db3 100644 --- a/components/index.rst +++ b/components/index.rst @@ -21,6 +21,7 @@ The Components security/index serializer templating + translation yaml/index .. include:: /components/map.rst.inc diff --git a/components/map.rst.inc b/components/map.rst.inc index e8427932fbe..1375612b63f 100644 --- a/components/map.rst.inc +++ b/components/map.rst.inc @@ -84,6 +84,11 @@ * :doc:`/components/templating` +* :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..f4469cd475b --- /dev/null +++ b/components/translation/introduction.rst @@ -0,0 +1,187 @@ +.. 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: + +* Use the official Git repository (https://github.com/symfony/Translation); +* :doc:`Install it via Composer` (``symfony/translation`` on `Packagist`_). + +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 +catalogues*). + +Configuration +~~~~~~~~~~~~~ + +The constructor of the ``Translator`` class needs two arguments: The locale +and the :class:`Symfony\\Component\\Translation\\MessageSelector` to use when +using pluralization (more about that later):: + + 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. + +Loading Message Catalogues +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The messages are stored in message catalogues inside the ``Translator`` +class. A message catalogue is like a dictionary of translations for a specific +locale. + +The Translation component uses Loader classes to load catalogues. You can load +multiple resources for the same locale, it will be combined into one +catalogue. + +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 + catalogues from PHP arrays. +* :class:`Symfony\\Component\\Translation\\Loader\\CsvFileLoader` - to load + catalogues from CSV files. +* :class:`Symfony\\Component\\Translation\\Loader\\PhpFileLoader` - to load + catalogues from PHP files. +* :class:`Symfony\\Component\\Translation\\Loader\\XliffFileLoader` - to load + catalogues from Xliff files. +* :class:`Symfony\\Component\\Translation\\Loader\\YamlFileLoader` - to load + catalogues from Yaml files (requires the :doc:`Yaml component`). + +All loaders, except the ``ArrayLoader``, requires 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 also use the ``addResource`` method. +The only difference is that you put the file name 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:`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. + +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). + +.. tip:: + + When a translation does not exist for a locale, the translator first tries + to find the translation for the language (e.g. ``fr`` if the locale is + ``fr_FR``). If this also fails, it looks for a translation using the + fallback locale. + +Fallback Locale +~~~~~~~~~~~~~~~ + +If the message is not located in the catalogue of the specific locale, the +translator will look into the catalogue of the fallback locale. You can set +this fallback locale by calling +:method:`Symfony\\Component\\Translation\\Translator::setFallbackLocale`:: + + // ... + $translator->setFallbackLocale('en_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..f5ff77a2c98 --- /dev/null +++ b/components/translation/usage.rst @@ -0,0 +1,370 @@ +.. 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\MessageSelector; + use Symfony\Component\Translation\Loader\ArrayLoader; + + $translator = new Translator('fr_FR', new MessageSelector()); + $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 catalogues. + +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 catalogues 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', + ); + +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