1.. _tutorials.i18n:
2
3Internationalization and localization with webapp2
4==================================================
5In this tutorial we will learn how to get started with
6:mod:`webapp2_extras.i18n`. This module provides a complete collection of
7tools to localize and internationalize apps. Using it you can create
8applications adapted for different locales and timezones and with
9internationalized date, time, numbers, currencies and more.
10
11
12Prerequisites
13-------------
14If you don't have a package installer in your system yet (like ``pip`` or
15``easy_install``), install one. See :ref:`tutorials.installing.packages`.
16
17
18Get Babel and Pytz
19------------------
20The i18n module depends on two libraries: ``babel`` and ``pytz`` (or
21``gaepytz``). So before we start you must add the ``babel`` and ``pytz``
22packages to your application directory (for App Engine) or install it in your
23virtual environment (for other servers).
24
25For App Engine, download ``babel`` and ``pytz`` and add those libraries to
26your app directory:
27
28- Babel: http://babel.edgewall.org/
29- Pytz: http://pypi.python.org/pypi/gaepytz
30
31For other servers, install those libraries in your system using ``pip``.
32App Engine users also need babel installed, as we use the command line
33utility provided py it to extract and update message catalogs.
34This assumes a `*nix` environment:
35
36.. code-block:: text
37
38   $ sudo pip install babel
39   $ sudo pip install gaepytz
40
41Or, if you don't have pip but have ``easy_install``:
42
43.. code-block:: text
44
45   $ sudo easy_install babel
46   $ sudo easy_install gaepytz
47
48
49Create a directory for translations
50-----------------------------------
51We need a directory inside our app to store a messages catalog extracted
52from templates and Python files. Create a directory named ``locale`` for
53this.
54
55If you want, later you can rename this directory the way you prefer and adapt
56the commands we describe below accordingly. If you do so, you must change the
57default i18n configuration to point to the right directory. The configuration
58is passed when you create an application, like this::
59
60    config = {}
61    config['webapp2_extras.i18n'] = {
62        'translations_path': 'path/to/my/locale/directory',
63    }
64
65    app = webapp2.WSGIApplication(config=config)
66
67If you use the default ``locale`` directory name, no configuration is needed.
68
69
70Create a simple app to be translated
71------------------------------------
72For the purposes of this tutorial we will create a very simple app with a
73single message to be translated. So create a new app and save this as
74``main.py``::
75
76    import webapp2
77
78    from webapp2_extras import i18n
79
80    class HelloWorldHandler(webapp2.RequestHandler):
81        def get(self):
82            # Set the requested locale.
83            locale = self.request.GET.get('locale', 'en_US')
84            i18n.get_i18n().set_locale(locale)
85
86            message = i18n.gettext('Hello, world!')
87            self.response.write(message)
88
89    app = webapp2.WSGIApplication([
90        ('/', HelloWorldHandler),
91    ], debug=True)
92
93    def main():
94        app.run()
95
96    if __name__ == '__main__':
97        main()
98
99Any string that should be localized in your code and templates must be wrapped
100by the function :func:`webapp2_extras.i18n.gettext` (or the shortcut ``_()``).
101
102Translated strings defined in module globals or class definitions should use
103:func:`webapp2_extras.i18n.lazy_gettext` instead, because we want translations
104to be dynamic -- if we call ``gettext()`` when the module is imported we'll
105set the value to a static translation for a given locale, and this is not
106what we want. ``lazy_gettext()`` solves this making the translation to be
107evaluated lazily, only when the string is used.
108
109
110Extract and compile translations
111--------------------------------
112We use the `babel command line interface <http://babel.edgewall.org/wiki/Documentation/cmdline.html>`_
113to extract, initialize, compile and update translations. Refer to Babel's
114manual for a complete description of the command options.
115
116The extract command can extract not only messages from several template engines
117but also ``gettext()`` (from :py:mod:`gettext`) and its variants from Python
118files. Access your project directory using the command line and follow this
119quick how-to:
120
121**1.** Extract all translations. We pass the current app directory to be
122scanned. This will create a ``messages.pot`` file in the ``locale``
123directory with all translatable strings that were found:
124
125.. code-block:: text
126
127   $ pybabel extract -o ./locale/messages.pot ./
128
129You can also provide a `extraction mapping file <http://babel.edgewall.org/wiki/Documentation/messages.html#extraction-method-mapping-and-configuration>`_
130that configures how messages are extracted. If the configuration file is
131saved as ``babel.cfg``, we point to it when extracting the messages:
132
133.. code-block:: text
134
135   $ pybabel extract -F ./babel.cfg -o ./locale/messages.pot ./
136
137**2.** Initialize the directory for each locale that your app will support.
138This is done only once per locale. It will use the ``messages.pot`` file
139created on step 1. Here we initialize three translations, ``en_US``, ``es_ES``
140and ``pt_BR``:
141
142.. code-block:: text
143
144   $ pybabel init -l en_US -d ./locale -i ./locale/messages.pot
145   $ pybabel init -l es_ES -d ./locale -i ./locale/messages.pot
146   $ pybabel init -l pt_BR -d ./locale -i ./locale/messages.pot
147
148**3.** Now the translation catalogs are created in the ``locale`` directory.
149Open each ``.po`` file and translate it. For the example above, we have only
150one message to translate: our ``Hello, world!``.
151
152Open ``/locale/es_ES/LC_MESSAGES/messages.po`` and translate it to
153``¡Hola, mundo!``.
154
155Open ``/locale/pt_BR/LC_MESSAGES/messages.po`` and translate it to
156``Olá, mundo!``.
157
158**4.** After all locales are translated, compile them with this command:
159
160.. code-block:: text
161
162   $ pybabel compile -f -d ./locale
163
164That's it.
165
166
167Update translations
168-------------------
169When translations change, first repeat step 1 above. It will create a new
170``.pot`` file with updated messages. Then update each locales:
171
172.. code-block:: text
173
174   $ pybabel update -l en_US -d ./locale/ -i ./locale/messages.pot
175   $ pybabel update -l es_ES -d ./locale/ -i ./locale/messages.pot
176   $ pybabel update -l pt_BR -d ./locale/ -i ./locale/messages.pot
177
178After you translate the new strings to each language, repeat step 4, compiling
179the translations again.
180
181
182Test your app
183-------------
184Start the development server pointing to the application you created for this
185tutorial and access the default language:
186
187    http://localhost:8080/
188
189Then try the Spanish version:
190
191    http://localhost:8080/?locale=es_ES
192
193And finally, try the Portuguese version:
194
195    http://localhost:8080/?locale=pt_BR
196
197Voilà! Our tiny app is now available in three languages.
198
199
200What else
201---------
202The :mod:`webapp2_extras.i18n` module provides several other functionalities
203besides localization. You can use it to internationalize dates, currencies
204and numbers, and there are helpers to set the locale or timezone automatically
205for each request. Explore the API documentation to learn more.
206