Table of Contents
The aim of internationalization (i18n) is to be able to support multiple natural languages without explicit changes in the program code or templates. In a properly internationalized application, you can add or remove languages with few to no code changes. You can even have your website automatically pick the correct language for your visitors based on their web browser language preferences.
WebCore includes one example project that demonstrates the use of translation functions. As examples are not included in the downloadable WebCore distribution, you need to get the source tarball from Github. The example is located in the examples/i18n directory. To run the example, make sure you have WebCore and Genshi installed. Then, run the following commands (the first one only needs to be run once):
Then point your web browser to http://localhost:8080/ and you should see “Hello World!”. It also has other methods you can try, such as time. Take a look at the source code to see more.
First, you will need to install the Babel distribution. It greatly simplifies creation and updating of locales in an application. It is probably a good idea to familiarize yourself with some of the basic concepts related to i18n. You can read about these from the Babel documentation.
Here, the “default language” is the language you use when writing text literals in the code and in the templates of your application. This is typically either English or your native language. This is the language that will be used as a basis for translating the strings to other languages. It also works as the fallback language for any untranslated strings. To add more fallback languages, you can use the add_fallback() function.
To enable i18n support and set the default language for the application, make the following changes to your application’s INI file:
web.locale.i18n = True
web.locale.fallback = fr
Replace fr with the appropriate ISO 639-1 code for the language you want to use as the fallback language (when automatic negotiation fails). If you omit the web.locale.preferred parameter, en will be the default. The``web.locale.i18n`` parameter is necessary for automatic language detection and inclusion of the translation machinery in the global template namespace. These modifications are unnecessary if you don’t need automatic language detection and only use the translation machinery explicitly in the code (covered later in this tutorial).
For Babel to be able to extract your translatable strings from your application, you need to tell it how to find them. For that, you need to create a file named babel.cfg in your project root (where setup.py is located). The following file is an example configuration file for Babel that extracts strings from Python code, Genshi templates (assumed to end with .html) and Mako templates (assumed to end with .mako). You can similarly extract messages from templates belonging to any template engine as long as it comes with an appropriate Babel plugin.
# Extraction from Python source files
[python: **.py]
# Extraction from Genshi templates
[genshi: **.html]
input_encoding = utf-8
# Extraction from Mako templates
[mako: **.mako]
input_encoding = utf-8
The format of the file matching patterns is described in the Babel documentation.
The last configuration step is adding some new sections to your setup.cfg file (create it if you don’t have one already). This is mostly done to define where your locale files should go. A newly created setup.cfg might look like this:
# Babel configuration
[compile_catalog]
domain = myproject
directory = myproject/locale
statistics = true
[extract_messages]
keywords = __:1,2 L_
mapping_file = babel.cfg
output_file = myproject/locale/myproject.pot
width = 80
[init_catalog]
domain = services
input_file = myproject/locale/myproject.pot
output_dir = myproject/locale
[update_catalog]
domain = services
input_file = myproject/locale/myproject.pot
output_dir = myproject/locale
previous = true
Just replace myproject with the actual name of your project.
In order for Babel to know which strings are eligible for translation, they need to be marked as such. Consider the following Python code:
print 'Hello World'
In order to mark that literal as translatable, it needs to be prefixed with _ or any other keyword previously defined in the extract_messages section of setup.cfg:
from web.core.locale import _
print _('Hello World')
WebCore provides the following convenience functions for translation:
| Function | Gettext equivalent | Notes |
|---|---|---|
| _ | ugettext | Returns unicode |
| __ | ungettext | Returns unicode |
| L_ | ugettext | Lazily evaluated, returns unicode |
The L_ function is special in the way that the wrapped string is not translated when the expression is evaluated, but instead each time the string is accessed. Thus you can use it to put translatable strings on the module or class level without it being translated at import time, but instead after the correct language for the request has been determined.
Different template engines handle message extraction differently. Genshi, for example, extracts all strings not inside ignored tags by default. Mako, on the other hand, always requires explicit use of translation functions. For other template engines, see their respective documentation for more information on this.
An example of a Genshi template prepared for i18n:
<html>
<head>
<title>Hello World</title>
</head>
<body>
<p>${_('Hello from WebCore v%s!') % web.release}</p>
</body>
</html>
The same for Mako would be:
<html>
<head>
<title>${_('Hello World')}</title>
</head>
<body>
<p>${_('Hello from WebCore v%s!') % web.release}</p>
</body>
</html>
The translation functions are automatically inserted into the template’s namespace by WebCore’s i18n middleware.
Note
One point to remember here is to defer string substitution to after the invocation of the translation function. Otherwise the translation procedure will likely fail.
To change the user’s current language, use the web.core.locale.set_lang() function. Similarly, web.core.locale.get_lang() will tell you what the user’s current language is. When you set the language, the setting is saved in the user’s session if sessions are enabled (web.sessions = True).
Applications sometimes need to work with several different languages within a single request. In such situations it is necessary to use a different translator than the default one:
from web.core.locale import get_translator
_ = get_translator('de').ugettext
print _('Hello World!')
This will print “Hallo Welt!”, assuming proper prior setup of the message catalog. Remember that for message extraction to work properly, the strings still need to be wrapped by _ or any other translation function.
When your configuration is set up and your translatable strings have been marked in your source code and templates, it is time to build the catalogs. First, you need to extract the translatable messages from your application into a .pot file, which will be used as a template for all translations:
$ python setup.py extract_messages
Next, you need to initialize the individual catalogs for each language you want to support:
$ python setup.py init_catalog -l fr
$ python setup.py init_catalog -l de
This step only needs to be done once for every new language you add. When you have the catalogs, you can issue the catalog update command which will create .po files for each language based on the .pot template created earlier:
$ python setup.py update_catalog
At this point you can start the actual translation work by editing the .po files of each language. When you are done, just compile the .po files to .mo files:
$ python setup.py compile_catalog
Now you’re done! You can try out your application in different languages by switching the language preferences of your web browser.
In the course of your application development, you will likely need to update both the source strings and the translations. The procedure for updating translations is as follows:
If you are only updating the translations and haven’t changed any source strings, you can skip the first two steps.