Source code for railgun.common.lazy_i18n

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# @file: railgun/common/lazy_i18n.py
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# This file is released under BSD 2-clause license.

"""Utilities for serializable lazy translated strings.

As mentioned in :ref:`i18n_everywhere`, we want our Railgun system to
support translations, even for the strings already stored in databases.

We know that a simple solution to the translations in a website system
is to use the :mod:`gettext` package.  Suppose we've created a message
category including the translation from "My name is %(name)s." to
"我的姓名是 %(name)s.", then we may write::

    from gettext import gettext

    >>> type(gettext('My name is %(name)s.'))
    str

    >>> print gettext('My name is %(name)s.') % {'name': 'Alice'}
    '我的姓名是 Alice.'

The main functionality of `gettext` is to lookup a translation table,
find the suitable translated text for input string, and then return it.
However, :mod:`gettext` can only be configured to use a global locale.

To support multi-users in a website environment, we may use the gettext
and lazy_gettext method from :mod:`flask.ext.babel`::

    from flask.ext.babel import gettext, lazy_gettext

    >>> gettext('My name is %(name)s.', name='Alice')
    '我的姓名是 Alice.'

    >>> type(gettext('My name is %(name)s.', name='Alice'))
    :class:`str`

    >>> type(lazy_gettext('My name is %(name)s.', name='Alice'))
    :class:`speaklater._LazyString`

The `gettext` from :mod:`flask.ext.babel` will use the locale settings
from current request, but it will translate the string at once, so you may
not create a global translated text with `gettext`.

On the other side, the `lazy_gettext` will store all the arguments
and create a :class:`speaklater._LazyString` instance, and will only
do the translation until it is being output.  So you can make a global
translated text with `lazy_gettext`.

However, we still have problems. :class:`speaklater._LazyString` could not
be serialized into a JSON message, nor could it be stored into database.

This is why I create my own :class:`GetTextString` and :func:`lazy_gettext`
method.  However, these utilities do have some limitations:

*   The arguments passed to :func:`lazy_gettext` must be "plain"
    objects, including :class:`bool`, :class:`str`, :class:`unicode` and
    the numeric types.
*   `ngettext`, `dgettext` and `ndgettext` are not supported.

.. note::

    When to use :func:`flask.ext.babel.lazy_gettext` and when to use
    :func:`railgun.common.lazy_i18n.lazy_gettext`?  Well, if you want
    to store the lazy string into database, or if you want to transfer
    the string over website api, you have to use :func:`lazy_gettext`
    provided by this module.  However, if you just want to use it
    for website display, both can work.
"""

from flask.ext.babel import gettext as _babel_gettext


[docs]class GetTextString(object): """Make a serializable lazy gettext object. :param __s: The text to be translated. :type __s: :class:`basestring` :param kwargs: Keyword arguments to be formatted. :type kwargs: :class:`dict` """ def __init__(self, __s, **kwargs): self.text = __s self.kwargs = kwargs
[docs] def render(self): """Render the lazy gettext object into :class:`unicode` string.""" # There's a very strange behaviour: gettext('') will return the version # string of babel. Get rid of it! if not self.text: return '' return _babel_gettext(self.text, **self.kwargs)
def __unicode__(self): return unicode(self.render()) def __str__(self): return str(self.render()) def __repr__(self): return '<GetText(%s)>' % self.render()
lazy_gettext = GetTextString
[docs]def lazystr_to_plain(s): """Convert a :class:`basestring` or a :class:`GetTextString` object to a JSON serializable plain object. You may refer to :ref:`json_GetTextString` for more details about the JSON format. :param s: The string object to be converted. :return: A plain object that is composed only with :class:`dict`, :class:`list`, :class:`str`, :class:`bool` and numeric types. :raises: :class:`TypeError` if `s` is not converible. """ import speaklater if s is None or isinstance(s, str) or isinstance(s, unicode): return s if isinstance(s, speaklater._LazyString): return unicode(s) if isinstance(s, GetTextString): return {'text': s.text, 'kwargs': s.kwargs} raise TypeError('"%s" (%s) is not a string object.' % (s, type(s)))
[docs]def plain_to_lazystr(s): """Convert a plain object to :class:`basestring` or :class:`GetTextString`. You may refer to :ref:`json_GetTextString` for more details about the JSON format. :param s: The plain object. :return: A :class:`basestring` or a :class:`GetTextString` object. :raises: :class:`KeyError` if `s` is not converible. """ if s is None or isinstance(s, str) or isinstance(s, unicode): return s return GetTextString(s['text'], **s['kwargs'])