From c8787fa998ceec0df3aef4eb83e71915db14a90c Mon Sep 17 00:00:00 2001 From: WouterJ Date: Thu, 29 Nov 2012 23:06:53 +0100 Subject: [PATCH 01/16] Bootstrapped the bootstrap Translation documentation --- book/translation.rst | 2 + components/index.rst | 1 + components/map.rst.inc | 4 ++ components/translation.rst | 137 +++++++++++++++++++++++++++++++++++++ 4 files changed, 144 insertions(+) create mode 100644 components/translation.rst diff --git a/book/translation.rst b/book/translation.rst index 98e8a87e8f4..a79ec77a506 100644 --- a/book/translation.rst +++ b/book/translation.rst @@ -96,6 +96,8 @@ typically set via a ``_locale`` attribute on your routes (see :ref:`book-transla .. index:: single: Translations; Basic translation +.. _basic-translation: + Basic Translation ----------------- diff --git a/components/index.rst b/components/index.rst index 01d348e8d69..aae1ba2cf23 100644 --- a/components/index.rst +++ b/components/index.rst @@ -26,6 +26,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 c363c469f05..61e6d599989 100644 --- a/components/map.rst.inc +++ b/components/map.rst.inc @@ -115,6 +115,10 @@ * :doc:`/components/templating/introduction` +* **Translation** + + * :doc:`/components/translation` + * :doc:`/components/yaml/index` * :doc:`/components/yaml/introduction` diff --git a/components/translation.rst b/components/translation.rst new file mode 100644 index 00000000000..6d183a46325 --- /dev/null +++ b/components/translation.rst @@ -0,0 +1,137 @@ +.. 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 many different ways: + +* Use the official Git repository (https://github.com/symfony/Translation); +* :doc:`Install it via Composer` (``symfony/translation`` on `Packagist`_). + +Usage +----- + +The :class:`Symfony\\Component\\Translation\\Translator` class is the main +entry point of the Translation component. + +.. code-block:: php + + 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( + 'Hello World!' => 'Bonjour', + ), 'fr_FR'); + + echo $translator->trans('Hello World!'); + +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. + +Loading catalogues +~~~~~~~~~~~~~~~~~~ + +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 a loader to the ``Translator``:: + + // ... + $translator->addLoader('array', new ArrayLoader()); + +The first argument is the key to which we 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 (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'); + +Translate Strings +----------------- + +After you have loaded your Message Catalogues, you can begin to translate your +strings. This is done with the +:method:`Symfony\\Component\\Translation\\Translator::trans` method:: + + // ... + $translator->addResource('array', array( + 'Hello World!' => 'Bonjour', + ), 'fr_FR'); + $translator->addResource('array', array( + 'Hello World!' => 'Hello World', + ), 'en_GB'); + + echo $translator->trans('Hello World!'); + // >> 'Bonjour' + +By default, the ``trans`` method uses the locale that is set in the +constructor of the ``Translator``. If you want to translate another locale, +you can change that by setting the fourth argument to the locale:: + + // ... + echo $translator->trans('Hello World!', array(), 'messages', 'en_GB'); + // >> 'Hello World!' + +Learn More +---------- + +The Translation component can do a lot more things. Read more about the usage +of this component in :ref:`the Translation book article `. +That article is specific about the Translation component in the Symfony2 +Framework, but most of the article is framework independent. + +.. _Packagist: https://packagist.org/packages/symfony/translation From 341de038ae22450e312fa50b648421ff53baa940 Mon Sep 17 00:00:00 2001 From: WouterJ Date: Sat, 27 Apr 2013 13:33:58 +0200 Subject: [PATCH 02/16] Added 'constructing the translator' --- components/translation.rst | 65 +++++++++++++++----------------------- 1 file changed, 25 insertions(+), 40 deletions(-) diff --git a/components/translation.rst b/components/translation.rst index 6d183a46325..09fb01298e8 100644 --- a/components/translation.rst +++ b/components/translation.rst @@ -36,16 +36,35 @@ entry point of the Translation component. echo $translator->trans('Hello World!'); -Message Catalogues ------------------- +Constructing the Translator +--------------------------- + +Before you can use the Translator, you need to configure it and load the +message catalogues. + +Configuration +~~~~~~~~~~~~~ + +The constructor of the ``Translator`` class needs to arguments: The locale and +a :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. + +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 +class. A message catalogue is like a dictionary of translations for a specific locale. -Loading catalogues -~~~~~~~~~~~~~~~~~~ - 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. @@ -100,38 +119,4 @@ instead of an array:: $translator->addLoader('yaml', new YamlFileLoader()); $translator->addResource('yaml', 'path/to/messages.fr.yml', 'fr_FR'); -Translate Strings ------------------ - -After you have loaded your Message Catalogues, you can begin to translate your -strings. This is done with the -:method:`Symfony\\Component\\Translation\\Translator::trans` method:: - - // ... - $translator->addResource('array', array( - 'Hello World!' => 'Bonjour', - ), 'fr_FR'); - $translator->addResource('array', array( - 'Hello World!' => 'Hello World', - ), 'en_GB'); - - echo $translator->trans('Hello World!'); - // >> 'Bonjour' - -By default, the ``trans`` method uses the locale that is set in the -constructor of the ``Translator``. If you want to translate another locale, -you can change that by setting the fourth argument to the locale:: - - // ... - echo $translator->trans('Hello World!', array(), 'messages', 'en_GB'); - // >> 'Hello World!' - -Learn More ----------- - -The Translation component can do a lot more things. Read more about the usage -of this component in :ref:`the Translation book article `. -That article is specific about the Translation component in the Symfony2 -Framework, but most of the article is framework independent. - .. _Packagist: https://packagist.org/packages/symfony/translation From 091a798d65bcc7faf3c5ec75232f07a6430631a5 Mon Sep 17 00:00:00 2001 From: WouterJ Date: Sat, 27 Apr 2013 13:44:46 +0200 Subject: [PATCH 03/16] Moved framework indepent stuff to component docs --- book/translation.rst | 560 +------------------------------------ components/translation.rst | 505 +++++++++++++++++++++++++++++++++ 2 files changed, 512 insertions(+), 553 deletions(-) diff --git a/book/translation.rst b/book/translation.rst index a79ec77a506..98d97a46e97 100644 --- a/book/translation.rst +++ b/book/translation.rst @@ -93,207 +93,6 @@ 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: - -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'); - - return new Response($translated); - } - -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: - -.. 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 -~~~~~~~~~~~~~~~~~~~~~~~ - -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; - -* 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; - -* If the message is located in the catalog, the translation is returned. If - not, the translator returns the original message. - -When using the ``trans()`` method, 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) - { - $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($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% - - - - - - .. 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 @@ -345,166 +144,15 @@ taste. :class:`Symfony\\Component\\Translation\\Loader\\LoaderInterface` interface. See the :ref:`dic-tags-translation-loader` tag for more information. -.. 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:: - - $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', - ); - -.. _translation-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`` +.. tip:: -When translating strings that are not in the default domain (``messages``), -you must specify the domain as the third argument of ``trans()``:: + 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: - $this->get('translator')->trans('Symfony2 is great', array(), 'admin'); + .. code-block:: bash -Symfony2 will now look for the message in the ``admin`` domain of the user's -locale. + $ php app/console cache:clear .. index:: single: Translations; User's locale @@ -533,51 +181,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 @@ -634,7 +237,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. @@ -642,122 +245,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 @@ -870,35 +357,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 @@ -1030,9 +488,5 @@ 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 diff --git a/components/translation.rst b/components/translation.rst index 09fb01298e8..fd9f84671ff 100644 --- a/components/translation.rst +++ b/components/translation.rst @@ -119,4 +119,509 @@ instead of an array:: $translator->addLoader('yaml', new YamlFileLoader()); $translator->addResource('yaml', 'path/to/messages.fr.yml', 'fr_FR'); +.. _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'); + + return new Response($t); + } + +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: + +.. 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 +~~~~~~~~~~~~~~~~~~~~~~~ + +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; + +* 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; + +* If the message is located in the catalog, the translation is returned. If + not, the translator returns the original message. + +When using the ``trans()`` method, Symfony2 looks for the exact string inside +the appropriate message catalog and returns it (if it exists). + +Message Placeholders +~~~~~~~~~~~~~~~~~~~~ + +Sometimes, a message containing a variable needs to be translated:: + + // ... + use Symfony\Component\HttpFoundation\Response; + + public function indexAction($name) + { + $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($name) + { + $t = $this->get('translator')->trans( + 'Hello %name%', + array('%name%' => $name) + ); + + return new Response($t); + } + +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 :phpfunction:`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. + +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 + + 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'); + +Symfony2 will now look for the message in the ``admin`` domain of the user's +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 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. + +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. + +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. + .. _Packagist: https://packagist.org/packages/symfony/translation +.. _`L10n`: http://en.wikipedia.org/wiki/Internationalization_and_localization +.. _`ISO 31-11`: http://en.wikipedia.org/wiki/Interval_(mathematics)#Notations_for_intervals +.. _`Translatable Extension`: https://github.com/l3pp4rd/DoctrineExtensions From 0bea6e45b98272f80f186cc3fdf60bea1f8ffd31 Mon Sep 17 00:00:00 2001 From: WouterJ Date: Mon, 29 Apr 2013 11:54:27 +0200 Subject: [PATCH 04/16] Rewrote component article --- book/translation.rst | 50 ++++++ components/translation.rst | 307 ++++++++++++++----------------------- 2 files changed, 163 insertions(+), 194 deletions(-) diff --git a/book/translation.rst b/book/translation.rst index 98d97a46e97..1e6133b742d 100644 --- a/book/translation.rst +++ b/book/translation.rst @@ -93,6 +93,43 @@ 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`). +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. + .. index:: single: Translations; Translation resource locations @@ -259,6 +296,11 @@ support for both Twig and PHP templates. Twig Templates ~~~~~~~~~~~~~~ +.. + However, the ``%var%`` + notation is required when translating in Twig templates, and is overall a + sensible convention to follow. + Symfony2 provides specialized Twig tags (``trans`` and ``transchoice``) to help with message translation of *static blocks of text*: @@ -469,6 +511,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 ------- @@ -490,3 +539,4 @@ steps: .. _`i18n`: http://en.wikipedia.org/wiki/Internationalization_and_localization .. _`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/translation.rst b/components/translation.rst index fd9f84671ff..74dc386c320 100644 --- a/components/translation.rst +++ b/components/translation.rst @@ -36,6 +36,8 @@ entry point of the Translation component. echo $translator->trans('Hello World!'); +.. document the fallback locale + Constructing the Translator --------------------------- @@ -51,6 +53,7 @@ pluralization (more about that later):: use Symfony\Component\Translation\Translator; use Symfony\Component\Translation\MessageSelector; + $translator = new Translator('fr_FR', new MessageSelector()); .. note:: @@ -119,85 +122,55 @@ instead of an array:: $translator->addLoader('yaml', new YamlFileLoader()); $translator->addResource('yaml', 'path/to/messages.fr.yml', 'fr_FR'); -.. _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'); - - return new Response($t); - } - -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: - -.. configuration-block:: +The Translation Process +----------------------- - .. code-block:: xml +To actually translate the message, the Translator uses a simple process: - - - - - - - Symfony2 is great - J'aime Symfony2 - - - - - - .. code-block:: php +* 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; - // messages.fr.php - return array( - 'Symfony2 is great' => 'J\'aime Symfony2', - ); +* If the message is located in the catalog, the translation is returned. If + not, the translator returns the original message. - .. code-block:: yaml +You start this process by calling +:method:`Symfony\\Component\\Translation\\Translator::trans`. Then, the +Translator looks for the exact string inside the appropriate message catalog +and returns it (if it exists). - # messages.fr.yml - Symfony2 is great: J'aime Symfony2 +Fallback Locale +~~~~~~~~~~~~~~~ -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``. +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`:: -The Translation Process -~~~~~~~~~~~~~~~~~~~~~~~ + // ... + $translator->setFallbackLocale('en_EN'); -To actually translate the message, Symfony2 uses a simple process: +Basic Translation +----------------- -* The ``locale`` of the current user, which is stored on the request (or - stored as ``_locale`` on the session), is determined; +Imagine you want to translate the string *"Symfony2 is great"* into french:: -* 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; + use Symfony\Component\Translation\Translator; + use Symfony\Component\Translation\MessageSelector; + use Symfony\Component\Translation\Loader\ArrayLoader; -* If the message is located in the catalog, the translation is returned. If - not, the translator returns the original message. + $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!'); -When using the ``trans()`` method, Symfony2 looks for the exact string inside -the appropriate message catalog and returns it (if it exists). +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 ~~~~~~~~~~~~~~~~~~~~ @@ -205,33 +178,23 @@ Message Placeholders Sometimes, a message containing a variable needs to be translated:: // ... - use Symfony\Component\HttpFoundation\Response; - - public function indexAction($name) - { - $t = $this->get('translator')->trans('Hello '.$name); + $translated = $translator->trans('Hello '.$name); - return new Response($t); - } + 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 +(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($name) - { - $t = $this->get('translator')->trans( - 'Hello %name%', - array('%name%' => $name) - ); + $translated = $translator->trans( + 'Hello %name%', + array('%name%' => $name) + ); - return new Response($t); - } + 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 @@ -241,7 +204,6 @@ is done just as before: .. code-block:: xml - @@ -256,22 +218,18 @@ is done just as before: .. 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 :phpfunction:`strtr function`. However, the ``%var%`` notation is - required when translating in Twig templates, and is overall a sensible - convention to follow. + using the PHP :phpfunction:`strtr function`. As you've seen, creating a translation is a two-step process: @@ -285,20 +243,22 @@ The second step is done by creating message catalogues that define the translati 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): +*"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 - @@ -317,7 +277,6 @@ for the individual translation, and can be the message in the main locale (e.g. .. code-block:: php - // src/Acme/DemoBundle/Resources/translations/messages.fr.php return array( 'Symfony2 is great' => 'J\'aime Symfony2', 'symfony2.great' => 'J\'aime Symfony2', @@ -325,22 +284,17 @@ for the individual translation, and can be the message in the main locale (e.g. .. 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'); + $translator->trans('Symfony2 is great'); - $t = $translator->trans('symfony2.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" @@ -377,7 +331,7 @@ Symfony2 will discover these files and use them when translating either .. code-block:: php - return array( + array( 'symfony2' => array( 'is' => array( 'great' => 'Symfony2 is great', @@ -393,7 +347,7 @@ Symfony2 will discover these files and use them when translating either ); The multiple levels are flattened into single id/translation pairs by - adding a dot (.) between every level, therefore the above examples are + adding a dot (``.``) between every level, therefore the above examples are equivalent to the following: .. configuration-block:: @@ -414,73 +368,8 @@ Symfony2 will discover these files and use them when translating either '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'); - -Symfony2 will now look for the message in the ``admin`` domain of the user's -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 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. - 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 @@ -508,7 +397,7 @@ all the forms as a string separated by a pipe (``|``):: To translate pluralized messages, use the :method:`Symfony\\Component\\Translation\\Translator::transChoice` method:: - $t = $this->get('translator')->transChoice( + $translator->transChoice( 'There is one apple|There are %count% apples', 10, array('%count%' => 10) @@ -524,7 +413,9 @@ 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:: +Here is the French translation: + +.. code-block:: text 'Il y a %count% pomme|Il y a %count% pommes' @@ -537,7 +428,9 @@ 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:: +you can optionally "tag" each string: + +.. code-block:: text 'one: There is one apple|some: There are %count% apples' @@ -554,13 +447,15 @@ same in the original message as in the translated one. 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 +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:: +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' @@ -570,7 +465,9 @@ 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:: +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' @@ -579,34 +476,64 @@ 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:: +of numbers: + +.. code-block:: text {1,2,3,4} -Or numbers between two other numbers:: +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 + +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 specific 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. can use ``-Inf`` and ``+Inf`` for the infinite. 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:: +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:: - $this->get('translator')->trans( + $translator->trans( 'Symfony2 is great', array(), 'messages', 'fr_FR' ); - $this->get('translator')->transChoice( + $translator->transChoice( '{0} There are no apples|{1} There is one apple|]1,Inf[ There are %count% apples', 10, array('%count%' => 10), @@ -614,14 +541,6 @@ locale to use for translation:: '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. - .. _Packagist: https://packagist.org/packages/symfony/translation .. _`L10n`: http://en.wikipedia.org/wiki/Internationalization_and_localization .. _`ISO 31-11`: http://en.wikipedia.org/wiki/Interval_(mathematics)#Notations_for_intervals -.. _`Translatable Extension`: https://github.com/l3pp4rd/DoctrineExtensions From be0aebc0dae1adda82d147fe566eb5934fc6f7bf Mon Sep 17 00:00:00 2001 From: WouterJ Date: Mon, 29 Apr 2013 12:28:14 +0200 Subject: [PATCH 05/16] Rewrote book article --- book/translation.rst | 70 +++++++++++++++++++++++--------------- components/translation.rst | 9 +++++ 2 files changed, 51 insertions(+), 28 deletions(-) diff --git a/book/translation.rst b/book/translation.rst index 1e6133b742d..6c3a241c88a 100644 --- a/book/translation.rst +++ b/book/translation.rst @@ -26,11 +26,12 @@ the user:: `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``; @@ -40,13 +41,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: @@ -94,7 +92,7 @@ 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`). 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 @@ -130,11 +128,31 @@ by defining a ``default_locale`` for the framework: 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; Translation resource locations +Using the Translation inside Controllers +---------------------------------------- + +When you want to use translation inside controllers, you need to get the +``translator`` service and use ``trans`` or ``transChoice``:: + + // src/Acme/DemoBundle/Controller/DemoController.php + namespace Amce\DemoBundle\Controller; + + // ... + + class DemoController extends Controller + { + public function indexAction() + { + $translator = $this->get('translator'); + + $translated = $translator->trans('Symfony2 is great!'); + + return new Response($translated); + } + } Translation Locations and Naming Conventions -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +-------------------------------------------- Symfony2 looks for message files (i.e. translations) in the following locations: @@ -157,7 +175,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); @@ -168,8 +186,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. @@ -181,7 +199,7 @@ taste. :class:`Symfony\\Component\\Translation\\Loader\\LoaderInterface` interface. See the :ref:`dic-tags-translation-loader` tag for more information. -.. tip:: +.. caution:: Each time you create a *new* translation resource (or install a bundle that includes a translation resource), be sure to clear your cache so @@ -191,9 +209,6 @@ taste. $ php app/console cache:clear -.. index:: - single: Translations; User's locale - Handling the User's Locale -------------------------- @@ -282,9 +297,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; In templates - Translations in Templates ------------------------- @@ -296,11 +308,6 @@ support for both Twig and PHP templates. Twig Templates ~~~~~~~~~~~~~~ -.. - However, the ``%var%`` - notation is required when translating in Twig templates, and is overall a - sensible convention to follow. - Symfony2 provides specialized Twig tags (``trans`` and ``transchoice``) to help with message translation of *static blocks of text*: @@ -316,6 +323,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. + .. tip:: If you need to use the percent character (``%``) in a string, escape it by @@ -480,7 +492,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:: diff --git a/components/translation.rst b/components/translation.rst index 74dc386c320..cdbc2a8986f 100644 --- a/components/translation.rst +++ b/components/translation.rst @@ -140,6 +140,13 @@ You start this process by calling 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 (``fr`` if the locale is + ``fr_FR`` for instance). If this also fails, it looks for a translation + using the fallback locale. + Fallback Locale ~~~~~~~~~~~~~~~ @@ -492,6 +499,8 @@ Or numbers between two other numbers: The left delimiter can be ``[`` (inclusive) or ``]`` (exclusive). The right delimiter can be ``[`` (exclusive) or ``]`` (inclusive). Beside numbers, you +.. _using-message-domains: + Using Message Domains --------------------- From 2446af463f45283793c4744e7c79a598c31d3593 Mon Sep 17 00:00:00 2001 From: WouterJ Date: Sun, 12 May 2013 17:06:42 +0200 Subject: [PATCH 06/16] Moved cookbook article --- components/map.rst.inc | 4 ++-- components/translation/index.rst | 7 +++++++ .../{translation.rst => translation/introduction.rst} | 0 3 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 components/translation/index.rst rename components/{translation.rst => translation/introduction.rst} (100%) diff --git a/components/map.rst.inc b/components/map.rst.inc index 61e6d599989..407232c6d81 100644 --- a/components/map.rst.inc +++ b/components/map.rst.inc @@ -115,9 +115,9 @@ * :doc:`/components/templating/introduction` -* **Translation** +* :doc:`/components/translation/index` - * :doc:`/components/translation` + * :doc:`/components/translation/introduction` * :doc:`/components/yaml/index` diff --git a/components/translation/index.rst b/components/translation/index.rst new file mode 100644 index 00000000000..05fd1ac5904 --- /dev/null +++ b/components/translation/index.rst @@ -0,0 +1,7 @@ +Translation +=========== + +.. toctree:: + :maxdepth: 2 + + introduction diff --git a/components/translation.rst b/components/translation/introduction.rst similarity index 100% rename from components/translation.rst rename to components/translation/introduction.rst From 5a3ff92a18e06a76066f5110dfa3af18db53747d Mon Sep 17 00:00:00 2001 From: WouterJ Date: Sun, 12 May 2013 17:33:02 +0200 Subject: [PATCH 07/16] Moved basic usage to its own article --- components/translation/introduction.rst | 417 +----------------------- components/translation/usage.rst | 394 ++++++++++++++++++++++ 2 files changed, 397 insertions(+), 414 deletions(-) create mode 100644 components/translation/usage.rst diff --git a/components/translation/introduction.rst b/components/translation/introduction.rst index cdbc2a8986f..8e334a2e586 100644 --- a/components/translation/introduction.rst +++ b/components/translation/introduction.rst @@ -16,28 +16,6 @@ You can install the component in many different ways: * Use the official Git repository (https://github.com/symfony/Translation); * :doc:`Install it via Composer` (``symfony/translation`` on `Packagist`_). -Usage ------ - -The :class:`Symfony\\Component\\Translation\\Translator` class is the main -entry point of the Translation component. - -.. code-block:: php - - 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( - 'Hello World!' => 'Bonjour', - ), 'fr_FR'); - - echo $translator->trans('Hello World!'); - -.. document the fallback locale - Constructing the Translator --------------------------- @@ -158,398 +136,9 @@ this fallback locale by calling // ... $translator->setFallbackLocale('en_EN'); -Basic Translation ------------------ - -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`. - -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 - -.. _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 specific 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. -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' - ); +Usage +----- - $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' - ); +Read how to use the Translation components in ":doc:`/components/translation/usage`" .. _Packagist: https://packagist.org/packages/symfony/translation -.. _`L10n`: http://en.wikipedia.org/wiki/Internationalization_and_localization -.. _`ISO 31-11`: http://en.wikipedia.org/wiki/Interval_(mathematics)#Notations_for_intervals diff --git a/components/translation/usage.rst b/components/translation/usage.rst new file mode 100644 index 00000000000..7571f4eabf2 --- /dev/null +++ b/components/translation/usage.rst @@ -0,0 +1,394 @@ +Usage +----- + +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`. + +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 + +.. _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 specific 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. +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 From b6cd3e956d6547eeb43722437260e67b59568f0a Mon Sep 17 00:00:00 2001 From: WouterJ Date: Sun, 12 May 2013 17:36:25 +0200 Subject: [PATCH 08/16] updated new article --- components/map.rst.inc | 1 + components/translation/index.rst | 1 + components/translation/usage.rst | 14 +++++++------- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/components/map.rst.inc b/components/map.rst.inc index 407232c6d81..5c7fbf251ed 100644 --- a/components/map.rst.inc +++ b/components/map.rst.inc @@ -118,6 +118,7 @@ * :doc:`/components/translation/index` * :doc:`/components/translation/introduction` + * :doc:`/components/translation/usage` * :doc:`/components/yaml/index` diff --git a/components/translation/index.rst b/components/translation/index.rst index 05fd1ac5904..3f87cbc1425 100644 --- a/components/translation/index.rst +++ b/components/translation/index.rst @@ -5,3 +5,4 @@ Translation :maxdepth: 2 introduction + usage diff --git a/components/translation/usage.rst b/components/translation/usage.rst index 7571f4eabf2..16778e1026b 100644 --- a/components/translation/usage.rst +++ b/components/translation/usage.rst @@ -1,5 +1,5 @@ Usage ------ +===== Imagine you want to translate the string *"Symfony2 is great"* into french:: @@ -20,7 +20,7 @@ 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:: @@ -90,7 +90,7 @@ The second step is done by creating message catalogues that define the translati 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 @@ -216,7 +216,7 @@ recommended format. These files are parsed by one of the loader classes. ); 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 @@ -294,7 +294,7 @@ same in the original message as in the translated one. 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 @@ -342,7 +342,7 @@ delimiter can be ``[`` (exclusive) or ``]`` (inclusive). Beside numbers, you .. _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". @@ -369,7 +369,7 @@ specified locale. 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 From cde0f7f8eec3f1d68870b357210a3a85a668b8e8 Mon Sep 17 00:00:00 2001 From: WouterJ Date: Sun, 12 May 2013 17:37:15 +0200 Subject: [PATCH 09/16] Added index directive --- components/translation/usage.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/translation/usage.rst b/components/translation/usage.rst index 16778e1026b..cfd7bd161e1 100644 --- a/components/translation/usage.rst +++ b/components/translation/usage.rst @@ -1,3 +1,6 @@ +.. index:: + single: Translation; Usage + Usage ===== From 56b02be28c2eedee8beecd40484ccf48ad6fffc0 Mon Sep 17 00:00:00 2001 From: WouterJ Date: Sun, 19 May 2013 17:59:06 +0200 Subject: [PATCH 10/16] Reread component docs --- components/translation/introduction.rst | 42 ++++++++++++++----------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/components/translation/introduction.rst b/components/translation/introduction.rst index 8e334a2e586..73907ccb04c 100644 --- a/components/translation/introduction.rst +++ b/components/translation/introduction.rst @@ -19,15 +19,17 @@ You can install the component in many different ways: Constructing the Translator --------------------------- -Before you can use the Translator, you need to configure it and load the -message catalogues. +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 to arguments: The locale and -a :class:`Symfony\\Component\\Translation\\MessageSelector` to use when using -pluralization (more about that later):: +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; @@ -56,9 +58,9 @@ 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. + catalogues from CSV files. * :class:`Symfony\\Component\\Translation\\Loader\\PhpFileLoader` - to load - catalogues from Php files. + catalogues from PHP files. * :class:`Symfony\\Component\\Translation\\Loader\\XliffFileLoader` - to load catalogues from Xliff files. * :class:`Symfony\\Component\\Translation\\Loader\\YamlFileLoader` - to load @@ -67,21 +69,21 @@ Loader too. The default loaders are: All loaders, except the ``ArrayLoader``, requires the :doc:`Config component`. -At first, you should add a loader to the ``Translator``:: +At first, you should add one or more loaders to the ``Translator``:: // ... $translator->addLoader('array', new ArrayLoader()); -The first argument is the key to which we 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. +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 (the first argument of the ``addLoader`` +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:: // ... @@ -106,24 +108,26 @@ 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 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; + 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`. Then, the +: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 (``fr`` if the locale is - ``fr_FR`` for instance). If this also fails, it looks for a translation - using the fallback locale. + 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 ~~~~~~~~~~~~~~~ From 55a30ab46b15846cdbd34ebd11bda92569ed9615 Mon Sep 17 00:00:00 2001 From: WouterJ Date: Sun, 19 May 2013 18:05:30 +0200 Subject: [PATCH 11/16] Reread usage docs --- components/translation/introduction.rst | 31 ++++++++++++++++++++- components/translation/usage.rst | 36 +++---------------------- 2 files changed, 34 insertions(+), 33 deletions(-) diff --git a/components/translation/introduction.rst b/components/translation/introduction.rst index 73907ccb04c..c498c7fc5eb 100644 --- a/components/translation/introduction.rst +++ b/components/translation/introduction.rst @@ -140,9 +140,38 @@ this fallback locale by calling // ... $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 specific 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. +can use ``-Inf`` and ``+Inf`` for the infinite. + Usage ----- -Read how to use the Translation components in ":doc:`/components/translation/usage`" +Read how to use the Translation components in ":doc:`/components/translation/usage`". .. _Packagist: https://packagist.org/packages/symfony/translation diff --git a/components/translation/usage.rst b/components/translation/usage.rst index cfd7bd161e1..287ea09add2 100644 --- a/components/translation/usage.rst +++ b/components/translation/usage.rst @@ -4,7 +4,7 @@ Usage ===== -Imagine you want to translate the string *"Symfony2 is great"* into french:: +Imagine you want to translate the string *"Symfony2 is great"* into French:: use Symfony\Component\Translation\Translator; use Symfony\Component\Translation\MessageSelector; @@ -79,7 +79,8 @@ is done just as before: .. note:: The placeholders can take on any form as the full message is reconstructed - using the PHP :phpfunction:`strtr function`. + 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: @@ -342,37 +343,8 @@ Or numbers between two other numbers: The left delimiter can be ``[`` (inclusive) or ``]`` (exclusive). The right delimiter can be ``[`` (exclusive) or ``]`` (inclusive). Beside numbers, you -.. _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 specific 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. -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 From 1d0e7b3559391d3c477abe25c9d3d9d09a6ea185 Mon Sep 17 00:00:00 2001 From: WouterJ Date: Sun, 19 May 2013 18:12:47 +0200 Subject: [PATCH 12/16] Reread book article --- book/translation.rst | 31 ++++++++++++++----------- components/translation/introduction.rst | 10 ++++++++ components/translation/usage.rst | 4 ++-- 3 files changed, 29 insertions(+), 16 deletions(-) diff --git a/book/translation.rst b/book/translation.rst index 6c3a241c88a..206da5a3ee0 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,10 +21,10 @@ 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 use the Translation component in the Symfony2 framework. Read the @@ -33,7 +33,8 @@ the Translator. Overall, the process has several common steps: #. 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; @@ -132,13 +133,14 @@ Using the Translation inside Controllers ---------------------------------------- When you want to use translation inside controllers, you need to get the -``translator`` service and use ``trans`` or ``transChoice``:: +``translator`` service and use +:method:`Symfony\\Component\\Translation\\Translator::trans` or +:method:`Symfony\\Component\\Translation\\Translator::transChoice`:: // src/Acme/DemoBundle/Controller/DemoController.php namespace Amce\DemoBundle\Controller; // ... - class DemoController extends Controller { public function indexAction() @@ -541,7 +543,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 diff --git a/components/translation/introduction.rst b/components/translation/introduction.rst index c498c7fc5eb..b0101376f7a 100644 --- a/components/translation/introduction.rst +++ b/components/translation/introduction.rst @@ -41,6 +41,14 @@ using pluralization (more about that later):: 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 ~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -175,3 +183,5 @@ Usage Read how to use the Translation components 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 index 287ea09add2..5ac78b2c1ce 100644 --- a/components/translation/usage.rst +++ b/components/translation/usage.rst @@ -1,8 +1,8 @@ .. index:: single: Translation; Usage -Usage -===== +Using the Translator +==================== Imagine you want to translate the string *"Symfony2 is great"* into French:: From 4c7d86a09ea18ef53ca0305d3b3471942bc2fc6d Mon Sep 17 00:00:00 2001 From: WouterJ Date: Thu, 6 Jun 2013 19:17:44 +0200 Subject: [PATCH 13/16] Fixed things mentioned by the great @xabbuh --- book/translation.rst | 8 ++++---- components/translation/introduction.rst | 14 +++++++------- components/translation/usage.rst | 1 + 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/book/translation.rst b/book/translation.rst index 206da5a3ee0..a4c912678cb 100644 --- a/book/translation.rst +++ b/book/translation.rst @@ -95,9 +95,9 @@ typically set via a ``_locale`` attribute on your routes (see :ref:`book-transla 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`_). +If the locale hasn't been set, 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: @@ -328,7 +328,7 @@ works when you use a placeholder following the ``%var%`` pattern. .. caution:: The ``%var%`` notation of placeholders is required when translating in - Twig templates. + Twig templates using the tag. .. tip:: diff --git a/components/translation/introduction.rst b/components/translation/introduction.rst index b0101376f7a..f4469cd475b 100644 --- a/components/translation/introduction.rst +++ b/components/translation/introduction.rst @@ -11,7 +11,7 @@ The Translation Component Installation ------------ -You can install the component in many different ways: +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`_). @@ -156,10 +156,11 @@ 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 specific 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:: +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()); @@ -175,12 +176,11 @@ you must specify the domain as the third argument of ``trans()``:: Symfony2 will now look for the message in the ``admin`` domain of the specified locale. -can use ``-Inf`` and ``+Inf`` for the infinite. Usage ----- -Read how to use the Translation components in ":doc:`/components/translation/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 diff --git a/components/translation/usage.rst b/components/translation/usage.rst index 5ac78b2c1ce..f5ff77a2c98 100644 --- a/components/translation/usage.rst +++ b/components/translation/usage.rst @@ -342,6 +342,7 @@ Or numbers between two other numbers: 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 ----------------------------- From 047789031d0e23d22258dfdbb2d2010524296ad5 Mon Sep 17 00:00:00 2001 From: WouterJ Date: Sun, 18 Aug 2013 15:33:22 +0200 Subject: [PATCH 14/16] Added new 2.2 features --- components/translation/introduction.rst | 26 ++++++++++++++++++++----- components/translation/usage.rst | 9 ++++----- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/components/translation/introduction.rst b/components/translation/introduction.rst index f4469cd475b..d75ab9dff42 100644 --- a/components/translation/introduction.rst +++ b/components/translation/introduction.rst @@ -27,9 +27,9 @@ 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):: +The constructor of the ``Translator`` class needs one argument: The locale. + +.. code-block:: php use Symfony\Component\Translation\Translator; use Symfony\Component\Translation\MessageSelector; @@ -67,15 +67,31 @@ Loader too. The default loaders are: catalogues from PHP arrays. * :class:`Symfony\\Component\\Translation\\Loader\\CsvFileLoader` - to load catalogues from CSV files. +* :class:`Symfony\\Component\\Translation\\Loader\\IcuDatFileLoader` - to load + catalogues form resource bundles. +* :class:`Symfony\\Component\\Translation\\Loader\\IcuResFileLoader` - to load + catalogues form resource bundles. +* :class:`Symfony\\Component\\Translation\\Loader\\IniFileLoader` - to load + catalogues form ini files. +* :class:`Symfony\\Component\\Translation\\Loader\\MoFileLoader` - to load + catalogues form gettext files. * :class:`Symfony\\Component\\Translation\\Loader\\PhpFileLoader` - to load catalogues from PHP files. +* :class:`Symfony\\Component\\Translation\\Loader\\PoFileLoader` - to load + catalogues form gettext files. +* :class:`Symfony\\Component\\Translation\\Loader\\QtFileLoader` - to load + catalogues form QT XML 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`. +.. 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``:: diff --git a/components/translation/usage.rst b/components/translation/usage.rst index f5ff77a2c98..78638b69076 100644 --- a/components/translation/usage.rst +++ b/components/translation/usage.rst @@ -7,10 +7,9 @@ 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 = new Translator('fr_FR'); $translator->addLoader('array', new ArrayLoader()); $translator->addResource('array', array( 'Symfony2 is great!' => 'J'aime Symfony2!', @@ -213,10 +212,10 @@ recommended format. These files are parsed by one of the loader classes. .. code-block:: php return array( - 'symfony2.is.great' => 'Symfony2 is great', - 'symfony2.is.amazing' => 'Symfony2 is amazing', + 'symfony2.is.great' => 'Symfony2 is great', + 'symfony2.is.amazing' => 'Symfony2 is amazing', 'symfony2.has.bundles' => 'Symfony2 has bundles', - 'user.login' => 'Login', + 'user.login' => 'Login', ); Pluralization From e0ca9b653ec1490a80e320e9cb4790ba011f1ffd Mon Sep 17 00:00:00 2001 From: WouterJ Date: Sat, 24 Aug 2013 16:04:59 +0200 Subject: [PATCH 15/16] Fixes --- book/translation.rst | 2 +- components/translation/introduction.rst | 42 ++++++++++++------------- components/translation/usage.rst | 4 +-- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/book/translation.rst b/book/translation.rst index a4c912678cb..e826d67a68e 100644 --- a/book/translation.rst +++ b/book/translation.rst @@ -132,7 +132,7 @@ by defining a ``default_locale`` for the framework: Using the Translation inside Controllers ---------------------------------------- -When you want to use translation inside controllers, you need to get the +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`:: diff --git a/components/translation/introduction.rst b/components/translation/introduction.rst index d75ab9dff42..937e40c67a6 100644 --- a/components/translation/introduction.rst +++ b/components/translation/introduction.rst @@ -22,7 +22,7 @@ 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*). +catalogs*). Configuration ~~~~~~~~~~~~~ @@ -49,42 +49,42 @@ The constructor of the ``Translator`` class needs one argument: The locale. *language* code, an underscore (``_``), then the `ISO3166 Alpha-2`_ *country* code (e.g. ``fr_FR`` for French/France) is recommended. -Loading Message Catalogues -~~~~~~~~~~~~~~~~~~~~~~~~~~ +Loading Message Catalogs +~~~~~~~~~~~~~~~~~~~~~~~~ -The messages are stored in message catalogues inside the ``Translator`` -class. A message catalogue is like a dictionary of translations for a specific +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 catalogues. You can load +The Translation component uses Loader classes to load catalogs. You can load multiple resources for the same locale, it will be combined into one -catalogue. +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 - catalogues from PHP arrays. + catalogs from PHP arrays. * :class:`Symfony\\Component\\Translation\\Loader\\CsvFileLoader` - to load - catalogues from CSV files. + catalogs from CSV files. * :class:`Symfony\\Component\\Translation\\Loader\\IcuDatFileLoader` - to load - catalogues form resource bundles. + catalogs form resource bundles. * :class:`Symfony\\Component\\Translation\\Loader\\IcuResFileLoader` - to load - catalogues form resource bundles. + catalogs form resource bundles. * :class:`Symfony\\Component\\Translation\\Loader\\IniFileLoader` - to load - catalogues form ini files. + catalogs form ini files. * :class:`Symfony\\Component\\Translation\\Loader\\MoFileLoader` - to load - catalogues form gettext files. + catalogs form gettext files. * :class:`Symfony\\Component\\Translation\\Loader\\PhpFileLoader` - to load - catalogues from PHP files. + catalogs from PHP files. * :class:`Symfony\\Component\\Translation\\Loader\\PoFileLoader` - to load - catalogues form gettext files. + catalogs form gettext files. * :class:`Symfony\\Component\\Translation\\Loader\\QtFileLoader` - to load - catalogues form QT XML files. + catalogs form QT XML files. * :class:`Symfony\\Component\\Translation\\Loader\\XliffFileLoader` - to load - catalogues from Xliff files. + catalogs from Xliff files. * :class:`Symfony\\Component\\Translation\\Loader\\YamlFileLoader` - to load - catalogues from Yaml files (requires the :doc:`Yaml component`). + catalogs from Yaml files (requires the :doc:`Yaml component`). .. versionadded:: 2.1 The ``IcuDatFileLoader``, ``IcuResFileLoader``, ``IniFileLoader``, @@ -134,7 +134,7 @@ 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" + 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 @@ -156,8 +156,8 @@ and returns it (if it exists). 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 +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`:: diff --git a/components/translation/usage.rst b/components/translation/usage.rst index 78638b69076..5d47ee9c1a5 100644 --- a/components/translation/usage.rst +++ b/components/translation/usage.rst @@ -19,7 +19,7 @@ Imagine you want to translate the string *"Symfony2 is great"* into French:: 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. +the message catalogs. Message Placeholders -------------------- @@ -89,7 +89,7 @@ As you've seen, creating a translation is a two-step process: #. 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 +The second step is done by creating message catalogs that define the translations for any number of different locales. Creating Translations From 9602da7b6040634b4bb745897599878593439cef Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Sat, 16 Nov 2013 16:10:23 -0600 Subject: [PATCH 16/16] [#2906] Final massive proofread of the translation book-component split Some things that were moved to the components were moved back in part to the book so that it still feels complete. I also took this opportunity to tweak some things to make them more understandable. --- book/translation.rst | 471 +++++++++++++++--------- components/index.rst | 2 +- components/translation/introduction.rst | 39 +- components/translation/usage.rst | 6 +- 4 files changed, 322 insertions(+), 196 deletions(-) diff --git a/book/translation.rst b/book/translation.rst index e826d67a68e..4dcbe5e2495 100644 --- a/book/translation.rst +++ b/book/translation.rst @@ -27,27 +27,30 @@ into the language of the user:: *country* code (e.g. ``fr_FR`` for French/France) is recommended. 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: +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`` (learn about this in ":doc:`/components/translation/usage`"); + ``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. + +.. _book-translation-configuration: Configuration ------------- 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:: @@ -79,92 +82,262 @@ 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`). -Fallback and Default Locale -~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. _book-translation-basic: -If the locale hasn't been set, the ``fallback`` configuration parameter will -be used by the ``Translator``. The parameter defaults to ``en`` (see -`Configuration`_). +Basic Translation +----------------- -Alternatively, you can guarantee that a locale is set on each user's request -by defining a ``default_locale`` for the framework: +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:: -.. configuration-block:: + // ... + use Symfony\Component\HttpFoundation\Response; - .. code-block:: yaml + public function indexAction() + { + $translated = $this->get('translator')->trans('Symfony2 is great'); - # app/config/config.yml - framework: - default_locale: en + 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 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:: .. code-block:: xml - - - en - + + + + + + + Symfony2 is great + J'aime Symfony2 + + + + .. code-block:: php - // app/config/config.php - $container->loadFromExtension('framework', array( - 'default_locale' => 'en', - )); + // messages.fr.php + return array( + 'Symfony2 is great' => 'J\'aime Symfony2', + ); -.. 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. + .. code-block:: yaml -Using the Translation inside Controllers ----------------------------------------- + # messages.fr.yml + Symfony2 is great: J'aime Symfony2 -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`:: +For information on where these files should be located, see :ref:`book-translation-resource-locations`. - // src/Acme/DemoBundle/Controller/DemoController.php - namespace Amce\DemoBundle\Controller; +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``. You can also translate +message inside your :ref:`templates `. - // ... - class DemoController extends Controller - { - public function indexAction() - { - $translator = $this->get('translator'); +The Translation Process +~~~~~~~~~~~~~~~~~~~~~~~ - $translated = $translator->trans('Symfony2 is great!'); +To actually translate the message, Symfony2 uses a simple process: - return new Response($translated); - } +* The ``locale`` of the current user, which is stored on the request is determined; + +* 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. + +When using the ``trans()`` method, Symfony2 looks for the exact string inside +the appropriate message catalog and returns it (if it exists). + +Message Placeholders +-------------------- + +Sometimes, a message containing a variable needs to be translated:: + + use Symfony\Component\HttpFoundation\Response; + + public function indexAction($name) + { + $translated = $this->get('translator')->trans('Hello '.$name); + + return new Response($translated); } -Translation Locations and Naming Conventions --------------------------------------------- +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"*). + +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`. + +Pluralization +------------- + +Another complication is when you have translations that may or may not be +plural, based on some variable: + +.. code-block:: text + + There is one apple. + There are 5 apples. + +To handle this, use the :method:`Symfony\\Component\\Translation\\Translator::transChoice` +method or the ``transchoice`` tag/filter in your :ref:`template `. + +For much more information, see :ref:`component-translation-pluralization` +in the Translation component documentation. + +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. + +.. caution:: + + The ``%var%`` notation of placeholders is required when translating in + Twig templates using the tag. + +.. tip:: + + If you need to use the percent character (``%``) in a string, escape it by + doubling it: ``{% trans %}Percent: %percent%%%{% endtrans %}`` + +You can also specify the message domain and pass some additional variables: + +.. code-block:: 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

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

bar

'|trans|raw }} + +.. tip:: + + You can set the translation domain for an entire Twig template with a single tag: + + .. code-block:: jinja + + {% trans_default_domain "app" %} + + Note that this only influences the current template, not any "included" + templates (in order to avoid side effects). + +.. 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) + ) ?> + +.. _book-translation-resource-locations: + +Translation Resource/File Names and Locations +--------------------------------------------- Symfony2 looks for message files (i.e. translations) in the following locations: -* the ``/Resources/translations`` directory; +* the ``app/Resources/translations`` directory; -* the ``/Resources//translations`` directory; +* the ``app/Resources//translations`` directory; -* the ``Resources/translations/`` directory of the bundle. +* the ``Resources/translations/`` directory inside of any bundle. -The locations are listed with the highest priority first. That is you can +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. The override mechanism works at a key level: only the overridden keys need @@ -172,9 +345,8 @@ 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 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``: +The filename of the translation files is also important: 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 ":ref:`using-message-domains`"; @@ -182,17 +354,17 @@ according to the following path: ``domain.locale.loader``: * **locale**: The locale that the translations are for (e.g. ``en_GB``, ``en``, etc); * **loader**: How Symfony2 should load and parse the file (e.g. ``xliff``, - ``php`` or ``yml``). + ``php``, ``yml``, etc). The loader can be the name of any registered loader. By default, Symfony -provides the following loaders: +provides many loaders, including: * ``xliff``: XLIFF 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. +taste. For more options, see :ref:`component-translator-message-catalogs`. .. note:: @@ -211,6 +383,26 @@ taste. $ php app/console cache:clear +.. _book-translation-fallback: + +Fallback Translation Locales +---------------------------- + +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: + +1. First, Symfony looks for the translation in a ``fr_FR`` translation resource + (e.g. ``messages.fr_FR.xfliff``); + +2. If it wasn't found, Symfony looks for the translation in a ``fr`` translation + resource (e.g. ``messages.fr.xliff``); + +3. If the translation still isn't found, Symfony uses the ``fallback`` configuration + parameter, which defaults to ``en`` (see `Configuration`_). + +.. _book-translation-user-locale: + Handling the User's Locale -------------------------- @@ -299,128 +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. -Translations in Templates -------------------------- +Setting a Default Locale +~~~~~~~~~~~~~~~~~~~~~~~~ -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. - -.. caution:: - - The ``%var%`` notation of placeholders is required when translating in - Twig templates using the tag. - -.. tip:: - - If you need to use the percent character (``%``) in a string, escape it by - doubling it: ``{% trans %}Percent: %percent%%%{% endtrans %}`` - -You can also specify the message domain and pass some additional variables: - -.. code-block:: 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 %} +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: -.. _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) - ) ?> + 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; diff --git a/components/index.rst b/components/index.rst index aae1ba2cf23..d8101f9b89a 100644 --- a/components/index.rst +++ b/components/index.rst @@ -26,7 +26,7 @@ The Components serializer stopwatch templating/index - translation + translation/index yaml/index .. include:: /components/map.rst.inc diff --git a/components/translation/introduction.rst b/components/translation/introduction.rst index 937e40c67a6..31c3074680e 100644 --- a/components/translation/introduction.rst +++ b/components/translation/introduction.rst @@ -13,8 +13,8 @@ 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`_). +* Use the official Git repository (https://github.com/symfony/Translation); Constructing the Translator --------------------------- @@ -49,6 +49,8 @@ The constructor of the ``Translator`` class needs one argument: The locale. *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 ~~~~~~~~~~~~~~~~~~~~~~~~ @@ -118,9 +120,9 @@ method), the second is the resource and the third argument is the locale:: 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:: +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()); @@ -133,7 +135,7 @@ 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 + :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; @@ -146,23 +148,28 @@ You start this process by calling 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. +.. _components-fallback-locales: -Fallback Locale -~~~~~~~~~~~~~~~ +Fallback Locales +~~~~~~~~~~~~~~~~ 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 +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('en_EN'); + $translator->setFallbackLocale(array('en')); .. _using-message-domains: diff --git a/components/translation/usage.rst b/components/translation/usage.rst index 5d47ee9c1a5..78adbadfb78 100644 --- a/components/translation/usage.rst +++ b/components/translation/usage.rst @@ -12,7 +12,7 @@ Imagine you want to translate the string *"Symfony2 is great"* into French:: $translator = new Translator('fr_FR'); $translator->addLoader('array', new ArrayLoader()); $translator->addResource('array', array( - 'Symfony2 is great!' => 'J'aime Symfony2!', + 'Symfony2 is great!' => 'J\'aime Symfony2!', ), 'fr_FR'); echo $translator->trans('Symfony2 is great!'); @@ -21,6 +21,8 @@ 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 -------------------- @@ -218,6 +220,8 @@ recommended format. These files are parsed by one of the loader classes. 'user.login' => 'Login', ); +.. _component-translation-pluralization: + Pluralization -------------