diff --git a/book/translation.rst b/book/translation.rst index 58add6ec2c8..13397b6e58b 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,18 +21,20 @@ 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; @@ -40,13 +42,10 @@ the process has several common steps: #. Determine, set and manage the user's locale for the request and optionally on the user's entire 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: @@ -85,210 +84,69 @@ not exist in the user's locale. 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 - -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() - { - $translated = $this->get('translator')->trans('Symfony2 is great'); +Fallback and Default Locale +~~~~~~~~~~~~~~~~~~~~~~~~~~~ - return new Response($translated); - } +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 each user's request +by defining a ``default_locale`` for the framework: .. 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: + 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 on the request (or - stored as ``_locale`` on the session), is determined; + + + en + -* 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( + '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). +.. 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. -.. index:: - single: Translations; Message placeholders +Using the Translation inside Controllers +---------------------------------------- -Message Placeholders -~~~~~~~~~~~~~~~~~~~~ +When you want to use translations inside controllers, you need to get the +``translator`` service and use +:method:`Symfony\\Component\\Translation\\Translator::trans` or +:method:`Symfony\\Component\\Translation\\Translator::transChoice`:: -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 { - $translated = $this->get('translator')->trans('Hello '.$name); - - return new Response($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":: - - // ... - use Symfony\Component\HttpFoundation\Response; + public function indexAction() + { + $translator = $this->get('translator'); - public function indexAction($name) - { - $translated = $this->get('translator')->trans( - 'Hello %name%', - array('%name%' => $name) - ); + $translated = $translator->trans('Symfony2 is great!'); - return new Response($translated); + 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 the following locations: @@ -311,7 +169,7 @@ to determine details about the translations. Each message file must be named according to the following path: ``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); @@ -322,8 +180,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. @@ -334,170 +192,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 +.. caution:: - # 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:: - - $translated = $translator->trans('Symfony2 is great'); - - $translated = $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 - - 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: - -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 -------------------------- @@ -512,9 +215,6 @@ via the ``request`` object:: $request->setLocale('en_US'); -.. index:: - single: Translations; Fallback and default locale - It is also possible to store the locale in the session instead of on a per request basis. If you do this, each subsequent request will have this locale. @@ -525,43 +225,6 @@ request basis. If you do this, each subsequent request will have this locale. 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_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 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 @@ -611,7 +274,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. @@ -619,125 +282,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:: - - $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 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -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 ------------------------- @@ -764,6 +308,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 @@ -847,35 +396,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 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. - .. _book-translation-constraint-messages: Translating Constraint Messages @@ -957,7 +477,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:: @@ -988,6 +510,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 ------- @@ -997,7 +526,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 @@ -1007,9 +537,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 865b2b274e6..9424185a06a 100644 --- a/components/index.rst +++ b/components/index.rst @@ -25,6 +25,7 @@ The Components serializer stopwatch templating/index + translation yaml/index .. include:: /components/map.rst.inc diff --git a/components/map.rst.inc b/components/map.rst.inc index acd892e0c67..c372ccb99a2 100644 --- a/components/map.rst.inc +++ b/components/map.rst.inc @@ -107,6 +107,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..937e40c67a6 --- /dev/null +++ b/components/translation/introduction.rst @@ -0,0 +1,203 @@ +.. 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 +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. + +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 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 catalog of the specific locale, the +translator will look into the catalog 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..5d47ee9c1a5 --- /dev/null +++ b/components/translation/usage.rst @@ -0,0 +1,369 @@ +.. 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. + +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', + ); + +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 pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy