Internationalizing Your Application

To internationalize your application, you have to:

  • Set up the configuration
  • Format your strings
  • Collect your strings
  • Translate your messages
  • Compile the message catalogs

Set Up the Configuration

  1. Enable the session_filter.on and i18n.run_template_filter settings in your configuration, preferably in app.cfg:

    session_filter.on = True
    i18n.run_template_filter = True
    
  2. Optionally set a default locale with i18n.default_locale. Example:

    i18n.default_locale = "tw"
    

See the configuration reference for the full list of i18n options available.

Format Your Strings

1. Wrap Your Strings

All text strings in your Python code you want to be translated should be included in the _() gettext function. This function is builtin to turbogears, so you don’t need to import it specifically into your module, if you have already called import turbogears. The _() function and all formatting funtions (defined in turbogears.i18n.format) return unicode strings.

For example:

from turbogears import controllers, expose

class Controller(controllers.Root):
    @expose(template="myapp.templates.welcome")
    def index(self):
        return dict(message=_("Welcome"))

If you want to explicitly pass in the locale in the _() call, you can do this:

return dict(message=_("Welcome", "de"))

2. Templates

Handling text strings in Genshi or Kid templates is somewhat easier. If you set the configuration setting i18n.run_template_filter to True, all text inside your Genshi or Kid templates will be passed through a translation function automatically.

Note

Note that text inside widget templates, etc. will also be translated.

The user locale (see below) can be overriden in your template by using the lang attribute. For example:

<div>
  <p>Welcome</p>
  <p lang="de">Welcome</p>
</div>

The first “Welcome” will be translated using the user locale setting, the second using the German locale. Assuming your user locale is English, you would see:

<div>
  <p>Welcome</p>
  <p lang="de">Willkommen</p>
</div>

While Genshi also supports translation of attribute values, this is not supported for Kid templates and so, if you use Kid, they have to be handled explicitly by using the _() call:

<img py:attrs="src=_('flag_icon_gb.bmp')" />

However, as of TurboGears 1.1, using the _() function in Genshi templates is not supported out-of-the box.

For more information about translation of Genshi templates, pleae see the Genhsi Internationalization and Localization documentation.

3. Localize Dates and Numbers

The turbogears.i18n package has a number of useful functions for handling date, location and number formats. Data for these formats is contained in Python modules under turbogears/i18n/data, for example the module for Danish is turbogears/i18n/data/da.py. The functions include:

format_date
Returns a localized date string.
format_currency
Returns a formatted currency value (e.g. in German 56.89 > 56,89).
format_decimal
Returns the given number formatted with grouping for thousands and decimal places acording to the currrent locale.
get_countries
Returns a list of tuples, with the international country code and localized name (e.g. (“AU”, “Australia”)).

All have full docstrings explaining parameters and provide sophisticated control over formatting.

Ensure any date or number strings are formatted using the turbogears.i18n formatting functions.

4. Localization of JavaScript

Since TurboGears 1.0.4 and in TurboGears 1.1 , it’s possible to localize JavaScript as well. To do so, encapsulate the strings in your .js-files in _()-calls as described above. This will make them subject to the collection process, as with other file-types as well.

The messages then appear in the .po-files as normal texts. Translate them as you desire, compile, and then invoke the new command

tg-admin i18n create_js_messages

The result are messages-<language>.js-files in your static javascript folder. To handle these properly at runtime, you need to include a widget in each rendered page. You can achieve this by setting

tg.include_widgets = ['turbogears.jsi18nwidget']

in your configuration.

This will define the needed _() via including i18n.js.

If for whatever reason the location for the messages-<language>.js-files is to be changed, you can use the --js-base-dir-option to the create_js_messages command. But then you have to make sure that the js-files are included properly. That can be done using the

turbogears.widgets.base.jsi18nwidget.register_package_provider(self,
    package=None, directory=None, domain=None)

method, where directory is the path that defaults to static/javascript and package is the package name registered as static directory - default is the current project’s base package. With domain you can change the name of the catalogs, default is messages.

Collect Your Strings

To collect your strings, you have to create your message catalogs in the right directory structure for all your text strings and supported languages.

By default, _() looks for subdirectory “locales” and domain “messages” in your project directory. You can override these settings in your config file by setting i18n.locale_dir and i18n.domain respectively.

If a language file(.mo) is not found, the _() call will return the plain text string.

TurboGears ships with a suite of tools that help you to build and maintain message catalogs for translation. One is “admi18n” from the Toolbox and another is part of tg-admin i18n command.

To collect the strings from your code an templates, using the tg-admin command, run the following in your project’s top-level directory:

tg-admin i18n collect

Translate Your Messages

For every language that you want to support, now run the following in your project’s top-level directory:

tg-admin i18n add <lang>

and replace <lang> with the two-letter code of the language. This will create a subdirectory in the locales directory, named after the language code. In this directory, under the LC_MESSAGES sub-directory, you will find several *.po files, which you need to translate by filling in the msgstr for every msgid.

Warning

*.po files should be saved with UTF-8 encoding and no byte-order marker (BOM) at the start of the file, or the compiling of the message catalogs will fail.

Compile the Message Catalogs

After you have collected the messages into *.po files and translated them, run the following command in your project’s top-level directory to convert the *.po files into the binary mesage catalogs (file extension .mo):

tg-admin i18n compile

Tips and Hints

Finding the User Locale

The default function finds the locale setting in the following order:

  1. By looking for a session value. By default the session key is locale, but this can be changed via the config setting i18n.session_key.
  2. By looking in the HTTP Accept-Language header for the most preferred language.
  3. By using the default locale (config setting i18n.default_locale, by default “en”).

The default locale function, turbogears.i18n.util._get_locale, can be overriden by your own callable using the config setting i18n.get_locale. This function takes no arguments and should return the locale name as a string.

Handling Non-ASCII Form Data

TurboGears automatically sets the correct charset info in Content-Type HTTP header and well-behaving web clients should send back form submissions with the same encoding. This usually just works but there are some buggy clients out there, so might want to tweak how requests are decoded.

If you prefer you may add the following to your HTML templates to enforce the page encoding:

<meta content="text/html; charset=UTF-8" http-equiv="content-type" />

You can also change the output encoding of your templates with the genshi.default_encoding or kid.encoding options repectively. These both default to "utf-8, which is the recommended setting.

When the browser sends non-ascii data in a request (via a textarea, for example), CherryPy would give you the string as is. TurboGears, however uses a request filter to decode all of the incoming request parameters into unicode strings using a given encoding. In most cases you should keep it that way, or the i18n filter or error handlers may behave unexpectedly.

You can, however, force the encoding with which the request data is decoded, via the decoding_filter.encoding configuration setting. Alternatively, you can set the fallback encoding, which is used if decoding the request using the encoding the client provided failed, with the configuration setting decoding_filter.default_encoding, which defaults to "utf-8".

Form Error Messages

For translation of FormEncode messages be sure to use a recent version of TurboGears, since version >= 1.0.2 automatically requires FormEncode >= 0.7.0, which brings support for i18n.

tg-admin i18n add also installs the FormEncode.mo and TurboGears.mo files into the project locale directory.

Custom Validators and Localized Error Messages

If you write custom validators and use _("...") to localize your messages you will get a TypeError exception when starting your app, e.g.:

TypeError: Error when calling the metaclass bases - expected string or buffer

You need to add a small “i18n hack” to your validator as demonstrated by Christoph Zwerschke:

class NoWar(validators.FancyValidator):
     def _(s): return s
     gettextargs = {'domain': 'messages'}
     messages = {'war': _("Don't mention the war!")}

     def validate_python(self, value, state):
         if 'war' in value:
             raise validators.Invalid(
                 self.message('war', state), value, state)

Please note that you need at least TurboGears 1.0.2 and FormEncode 0.7 for localized error messages.

i18n Without Using the Session

You don’t need to use the session module to get translated messages. You can insert a custom function into the TurboGears configuration, which returns an appropriate locale string like “de”. This is then used when TurboGears needs to translate something.

The configuration change should take place in the start-up code created by tg-admin quickstart (i.e. the start function in commands.py in your project’s package directory). Assuming a very simple scenario where you support only one language, this may be as easy as this:

[...]
from <package>.controllers import Root

turbogears.config.update({'i18n.get_locale' : lambda: "de"})

turbogears.start_server(Root())

After this modification, all localized strings should be displayed in German.