From 99f68b3f091064a0a521cf061bcf77f5eceaca4e Mon Sep 17 00:00:00 2001 From: WouterJ Date: Thu, 29 Nov 2012 23:06:53 +0100 Subject: [PATCH 01/15] 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 58add6ec2c8..e54d0e5cc9e 100644 --- a/book/translation.rst +++ b/book/translation.rst @@ -88,6 +88,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 865b2b274e6..9424185a06a 100644 --- a/components/index.rst +++ b/components/index.rst @@ -25,6 +25,7 @@ The Components serializer stopwatch templating/index + translation yaml/index .. include:: /components/map.rst.inc diff --git a/components/map.rst.inc b/components/map.rst.inc index acd892e0c67..0877c94ba24 100644 --- a/components/map.rst.inc +++ b/components/map.rst.inc @@ -107,6 +107,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 4ddb7aa2f96a9f721f23ce7059779374419b6aa1 Mon Sep 17 00:00:00 2001 From: WouterJ Date: Sat, 27 Apr 2013 13:33:58 +0200 Subject: [PATCH 02/15] 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 5b0332106c440bbc7915889d0c2818bbcc68cc05 Mon Sep 17 00:00:00 2001 From: WouterJ Date: Sat, 27 Apr 2013 13:44:46 +0200 Subject: [PATCH 03/15] Moved framework indepent stuff to component docs --- book/translation.rst | 556 +------------------------------------ components/translation.rst | 505 +++++++++++++++++++++++++++++++++ 2 files changed, 512 insertions(+), 549 deletions(-) diff --git a/book/translation.rst b/book/translation.rst index e54d0e5cc9e..bf3e19487a1 100644 --- a/book/translation.rst +++ b/book/translation.rst @@ -85,207 +85,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 @@ -336,167 +135,15 @@ taste. providing a custom class implementing the :class:`Symfony\\Component\\Translation\\Loader\\LoaderInterface` interface. -.. index:: - single: Translations; Creating translation resources - -Creating Translations -~~~~~~~~~~~~~~~~~~~~~ - -The act of creating translation files is an important part of "localization" -(often abbreviated `L10n`_). Translation files consist of a series of -id-translation pairs for the given domain and locale. The source is the identifier -for the individual translation, and can be the message in the main locale (e.g. -"Symfony is great") of your application or a unique identifier (e.g. -"symfony2.great" - see the sidebar below): - -.. configuration-block:: - - .. code-block:: xml - - - - - - - - Symfony2 is great - J'aime Symfony2 - - - symfony2.great - J'aime Symfony2 - - - - - - .. code-block:: php - - // src/Acme/DemoBundle/Resources/translations/messages.fr.php - return array( - 'Symfony2 is great' => 'J\'aime Symfony2', - 'symfony2.great' => 'J\'aime Symfony2', - ); - - .. code-block:: yaml - - # src/Acme/DemoBundle/Resources/translations/messages.fr.yml - Symfony2 is great: J'aime Symfony2 - symfony2.great: J'aime Symfony2 - -Symfony2 will discover these files and use them when translating either -"Symfony2 is great" or "symfony2.great" into a French language locale (e.g. -``fr_FR`` or ``fr_BE``). - -.. sidebar:: Using Real or Keyword Messages - - This example illustrates the two different philosophies when creating - messages to be translated:: - - $translated = $translator->trans('Symfony2 is great'); - - $translated = $translator->trans('symfony2.great'); - - In the first method, messages are written in the language of the default - locale (English in this case). That message is then used as the "id" - when creating translations. - - In the second method, messages are actually "keywords" that convey the - idea of the message. The keyword message is then used as the "id" for - any translations. In this case, translations must be made for the default - locale (i.e. to translate ``symfony2.great`` to ``Symfony2 is great``). - - The second method is handy because the message key won't need to be changed - in every translation file if you decide that the message should actually - read "Symfony2 is really great" in the default locale. - - The choice of which method to use is entirely up to you, but the "keyword" - format is often recommended. - - Additionally, the ``php`` and ``yaml`` file formats support nested ids to - avoid repeating yourself if you use keywords instead of real text for your - ids: - - .. configuration-block:: - - .. code-block:: yaml - - symfony2: - is: - great: Symfony2 is great - amazing: Symfony2 is amazing - has: - bundles: Symfony2 has bundles - user: - login: Login - - .. code-block:: php - - return array( - 'symfony2' => array( - 'is' => array( - 'great' => 'Symfony2 is great', - 'amazing' => 'Symfony2 is amazing', - ), - 'has' => array( - 'bundles' => 'Symfony2 has bundles', - ), - ), - 'user' => array( - 'login' => 'Login', - ), - ); - - The multiple levels are flattened into single id/translation pairs by - adding a dot (.) between every level, therefore the above examples are - equivalent to the following: - - .. configuration-block:: - - .. code-block:: yaml - - symfony2.is.great: Symfony2 is great - symfony2.is.amazing: Symfony2 is amazing - symfony2.has.bundles: Symfony2 has bundles - user.login: Login - - .. code-block:: php - - return array( - 'symfony2.is.great' => 'Symfony2 is great', - 'symfony2.is.amazing' => 'Symfony2 is amazing', - 'symfony2.has.bundles' => 'Symfony2 has bundles', - 'user.login' => 'Login', - ); - -.. index:: - single: Translations; Message domains - - -.. _using-message-domains: - -Using Message Domains ---------------------- - -As you've seen, message files are organized into the different locales that -they translate. The message files can also be organized further into "domains". -When creating message files, the domain is the first portion of the filename. -The default domain is ``messages``. For example, suppose that, for organization, -translations were split into three different domains: ``messages``, ``admin`` -and ``navigation``. The French translation would have the following message -files: - -* ``messages.fr.xliff`` -* ``admin.fr.xliff`` -* ``navigation.fr.xliff`` +.. 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 @@ -514,9 +161,6 @@ via the ``request`` object:: $request->setLocale('en_US'); -.. index:: - single: Translations; Fallback and default locale - It is also possible to store the locale in the session instead of on a per request basis. If you do this, each subsequent request will have this locale. @@ -527,43 +171,6 @@ request basis. If you do this, each subsequent request will have this locale. See the :ref:`book-translation-locale-url` section below about setting the locale via routing. -Fallback and Default Locale -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -If the locale hasn't been set explicitly in the session, the ``fallback_locale`` -configuration parameter will be used by the ``Translator``. The parameter -defaults to ``en`` (see `Configuration`_). - -Alternatively, you can guarantee that a locale is set on each user's request -by defining a ``default_locale`` for the framework: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - framework: - default_locale: en - - .. code-block:: xml - - - - en - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('framework', array( - 'default_locale' => 'en', - )); - -.. versionadded:: 2.1 - The ``default_locale`` parameter was defined under the session key - originally, however, as of 2.1 this has been moved. This is because the - locale is now set on the request instead of the session. - .. _book-translation-locale-url: The Locale and the URL @@ -613,7 +220,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. @@ -621,122 +228,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 @@ -849,35 +340,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 @@ -1009,9 +471,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 649cf846d5db21a33bf176505faf780609c6b33e Mon Sep 17 00:00:00 2001 From: WouterJ Date: Mon, 29 Apr 2013 11:54:27 +0200 Subject: [PATCH 04/15] 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 bf3e19487a1..e520a1bbf5e 100644 --- a/book/translation.rst +++ b/book/translation.rst @@ -85,6 +85,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 @@ -242,6 +279,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*: @@ -452,6 +494,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 ------- @@ -473,3 +522,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 0e495e873d50aabee2234cb44f77cc3bf691b995 Mon Sep 17 00:00:00 2001 From: WouterJ Date: Mon, 29 Apr 2013 12:28:14 +0200 Subject: [PATCH 05/15] 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 e520a1bbf5e..fb327821364 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: @@ -86,7 +84,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 @@ -122,11 +120,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: @@ -149,7 +167,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); @@ -160,8 +178,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. @@ -172,7 +190,7 @@ taste. providing a custom class implementing the :class:`Symfony\\Component\\Translation\\Loader\\LoaderInterface` interface. -.. 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 @@ -182,9 +200,6 @@ taste. $ php app/console cache:clear -.. index:: - single: Translations; User's locale - Handling the User's Locale -------------------------- @@ -265,9 +280,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 ------------------------- @@ -279,11 +291,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*: @@ -299,6 +306,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 @@ -463,7 +475,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 b67185346142db7b39d3a9879cf8ca8f5d818798 Mon Sep 17 00:00:00 2001 From: WouterJ Date: Sun, 12 May 2013 17:06:42 +0200 Subject: [PATCH 06/15] 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 0877c94ba24..b18da704496 100644 --- a/components/map.rst.inc +++ b/components/map.rst.inc @@ -107,9 +107,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 56174184fc293184cb143e76ceb19bb9bacea87f Mon Sep 17 00:00:00 2001 From: WouterJ Date: Sun, 12 May 2013 17:33:02 +0200 Subject: [PATCH 07/15] 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 e19b0f7a5455e9d18df9c9180e8951ac46d25062 Mon Sep 17 00:00:00 2001 From: WouterJ Date: Sun, 12 May 2013 17:36:25 +0200 Subject: [PATCH 08/15] 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 b18da704496..c372ccb99a2 100644 --- a/components/map.rst.inc +++ b/components/map.rst.inc @@ -110,6 +110,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 865c31c5abe7b5ef03d1d79ac1af7c08e3d95a85 Mon Sep 17 00:00:00 2001 From: WouterJ Date: Sun, 12 May 2013 17:37:15 +0200 Subject: [PATCH 09/15] 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 8b0f804caeebe3a931f0bd6f4084cde5ddf994c9 Mon Sep 17 00:00:00 2001 From: WouterJ Date: Sun, 19 May 2013 17:59:06 +0200 Subject: [PATCH 10/15] 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 67d1d00dd8291ab08d5b512c221145540a6989e7 Mon Sep 17 00:00:00 2001 From: WouterJ Date: Sun, 19 May 2013 18:05:30 +0200 Subject: [PATCH 11/15] 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 631c028dcf04555c7f165b25ae1028b0ea42065b Mon Sep 17 00:00:00 2001 From: WouterJ Date: Sun, 19 May 2013 18:12:47 +0200 Subject: [PATCH 12/15] 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 fb327821364..203c81907b4 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; @@ -124,13 +125,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() @@ -524,7 +526,8 @@ steps: * Abstract messages in your application by wrapping each in either the :method:`Symfony\\Component\\Translation\\Translator::trans` or - :method:`Symfony\\Component\\Translation\\Translator::transChoice` methods; + :method:`Symfony\\Component\\Translation\\Translator::transChoice` methods + (learn about this in ":doc:`/components/translation/usage`"); * Translate each message into multiple locales by creating translation message files. Symfony2 discovers and processes each file because its name follows 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 95fb6bf1db19556240a3b78187d6c8894614596a Mon Sep 17 00:00:00 2001 From: WouterJ Date: Thu, 6 Jun 2013 19:17:44 +0200 Subject: [PATCH 13/15] 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 203c81907b4..0048d491394 100644 --- a/book/translation.rst +++ b/book/translation.rst @@ -87,9 +87,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: @@ -311,7 +311,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 7b2a580b9b95a26dd97becd46e0e7d8d2846ffdf Mon Sep 17 00:00:00 2001 From: WouterJ Date: Sun, 18 Aug 2013 15:33:22 +0200 Subject: [PATCH 14/15] 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 7f28ddce674ac44554af6b1b3bca781cdb993311 Mon Sep 17 00:00:00 2001 From: WouterJ Date: Sat, 24 Aug 2013 16:04:59 +0200 Subject: [PATCH 15/15] 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 0048d491394..13397b6e58b 100644 --- a/book/translation.rst +++ b/book/translation.rst @@ -124,7 +124,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