Translation support (bottle_utils.i18n)

How it works

This module provides plugins and functions for translation and language selection.

Language selection is based on URLs. Each path in the app is augmented with locale prefix. Therefore, /foo/bar/baz becomes /LOCALE/foo/bar/baz where LOCALE is any of the locales chosen as translation targets. When one of the supported locales is found in the incoming request’s path, then that locale is activated.

Translation is performed by calling several of the translation functions provied by this module. These are simple gettext() and ngettext() wrappers that are lazily evaluated with the help of Lazy class.

This module does not deal with message extraction or compilation. For this, you can use the standard GNU Gettext utilities.

Setting up the app for translation

To activate translations and language selection, you will need to configure the plugin and middleware.

Note

The bottle plugin class, I18NPlugin, double as WSGI middleware.

First prepare a list of languages you want to support:

LANGS = [
    ('de_DE', 'Deutsch'),
    ('en_US', 'English'),
    ('fr_FR', 'français'),
    ('es_ES', 'español'),
    ('zh_CN', '中文')
]

Also decide which locale you would like to use as default.

DEFAULT_LOCAL = 'en_US'

Finally you need to decide where you want to keep the locale directory where translations are looked up.

LOCALES_DIR = './locales'

To install the plugin and middle, you can simply pass the I18NPlugin class a bottle app object.

from bottle_utils.i18n import I18NPlugin
app = bottle.default_app()
wsgi_app = I18NPlugin(app,
                      languages=LANGS,
                      default_locale=DEFAULT_LOCALE,
                      locale_dir=LOCALES_DIR)

This installs both the Bottle plugin and the WSGI middleware, and returns a WSGI application object.

If, for any reason, you do not want the i18n WSGI middleware to be the first in the stack, you can chain middleware as usual:

from bottle_utils.i18n import I18NPlugin
app = bottle.default_app()
wsgi = SomeMiddleware(app)
wsgi = I18NPlugin(wsgi, *other_args)
wsgi.install_plugin(app)
wsgi = SomeOtherPlugin(wsgi)

The install_plugin() method only works on the wsgi app returned from the plugin class. After wrapping with another plugin, it is no longer available so it must be called immediately.

Translating in Python code

To translate in Python code, use the lazy_gettext(), lazy_ngettext(), and similar translation functions.

lazy_gettext() is usually imported as _, which is a common convention (alias) for gettext(). Other methods are aliased without the lazy_ prefix.

from bottle_utils.i18n import lazy_ngettext as ngettext, lazy_gettext as _

def handler():
    return _('This is a translatable string')

This is a convention that allows the gettext utilities to successfully extract the translation strings.

Warning

The translation functions provided by this module do not work outside of request context. If you call them in a separate thread or a subprocess, you will get an exception. If your design allows for it, convert the lazy instances to strings before passing them to code running outside the request context.

Translating in templates

Translating in templates is highly dependent on your template engine. Some engines like Jinja2 may provide their own i18n mechanisms. In engines like SimpleTemplate and Mako, the process is pretty straightfoward. The translation methods are available in the templates using the naming convention discussed in the Translating in Python code section.

<p>{{ _('Current time') }}: {{ time }}</p>

Note

In template engines that use Python in templates (SimpleTemplate, Mako, etc), the similarity between Python syntax and template syntax (the Python portion of the template anyway) allows us to extract messages from the templates the same way we do from Python code simply by asking the xgettext tool to treat the template files as Python source code.

Module contents

class bottle_utils.i18n.I18NPlugin(app, langs, default_locale, locale_dir, domain='messages', noplugin=False)[source]

Bottle plugin and WSGI middleware for handling i18n routes. This class is a middleware. However, if the app argument is a Bottle object (bottle app), it will also install itself as a plugin. The plugin follows the version 2 API and implements the apply() method which applies the plugin to all routes. The plugin and middleware parts were merged into one class because they depend on each other and can’t really be used separately.

During initialization, the class will set up references to locales, directory paths, and build a mapping between locale names and appropriate gettext translation APIs. The translation APIs are created using the gettext.translation() call. This call tries to access matching .mo file in the locale directory, and will emit a warning if such file is not found. If a .mo file does not exist for a given locale, or it is not readable, the API for that locale will be downgraded to generic gettext API.

The class will also update the bottle.BaseTemplate.defaults dict with translation-related methods so they are always available in templates (at least those that are rendered using bottle’s API. The following variables become available in all templates:

In addition, two functions for generating i18n-specific paths are added to the default context:

The middleware itself derives the desired locale from the URL. It does not read cookies or headers. It only looks for the /ll_cc/ prefix where ll is the two-ltter language ID, and cc is country code. If it finds such a prefix, it will set the locale in the envionment dict (LOCALE key) and fix the path so it doesn’t include the prefix. This allows the bottle app to have routes matching any number of locales. If it doesn’t find the prefix, it will redirect to the default locale.

If there is no appropriate locale, and LOCALE key is therfore set to None, the plugin will automatically respond with a 302 redirect to a location of the default locale.

The plugin reads the LOCALE key set by the middleware, and aliases the API for that locale as request.gettext. It also sets request.locale attribute to the selected locale. These attributes are used by the lazy_gettext`() and lazy_ngettext(), as well as i18n_path() and i18n_url() functions.

The plugin installation during initialization can be competely suppressed, if you wish (e.g., you wish to apply the plugin yourself some other way).

The locale directory should be in a format which gettext.translations() understands. This is a path that contains a subtree matching this format:

locale_dir/LANG/LC_MESSAGES/DOMAIN.mo

The LANG should match any of the supported languages, and DOMAIN should match the specified domain.

match_locale(path)[source]

Match the locale based on prefix in request path. You can customize this method for a different way of obtaining locale information.

Returning None from this method causes the plugin to use the default locale.

The return value of this method is stored in the environment dictionary as LOCALE key. It is then used by the plugin part of this class to provide translation methods to the rest of the app.

set_locale(locale)[source]

Store the passed in locale in a ‘locale’ cookie, which is used to override the value of the global default_locale.

static strip_prefix(path, locale)[source]

Strip the locale prefix from the path. This static method is used to recalculate the request path that should be passed to Bottle. The return value of this method replaces the PATH_INFO key in the environment dictionary, and the original path is saved in ORIGINAL_PATH key.

bottle_utils.i18n.dummy_gettext(message)[source]

Mimic gettext() function. This is a passthrough function with the same signature as gettext(). It can be used to simulate translation for applications that are untranslated, without the overhead of calling the real gettext().

bottle_utils.i18n.dummy_ngettext(singular, plural, n)[source]

Mimic ngettext() function. This is a passthrough function with the same signature as ngettext(). It can be used to simulate translation for applications that are untranslated, without the overhead of calling the real ngettext().

This function returns the verbatim singular message if n is 1, otherwise the verbatim plural message.

bottle_utils.i18n.dummy_npgettext(context, singular, plural, n)[source]

Mimic npgettext() function. This is a passthrough function with teh same signature as npgettext(). It can be used to simulate translation for applications that are untranslated, without the overhead of calling the real npgettext() function.

bottle_utils.i18n.dummy_pgettext(context, message)[source]

Mimic pgettext() function. This is a passthrough function with the same signature as pgettext(). It can be used to simulate translation for applications that are untranslated, without the overhead of calling the real ``pgettext()`.

bottle_utils.i18n.full_path()[source]

Calculate full path including query string for current request. This is a helper function used by i18n_path(). It uses the current request context to obtain information about the path.

bottle_utils.i18n.i18n_path(*args, **kwargs)[source]

Return current request path or specified path for given or current locale. This function can be used to obtain paths for different locales.

If no path argument is passed, the full_path() is called to obtain the full path for current request.

If locale argument is omitted, current locale is used.

bottle_utils.i18n.i18n_url(*args, **kwargs)[source]

Return a named route in localized form. This function is a light wrapper around Bottle’s get_url() function. It passes the result to i18n_path().

If locale keyword argument is passed, it will be used instead of the currently selected locale.

bottle_utils.i18n.i18n_view(tpl_base_name=None, **defaults)[source]

Renders a template with locale name as suffix. Unlike the normal view decorator, the template name should not have an extension. The locale names are appended to the base template name using underscore (‘_’) as separator, and lower-case locale identifier.

Any additional keyword arguments are used as default template variables.

For example:

@i18n_view('foo')
def render_foo():
    # Renders 'foo_en' for English locale, 'foo_fr' for French, etc.
    return
bottle_utils.i18n.lazy_gettext(*args, **kwargs)[source]

Lazily evaluated version of gettext().

This function uses the appropriate Gettext API object based on the value of bottle.request.gettext set by the plugin. It will fail with AttributeError exception if the plugin is not installed.

bottle_utils.i18n.lazy_ngettext(*args, **kwargs)[source]

Lazily evaluated version of ngettext().

This function uses the appropriate Gettext API object based on the value of bottle.request.gettext set by the plugin. It will fail with AttributeError exception if the plugin is not installed.

bottle_utils.i18n.lazy_npgettext(context, singular, plural, n)[source]

bottle_utils.i18n.lazy_ngettext() wrapper with message context.

This function is a wrapper around bottle_utils.i18n.lazy_ngettext() that provides message context. It is useful in situations where messages are used in several different contexts for which separate translations may be required for different languages.

The function itself is not lazy, but it returns the return value of lazy_ngettext(), and it is effectively lazy. Hence the name.

bottle_utils.i18n.lazy_pgettext(context, message)[source]

lazy_gettext() wrapper with message context.

This function is a wrapper around lazy_gettext() that provides message context. It is useful in situations where short messages (usually one word) are used in several different contexts for which separate translations may be needed in different languages.

The function itself is not lazily evaluated, but its return value comes from lazy_gettext() call, and it is effectively lazy as a result.