cherrypy-foundation 1.0.0__py3-none-any.whl → 1.0.0a1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cherrypy_foundation/components/ColorModes.jinja +4 -5
- cherrypy_foundation/components/Datatable.jinja +2 -2
- cherrypy_foundation/components/Datatable.js +2 -2
- cherrypy_foundation/components/Field.jinja +6 -16
- cherrypy_foundation/components/Fields.jinja +2 -0
- cherrypy_foundation/components/Typeahead.css +1 -6
- cherrypy_foundation/components/Typeahead.jinja +2 -2
- cherrypy_foundation/components/__init__.py +2 -2
- cherrypy_foundation/components/tests/test_static.py +1 -1
- cherrypy_foundation/error_page.py +17 -20
- cherrypy_foundation/flash.py +15 -17
- cherrypy_foundation/form.py +2 -2
- cherrypy_foundation/logging.py +2 -2
- cherrypy_foundation/passwd.py +2 -2
- cherrypy_foundation/plugins/db.py +9 -35
- cherrypy_foundation/plugins/ldap.py +38 -46
- cherrypy_foundation/plugins/restapi.py +1 -1
- cherrypy_foundation/plugins/scheduler.py +84 -208
- cherrypy_foundation/plugins/smtp.py +46 -78
- cherrypy_foundation/plugins/tests/test_db.py +4 -4
- cherrypy_foundation/plugins/tests/test_ldap.py +3 -76
- cherrypy_foundation/plugins/tests/test_scheduler.py +50 -58
- cherrypy_foundation/plugins/tests/test_smtp.py +7 -40
- cherrypy_foundation/tests/__init__.py +0 -72
- cherrypy_foundation/tests/test_error_page.py +1 -7
- cherrypy_foundation/tests/test_passwd.py +2 -2
- cherrypy_foundation/tools/auth.py +38 -59
- cherrypy_foundation/tools/auth_mfa.py +88 -89
- cherrypy_foundation/tools/errors.py +27 -0
- cherrypy_foundation/tools/i18n.py +153 -246
- cherrypy_foundation/tools/jinja2.py +13 -29
- cherrypy_foundation/tools/ratelimit.py +27 -37
- cherrypy_foundation/tools/secure_headers.py +5 -1
- cherrypy_foundation/tools/sessions_timeout.py +21 -23
- cherrypy_foundation/tools/tests/locales/en/LC_MESSAGES/messages.mo +0 -0
- cherrypy_foundation/tools/tests/locales/{de → en}/LC_MESSAGES/messages.po +2 -2
- cherrypy_foundation/tools/tests/test_auth.py +4 -21
- cherrypy_foundation/tools/tests/test_i18n.py +6 -81
- cherrypy_foundation/tools/tests/test_ratelimit.py +2 -2
- cherrypy_foundation/url.py +25 -25
- cherrypy_foundation/widgets.py +2 -2
- cherrypy_foundation-1.0.0a1.dist-info/METADATA +42 -0
- {cherrypy_foundation-1.0.0.dist-info → cherrypy_foundation-1.0.0a1.dist-info}/RECORD +46 -65
- {cherrypy_foundation-1.0.0.dist-info → cherrypy_foundation-1.0.0a1.dist-info}/WHEEL +1 -1
- cherrypy_foundation/components/Flash.jinja +0 -13
- cherrypy_foundation/components/LocaleSelection.jinja +0 -13
- cherrypy_foundation/components/LocaleSelection.js +0 -26
- cherrypy_foundation/plugins/tests/test_scheduler_db.py +0 -107
- cherrypy_foundation/sessions.py +0 -93
- cherrypy_foundation/tests/templates/test_flash.html +0 -9
- cherrypy_foundation/tests/templates/test_form.html +0 -16
- cherrypy_foundation/tests/templates/test_url.html +0 -15
- cherrypy_foundation/tests/test_flash.py +0 -61
- cherrypy_foundation/tests/test_form.py +0 -148
- cherrypy_foundation/tests/test_logging.py +0 -78
- cherrypy_foundation/tests/test_sessions.py +0 -89
- cherrypy_foundation/tests/test_url.py +0 -161
- cherrypy_foundation/tools/tests/components/Button.jinja +0 -2
- cherrypy_foundation/tools/tests/locales/de/LC_MESSAGES/messages.mo +0 -0
- cherrypy_foundation/tools/tests/templates/test_jinja2.html +0 -11
- cherrypy_foundation/tools/tests/templates/test_jinjax.html +0 -9
- cherrypy_foundation/tools/tests/templates/test_jinjax_i18n.html +0 -22
- cherrypy_foundation/tools/tests/test_auth_mfa.py +0 -369
- cherrypy_foundation/tools/tests/test_jinja2.py +0 -153
- cherrypy_foundation/tools/tests/test_secure_headers.py +0 -200
- cherrypy_foundation-1.0.0.dist-info/METADATA +0 -71
- {cherrypy_foundation-1.0.0.dist-info → cherrypy_foundation-1.0.0a1.dist-info}/licenses/LICENSE.md +0 -0
- {cherrypy_foundation-1.0.0.dist-info → cherrypy_foundation-1.0.0a1.dist-info}/top_level.txt +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Internationalisation tool for cherrypy
|
|
2
|
-
# Copyright (C) 2012-
|
|
2
|
+
# Copyright (C) 2012-2025 Patrik Dufresne
|
|
3
3
|
#
|
|
4
4
|
# This program is free software: you can redistribute it and/or modify
|
|
5
5
|
# it under the terms of the GNU General Public License as published by
|
|
@@ -14,116 +14,95 @@
|
|
|
14
14
|
# You should have received a copy of the GNU General Public License
|
|
15
15
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
16
16
|
|
|
17
|
-
"""
|
|
18
|
-
Internationalization (i18n) and Localization (l10n) support for CherryPy.
|
|
19
|
-
|
|
20
|
-
This module provides a CherryPy tool that integrates GNU gettext and Babel
|
|
21
|
-
to handle language selection, translations, locale-aware formatting, and
|
|
22
|
-
timezone handling on a per-request basis.
|
|
23
|
-
|
|
24
|
-
The active language is resolved in the following order (highest priority first):
|
|
17
|
+
"""Internationalization and Localization for CherryPy
|
|
25
18
|
|
|
26
|
-
|
|
27
|
-
2. User-defined callback (``tools.i18n.func``)
|
|
28
|
-
3. HTTP ``Accept-Language`` request header
|
|
29
|
-
4. Default language configured via ``tools.i18n.default``
|
|
19
|
+
This tool provides locales and loads translations in the following order:
|
|
30
20
|
|
|
31
|
-
|
|
32
|
-
|
|
21
|
+
1. `with i18n.preferred_lang()`
|
|
22
|
+
2. `tools.i18n.func` callback function (optional)
|
|
23
|
+
3. HTTP Accept-Language headers
|
|
24
|
+
4. `tools.i18n.default` value
|
|
33
25
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
26
|
+
The tool uses `babel` <http://babel.edgewall.org> for localization and
|
|
27
|
+
handling translations. Within your Python code you can use four functions
|
|
28
|
+
defined in this module and the loaded locale provided as `i18n.get_translation()`
|
|
37
29
|
|
|
38
|
-
|
|
39
|
-
``ungettext``:
|
|
30
|
+
Example::
|
|
40
31
|
|
|
41
|
-
from i18n import
|
|
32
|
+
from i18n import ugettext as _, ungettext
|
|
42
33
|
|
|
43
|
-
class MyController:
|
|
34
|
+
class MyController(object):
|
|
44
35
|
@cherrypy.expose
|
|
45
36
|
def index(self):
|
|
46
37
|
locale = cherrypy.response.i18n.locale
|
|
47
|
-
s1 = _(u
|
|
48
|
-
s2 =
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
2
|
|
52
|
-
)
|
|
53
|
-
return "<br />".join([s1, s2, locale.display_name])
|
|
38
|
+
s1 = _(u'Translateable string')
|
|
39
|
+
s2 = ungettext(u'There is one string.',
|
|
40
|
+
u'There are more strings.', 2)
|
|
41
|
+
return u'<br />'.join([s1, s2, locale.display_name])
|
|
54
42
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
43
|
+
If you have code (e.g. database models) that is executed before the response
|
|
44
|
+
object is available, use the *_lazy functions to mark the strings
|
|
45
|
+
translateable. They will be translated later on, when the text is used (and
|
|
46
|
+
hopefully the response object is available then).
|
|
58
47
|
|
|
59
|
-
|
|
60
|
-
(e.g. model definitions or module-level constants), use the ``*_lazy``
|
|
61
|
-
helpers. These defer translation until the value is actually rendered:
|
|
48
|
+
Example::
|
|
62
49
|
|
|
63
|
-
from i18n_tool import
|
|
50
|
+
from i18n_tool import ugettext_lazy
|
|
64
51
|
|
|
65
52
|
class Model:
|
|
66
|
-
|
|
53
|
+
def __init__(self):
|
|
54
|
+
name = ugettext_lazy(u'Name of the model')
|
|
67
55
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
56
|
+
For your templates read the documentation of your template engine how to
|
|
57
|
+
integrate babel with it. I think `Genshi<http://genshi.edgewall.org>`_ and
|
|
58
|
+
`Jinja 2<http://jinja.pocoo.org`_ support it out of the box.
|
|
71
59
|
|
|
72
|
-
For template rendering, i18n integrate with jinja2.
|
|
73
60
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
61
|
+
Settings for the CherryPy configuration::
|
|
62
|
+
|
|
63
|
+
[/]
|
|
64
|
+
tools.i18n.on = True
|
|
65
|
+
tools.i18n.default = Your language with territory (e.g. 'en_US')
|
|
66
|
+
tools.i18n.mo_dir = Directory holding the locale directories
|
|
67
|
+
tools.i18n.domain = Your gettext domain (e.g. application name)
|
|
80
68
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
69
|
+
The mo_dir must contain subdirectories named with the language prefix
|
|
70
|
+
for all translations, containing a LC_MESSAGES dir with the compiled
|
|
71
|
+
catalog file in it.
|
|
84
72
|
|
|
85
|
-
Example
|
|
73
|
+
Example::
|
|
86
74
|
|
|
87
75
|
[/]
|
|
88
76
|
tools.i18n.on = True
|
|
89
|
-
tools.i18n.default =
|
|
90
|
-
tools.i18n.mo_dir =
|
|
91
|
-
tools.i18n.domain =
|
|
77
|
+
tools.i18n.default = 'en_US'
|
|
78
|
+
tools.i18n.mo_dir = '/home/user/web/myapp/i18n'
|
|
79
|
+
tools.i18n.domain = 'myapp'
|
|
92
80
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
81
|
+
Now the tool will look for a file called myapp.mo in
|
|
82
|
+
/home/user/web/myapp/i18n/en/LC_MESSACES/
|
|
83
|
+
or generic: <mo_dir>/<language>/LC_MESSAGES/<domain>.mo
|
|
96
84
|
|
|
97
|
-
|
|
85
|
+
That's it.
|
|
98
86
|
|
|
87
|
+
:License: BSD
|
|
88
|
+
:Author: Thorsten Weimann <thorsten.weimann (at) gmx (dot) net>
|
|
89
|
+
:Date: 2010-02-08
|
|
99
90
|
"""
|
|
100
91
|
|
|
101
|
-
|
|
92
|
+
|
|
102
93
|
import os
|
|
94
|
+
import threading
|
|
103
95
|
from contextlib import contextmanager
|
|
104
|
-
from contextvars import ContextVar
|
|
105
96
|
from functools import lru_cache
|
|
106
|
-
from gettext import NullTranslations, translation
|
|
107
97
|
|
|
108
98
|
import cherrypy
|
|
109
99
|
import pytz
|
|
110
100
|
from babel import dates
|
|
111
101
|
from babel.core import Locale, get_global
|
|
112
|
-
from babel.support import LazyProxy, Translations
|
|
113
|
-
|
|
114
|
-
_preferred_lang = ContextVar('preferred_lang', default=())
|
|
115
|
-
_preferred_timezone = ContextVar('preferred_timezone', default=())
|
|
116
|
-
_translation = ContextVar('translation', default=None)
|
|
117
|
-
_tzinfo = ContextVar('tzinfo', default=None)
|
|
118
|
-
|
|
102
|
+
from babel.support import LazyProxy, NullTranslations, Translations
|
|
119
103
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
Lookup configuration from request, if available. Fallback to global config.
|
|
123
|
-
"""
|
|
124
|
-
if getattr(cherrypy, 'request') and getattr(cherrypy.request, 'config') and key in cherrypy.request.config:
|
|
125
|
-
return cherrypy.request.config[key]
|
|
126
|
-
return cherrypy.config.get(key, default)
|
|
104
|
+
# Store current translation and preferred_lang
|
|
105
|
+
_current = threading.local()
|
|
127
106
|
|
|
128
107
|
|
|
129
108
|
@contextmanager
|
|
@@ -134,20 +113,21 @@ def preferred_lang(lang):
|
|
|
134
113
|
with i18n.preferred_lang('fr'):
|
|
135
114
|
i18n.gettext('some string')
|
|
136
115
|
"""
|
|
137
|
-
|
|
138
|
-
|
|
116
|
+
assert lang is None or isinstance(lang, str)
|
|
117
|
+
prev_lang = getattr(_current, 'preferred_lang', [])
|
|
118
|
+
prev_trans = getattr(_current, 'translation', None)
|
|
139
119
|
try:
|
|
140
|
-
# Update
|
|
120
|
+
# Update prefered lang and clear translation.
|
|
141
121
|
if lang:
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
122
|
+
_current.preferred_lang = [lang]
|
|
123
|
+
elif hasattr(_current, 'preferred_lang'):
|
|
124
|
+
del _current.preferred_lang
|
|
125
|
+
_current.translation = None
|
|
146
126
|
yield
|
|
147
127
|
finally:
|
|
148
128
|
# Restore previous value
|
|
149
|
-
|
|
150
|
-
|
|
129
|
+
_current.preferred_lang = prev_lang
|
|
130
|
+
_current.translation = prev_trans
|
|
151
131
|
|
|
152
132
|
|
|
153
133
|
@contextmanager
|
|
@@ -158,24 +138,22 @@ def preferred_timezone(timezone):
|
|
|
158
138
|
with i18n.preferred_lang('America/Montreal'):
|
|
159
139
|
i18n.format_datetime(...)
|
|
160
140
|
"""
|
|
161
|
-
|
|
162
|
-
|
|
141
|
+
assert timezone is None or isinstance(timezone, str)
|
|
142
|
+
prev_timezone = getattr(_current, 'preferred_timezone', [])
|
|
143
|
+
prev_tzinfo = getattr(_current, 'tzinfo', None)
|
|
163
144
|
try:
|
|
164
|
-
# Update
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
else:
|
|
168
|
-
token_t = _preferred_timezone.set(tuple())
|
|
169
|
-
token_z = _tzinfo.set(None)
|
|
145
|
+
# Update prefered lang and clear translation.
|
|
146
|
+
_current.preferred_timezone = [timezone] + prev_timezone
|
|
147
|
+
_current.tzinfo = None
|
|
170
148
|
yield
|
|
171
149
|
finally:
|
|
172
150
|
# Restore previous value
|
|
173
|
-
|
|
174
|
-
|
|
151
|
+
_current.preferred_timezone = prev_timezone
|
|
152
|
+
_current.tzinfo = prev_tzinfo
|
|
175
153
|
|
|
176
154
|
|
|
177
|
-
@lru_cache(maxsize=
|
|
178
|
-
def _search_translation(dirname, domain, *
|
|
155
|
+
@lru_cache(maxsize=10)
|
|
156
|
+
def _search_translation(dirname, domain, *langs):
|
|
179
157
|
"""
|
|
180
158
|
Loads the first existing translations for known locale.
|
|
181
159
|
|
|
@@ -183,37 +161,22 @@ def _search_translation(dirname, domain, *locales, sourcecode_lang='en'):
|
|
|
183
161
|
langs : List
|
|
184
162
|
List of languages as returned by `parse_accept_language_header`.
|
|
185
163
|
dirname : String
|
|
186
|
-
|
|
164
|
+
Directory of the translations (`tools.I18nTool.mo_dir`).
|
|
165
|
+
Might be a list of directories.
|
|
187
166
|
domain : String
|
|
188
|
-
Gettext domain of the catalog (`tools.
|
|
167
|
+
Gettext domain of the catalog (`tools.I18nTool.domain`).
|
|
189
168
|
|
|
190
169
|
:returns: Translations, the corresponding Locale object.
|
|
191
170
|
"""
|
|
192
|
-
if not isinstance(
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
#
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
# If exception occur while loading the translation file. The file is probably corrupted.
|
|
202
|
-
cherrypy.log(
|
|
203
|
-
f'failed to load gettext catalog domain={domain} localedir={dirname} locale={locale}',
|
|
204
|
-
context='I18N',
|
|
205
|
-
severity=logging.WARNING,
|
|
206
|
-
traceback=True,
|
|
207
|
-
)
|
|
208
|
-
continue
|
|
209
|
-
if t.__class__ is NullTranslations and not locale.startswith(sourcecode_lang):
|
|
210
|
-
# Continue searching if translation is not found.
|
|
211
|
-
continue
|
|
212
|
-
t.locale = Locale.parse(locale)
|
|
213
|
-
return t
|
|
214
|
-
# If translation file not found, return default
|
|
215
|
-
t = NullTranslations()
|
|
216
|
-
t.locale = Locale(sourcecode_lang)
|
|
171
|
+
if not isinstance(langs, (list, tuple)):
|
|
172
|
+
langs = [langs]
|
|
173
|
+
t = Translations.load(dirname, langs, domain)
|
|
174
|
+
# Ignore null translation
|
|
175
|
+
if t.__class__ is NullTranslations:
|
|
176
|
+
return None
|
|
177
|
+
# Get Locale from file name
|
|
178
|
+
lang = t.files[0].split('/')[-3]
|
|
179
|
+
t.locale = Locale.parse(lang)
|
|
217
180
|
return t
|
|
218
181
|
|
|
219
182
|
|
|
@@ -221,37 +184,23 @@ def get_language_name(lang_code):
|
|
|
221
184
|
"""
|
|
222
185
|
Translate the language code into it's language display name.
|
|
223
186
|
"""
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
return lang_code
|
|
228
|
-
translation = get_translation()
|
|
229
|
-
return locale.get_language_name(translation.locale)
|
|
187
|
+
locale = Locale.parse(lang_code)
|
|
188
|
+
trans = get_translation()
|
|
189
|
+
return locale.get_language_name(trans.locale)
|
|
230
190
|
|
|
231
191
|
|
|
232
192
|
def get_timezone():
|
|
233
193
|
"""
|
|
234
194
|
Get the best timezone information for the current context.
|
|
235
|
-
|
|
236
|
-
The timezone returned is determined with the following priorities:
|
|
237
|
-
|
|
238
|
-
* value of preferred_timezone()
|
|
239
|
-
* tools.i18n.default_timezone
|
|
240
|
-
* default server time.
|
|
241
|
-
|
|
242
195
|
"""
|
|
243
196
|
# When tzinfo is defined, use it
|
|
244
|
-
tzinfo =
|
|
197
|
+
tzinfo = getattr(_current, 'tzinfo', None)
|
|
245
198
|
if tzinfo is not None:
|
|
246
199
|
return tzinfo
|
|
247
200
|
# Otherwise search for a valid timezone.
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
preferred_timezone = (
|
|
252
|
-
*preferred_timezone,
|
|
253
|
-
default,
|
|
254
|
-
)
|
|
201
|
+
tzinfo = None
|
|
202
|
+
default_timezone = cherrypy.config.get('tools.i18n.default_timezone')
|
|
203
|
+
preferred_timezone = getattr(_current, 'preferred_timezone', [default_timezone])
|
|
255
204
|
for timezone in preferred_timezone:
|
|
256
205
|
try:
|
|
257
206
|
tzinfo = dates.get_timezone(timezone)
|
|
@@ -261,8 +210,8 @@ def get_timezone():
|
|
|
261
210
|
# If we can't find a valid timezone using the default and preferred value, fall back to server timezone.
|
|
262
211
|
if tzinfo is None:
|
|
263
212
|
tzinfo = dates.get_timezone(None)
|
|
264
|
-
|
|
265
|
-
return tzinfo
|
|
213
|
+
_current.tzinfo = tzinfo
|
|
214
|
+
return _current.tzinfo
|
|
266
215
|
|
|
267
216
|
|
|
268
217
|
def get_translation():
|
|
@@ -270,48 +219,37 @@ def get_translation():
|
|
|
270
219
|
Get the best translation for the current context.
|
|
271
220
|
"""
|
|
272
221
|
# When translation is defined, use it
|
|
273
|
-
translation =
|
|
222
|
+
translation = getattr(_current, 'translation', None)
|
|
274
223
|
if translation is not None:
|
|
275
224
|
return translation
|
|
276
225
|
|
|
277
226
|
# Otherwise, we need to search the translation.
|
|
278
227
|
# `preferred_lang` should always has a sane value within a cherrypy request because of hooks
|
|
279
228
|
# But we also need to support calls outside cherrypy.
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
)
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
translation = _search_translation(mo_dir, domain, *preferred_lang, sourcecode_lang=sourcecode_lang)
|
|
291
|
-
_translation.set(translation)
|
|
292
|
-
return translation
|
|
229
|
+
default = cherrypy.config.get('tools.i18n.default')
|
|
230
|
+
preferred_lang = getattr(_current, 'preferred_lang', [default])
|
|
231
|
+
mo_dir = cherrypy.config.get('tools.i18n.mo_dir')
|
|
232
|
+
domain = cherrypy.config.get('tools.i18n.domain')
|
|
233
|
+
trans = _search_translation(mo_dir, domain, *preferred_lang)
|
|
234
|
+
if trans is None:
|
|
235
|
+
trans = NullTranslations()
|
|
236
|
+
trans.locale = Locale('en')
|
|
237
|
+
_current.translation = trans
|
|
238
|
+
return _current.translation
|
|
293
239
|
|
|
294
240
|
|
|
295
241
|
def list_available_locales():
|
|
296
242
|
"""
|
|
297
243
|
Return a list of available translations.
|
|
298
244
|
"""
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
sourcecode_lang = _get_config('tools.i18n.sourcecode_lang', 'en')
|
|
302
|
-
return_value.append(Locale.parse(sourcecode_lang))
|
|
303
|
-
# Then scan language directory for more translation.
|
|
304
|
-
mo_dir = _get_config('tools.i18n.mo_dir')
|
|
305
|
-
domain = _get_config('tools.i18n.domain', 'messages')
|
|
245
|
+
mo_dir = cherrypy.config.get('tools.i18n.mo_dir', False)
|
|
246
|
+
domain = cherrypy.config.get('tools.i18n.domain')
|
|
306
247
|
if not mo_dir:
|
|
307
248
|
return
|
|
308
249
|
for lang in os.listdir(mo_dir):
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
except Exception:
|
|
313
|
-
continue
|
|
314
|
-
return return_value
|
|
250
|
+
trans = _search_translation(mo_dir, domain, lang)
|
|
251
|
+
if trans is not None:
|
|
252
|
+
yield trans.locale
|
|
315
253
|
|
|
316
254
|
|
|
317
255
|
def list_available_timezones():
|
|
@@ -324,7 +262,7 @@ def list_available_timezones():
|
|
|
324
262
|
|
|
325
263
|
|
|
326
264
|
# Public translation functions
|
|
327
|
-
def
|
|
265
|
+
def ugettext(message):
|
|
328
266
|
"""Standard translation function. You can use it in all your exposed
|
|
329
267
|
methods and everywhere where the response object is available.
|
|
330
268
|
|
|
@@ -335,13 +273,13 @@ def gettext(message):
|
|
|
335
273
|
:returns: The translated message.
|
|
336
274
|
:rtype: Unicode
|
|
337
275
|
"""
|
|
338
|
-
return get_translation().
|
|
276
|
+
return get_translation().ugettext(message)
|
|
339
277
|
|
|
340
278
|
|
|
341
|
-
|
|
279
|
+
gettext = ugettext
|
|
342
280
|
|
|
343
281
|
|
|
344
|
-
def
|
|
282
|
+
def ungettext(singular, plural, num):
|
|
345
283
|
"""Like ugettext, but considers plural forms.
|
|
346
284
|
|
|
347
285
|
:parameters:
|
|
@@ -356,29 +294,34 @@ def ngettext(singular, plural, num):
|
|
|
356
294
|
:returns: The translated message as singular or plural.
|
|
357
295
|
:rtype: Unicode
|
|
358
296
|
"""
|
|
359
|
-
return get_translation().
|
|
297
|
+
return get_translation().ungettext(singular, plural, num)
|
|
360
298
|
|
|
361
299
|
|
|
362
|
-
|
|
300
|
+
ngettext = ungettext
|
|
363
301
|
|
|
364
302
|
|
|
365
303
|
def gettext_lazy(message):
|
|
366
|
-
"""Like
|
|
304
|
+
"""Like ugettext, but lazy.
|
|
367
305
|
|
|
368
306
|
:returns: A proxy for the translation object.
|
|
369
307
|
:rtype: LazyProxy
|
|
370
308
|
"""
|
|
371
309
|
|
|
372
310
|
def func():
|
|
373
|
-
return get_translation().
|
|
311
|
+
return get_translation().ugettext(message)
|
|
374
312
|
|
|
375
313
|
return LazyProxy(func, enable_cache=False)
|
|
376
314
|
|
|
377
315
|
|
|
378
316
|
def format_datetime(datetime=None, format='medium', tzinfo=None):
|
|
379
317
|
"""
|
|
380
|
-
|
|
318
|
+
Wraper arround babel format_datetime to provide a default locale.
|
|
319
|
+
The timezone used to format the date is determine with the following priorities:
|
|
320
|
+
* value of tzinfo
|
|
321
|
+
* value of get_timezone()
|
|
322
|
+
* default server time.
|
|
381
323
|
"""
|
|
324
|
+
# When formating date or english, let use en_GB as it's to most complete translation.
|
|
382
325
|
return dates.format_datetime(
|
|
383
326
|
datetime=datetime,
|
|
384
327
|
format=format,
|
|
@@ -389,19 +332,14 @@ def format_datetime(datetime=None, format='medium', tzinfo=None):
|
|
|
389
332
|
|
|
390
333
|
def format_date(datetime=None, format='medium', tzinfo=None):
|
|
391
334
|
"""
|
|
392
|
-
|
|
335
|
+
Wraper arround babel format_date to provide a default locale.
|
|
393
336
|
"""
|
|
394
|
-
|
|
395
|
-
return dates.format_datetime(
|
|
396
|
-
datetime=datetime,
|
|
397
|
-
format=dates.get_date_format(format),
|
|
398
|
-
locale=get_translation().locale,
|
|
399
|
-
tzinfo=tzinfo or get_timezone(),
|
|
400
|
-
)
|
|
337
|
+
return format_datetime(datetime=datetime, format=dates.get_date_format(format), tzinfo=tzinfo)
|
|
401
338
|
|
|
402
339
|
|
|
403
|
-
def get_timezone_name(tzinfo
|
|
404
|
-
|
|
340
|
+
def get_timezone_name(tzinfo):
|
|
341
|
+
locale = Locale(get_translation().locale.language)
|
|
342
|
+
return dates.get_timezone_name(tzinfo, width='long', locale=locale)
|
|
405
343
|
|
|
406
344
|
|
|
407
345
|
def _load_default(mo_dir, domain, default, **kwargs):
|
|
@@ -409,60 +347,35 @@ def _load_default(mo_dir, domain, default, **kwargs):
|
|
|
409
347
|
Initialize the language using the default value from the configuration.
|
|
410
348
|
"""
|
|
411
349
|
# Clear current translation
|
|
412
|
-
|
|
413
|
-
|
|
350
|
+
_current.preferred_lang = [default]
|
|
351
|
+
_current.preferred_timezone = [kwargs['default_timezone']] if 'default_timezone' in kwargs else []
|
|
352
|
+
cherrypy.request._i18n_lang_func = kwargs.get('lang', kwargs.get('func', False))
|
|
353
|
+
cherrypy.request._i18n_tzinfo_func = kwargs.get('tzinfo', False)
|
|
414
354
|
# Clear current translation
|
|
415
|
-
|
|
355
|
+
_current.translation = None
|
|
416
356
|
# Clear current timezone
|
|
417
|
-
|
|
357
|
+
_current.tzinfo = None
|
|
418
358
|
|
|
419
359
|
|
|
420
360
|
def _load_accept_language(**kwargs):
|
|
421
361
|
"""
|
|
422
|
-
When running within a request, load the
|
|
423
|
-
"""
|
|
424
|
-
if cherrypy.request.headers.elements('Accept-Language'):
|
|
425
|
-
# Sort language by quality
|
|
426
|
-
languages = sorted(cherrypy.request.headers.elements('Accept-Language'), key=lambda x: x.qvalue, reverse=True)
|
|
427
|
-
_preferred_lang.set(tuple(lang.value.replace('-', '_') for lang in languages))
|
|
428
|
-
# Clear current translation
|
|
429
|
-
_translation.set(None)
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
def _load_cookie_language(**kwargs):
|
|
433
|
-
"""
|
|
434
|
-
Load preferred language from a request cookie.
|
|
435
|
-
|
|
436
|
-
Expected cookie value formats:
|
|
437
|
-
- en
|
|
438
|
-
- en_US
|
|
439
|
-
- en-US
|
|
362
|
+
When running within a request, load the prefered language from Accept-Language
|
|
440
363
|
"""
|
|
441
|
-
# Skip this step if cookie name is not defined.
|
|
442
|
-
cookie_name = _get_config('tools.i18n.cookie_name')
|
|
443
|
-
if not cookie_name:
|
|
444
|
-
return
|
|
445
|
-
|
|
446
|
-
# Check if value defined in cookie.
|
|
447
|
-
cookie = cherrypy.request.cookie
|
|
448
|
-
if cookie_name not in cookie:
|
|
449
|
-
return
|
|
450
|
-
|
|
451
|
-
try:
|
|
452
|
-
value = cookie[cookie_name].value.replace('-', '_')
|
|
453
|
-
except Exception:
|
|
454
|
-
return
|
|
455
364
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
365
|
+
if cherrypy.request.headers.elements('Accept-Language'):
|
|
366
|
+
count = 0
|
|
367
|
+
for x in cherrypy.request.headers.elements('Accept-Language'):
|
|
368
|
+
_current.preferred_lang.insert(count, x.value.replace('-', '_'))
|
|
369
|
+
count += 1
|
|
370
|
+
# Clear current translation
|
|
371
|
+
_current.translation = None
|
|
459
372
|
|
|
460
373
|
|
|
461
374
|
def _load_func_language(**kwargs):
|
|
462
375
|
"""
|
|
463
|
-
When running a request where a current user is found, load
|
|
376
|
+
When running a request where a current user is found, load prefered language from user preferences.
|
|
464
377
|
"""
|
|
465
|
-
func =
|
|
378
|
+
func = getattr(cherrypy.request, '_i18n_lang_func', False)
|
|
466
379
|
if not func:
|
|
467
380
|
return
|
|
468
381
|
try:
|
|
@@ -472,16 +385,16 @@ def _load_func_language(**kwargs):
|
|
|
472
385
|
if not lang:
|
|
473
386
|
return
|
|
474
387
|
# Add custom lang to preferred_lang
|
|
475
|
-
|
|
388
|
+
_current.preferred_lang.insert(0, lang)
|
|
476
389
|
# Clear current translation
|
|
477
|
-
|
|
390
|
+
_current.translation = None
|
|
478
391
|
|
|
479
392
|
|
|
480
393
|
def _load_func_tzinfo(**kwargs):
|
|
481
394
|
"""
|
|
482
|
-
When running a request, load the
|
|
395
|
+
When running a request, load the prefered timezone information from user preferences.
|
|
483
396
|
"""
|
|
484
|
-
func =
|
|
397
|
+
func = getattr(cherrypy.request, '_i18n_tzinfo_func', False)
|
|
485
398
|
if not func:
|
|
486
399
|
return
|
|
487
400
|
try:
|
|
@@ -491,9 +404,9 @@ def _load_func_tzinfo(**kwargs):
|
|
|
491
404
|
if not tzinfo:
|
|
492
405
|
return
|
|
493
406
|
# Add custom lang to preferred_lang
|
|
494
|
-
|
|
407
|
+
_current.preferred_timezone.insert(0, tzinfo)
|
|
495
408
|
# Clear current translation
|
|
496
|
-
|
|
409
|
+
_current.tzinfo = None
|
|
497
410
|
|
|
498
411
|
|
|
499
412
|
def _set_content_language(**kwargs):
|
|
@@ -502,12 +415,7 @@ def _set_content_language(**kwargs):
|
|
|
502
415
|
language of `cherrypy.response.i18n.locale`.
|
|
503
416
|
"""
|
|
504
417
|
if 'Content-Language' not in cherrypy.response.headers:
|
|
505
|
-
|
|
506
|
-
translation = _translation.get()
|
|
507
|
-
if translation:
|
|
508
|
-
locale = translation.locale
|
|
509
|
-
language_tag = f"{locale.language}-{locale.territory}" if locale.territory else locale.language
|
|
510
|
-
cherrypy.response.headers['Content-Language'] = language_tag
|
|
418
|
+
cherrypy.response.headers['Content-Language'] = str(get_translation().locale)
|
|
511
419
|
|
|
512
420
|
|
|
513
421
|
class I18nTool(cherrypy.Tool):
|
|
@@ -520,7 +428,6 @@ class I18nTool(cherrypy.Tool):
|
|
|
520
428
|
cherrypy.Tool._setup(self)
|
|
521
429
|
# Attach additional hooks as different priority to update preferred lang with more accurate preferences.
|
|
522
430
|
cherrypy.request.hooks.attach('before_handler', _load_accept_language, priority=60)
|
|
523
|
-
cherrypy.request.hooks.attach('before_handler', _load_cookie_language, priority=70)
|
|
524
431
|
cherrypy.request.hooks.attach('before_handler', _load_func_language, priority=75)
|
|
525
432
|
cherrypy.request.hooks.attach('before_handler', _load_func_tzinfo, priority=75)
|
|
526
433
|
cherrypy.request.hooks.attach('before_finalize', _set_content_language)
|