summaryrefslogtreecommitdiff
path: root/docs/narr/i18n.rst
blob: 45bbab3429b7d37e54d95e9444729aa74dfb6a53 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
.. index::
   single: i18n
   single: internationalization

.. _i18n_chapter:

Using Internationalization
==========================

:mod:`repoze.bfg` offers an internationalization (i18n) subsystem that
can be used to translate the text of buttons, the text of error
messages and other software-defined values into the native language of
aq user of your :mod:`repoze-bfg` driven website.

Activating Translation
----------------------

By default, a :mod:`repoze.bfg` application performs no translation
without explicitly configuring a :term:`translator factory`.  To make
any translation at all happen, you must pass a translator factory
object to your application's
:mod:`repoze.bfg.configuration.Configurator` by supplying it with a
``translator_factory`` argument.  For example:

.. code-block:: python
   :linenos:

   from repoze.bfg.configuration import Configurator
   from repoze.bfg.i18n import InterpolationOnlyTranslator
   config = Configurator(translator_factory=InterpolationOnlyTranslator)

.. note:: At the time of this writing, only one (very weak) translator
   factory named :class:`repoze.bfg.i18n.InterpolationOnlyTranslator`
   ships as part of the :mod:`repoze.bfg` software.  This class only
   does basic interpolation of mapping values; it does not actually do
   any language translation.

Creating a Translation String
-----------------------------

While you write your software, you can insert specialized markup into
your Python code that makes it possible for the system to translate
text values into the languages used by your application's users.  This
markup generates a :term:`translation string`.  A translation string
is an object that behave mostly like a normal Unicode object, except
that it also carries around extra information related to its job as
part of :mod:`repoze.bfg` the translation machinery.

Using The ``TranslationString`` Class
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

One way to create a translation string is to use the
:class:`repoze.bfg.i18n.TranslationString` callable:

.. code-block:: python
   :linenos:

   from repoze.bfg.i18n import TranslationString
   ts = TranslationString('Add')

This creates a Unicode-like object that is a TranslationString.

The first argument to :class:`repoze.bfg.i18n.TranslationString` is
the ``text``; it is required.  The ``text`` value acts as a default
value for the translation string if a translation to the user's
language cannot be found at translation time.  The ``text`` argument
must be a Unicode object or an ASCII string.  The text may optionally
contain *replacement markers*.  For instance:

.. code-block:: python
   :linenos:

   from repoze.bfg.i18n import TranslationString
   ts = TranslationString('Add ${number}')

Within the string above, ``${stuff}`` is a replacement marker.  It
will be replaced by whatever is in the *mapping* for a translation
string.  The mapping may be supplied at the same time as the
replacement marker:

.. code-block:: python
   :linenos:

   from repoze.bfg.i18n import TranslationString
   ts = TranslationString('Add ${number}', mapping={'number':1})

Any number of replacement markers can be present in th text value, any
number of times.  Only markers which can be replaced by the values in
the *mapping* will be replaced at translation time.  The others will
not be interpolated and will be output literally.

A translation string should also usually carry a *domain*.  The domain
represents a translation category to disambiguate it from other
translations of the same msgid, in case they conflict.

.. code-block:: python
   :linenos:

   from repoze.bfg.i18n import TranslationString
   ts = TranslationString('Add ${number}', mapping={'number':1}, 
                          domain='form')

The above translation string named a domain of "form".  A
:term:`translator` function will often use the domain to locate a file
on the filesystem which contains translations for a given context.  In
this case, if it were trying to translate to our msgid to German, it
might try to find a translation from a :term:`gettext` file like this
one::

   locale/de/LC_MESSAGES/form.mo

In other words, it would want to take translations from the "form.mo"
translation file in the German language.

Domain translation support is dependent upon the :term:`translator
factory` in use.  Not all translator factories use domain information
that is associated with a translation string.  However, it is always
safe to associate a given translation string with a domain; the
information is ignored by translators that don't support it.

Finally, the TranslationString constructor accepts a ``msgid``
argument.  If a ``msgid`` argument is supplied, it is used as the
*message identifier* for the translation string.  When ``msgid`` is
``None``, the ``text`` value passed to a TranslationString is used as
an implicit message identifier.  Message identifiers are matched with
translations in translation files, so it is often useful to create
translation strings with "opaque" message identifiers unrelated to
their default text:

.. code-block:: python
   :linenos:

   from repoze.bfg.i18n import TranslationString
   ts = TranslationString('Add ${number}', msgid='add-number',
                           domain='form', mapping={'number':1})

Using the ``bfg_tstr`` Translation String Factory
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Another way to generate a translation string is to use the
:attr:`repoze.bfg.i18n.bfg_tstr` object.  This object is a
*translation string factory*.  Basically a translation string factory
presets the ``domain`` value of any :term:`translation string`
generated by using it.  For example:

.. code-block:: python
   :linenos:

   from repoze.bfg.i18n import bfg_tstr as _
   ts = _('Add ${number}', msgid='add-number', mapping={'number':1})

.. note:: We imported ``bfg_tstr`` as the name ``_``.  This is a
   convention which will be supported by translation file generation
   tools.

The result of calling ``bfg_tstr`` is a
:class:`repoze.bfg.i18n.TranslationString` instance.  Even though a
``domain`` value was not passed to bfg_tstr (as would have been
necessary if the :class:`repoze.bfg.i18n.TranslationString`
constructor were used instead of a translation string factory), the
``domain`` attribute of the resulting translation string will be
``bfg``.  As a result, the previous code example is completely
equivalent (except for spelling) to:

.. code-block:: python
   :linenos:

   from repoze.bfg.i18n import TranslationString as _
   ts = _('Add ${number}', msgid='add-number', mapping={'number':1}, 
          domain='form')

Using the ``TranslationStringFactory`` Class
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

You can set up your own translation string factory much like the one
provided as :mod:`repoze.bfg.i18n.bfg_tstr` by using the
:class:`repoze.bfg.i18n.TranslationStringFactory` class.  For example,
if you'd like to create a translation string factory which presets the
``domain`` value of generated translation strings to ``form``, you'd
do something like this:

.. code-block:: python
   :linenos:

   from repoze.bfg.i18n import TranslationStringFactory
   _ = TranslationStringFactory('form')
   ts = _('Add ${number}', msgid='add-number', mapping={'number':1})

.. note:: We created this factory with the name ``_``.  This is a
   convention which will be supported by translation file generation
   tools.

Performing a Translation by Hand
--------------------------------

If you need to perform translation "by hand" in an application, use
the :func:`repoze.bfg.i18n.get_translator` function to obtain a
:term:`translator` .  :func:`repoze.bfg.i18n.get_translator` will
return either the current translator defined by the
``translator_factory`` passed to the Configurator at startup or a
default translator if no explicit translator factory has been
registered.

Remember that a translator is a callable which accepts either a
:term:`translation string` and which returns a Unicode object
representing the translation.  So, generating a translation in a view
component of your application might look like so:

.. code-block:: python
   :linenos:

   from repoze.bfg.i18n import get_translator

   from repoze.bfg.i18n import bfg_tstr as _
   ts = _('Add ${number}', mapping={'number':1})

   def aview(request):
       translator = get_translator(request)
       translated = translator(ts)

A translator may be called any number of times after being retrieved
from the ``get_translator`` function.

Defining A Translator Factory
-----------------------------

A translator factory is an object which accepts a :term:`request` and
which returns a :term:`translator` callable.

The :term:`translator` callable returned by a translator factory must
accept a single positional argument which represents a
:term:`translation string` and should return a fully localized and
expanded translation of the translation string.

A simplistic implementation of both a translator factory and a
translator (via its constructor and ``__call__`` methods respecively)
named :class:`repoze.bfg.i18n.InterpolationOnlyTranslator` is defined.
Here it is:

.. code-block:: python
   :linenos:

    from repoze.bfg.i18n import interpolate

    class InterpolationOnlyTranslator(object):
        def __init__(self, request):
            self.request = request

        def __call__(self, message):
            mapping = getattr(message, 'mapping', None)
            return interpolate(message, mapping)

The exact operation of a translator is left to the implementor of a
particular translator factory.  You can define and use your own
translator factory by passing it as the ``translator_factory``
argument to the :class:`repoze.bfg.configuration.Configurator`
constructor.

.. code-block:: python
   :linenos:

   from repoze.bfg.configuration import Configurator
   from repoze.bfg.i18n import InterpolationOnlyTranslator
   config = Configurator(translator_factory=InterpolationOnlyTranslator, ...)