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 aBottle
object (bottle app), it will also install itself as a plugin. The plugin follows the version 2 API and implements theapply()
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:_
: alias forlazy_gettext()
gettext
: alias forlazy_gettext()
ngettext
: alias forlazy_ngettext()
pgettext
: alias forlazy_pgettext()
npgettext
: alias forlazy_pngettext()
languages
: iterable containing available languages as(locale, name)
tuples
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 wherell
is the two-ltter language ID, andcc
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 toNone
, 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 asrequest.gettext
. It also setsrequest.locale
attribute to the selected locale. These attributes are used by thelazy_gettext`()
andlazy_ngettext()
, as well asi18n_path()
andi18n_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, andDOMAIN
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.
-
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 inORIGINAL_PATH
key.
-
bottle_utils.i18n.
dummy_gettext
(message)[source]¶ Mimic
gettext()
function. This is a passthrough function with the same signature asgettext()
. It can be used to simulate translation for applications that are untranslated, without the overhead of calling the realgettext()
.
-
bottle_utils.i18n.
dummy_ngettext
(singular, plural, n)[source]¶ Mimic
ngettext()
function. This is a passthrough function with the same signature asngettext()
. It can be used to simulate translation for applications that are untranslated, without the overhead of calling the realngettext()
.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 asnpgettext()
. It can be used to simulate translation for applications that are untranslated, without the overhead of calling the realnpgettext()
function.
-
bottle_utils.i18n.
dummy_pgettext
(context, message)[source]¶ Mimic
pgettext()
function. This is a passthrough function with the same signature aspgettext()
. 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, thefull_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 toi18n_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 withAttributeError
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 withAttributeError
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.