summaryrefslogtreecommitdiff
path: root/doc/source/user/usage.rst
blob: d26acd107e945cd93e476f428f2c41c395b76605 (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
266
267
268
269
270
271
272
273
274
.. _usage:

=====================================================
 How to Use oslo.i18n in Your Application or Library
=====================================================

Installing
==========

At the command line::

    $ pip install oslo.i18n

.. _integration-module:

Creating an Integration Module
==============================

To use oslo.i18n in a project (e.g. myapp), you will need to create a
small integration module to hold an instance of
:class:`~oslo_i18n.TranslatorFactory` and references to
the marker functions the factory creates.

.. note::

   Libraries probably do not want to expose the new integration module
   as part of their public API, so rather than naming it
   ``myapp.i18n`` it should be called ``myapp._i18n`` to indicate that
   it is a private implementation detail, and not meant to be used
   outside of the library's own code.

.. note::

   Starting with the Pike series, OpenStack no longer supports log
   translation. It is not necessary to add translation instructions to
   new code, and the instructions can be removed from old code.  Refer
   to the email thread `understanding log domain change
   <http://lists.openstack.org/pipermail/openstack-dev/2017-March/thread.html#113365>`_
   on the openstack-dev mailing list for more details.

.. code-block:: python

    # myapp/_i18n.py

    import oslo_i18n

    DOMAIN = "myapp"

    _translators = oslo_i18n.TranslatorFactory(domain=DOMAIN)

    # The primary translation function using the well-known name "_"
    _ = _translators.primary

    # The contextual translation function using the name "_C"
    # requires oslo.i18n >=2.1.0
    _C = _translators.contextual_form

    # The plural translation function using the name "_P"
    # requires oslo.i18n >=2.1.0
    _P = _translators.plural_form

    # Translators for log levels.
    #
    # NOTE(dhellmann): This is not needed for new projects as of the
    # Pike series.
    #
    # The abbreviated names are meant to reflect the usual use of a short
    # name like '_'. The "L" is for "log" and the other letter comes from
    # the level.
    _LI = _translators.log_info
    _LW = _translators.log_warning
    _LE = _translators.log_error
    _LC = _translators.log_critical


    def get_available_languages():
        return oslo_i18n.get_available_languages(DOMAIN)

Then, in the rest of your code, use the appropriate marker function
for each message:

.. code-block:: python

    from myapp._i18n import _, _LW, _LE

    # ...

    variable = "openstack"
    LOG.warning(_LW('warning message: %s'), variable)

    # ...

    try:

        # ...

    except AnException1:

        # Log only
        LOG.exception(_LE('exception message'))

    except AnException2:

        # Raise only
        raise RuntimeError(_('exception message'))

    else:

        # Log and Raise
        msg = _('Unexpected error message')
        LOG.exception(msg)
        raise RuntimeError(msg)

.. note::

   The import of multiple modules from _i18n on a single line is
   a valid exception to
   `OpenStack Style Guidelines <http://docs.openstack.org/developer/hacking/#imports>`_
   for import statements.


It is important to use the marker functions (e.g. _LI), rather than
the longer form of the name, because the tool that scans the source
code for translatable strings looks for the marker function names.

.. warning::

    The old method of installing a version of ``_()`` in the builtins
    namespace is deprecated. Modifying the global namespace affects
    libraries as well as the application, so it may interfere with
    proper message catalog lookups. Calls to
    :func:`gettextutils.install` should be replaced with the
    application or library integration module described here.


Handling hacking Objections to Imports
======================================

The `OpenStack Style Guidelines <http://docs.openstack.org/developer/hacking/#imports>`_
prefer importing modules and accessing names from those modules after
import, rather than importing the names directly. For example:

::

    # WRONG
    from foo import bar

    bar()

    # RIGHT

    import foo

    foo.bar()

The linting tool hacking_ will typically complain about importing
names from within modules. It is acceptable to bypass this for the
translation marker functions, because they must have specific names
and their use pattern is dictated by the message catalog extraction
tools rather than our style guidelines. To bypass the hacking check
for imports from this integration module, add an import exception to
``tox.ini``.

For example::

    # tox.ini
    [hacking]
    import_exceptions = myapp._i18n

.. _hacking: https://pypi.python.org/pypi/hacking

.. _lazy-translation:

Lazy Translation
================

Lazy translation delays converting a message string to the translated
form as long as possible, including possibly never if the message is
not logged or delivered to the user in some other way. It also
supports logging translated messages in multiple languages, by
configuring separate log handlers.

Lazy translation is implemented by returning a special object from the
translation function, instead of a unicode string. That special
message object supports some, but not all, string manipulation
APIs. For example, concatenation with addition is not supported, but
interpolation of variables is supported. Depending on how translated
strings are used in an application, these restrictions may mean that
lazy translation cannot be used, and so it is not enabled by default.

To enable lazy translation, call :func:`enable_lazy`.

::

    import oslo_i18n

    oslo_i18n.enable_lazy()

Translating Messages
====================

Use :func:`~oslo_i18n.translate` to translate strings to
a specific locale. :func:`translate` handles delayed translation and
strings that have already been translated immediately. It should be
used at the point where the locale to be used is known, which is often
just prior to the message being returned or a log message being
emitted.

::

    import oslo_i18n

    trans_msg = oslo_i18n.translate(msg, my_locale)

If a locale is not specified the default locale is used.

Available Languages
===================

Only the languages that have translations provided are available for
translation. To determine which languages are available the
:func:`~oslo_i18n.get_available_languages` is provided. The integration
module provides a domain defined specific function.

.. code-block:: python

    import myapp._i18n

    languages = myapp._i18n.get_available_languages()

.. seealso::

   * :doc:`guidelines`

Displaying translated messages
==============================

Several preparations are required to display translated messages in your
running application.

Preferred language
  You need to specify your preferred language through an environment variable.
  The preferred language can be specified by ``LANGUAGE``, ``LC_ALL``,
  ``LC_MESSAGES``, or ``LANGUAGE`` (A former one has a priority).

  ``oslo_i18n.translate()`` can be used to translate a string to override the
  preferred language.

  .. note::

     You need to use ``enable_lazy()`` to override the preferred language
     by using ``oslo_i18n.translate()``.

Locale directory
  Python ``gettext`` looks for binary ``mo`` files for the given domain
  using the path ``<localedir>/<language>/LC_MESSAGES/<domain>.mo``.
  The default locale directory varies on distributions,
  and it is ``/usr/share/locale`` in most cases.

  If you store message catalogs in a different location,
  you need to specify the location via an environment variable
  named ``<DOMAIN>_LOCALEDIR`` where ``<DOMAIN>`` is an upper-case
  domain name with replacing ``_`` and ``.`` with ``-``.
  For example, ``NEUTRON_LOCALEDIR`` for a domain ``neutron`` and
  ``OSLO_I18N_LOCALEDIR`` for a domain ``oslo_i18n``.

  .. note::

     When you specify locale directories via ``<DOMAIN>_LOCALEDIR``
     environment variables, you need to specify an environment variable per
     domain. More concretely, if your application using a domain ``myapp`
     uses oslo.policy, you need to specify both ``MYAPP_LOCALEDIR`` and
     ``OSLO_POLICY_LOCALEDIR`` to ensure that translation messages from
     both your application and oslo.policy are displayed.