CherryPy Project Download

LocalizationTranslation: i18n_tool.py

Line 
1 # -*- coding: utf-8 -*-
2
3 """Internationalization and Localization for CherryPy
4
5 **Tested with CherryPy 3.1.2**
6
7 This tool provides locales and loads translations based on the
8 HTTP-ACCEPT-LANGUAGE header. If no header is send or the given language
9 is not supported by the application, it falls back to
10 `tools.I18nTool.default`. Set `default` to the native language used in your
11 code for strings, so you must not provide a .mo file for it.
12
13 The tool uses `babel<http://babel.edgewall.org>`_ for localization and
14 handling translations. Within your Python code you can use four functions
15 defined in this module and the loaded locale provided as
16 `cherrypy.response.i18n.locale`.
17
18 Example::
19
20     from i18n_tool import ugettext as _, ungettext
21
22     class MyController(object):
23         @cherrypy.expose
24         def index(self):
25             loc = cherrypy.response.i18n.locale
26             s1 = _(u'Translateable string')
27             s2 = ungettext(u'There is one string.',
28                            u'There are more strings.', 2)
29             return u'<br />'.join([s1, s2, loc.display_name])
30
31 If you have code (e.g. database models) that is executed before the response
32 object is available, use the *_lazy functions to mark the strings
33 translateable. They will be translated later on, when the text is used (and
34 hopefully the response object is available then).
35
36 Example::
37
38     from i18n_tool import ugettext_lazy
39
40     class Model:
41         def __init__(self):
42             name = ugettext_lazy(u'Name of the model')
43
44 For your templates read the documentation of your template engine how to
45 integrate babel with it. I think `Genshi<http://genshi.edgewall.org>`_ and
46 `Jinja 2<http://jinja.pocoo.org`_ support it out of the box.
47
48
49 Settings for the CherryPy configuration::
50
51     [/]
52     tools.I18nTool.on = True
53     tools.I18nTool.default = Your language with territory (e.g. 'en_US')
54     tools.I18nTool.mo_dir = Directory holding the locale directories
55     tools.I18nTool.domain = Your gettext domain (e.g. application name)
56
57 The mo_dir must contain subdirectories named with the language prefix
58 for all translations, containing a LC_MESSAGES dir with the compiled
59 catalog file in it.
60
61 Example::
62
63     [/]
64     tools.I18nTool.on = True
65     tools.I18nTool.default = 'en_US'
66     tools.I18nTool.mo_dir = '/home/user/web/myapp/i18n'
67     tools.I18nTool.domain = 'myapp'
68
69     Now the tool will look for a file called myapp.mo in
70     /home/user/web/myapp/i18n/en/LC_MESSACES/
71     or generic: <mo_dir>/<language>/LC_MESSAGES/<domain>.mo
72
73 That's it.
74
75 :License: BSD
76 :Author: Thorsten Weimann <thorsten.weimann (at) gmx (dot) net>
77 :Date: 2010-02-08
78 """
79
80 import re
81
82 import cherrypy
83 from babel.core import Locale, UnknownLocaleError
84 from babel.support import Translations, LazyProxy
85
86 try:
87     # Python 2.6 and above
88     from collections import namedtuple
89     Lang = namedtuple('Lang', 'locale trans')
90 except ImportError:
91     # Python 2.5
92     class Lang(object):
93         def __init__(self, locale, trans):
94             self.locale = locale
95             self.trans = trans
96
97
98 # Cache for Translations and Locale objects
99 _languages = {}
100
101
102 # Exception
103 class ImproperlyConfigured(Exception):
104     """Raised if no known locale were found."""
105
106
107 # Public translation functions
108 def ugettext(message):
109     """Standard translation function. You can use it in all your exposed
110     methods and everywhere where the response object is available.
111
112     :parameters:
113         message : Unicode
114             The message to translate.
115
116     :returns: The translated message.
117     :rtype: Unicode
118     """
119     return cherrypy.response.i18n.trans.ugettext(message)
120
121 def ugettext_lazy(message):
122     """Like ugettext, but lazy.
123
124     :returns: A proxy for the translation object.
125     :rtype: LazyProxy
126     """
127     def get_translation():
128         return cherrypy.response.i18n.trans.ugettext(message)
129     return LazyProxy(get_translation)
130
131 def ungettext(singular, plural, num):
132     """Like ugettext, but considers plural forms.
133
134     :parameters:
135         singular : Unicode
136             The message to translate in singular form.
137         plural : Unicode
138             The message to translate in plural form.
139         num : Integer
140             Number to apply the plural formula on. If num is 1 or no
141             translation is found, singular is returned.
142
143     :returns: The translated message as singular or plural.
144     :rtype: Unicode
145     """
146     return cherrypy.response.i18n.trans.ungettext(singular, plural, num)
147
148 def ungettext_lazy(singular, plural, num):
149     """Like ungettext, but lazy.
150
151     :returns: A proxy for the translation object.
152     :rtype: LazyProxy
153     """
154     def get_translation():
155         return cherrypy.response.i18n.trans.ungettext(singular, plural, num)
156     return LazyProxy(get_translation)
157
158
159 def load_translation(langs, dirname, domain):
160     """Loads the first existing translations for known locale and saves the
161     `Lang` object in a global cache for faster lookup on the next request.
162
163     :parameters:
164         langs : List
165             List of languages as returned by `parse_accept_language_header`.
166         dirname : String
167             Directory of the translations (`tools.I18nTool.mo_dir`).
168         domain : String
169             Gettext domain of the catalog (`tools.I18nTool.domain`).
170
171     :returns: Lang object with two attributes (Lang.trans = the translations
172               object, Lang.locale = the corresponding Locale object).
173     :rtype: Lang
174     :raises: ImproperlyConfigured if no locale where known.
175     """
176     locale = None
177     for lang in langs:
178         short = lang[:2].lower()
179         try:
180             locale = Locale.parse(lang)
181             if (domain, short) in _languages:
182                 return _languages[(domain, short)]
183             trans = Translations.load(dirname, short, domain)
184         except (ValueError, UnknownLocaleError):
185             continue
186         # If the translation was found, exit loop
187         if isinstance(trans, Translations):
188             break
189     if locale is None:
190         raise ImproperlyConfigured('Default locale not known.')
191     _languages[(domain, short)] = res = Lang(locale, trans)
192     return res
193
194
195 def get_lang(mo_dir, default, domain):
196     """Main function which will be invoked during the request by `I18nTool`.
197     If the SessionTool is on and has a lang key, this language get the
198     highest priority. Default language get the lowest priority.
199     The `Lang` object will be saved as `cherrypy.response.i18n` and the
200     language string will also saved as `cherrypy.session['_lang_']` (if
201     SessionTool is on).
202
203     :parameters:
204         mo_dir : String
205             `tools.I18nTool.mo_dir`
206         default : String
207             `tools.I18nTool.default`
208         domain : String
209             `tools.I18nTool.domain`
210     """
211     langs = [x.value.replace('-', '_') for x in
212              cherrypy.request.headers.elements('Accept-Language')]
213     sessions_on = cherrypy.request.config.get('tools.sessions.on', False)
214     if sessions_on and cherrypy.session.get('_lang_', ''):
215         langs.insert(0, cherrypy.session.get('_lang_', '__'))
216     langs.append(default)
217     loc = load_translation(langs, mo_dir, domain)
218     cherrypy.response.i18n = loc
219     if sessions_on:
220         cherrypy.session['_lang_'] = str(loc.locale)
221
222
223 def set_lang():
224     """Sets the Content-Language response header (if not already set) to the
225     language of `cherrypy.response.i18n.locale`.
226     """
227     if 'Content-Language' not in cherrypy.response.headers:
228         cherrypy.response.headers['Content-Language'] = str(
229                 cherrypy.response.i18n.locale)
230
231
232 class I18nTool(cherrypy.Tool):
233     """Tool to integrate babel translations in CherryPy."""
234
235     def __init__(self):
236         self._name = 'I18nTool'
237         self._point = 'before_handler'
238         self.callable = get_lang
239         # Make sure, session tool (priority 50) is loaded before
240         self._priority = 100
241
242     def _setup(self):
243         c = cherrypy.request.config
244         if c.get('tools.staticdir.on', False) or \
245            c.get('tools.staticfile.on', False):
246             return
247         cherrypy.Tool._setup(self)
248         cherrypy.request.hooks.attach('before_finalize', set_lang)
249
250
251 cherrypy.tools.I18nTool = I18nTool()
252

Hosted by WebFaction

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