Source code for railgun.website.i18n
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# @file: railgun/website/i18n.py
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# This file is released under BSD 2-clause license.
import os
from flask import request
from flask.ext.babel import Babel, get_locale, Locale
from flask.ext.login import current_user
from .context import app
#: A :class:`flask.ext.babel.Babel` object. It extends the
#: :data:`~railgun.website.context.app` to support translations for
#: each request.
babel = Babel(app)
[docs]def list_locales():
"""Get a list of available :class:`babel.core.Locale` objects.
Whatever translations the system provides, "en" should always
appear in the list. The returned list should be sorted in the order
of display names, which may be vary in different languages.
:return: :class:`list` of :class:`babel.core.Locale` objects.
"""
ret = [Locale('en')] + babel.list_translations()
return sorted(ret, cmp=lambda a, b: cmp(a.display_name, b.display_name))
[docs]def get_best_locale_name(locale_names):
"""Select the best matching locale from `locale_names` according to
current request.
The locales are evaluated according to the language, script and territory.
If no best choice is found, fallback to ``config.DEFAULT_LOCALE``.
Moreover, if even ``config.DEFAULT_LOCALE`` does not appear in
`locale_names`, choose the last item in `locale_names`.
Usage:
.. code-block:: python
# If current user prefers zh-CN, or zh-TW.
>>> get_best_locale_name(['zh-cn', 'en'])
'zh-cn'
# If current user prefers en-US.
>>> get_best_locale_name(['zh-cn', 'en'])
'en'
# If current user prefers ja-JP, and default locale is zh-CN.
>>> get_best_locale_name(['zh-cn', 'en'])
'zh-cn'
# If current user prefers ja-JP, and default locale is zh-CN.
>>> get_best_locale_name(['fr', 'en'])
'en'
:param locale_names: List of alternative locale names.
:type locale_names: :class:`list`
:return: The best matching or fallback locale name.
:rtype: :class:`str`
"""
top_score = 0
top_name = None
req_locale = get_locale()
for name in locale_names:
l = Locale.parse(name.replace('-', '_'))
# If locale object is the same, return True at once
if l == req_locale:
return name
# If language not match, give up this locale
if l.language != req_locale.language:
continue
# Exam the matched score
score = 0
# script match, give 5
if l.script == req_locale.script:
score += 5
# territory match, give 3
if l.territory == req_locale.territory:
score += 3
# variant match, give 3
# TODO: check these score weights
if l.variant == req_locale.variant:
score += 3
# Update top_name if > top_score
if score > top_score:
top_name = name
top_score = score
# Fallback to default locale or last locale
if top_name is None:
if app.config['DEFAULT_LOCALE'] in locale_names:
top_name = app.config['DEFAULT_LOCALE']
else:
top_name = locale_names[-1]
return top_name
# Flask-Babel only accepts zh_Hans_CN to construct locale object for zh_CN,
# However the browser reports zh_CN. So load the alias names to locales
# here.
def __load_aliases():
ret = {}
aliases_path = os.path.join(app.config['TRANSLATION_DIR'], 'aliases.txt')
try:
f = open(aliases_path, 'rb')
for l in f:
arr = l.split('=')
if len(arr) < 2:
continue
k, v = arr[0].strip(), arr[1].strip()
if k and v:
ret[k] = v
f.close()
except Exception:
pass
return ret
locale_aliases = __load_aliases()
# Construct the best match list for locale selector according to the
# locale files which babel has loaded, and the alias name table.
def __make_best_match():
trans = babel.list_translations()
ret = set(['en'])
for t in trans:
tname = str(t)
ret.add(tname)
for t in locale_aliases.iterkeys():
ret.add(t)
return list(ret)
best_matches = __make_best_match()
@babel.localeselector
def __select_request_locale():
"""Detect the request locale according to the user setting and browser
request headers.
"""
if current_user.is_authenticated():
return current_user.locale
best_match = request.accept_languages.best_match(best_matches)
return locale_aliases.get(best_match, best_match)
@babel.timezoneselector
def __select_request_timezone():
"""Detect the request timezone according to the user settings.
Flask-Babel should be initialized to format datetime in the user's
timezone after this method is called, so the timezone name should be
compatible with :mod:`flask.ext.babel`.
"""
# TODO: set the correct time zone according to user configuration.
# if possible, also guess by client ip.
if current_user.is_authenticated():
return current_user.timezone
return None
@app.context_processor
def __inject_template_context():
"""Add `pagelng` to Jinja2 template context.
`pagelng` is the locale name which can be used to describe the language of
html elements. For example::
<html lang={{ pagelang }}>
...
</html>
"""
pagelng = get_locale()
if pagelng.territory:
pagelng = '%s-%s' % (pagelng.language, pagelng.territory)
else:
pagelng = pagelng.language
return dict(pagelng=pagelng)