CherryPy Project Download

Tool to integrate babel (localization and gettext translations) in CherryPy.

# -*- coding: utf-8 -*-

"""Internationalization and Localization for CherryPy

This tool provides locales and loads translations based on the
HTTP-ACCEPT-LANGUAGE header. If no header is send or the given language
is not supported by the application, it falls back to
`tools.I18nTool.default`. Set `default` to the native language used in your
code for strings, so you must not provide a .mo file for it.

The tool uses `babel<>`_ for localization and
handling translations. Within your Python code you can use four functions
defined in this module and the loaded locale provided as


    from i18n_tool import ugettext as _, ungettext

    class MyController(object):
        def index(self):
            loc = cherrypy.response.i18n.locale
            s1 = _(u'Translateable string')
            s2 = ungettext(u'There is one string.',
                           u'There are more strings.', 2)
            return u'<br />'.join([s1, s2, loc.display_name])

If you have code (e.g. database models) that is executed before the response
object is available, use the *_lazy functions to mark the strings
translateable. They will be translated later on, when the text is used (and
hopefully the response object is available then).


    from i18n_tool import ugettext_lazy

    class Model:
        def __init__(self):
            name = ugettext_lazy(u'Name of the model')

For your templates read the documentation of your template engine how to
integrate babel with it. I think `Genshi<>`_ and
`Jinja 2<`_ support it out of the box.

Settings for the CherryPy configuration::

    tools.I18nTool.on = True
    tools.I18nTool.default = Your language with territory (e.g. 'en_US')
    tools.I18nTool.mo_dir = Directory holding the locale directories
    tools.I18nTool.domain = Your gettext domain (e.g. application name)

The mo_dir must contain subdirectories named with the language prefix
for all translations, containing a LC_MESSAGES dir with the compiled
catalog file in it.


    tools.I18nTool.on = True
    tools.I18nTool.default = 'en_US'
    tools.I18nTool.mo_dir = '/home/user/web/myapp/i18n'
    tools.I18nTool.domain = 'myapp'

    Now the tool will look for a file called in
    or generic: <mo_dir>/<language>/LC_MESSAGES/<domain>.mo

That's it.

:License: BSD
:Author: Thorsten Weimann <thorsten.weimann (at) gmx (dot) net>
:Date: 2010-02-05

import re

import cherrypy
from babel.core import Locale, UnknownLocaleError
from import Translations, LazyProxy

    # Python 2.6 and above
    from collections import namedtuple
    Lang = namedtuple('Lang', 'locale trans')
except ImportError:
    # Python 2.5
    class Lang(object):
        def __init__(self, locale, trans):
            self.locale = locale
            self.trans = trans

# Cache for Translations and Locale objects
_languages = {}

# Regex taken from django project ( to
# extract the HTTP-ACCEPT-LANGUAGE header
accept_language_re = re.compile(r'''
    ([A-Za-z]{1,8}(?:-[A-Za-z]{1,8})*|\*) # "en", "en-us", "x-y-z", "*"
    (?:;q=(0(?:\.\d{,3})?|1(?:.0{,3})?))? # Optional "q=1.00", "q=0.8"
    (?:\s*,\s*|$)                         # Multiple accepts per header
    ''', re.VERBOSE)

# Exception
class ImproperlyConfigured(Exception):
    """Raised if no known locale were found."""

# Public translation functions
def ugettext(message):
    """Standard translation function. You can use it in all your exposed
    methods and everywhere where the response object is available.

        message : Unicode
            The message to translate.

    :returns: The translated message.
    :rtype: Unicode
    return cherrypy.response.i18n.trans.ugettext(message)

def ugettext_lazy(message):
    """Like ugettext, but lazy.

    :returns: A proxy for the translation object.
    :rtype: LazyProxy
    def get_translation():
        return cherrypy.response.i18n.trans.ugettext(message)
    return LazyProxy(get_translation)

def ungettext(singular, plural, num):
    """Like ugettext, but considers plural forms.

        singular : Unicode
            The message to translate in singular form.
        plural : Unicode
            The message to translate in plural form.
        num : Integer
            Number to apply the plural formula on. If num is 1 or no
            translation is found, singular is returned.

    :returns: The translated message as singular or plural.
    :rtype: Unicode
    return cherrypy.response.i18n.trans.ungettext(singular, plural, num)

def ungettext_lazy(singular, plural, num):
    """Like ungettext, but lazy.

    :returns: A proxy for the translation object.
    :rtype: LazyProxy
    def get_translation():
        return cherrypy.response.i18n.trans.ungettext(singular, plural, num)
    return LazyProxy(get_translation)

# Function taken from django project
def parse_accept_language_header(raw):
    """Splits the content of the Accept-Language header to a list of

        raw : String
            The raw header (e.g. 'de-DE,de;q=0.9,en;q=0.8').

    :returns: List of languages sorted by priority if given.
    :rtype: List
    langs = []
    pieces = accept_language_re.split(raw)
    if pieces[-1]:
        return []
    for i in range(0, len(pieces) - 1, 3):
        first, lang, priority = pieces[i:i+3]
        if first:
            return []
        priority = priority and float(priority) or 1.0
        lang = lang.replace('-', '_')
        langs.append((lang, priority))
    langs.sort(lambda x, y: -cmp(x[1], y[1]))
    return [x[0] for x in langs]

def load_translation(langs, dirname, domain):
    """Loads the first existing translations for known locale and saves the
    `Lang` object in a global cache for faster lookup on the next request.

        langs : List
            List of languages as returned by `parse_accept_language_header`.
        dirname : String
            Directory of the translations (`tools.I18nTool.mo_dir`).
        domain : String
            Gettext domain of the catalog (`tools.I18nTool.domain`).

    :returns: Lang object with two attributes (Lang.trans = the translations
              object, Lang.locale = the corresponding Locale object).
    :rtype: Lang
    :raises: ImproperlyConfigured if no locale where known.
    locale = None
    for lang in langs:
        short = lang[:2].lower()
            locale = Locale.parse(lang)
            if (domain, short) in _languages:
                return _languages[(domain, short)]
            trans = Translations.load(dirname, short, domain)
        except (ValueError, UnknownLocaleError):
        # If the translation was found, exit loop
        if isinstance(trans, Translations):
    if locale is None:
        raise ImproperlyConfigured('Default locale not known.')
    _languages[(domain, short)] = res = Lang(locale, trans)
    return res

def get_lang(mo_dir, default, domain):
    """Main function which will be invoked during the request by `I18nTool`.
    If the SessionTool is on and has a lang key, this language get the
    highest priority. Default language get the lowest priority.
    The `Lang` object will be saved as `cherrypy.response.i18n` and the
    language string will also saved as `cherrypy.session['_lang_']` (if
    SessionTool is on).

        mo_dir : String
        default : String
        domain : String
    raw_langs = cherrypy.request.headers.get('Accept-Language', default)
    langs = parse_accept_language_header(raw_langs)
    sessions_on = cherrypy.request.config.get('tools.sessions.on', False)
    if sessions_on and cherrypy.session.get('_lang_', ''):
        langs.insert(0, cherrypy.session.get('_lang_', '__'))
    loc = load_translation(langs, mo_dir, domain)
    cherrypy.response.i18n = loc
    if sessions_on:
        cherrypy.session['_lang_'] = str(loc.locale)

def set_lang():
    """Sets the Content-Language response header (if not already set) to the
    language of `cherrypy.response.i18n.locale`.
    if 'Content-Language' not in cherrypy.response.headers:
        cherrypy.response.headers['Content-Language'] = str(

class I18nTool(cherrypy.Tool):
    """Tool to integrate babel translations in CherryPy."""

    def __init__(self):
        self._name = 'I18nTool'
        self._point = 'before_handler'
        self.callable = get_lang
        # Make sure, session tool (priority 50) is loaded before
        self._priority = 100

    def _setup(self):
        c = cherrypy.request.config
        if c.get('tools.staticdir.on', False) or \
           c.get('tools.staticfile.on', False):
        cherrypy.request.hooks.attach('before_finalize', set_lang) = I18nTool()


Hosted by WebFaction

Log in as guest/cherrypy to create/edit wiki pages