This repository has been archived by the owner on Apr 27, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathi18n_utils.py
190 lines (155 loc) · 6.67 KB
/
i18n_utils.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
#!/usr/bin/env python
#
# Copyright 2013 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""A small module for i18n of webapp2 and jinja2 based apps.
The idea of this example, especially for how to translate strings in
Javascript is originally from an implementation of Django i18n.
"""
import gettext
import json
import os
import jinja2
import webapp2
from webob import Request
from webob.acceptparse import AcceptLanguage
def convert_translations_to_dict(js_translations):
"""Convert a GNUTranslations object into a dict for jsonifying.
Args:
js_translations: GNUTranslations object to be converted.
Returns:
A dictionary representing the GNUTranslations object.
"""
plural = None
n_plural = 2
if '' in js_translations._catalog:
for l in js_translations._catalog[''].split('\n'):
if l.startswith('Plural-Forms:'):
plural = l.split(':', 1)[1].strip()
if plural is not None:
for raw_element in plural.split(';'):
element = raw_element.strip()
if element.startswith('nplurals='):
n_plural = int(element.split('=', 1)[1])
elif element.startswith('plural='):
plural = element.split('=', 1)[1]
else:
n_plural = 2
plural = '(n == 1) ? 0 : 1'
translations_dict = {'plural': plural, 'catalog': {}, 'fallback': None}
if js_translations._fallback is not None:
translations_dict['fallback'] = convert_translations_to_dict(
js_translations._fallback
)
for key, value in js_translations._catalog.items():
if key == '':
continue
if type(key) in (str, unicode):
translations_dict['catalog'][key] = value
elif type(key) == tuple:
if not key[0] in translations_dict['catalog']:
translations_dict['catalog'][key[0]] = [''] * n_plural
translations_dict['catalog'][key[0]][int(key[1])] = value
return translations_dict
class BaseHandler(webapp2.RequestHandler):
"""A base handler for installing i18n-aware Jinja2 environment."""
@webapp2.cached_property
def jinja2_env(self):
"""Cached property for a Jinja2 environment.
Returns:
Jinja2 Environment object.
"""
jinja2_env = jinja2.Environment(
loader=jinja2.FileSystemLoader(
os.path.join(os.path.dirname(__file__), 'templates')),
extensions=['jinja2.ext.i18n'])
jinja2_env.install_gettext_translations(
self.request.environ['i18n_utils.active_translation'])
jinja2_env.globals['get_i18n_js_tag'] = self.get_i18n_js_tag
return jinja2_env
def get_i18n_js_tag(self):
"""Generates a Javascript tag for i18n in Javascript.
This instance method is installed to the global namespace of
the Jinja2 environment, so you can invoke this method just
like `{{ get_i18n_js_tag() }}` from anywhere in your Jinja2
template.
Returns:
A 'javascript' HTML tag which contains functions and
translation messages for i18n.
"""
template = self.jinja2_env.get_template('javascript_tag.jinja2')
return template.render({'javascript_body': self.get_i18n_js()})
def get_i18n_js(self):
"""Generates a Javascript body for i18n in Javascript.
If you want to load these javascript code from a static HTML
file, you need to create another handler which just returns
the code generated by this function.
Returns:
Actual javascript code for functions and translation
messages for i18n.
"""
try:
js_translations = gettext.translation(
'jsmessages', 'locales', fallback=False,
languages=self.request.environ[
'i18n_utils.preferred_languages'],
codeset='utf-8')
except IOError:
template = self.jinja2_env.get_template('null_i18n_js.jinja2')
return template.render()
translations_dict = convert_translations_to_dict(js_translations)
template = self.jinja2_env.get_template('i18n_js.jinja2')
return template.render(
{'translations': json.dumps(translations_dict, indent=1)})
class I18nMiddleware(object):
"""A WSGI middleware for i18n.
This middleware determines users' preferred language, loads the
translations files, and install it to the builtin namespace of the
Python runtime.
"""
def __init__(self, app, default_language='en', locale_path=None):
"""A constructor for this middleware.
Args:
app: A WSGI app that you want to wrap with this
middleware.
default_language: fallback language; ex: 'en', 'ja', etc.
locale_path: A directory containing the translations
file. (defaults to 'locales' directory)
"""
self.app = app
if locale_path is None:
locale_path = os.path.join(
os.path.abspath(os.path.dirname(__file__)), 'locales')
self.locale_path = locale_path
self.default_language = default_language
def __call__(self, environ, start_response):
"""Called by WSGI when a request comes in.
Args:
environ: A dict holding environment variables.
start_response: A WSGI callable (PEP333).
Returns:
Application response data as an iterable. It just returns
the return value of the inner WSGI app.
"""
req = Request(environ)
preferred_languages = list(req.accept_language)
if not self.default_language in preferred_languages:
preferred_languages.append(self.default_language)
translation = gettext.translation(
'messages', self.locale_path, fallback=True,
languages=preferred_languages, codeset='utf-8')
translation.install(unicode=True, names=['gettext', 'ngettext'])
environ['i18n_utils.active_translation'] = translation
environ['i18n_utils.preferred_languages'] = preferred_languages
return self.app(environ, start_response)