Skip to content

Commit b75012d

Browse files
committed
added documentation for translations
1 parent 0502439 commit b75012d

File tree

4 files changed

+327
-3
lines changed

4 files changed

+327
-3
lines changed

guides/bundles/best_practices.rst

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ follows::
6464
config/
6565
doc/
6666
index.rst
67+
translations/
6768
views/
6869
web/
6970
Tests/
@@ -91,16 +92,17 @@ files are going to be part of the repository.
9192

9293
The following classes and files have specific emplacements:
9394

94-
========================= =====================
95+
========================= ===========================
9596
Type Directory
96-
========================= =====================
97+
========================= ===========================
9798
Controllers ``Controller/``
99+
Translation files ``Resources/translations/``
98100
Templates ``Resources/views/``
99101
Unit and Functional Tests ``Tests/``
100102
Web Resources ``Resources/web/``
101103
Configuration ``Resources/config/``
102104
Commands ``Command/``
103-
========================= =====================
105+
========================= ===========================
104106

105107
Classes
106108
-------
@@ -166,6 +168,14 @@ must provide two slots: ``content`` and ``head``).
166168
The only other template engine supported is Twig, but only for specific
167169
cases.
168170

171+
Translation Files
172+
-----------------
173+
174+
If a bundle provides message translations, they must be defined in the XLIFF
175+
format; the domain should be named after the bundle name (``bundle.hello``).
176+
177+
A bundle must not override existing messages from another bundle.
178+
169179
Configuration
170180
-------------
171181

guides/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Dive into Symfony2 with the topical guides:
1010
doctrine/index
1111
emails
1212
templating/index
13+
translation
1314
validator
1415
forms
1516
event/index

guides/map.rst.inc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
* :doc:`/guides/emails`
2121
* :doc:`/guides/validator`
2222
* :doc:`/guides/forms`
23+
* :doc:`/guides/translation`
2324
* **Templating**:
2425

2526
* :doc:`/guides/templating/helpers` |

guides/translation.rst

Lines changed: 312 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,312 @@
1+
Translations
2+
============
3+
4+
The :namespace:`Symfony\\Component\\Translation` Component provides a way to
5+
internationalize all the messages in your application.
6+
7+
A *message* can be any string you want to internationalize. Messages are
8+
categorized by locale and domain.
9+
10+
A *domain* allows you to better organized your messages in a given locale (it
11+
can be any string; by default, all messages are stored under the ``messages``
12+
domain).
13+
14+
A *locale* can be any string, but we recommended to use the ISO639-1 language
15+
code, followed by a underscore (``_``), followed by the ISO3166 country code
16+
(use ``fr_FR`` for French/France for instance).
17+
18+
Configuration
19+
-------------
20+
21+
Before using the translator features, enable it in your configuration:
22+
23+
.. configuration-block::
24+
25+
.. code-block:: yaml
26+
27+
# app/config/config.yml
28+
app.config:
29+
translator: { fallback: en }
30+
31+
.. code-block:: xml
32+
33+
<!-- app/config/config.xml -->
34+
<app:config>
35+
<app:translator fallback="en" />
36+
</app:config>
37+
38+
.. code-block:: php
39+
40+
// app/config/config.php
41+
$container->loadFromExtension('app', 'config', array(
42+
'translator' => array('fallback' => 'en'),
43+
));
44+
45+
The ``fallback`` attribute defines the fallback locale when a translation does
46+
not exist in the user locale.
47+
48+
.. tip::
49+
When a translation does not exist for a locale, the translator tries to
50+
find the translation for the language (``fr`` when the locale is ``fr_FR``
51+
for instance); if it also fails, it looks for a translation for the
52+
fallback locale.
53+
54+
The locale used in translations is the one stored in the user session.
55+
56+
Translations
57+
------------
58+
59+
Translations are available through the ``translator`` service
60+
(:class:`Symfony\\Component\\Translation\\Translator`). Use the
61+
:method:`Symfony\\Component\\Translation\\Translator::trans` method to
62+
translate a message::
63+
64+
$t = $this['translator']->trans('Symfony2 is great!');
65+
66+
If you have placeholders in strings, pass their values as the second
67+
argument::
68+
69+
$t = $this['translator']->trans('Symfony2 is {{ what }}!', array('{{ what }}' => 'great'));
70+
71+
.. note::
72+
The placeholders can have any form, but using the ``{{ var }}`` notation
73+
allows the message to be used in Twig templates.
74+
75+
By default, the translator looks for messages in the default ``messages``
76+
domain. Override it via the third argument::
77+
78+
$t = $this['translator']->trans('Symfony2 is great!', array(), 'applications');
79+
80+
Catalogues
81+
----------
82+
83+
Translations are stored on the filesystem and discovered by Symfony2, thanks
84+
to some conventions.
85+
86+
Store translations for messages found in a bundle under the
87+
``Resources/translations/`` directory; and override them under the
88+
``app/translations/`` directory.
89+
90+
Each message file must be named according to the following pattern:
91+
``domain.locale.loader`` (the domain name, followed by a dot (``.``), followed
92+
by the locale name, followed by a dot (``.``), followed by the loader name.)
93+
94+
The loader can be the name of any registered loader. By default, Symfony2
95+
provides the following loaders:
96+
97+
* ``php``: PHP file;
98+
* ``xliff``: XLIFF file;
99+
* ``yaml``: YAML file.
100+
101+
Each file consists of pairs of id/translation strings for the given domain and
102+
locale. The id can be the message in the main locale of your application of a
103+
unique identifier:
104+
105+
.. configuration-block::
106+
107+
.. code-block:: xml
108+
109+
<?xml version="1.0"?>
110+
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
111+
<file source-language="en" datatype="plaintext" original="file.ext">
112+
<body>
113+
<trans-unit id="1">
114+
<source>Symfony2 is great</source>
115+
<target>J'aime Symfony2</target>
116+
</trans-unit>
117+
<trans-unit id="2">
118+
<source>symfony.great</source>
119+
<target>J'aime Symfony2</target>
120+
</trans-unit>
121+
</body>
122+
</file>
123+
</xliff>
124+
125+
.. code-block:: php
126+
127+
return array(
128+
'Symfony2 is great' => 'J\'aime Symfony2',
129+
'symfony.great' => 'J\'aime Symfony2',
130+
);
131+
132+
.. note::
133+
You can also store translations in a database, or any other storage by
134+
providing a custom
135+
:class:`Symfony\\Component\\Translation\\Loader\\LoaderInterface` class.
136+
See below to learn how to register custom loaders.
137+
138+
Pluralization
139+
-------------
140+
141+
Message pluralization is a tough topic as the rules can be quite complex. For
142+
instance, here is the mathematic representation of the Russian pluralization
143+
rules::
144+
145+
(($number % 10 == 1) && ($number % 100 != 11)) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2);
146+
147+
As you can see, in Russian, you can have three different plural forms, based
148+
on this algorithm. For each form, the plural is different, and so the
149+
translation is also different. In such a case, you can provide all
150+
pluralization forms as strings separated by pipes (``|``)::
151+
152+
'The is one apple|There are {{ count }} apples'
153+
154+
Based on a given number, the translator chooses the right plural form. If
155+
``count`` is ``1``, the translator will use the first string (``The is one
156+
apple``) as the translation, if not, it will use ``There are {{ count }}
157+
apples``.
158+
159+
Here is the French translation::
160+
161+
'Il y a {{ count }} pomme|Il y a {{ count }} pommes'
162+
163+
Even if the string looks similar (it is made of two sub-strings separated by a
164+
pipe), the French rules are different: the first form (no plural) is used when
165+
``count`` is ``0`` or ``1``. So, the translator will automatically use the
166+
first string (``Il y a {{ count }} pomme``) when ``count`` is ``0`` or ``1``.
167+
168+
The rules are quite simple for English and French, but for Russian, you'd
169+
better have a hint to know which rule matches which string. To help
170+
translators, you can optionally "tag" each string like this::
171+
172+
'one: There is one apple|some: There are {{ count }} apples'
173+
174+
'none_or_one: Il y a {{ count }} pomme|some: Il y a {{ count }} pommes'
175+
176+
The tags are really only hints for translators to help them understand the
177+
context of the translation (note that the tags do not need to be the same in
178+
the original message and in the translated one).
179+
180+
.. tip:
181+
As tags are optional, the translator doesn't use them (the translator will
182+
only get a string based on its position in the string).
183+
184+
Sometimes, you want a different translation for specific cases (for ``0``, or
185+
when the count is large enough, when the count is negative, ...). For such
186+
cases, you can use explicit math ranges::
187+
188+
'{0} There is no apples|{1} There is one apple|]1,19] There are {{ count }} apples|[20,Inf] There are many apples'
189+
190+
You can also mix explicit math rules and standard rules. The position for
191+
standard rules is defined after removing the explicit rules::
192+
193+
'{0} There is no apples|[20,Inf] There are many apples|There is one apple|a_few: There are {{ count }} apples'
194+
195+
An :class:`Symfony\\Component\\Translator\\Range` can represent a finite set
196+
of numbers::
197+
198+
{1,2,3,4}
199+
200+
Or numbers between two other numbers::
201+
202+
[1, +Inf]
203+
]-1,2[
204+
205+
The left delimiter can be ``[`` (inclusive) or ``]`` (exclusive). The right
206+
delimiter can be ``[`` (exclusive) or ``]`` (inclusive). Beside numbers, you
207+
can use ``-Inf`` and ``+Inf`` for the infinite.
208+
209+
The translator
210+
:method:`Symfony\\Component\\Translation\\Translator::transChoice` method
211+
knows how to deal with plural::
212+
213+
$t = $this['translator']->transChoice(
214+
'{0} There is no apples|{1} There is one apple|]1,Inf] There are {{ count }} apples',
215+
10,
216+
array('{{ count }}' => 10)
217+
);
218+
219+
Notice that the second argument is the number to use to determine which plural
220+
string to use.
221+
222+
Translations in Templates
223+
-------------------------
224+
225+
Most of the time, translation occurs in templates. Symfony2 provides native
226+
support for both PHP and Twig templates.
227+
228+
PHP Templates
229+
~~~~~~~~~~~~~
230+
231+
The translator service is accessible in PHP templates through the
232+
``translator`` helper:
233+
234+
.. code-block:: html+php
235+
236+
<?php echo $view['translator']->trans('Symfony2 is great!') ?>
237+
238+
<?php echo $view['translator']->transChoice(
239+
'{0} There is no apples|{1} There is one apple|]1,Inf] There are {{ count }} apples',
240+
10,
241+
array('{{ count }}' => 10)
242+
) ?>
243+
244+
Twig Templates
245+
~~~~~~~~~~~~~~
246+
247+
Symfony2 provides specialized Twig tags (``trans`` and ``transChoice``) to
248+
help with message translation:
249+
250+
.. code-block:: jinja
251+
252+
{% trans "Symfony2 is great!" %}
253+
254+
{% trans %}
255+
Foo {{ name }}
256+
{% endtrans %}
257+
258+
{% transchoice count %}
259+
{0} There is no apples|{1} There is one apple|]1,Inf] There are {{ count }} apples
260+
{% endtranschoice %}
261+
262+
The ``transChoice`` tag automatically get the variables from the current
263+
context and pass them to the translator. This mechanism only works when you
264+
use placeholder using the ``{{ var }}`` pattern.
265+
266+
You can also specify the message domain:
267+
268+
.. code-block:: jinja
269+
270+
{% trans "Foo {{ name }}" from app %}
271+
272+
{% trans from app %}
273+
Foo {{ name }}
274+
{% endtrans %}
275+
276+
{% transchoice count from app %}
277+
{0} There is no apples|{1} There is one apple|]1,Inf] There are {{ count }} apples
278+
{% endtranschoice %}
279+
280+
.. _translation_loader_tag:
281+
282+
Enabling Custom Loaders
283+
-----------------------
284+
285+
To enable a custom loader, add it as a regular service in one of your
286+
configuration, tag it with ``translation.loader`` and define an ``alias``
287+
attribute (for filesystem based loaders, the alias is the file extension you
288+
must use to reference the loader):
289+
290+
.. configuration-block::
291+
292+
.. code-block:: yaml
293+
294+
services:
295+
translation.loader.your_helper_name:
296+
class: Fully\Qualified\Loader\Class\Name
297+
tags:
298+
- { name: translation.loader, alias: alias_name }
299+
300+
.. code-block:: xml
301+
302+
<service id="translation.loader.your_helper_name" class="Fully\Qualified\Loader\Class\Name">
303+
<tag name="translation.loader" alias="alias_name" />
304+
</service>
305+
306+
.. code-block:: php
307+
308+
$container
309+
->register('translation.loader.your_helper_name', 'Fully\Qualified\Loader\Class\Name')
310+
->addTag('translation.loader', array('alias' => 'alias_name'))
311+
;
312+

0 commit comments

Comments
 (0)