cherrypy-foundation 1.0.0a13__py3-none-any.whl → 1.0.0a15__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 +5 -4
- cherrypy_foundation/components/Datatable.jinja +2 -2
- cherrypy_foundation/components/LocaleSelection.jinja +13 -0
- cherrypy_foundation/components/LocaleSelection.js +26 -0
- cherrypy_foundation/tests/__init__.py +72 -0
- cherrypy_foundation/tests/templates/test_flash.html +2 -1
- cherrypy_foundation/tests/templates/test_form.html +2 -1
- cherrypy_foundation/tests/templates/test_url.html +1 -1
- cherrypy_foundation/tools/i18n.py +39 -5
- cherrypy_foundation/tools/jinja2.py +14 -1
- cherrypy_foundation/tools/tests/templates/test_jinja2.html +2 -1
- cherrypy_foundation/tools/tests/templates/test_jinjax.html +3 -2
- cherrypy_foundation/tools/tests/templates/test_jinjax_i18n.html +22 -0
- cherrypy_foundation/tools/tests/test_i18n.py +25 -0
- cherrypy_foundation/tools/tests/test_jinja2.py +33 -3
- cherrypy_foundation/url.py +2 -3
- {cherrypy_foundation-1.0.0a13.dist-info → cherrypy_foundation-1.0.0a15.dist-info}/METADATA +1 -1
- {cherrypy_foundation-1.0.0a13.dist-info → cherrypy_foundation-1.0.0a15.dist-info}/RECORD +21 -19
- cherrypy_foundation/tools/tests/templates/test_jinja2_i18n.html +0 -11
- {cherrypy_foundation-1.0.0a13.dist-info → cherrypy_foundation-1.0.0a15.dist-info}/WHEEL +0 -0
- {cherrypy_foundation-1.0.0a13.dist-info → cherrypy_foundation-1.0.0a15.dist-info}/licenses/LICENSE.md +0 -0
- {cherrypy_foundation-1.0.0a13.dist-info → cherrypy_foundation-1.0.0a15.dist-info}/top_level.txt +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
{# def header="Toggle theme", light_label="Light", dark_label="Dark", auto_label="Auto" #}
|
|
1
2
|
{#css vendor/bootstrap5/css/bootstrap.min.css #}
|
|
2
3
|
{#js vendor/popper/popper.min.js, vendor/bootstrap5/js/bootstrap.min.js, vendor/bootstrap5/js/color-modes.js #}
|
|
3
4
|
<svg class="d-none" xmlns="http://www.w3.org/2000/svg">
|
|
@@ -21,7 +22,7 @@
|
|
|
21
22
|
</svg>
|
|
22
23
|
<li>
|
|
23
24
|
<span id="bd-theme" class="dropdown-item disabled">
|
|
24
|
-
<span id="bd-theme-text">{
|
|
25
|
+
<span id="bd-theme-text">{{ header }}</span>
|
|
25
26
|
<svg aria-hidden="true"
|
|
26
27
|
class="theme-icon-active visually-hidden"
|
|
27
28
|
width="16"
|
|
@@ -40,7 +41,7 @@
|
|
|
40
41
|
<use href="#sun-fill">
|
|
41
42
|
</use>
|
|
42
43
|
</svg>
|
|
43
|
-
{
|
|
44
|
+
{{ light_label }}
|
|
44
45
|
</button>
|
|
45
46
|
</li>
|
|
46
47
|
<li>
|
|
@@ -52,7 +53,7 @@
|
|
|
52
53
|
<use href="#moon-stars-fill">
|
|
53
54
|
</use>
|
|
54
55
|
</svg>
|
|
55
|
-
{
|
|
56
|
+
{{ dark_label }}
|
|
56
57
|
</button>
|
|
57
58
|
</li>
|
|
58
59
|
<li>
|
|
@@ -64,6 +65,6 @@
|
|
|
64
65
|
<use href="#circle-half">
|
|
65
66
|
</use>
|
|
66
67
|
</svg>
|
|
67
|
-
{
|
|
68
|
+
{{ auto_label }}
|
|
68
69
|
</button>
|
|
69
70
|
</li>
|
|
@@ -15,14 +15,14 @@
|
|
|
15
15
|
{% set buttons_cfg_default = {
|
|
16
16
|
"dom": {
|
|
17
17
|
"button": {
|
|
18
|
-
"className": "btn btn-sm ms-1 mb-
|
|
18
|
+
"className": "btn btn-sm ms-1 mb-2 mb-sm-0",
|
|
19
19
|
"active": "active"
|
|
20
20
|
},
|
|
21
21
|
"collection": {
|
|
22
22
|
"tag": "div",
|
|
23
23
|
"button": {
|
|
24
24
|
"tag": "a",
|
|
25
|
-
"className": "btn btn-sm btn-link mb-
|
|
25
|
+
"className": "btn btn-sm btn-link mb-2 mb-sm-0 w-100 text-start",
|
|
26
26
|
"active": "active",
|
|
27
27
|
"disabled": "disabled"
|
|
28
28
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{# def header="Language selection" #}
|
|
2
|
+
<li>
|
|
3
|
+
<h6 class="dropdown-header">{{ header }}</h6>
|
|
4
|
+
</li>
|
|
5
|
+
{% set cur_locale = get_translation().locale %}
|
|
6
|
+
{% for locale in list_available_locales() %}
|
|
7
|
+
<li>
|
|
8
|
+
<button aria-pressed="{{ 'true' if cur_locale == locale else 'false' }}"
|
|
9
|
+
class="dropdown-item btn-locale{{ ' active' if cur_locale == locale else '' }}"
|
|
10
|
+
data-locale="{{ locale.language }}"
|
|
11
|
+
type="button">{{ locale.display_name.capitalize() }}</button>
|
|
12
|
+
</li>
|
|
13
|
+
{% endfor %}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Handle locale selection.
|
|
3
|
+
*/
|
|
4
|
+
document.addEventListener('DOMContentLoaded', function () {
|
|
5
|
+
const LOCALE_COOKIE_NAME = 'locale';
|
|
6
|
+
const ONE_YEAR = 60 * 60 * 24 * 365;
|
|
7
|
+
|
|
8
|
+
function setLocaleCookie(locale) {
|
|
9
|
+
document.cookie = `${LOCALE_COOKIE_NAME}=${locale}; path=/; max-age=${ONE_YEAR}; SameSite=Lax`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function onLanguageClick(event) {
|
|
13
|
+
const locale = event.target.dataset.locale;
|
|
14
|
+
if (!locale) return;
|
|
15
|
+
|
|
16
|
+
setLocaleCookie(locale);
|
|
17
|
+
|
|
18
|
+
// Reload page so backend can use the new language
|
|
19
|
+
window.location.reload();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
document.querySelectorAll('.btn-locale').forEach(btn => {
|
|
23
|
+
btn.addEventListener('click', onLanguageClick);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# CherryPy foundation
|
|
2
|
+
# Copyright (C) 2026 IKUS Software
|
|
3
|
+
#
|
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU General Public License
|
|
15
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
16
|
+
|
|
17
|
+
import os
|
|
18
|
+
import tempfile
|
|
19
|
+
import unittest
|
|
20
|
+
from contextlib import contextmanager
|
|
21
|
+
|
|
22
|
+
from selenium import webdriver
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class SeleniumUnitTest:
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def _session_id(self):
|
|
29
|
+
if hasattr(self, 'cookies') and self.cookies:
|
|
30
|
+
for unused, value in self.cookies:
|
|
31
|
+
for part in value.split(';'):
|
|
32
|
+
key, unused, value = part.partition('=')
|
|
33
|
+
if key == 'session_id':
|
|
34
|
+
return value
|
|
35
|
+
|
|
36
|
+
@contextmanager
|
|
37
|
+
def selenium(self, headless=True, implicitly_wait=3):
|
|
38
|
+
"""
|
|
39
|
+
Decorator to load selenium for a test.
|
|
40
|
+
"""
|
|
41
|
+
# Skip selenium test is display is not available.
|
|
42
|
+
if not os.environ.get('DISPLAY', False):
|
|
43
|
+
raise unittest.SkipTest("selenium require a display")
|
|
44
|
+
# Start selenium driver
|
|
45
|
+
options = webdriver.ChromeOptions()
|
|
46
|
+
if headless:
|
|
47
|
+
options.add_argument('--headless')
|
|
48
|
+
options.add_argument('--disable-gpu')
|
|
49
|
+
options.add_argument('--window-size=1280,800')
|
|
50
|
+
options.add_argument('--no-sandbox')
|
|
51
|
+
options.add_argument('--disable-dev-shm-usage')
|
|
52
|
+
options.add_argument('--lang=en-US')
|
|
53
|
+
driver = webdriver.Chrome(options=options)
|
|
54
|
+
try:
|
|
55
|
+
# If logged in, reuse the same session id.
|
|
56
|
+
if self._session_id:
|
|
57
|
+
driver.get(f'{self.baseurl}/login/')
|
|
58
|
+
driver.add_cookie({"name": "session_id", "value": self.session_id})
|
|
59
|
+
# Configure download folder
|
|
60
|
+
download = os.path.join(os.path.expanduser('~'), 'Downloads')
|
|
61
|
+
os.makedirs(download, exist_ok=True)
|
|
62
|
+
self._selenium_download_dir = tempfile.mkdtemp(dir=download, prefix='selenium-download-')
|
|
63
|
+
driver.execute_cdp_cmd(
|
|
64
|
+
'Page.setDownloadBehavior', {'behavior': 'allow', 'downloadPath': self._selenium_download_dir}
|
|
65
|
+
)
|
|
66
|
+
# Set default wait.
|
|
67
|
+
driver.implicitly_wait(implicitly_wait)
|
|
68
|
+
yield driver
|
|
69
|
+
finally:
|
|
70
|
+
# Code to release resource, e.g.:
|
|
71
|
+
driver.close()
|
|
72
|
+
driver = None
|
|
@@ -296,6 +296,11 @@ def list_available_locales():
|
|
|
296
296
|
"""
|
|
297
297
|
Return a list of available translations.
|
|
298
298
|
"""
|
|
299
|
+
return_value = []
|
|
300
|
+
# Always return the source code locale.
|
|
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.
|
|
299
304
|
mo_dir = _get_config('tools.i18n.mo_dir')
|
|
300
305
|
domain = _get_config('tools.i18n.domain', 'messages')
|
|
301
306
|
if not mo_dir:
|
|
@@ -303,9 +308,10 @@ def list_available_locales():
|
|
|
303
308
|
for lang in os.listdir(mo_dir):
|
|
304
309
|
if os.path.exists(os.path.join(mo_dir, lang, 'LC_MESSAGES', f'{domain}.mo')):
|
|
305
310
|
try:
|
|
306
|
-
|
|
311
|
+
return_value.append(Locale.parse(lang))
|
|
307
312
|
except Exception:
|
|
308
313
|
continue
|
|
314
|
+
return return_value
|
|
309
315
|
|
|
310
316
|
|
|
311
317
|
def list_available_timezones():
|
|
@@ -405,8 +411,6 @@ def _load_default(mo_dir, domain, default, **kwargs):
|
|
|
405
411
|
# Clear current translation
|
|
406
412
|
_preferred_lang.set(tuple())
|
|
407
413
|
_preferred_timezone.set(tuple())
|
|
408
|
-
cherrypy.request._i18n_lang_func = kwargs.get('lang', kwargs.get('func', False))
|
|
409
|
-
cherrypy.request._i18n_tzinfo_func = kwargs.get('tzinfo', False)
|
|
410
414
|
# Clear current translation
|
|
411
415
|
_translation.set(None)
|
|
412
416
|
# Clear current timezone
|
|
@@ -425,11 +429,40 @@ def _load_accept_language(**kwargs):
|
|
|
425
429
|
_translation.set(None)
|
|
426
430
|
|
|
427
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
|
|
440
|
+
"""
|
|
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
|
+
|
|
456
|
+
# Set preferred language and clear cached translation
|
|
457
|
+
_preferred_lang.set((value,))
|
|
458
|
+
_translation.set(None)
|
|
459
|
+
|
|
460
|
+
|
|
428
461
|
def _load_func_language(**kwargs):
|
|
429
462
|
"""
|
|
430
463
|
When running a request where a current user is found, load preferred language from user preferences.
|
|
431
464
|
"""
|
|
432
|
-
func =
|
|
465
|
+
func = _get_config('tools.i18n.lang', default=_get_config('tools.i18n.func'))
|
|
433
466
|
if not func:
|
|
434
467
|
return
|
|
435
468
|
try:
|
|
@@ -448,7 +481,7 @@ def _load_func_tzinfo(**kwargs):
|
|
|
448
481
|
"""
|
|
449
482
|
When running a request, load the preferred timezone information from user preferences.
|
|
450
483
|
"""
|
|
451
|
-
func =
|
|
484
|
+
func = _get_config('tools.i18n.tzinfo')
|
|
452
485
|
if not func:
|
|
453
486
|
return
|
|
454
487
|
try:
|
|
@@ -487,6 +520,7 @@ class I18nTool(cherrypy.Tool):
|
|
|
487
520
|
cherrypy.Tool._setup(self)
|
|
488
521
|
# Attach additional hooks as different priority to update preferred lang with more accurate preferences.
|
|
489
522
|
cherrypy.request.hooks.attach('before_handler', _load_accept_language, priority=60)
|
|
523
|
+
cherrypy.request.hooks.attach('before_handler', _load_cookie_language, priority=70)
|
|
490
524
|
cherrypy.request.hooks.attach('before_handler', _load_func_language, priority=75)
|
|
491
525
|
cherrypy.request.hooks.attach('before_handler', _load_func_tzinfo, priority=75)
|
|
492
526
|
cherrypy.request.hooks.attach('before_finalize', _set_content_language)
|
|
@@ -80,7 +80,17 @@ class Jinja2Tool(cherrypy.Tool):
|
|
|
80
80
|
|
|
81
81
|
# Enable translation if available
|
|
82
82
|
if hasattr(cherrypy.tools, 'i18n'):
|
|
83
|
-
from .i18n import
|
|
83
|
+
from .i18n import (
|
|
84
|
+
format_date,
|
|
85
|
+
format_datetime,
|
|
86
|
+
get_language_name,
|
|
87
|
+
get_timezone_name,
|
|
88
|
+
get_translation,
|
|
89
|
+
list_available_locales,
|
|
90
|
+
list_available_timezones,
|
|
91
|
+
ugettext,
|
|
92
|
+
ungettext,
|
|
93
|
+
)
|
|
84
94
|
|
|
85
95
|
env.add_extension('jinja2.ext.i18n')
|
|
86
96
|
env.install_gettext_callables(ugettext, ungettext, newstyle=True)
|
|
@@ -88,6 +98,9 @@ class Jinja2Tool(cherrypy.Tool):
|
|
|
88
98
|
env.filters['format_datetime'] = format_datetime
|
|
89
99
|
env.globals['get_language_name'] = get_language_name
|
|
90
100
|
env.globals['get_translation'] = get_translation
|
|
101
|
+
env.globals['list_available_locales'] = list_available_locales
|
|
102
|
+
env.globals['list_available_timezones'] = list_available_timezones
|
|
103
|
+
env.globals['get_timezone_name'] = get_timezone_name
|
|
91
104
|
|
|
92
105
|
# Update globals, filters and tests
|
|
93
106
|
env.globals.update(globals)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="{{ get_translation().locale }}">
|
|
3
|
+
<head>
|
|
4
|
+
<title>test-jinja2</title>
|
|
5
|
+
{{ catalog.render_assets() }}
|
|
6
|
+
</head>
|
|
7
|
+
<body>
|
|
8
|
+
<!-- Selector -->
|
|
9
|
+
<div class="dropdown">
|
|
10
|
+
<button class="btn btn-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
|
11
|
+
Dropdown button
|
|
12
|
+
</button>
|
|
13
|
+
<ul class="dropdown-menu">
|
|
14
|
+
<LocaleSelection />
|
|
15
|
+
</ul>
|
|
16
|
+
</div>
|
|
17
|
+
{{ get_language_name(get_translation().locale) }}<br/>
|
|
18
|
+
{% trans %}Some text to translate{% endtrans %}<br/>
|
|
19
|
+
{{ my_datetime | format_datetime(format='full') }}<br/>
|
|
20
|
+
{{ my_date | format_date(format='full') }}
|
|
21
|
+
</body>
|
|
22
|
+
</html>
|
|
@@ -76,6 +76,7 @@ class AbstractI18nTest(helper.CPWebCase):
|
|
|
76
76
|
'tools.i18n.default': cls.default_lang,
|
|
77
77
|
'tools.i18n.mo_dir': importlib.resources.files(__package__) / 'locales',
|
|
78
78
|
'tools.i18n.domain': 'messages',
|
|
79
|
+
'tools.i18n.cookie_name': 'locale',
|
|
79
80
|
}
|
|
80
81
|
)
|
|
81
82
|
cherrypy.tree.mount(Root(), '/')
|
|
@@ -124,6 +125,13 @@ class TestI18nWebCase(AbstractI18nTest):
|
|
|
124
125
|
# Tehn page return en-US
|
|
125
126
|
self.assertHeaderItemValue("Content-Language", "en-US")
|
|
126
127
|
|
|
128
|
+
def test_cookie_fr(self):
|
|
129
|
+
# When calling with locale variant
|
|
130
|
+
self.getPage("/", headers=[("Accept-Language", "en-US"), ("Cookie", "locale=fr")])
|
|
131
|
+
self.assertStatus('200 OK')
|
|
132
|
+
# Tehn page return en-US
|
|
133
|
+
self.assertHeaderItemValue("Content-Language", "fr")
|
|
134
|
+
|
|
127
135
|
def test_with_preferred_lang(self):
|
|
128
136
|
# Given a default lang 'en'
|
|
129
137
|
date = datetime.fromtimestamp(1680111611, timezone.utc)
|
|
@@ -175,6 +183,23 @@ class TestI18nWebCase(AbstractI18nTest):
|
|
|
175
183
|
with i18n.preferred_lang('de_CH'):
|
|
176
184
|
self.assertEqual('29. März 2023, 19:40:11 MESZ', i18n.format_datetime(date, format='long'))
|
|
177
185
|
|
|
186
|
+
def test_list_available_locales(self):
|
|
187
|
+
self.assertEqual(['de', 'en', 'fr'], sorted([str(l) for l in i18n.list_available_locales()]))
|
|
188
|
+
|
|
189
|
+
def test_list_available_timezones(self):
|
|
190
|
+
timezones = i18n.list_available_timezones()
|
|
191
|
+
self.assertIn('America/Toronto', timezones)
|
|
192
|
+
|
|
193
|
+
def test_get_timezone_name(self):
|
|
194
|
+
with i18n.preferred_lang('en'):
|
|
195
|
+
self.assertEqual('Eastern Time', i18n.get_timezone_name('America/Toronto'))
|
|
196
|
+
self.assertEqual('ET', i18n.get_timezone_name('America/Toronto', width='short'))
|
|
197
|
+
self.assertEqual('Eastern Time', i18n.get_timezone_name('America/Toronto', width='long'))
|
|
198
|
+
with i18n.preferred_lang('fr'):
|
|
199
|
+
self.assertEqual('heure de l’Est nord-américain', i18n.get_timezone_name('America/Toronto'))
|
|
200
|
+
self.assertEqual('HE', i18n.get_timezone_name('America/Toronto', width='short'))
|
|
201
|
+
self.assertEqual('heure de l’Est nord-américain', i18n.get_timezone_name('America/Toronto', width='long'))
|
|
202
|
+
|
|
178
203
|
|
|
179
204
|
class TestI18nDefaultLangWebCase(AbstractI18nTest):
|
|
180
205
|
default_lang = 'FR'
|
|
@@ -21,6 +21,10 @@ import cherrypy
|
|
|
21
21
|
from cherrypy.test import helper
|
|
22
22
|
from parameterized import parameterized
|
|
23
23
|
|
|
24
|
+
from cherrypy_foundation.components import StaticMiddleware
|
|
25
|
+
from cherrypy_foundation.tests import SeleniumUnitTest
|
|
26
|
+
from cherrypy_foundation.url import url_for
|
|
27
|
+
|
|
24
28
|
from .. import i18n # noqa
|
|
25
29
|
from .. import jinja2 # noqa
|
|
26
30
|
|
|
@@ -38,9 +42,18 @@ def extra_processor():
|
|
|
38
42
|
return {'var2': 'bar'}
|
|
39
43
|
|
|
40
44
|
|
|
41
|
-
@cherrypy.tools.
|
|
45
|
+
@cherrypy.tools.i18n(on=False)
|
|
46
|
+
@cherrypy.tools.sessions(on=False)
|
|
47
|
+
class Static:
|
|
48
|
+
|
|
49
|
+
components = StaticMiddleware()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@cherrypy.tools.jinja2(on=False, env=env, extra_processor=extra_processor)
|
|
42
53
|
class Root:
|
|
43
54
|
|
|
55
|
+
static = Static()
|
|
56
|
+
|
|
44
57
|
@cherrypy.expose
|
|
45
58
|
@cherrypy.tools.jinja2(template='test_jinja2.html')
|
|
46
59
|
def index(self):
|
|
@@ -52,12 +65,13 @@ class Root:
|
|
|
52
65
|
return {'var1': 'test-jinjax'}
|
|
53
66
|
|
|
54
67
|
@cherrypy.expose
|
|
55
|
-
@cherrypy.tools.jinja2(template='
|
|
68
|
+
@cherrypy.tools.jinja2(template='test_jinjax_i18n.html')
|
|
56
69
|
@cherrypy.tools.i18n(
|
|
57
70
|
default='fr',
|
|
58
71
|
default_timezone='America/Toronto',
|
|
59
72
|
mo_dir=importlib.resources.files(__package__) / 'locales',
|
|
60
73
|
domain='messages',
|
|
74
|
+
cookie_name='locale', # For LocaleSelection
|
|
61
75
|
)
|
|
62
76
|
def localized(self):
|
|
63
77
|
return {
|
|
@@ -66,7 +80,7 @@ class Root:
|
|
|
66
80
|
}
|
|
67
81
|
|
|
68
82
|
|
|
69
|
-
class Jinja2Test(helper.CPWebCase):
|
|
83
|
+
class Jinja2Test(helper.CPWebCase, SeleniumUnitTest):
|
|
70
84
|
default_lang = None
|
|
71
85
|
interactive = False
|
|
72
86
|
|
|
@@ -95,6 +109,7 @@ class Jinja2Test(helper.CPWebCase):
|
|
|
95
109
|
# Then the page is render dynamically using page context
|
|
96
110
|
self.assertInBody('<a class="btn btn-primary" href="http://example.com">foo</a>')
|
|
97
111
|
|
|
112
|
+
@skipUnless(HAS_JINJAX, reason='Required jinjax')
|
|
98
113
|
@parameterized.expand(
|
|
99
114
|
[
|
|
100
115
|
('server_default', {}, 'fr'),
|
|
@@ -121,3 +136,18 @@ class Jinja2Test(helper.CPWebCase):
|
|
|
121
136
|
self.assertInBody('Some text to translate')
|
|
122
137
|
self.assertInBody('Wednesday, November 26, 2025, 6:16:00\u202fAM Eastern Standard Time')
|
|
123
138
|
self.assertInBody('Monday, December 22, 2025')
|
|
139
|
+
|
|
140
|
+
@skipUnless(HAS_JINJAX, reason='Required jinjax')
|
|
141
|
+
def test_get_page_i18n_selenium(self):
|
|
142
|
+
# Given a localized page render with jinja2
|
|
143
|
+
with self.selenium() as driver:
|
|
144
|
+
# When querying the page
|
|
145
|
+
driver.get(url_for('localized'))
|
|
146
|
+
# Then page load without error in english (enforced chronium lang)
|
|
147
|
+
self.assertFalse(driver.get_log('browser'))
|
|
148
|
+
self.assertEqual('en_US', driver.find_element('css selector', 'html').get_attribute('lang'))
|
|
149
|
+
# When user select a language
|
|
150
|
+
btn = driver.find_element('css selector', 'button[data-locale=fr]')
|
|
151
|
+
btn.click()
|
|
152
|
+
# Then page is reloaded with in French.
|
|
153
|
+
self.assertEqual('fr', driver.find_element('css selector', 'html').get_attribute('lang'))
|
cherrypy_foundation/url.py
CHANGED
|
@@ -60,8 +60,7 @@ def url_for(*args, _relative=None, _base=None, **kwargs):
|
|
|
60
60
|
elif not path.startswith('.'):
|
|
61
61
|
path = urljoin('/', path)
|
|
62
62
|
# Outside a request, use cherrypy.tools.proxy config
|
|
63
|
-
if not cherrypy.request.app:
|
|
64
|
-
|
|
65
|
-
_base = cherrypy.config.get('tools.proxy.base', None)
|
|
63
|
+
if not cherrypy.request.app and _base is None:
|
|
64
|
+
_base = cherrypy.config.get('tools.proxy.base', None)
|
|
66
65
|
# Use cherrypy to build the URL
|
|
67
66
|
return cherrypy.url(path=path, qs=qs, relative=_relative, base=_base)
|
|
@@ -4,11 +4,11 @@ cherrypy_foundation/flash.py,sha256=fFRbutUX6c1lVHqjehmO9y98dJgmfNCjhd76t2mth2s,
|
|
|
4
4
|
cherrypy_foundation/form.py,sha256=8c9dO0o47sK3CBosTkGXoVRtzNQwY0aw0vNZfTqmhvo,3994
|
|
5
5
|
cherrypy_foundation/logging.py,sha256=YIOK5ZAZLCv52YDdP66yBYpEX1C336JnI3wnrTKl1Lw,3468
|
|
6
6
|
cherrypy_foundation/passwd.py,sha256=ZGdrBNKtLP75l01W6VEd8cIjSQ3guJ_YVPEfbSew7T0,2144
|
|
7
|
-
cherrypy_foundation/url.py,sha256=
|
|
7
|
+
cherrypy_foundation/url.py,sha256=IjDADMIZTZbQ5mECSsoz_MsyJvA85amV-93J_E3hbb4,2854
|
|
8
8
|
cherrypy_foundation/widgets.py,sha256=0B5Y2V6x5Ufl6ExR3tc0Olrzj7N4TAAOtqGq_MUxBG0,1549
|
|
9
|
-
cherrypy_foundation/components/ColorModes.jinja,sha256=
|
|
9
|
+
cherrypy_foundation/components/ColorModes.jinja,sha256=Ai2fy1qHFwEgutvyvvGjKJmffcBdNb7wmY20DJqZ8R4,3528
|
|
10
10
|
cherrypy_foundation/components/Datatable.css,sha256=7wSwgdA61vYCdEuQ0bp2o0oSvu5mGLN1c6ovCUSe718,947
|
|
11
|
-
cherrypy_foundation/components/Datatable.jinja,sha256=
|
|
11
|
+
cherrypy_foundation/components/Datatable.jinja,sha256=qUJyp8FCBdpH9J8kIvVzemRyyra0ushpZtTj5BhN9H8,3391
|
|
12
12
|
cherrypy_foundation/components/Datatable.js,sha256=jiV78bJPNMTLcjrX03vaYcsEdRbI6ctzSvCo880H8Hw,12072
|
|
13
13
|
cherrypy_foundation/components/Field.css,sha256=CtOkvIbix7ykrOKLJxQJLJsWfEwFqfducJ1BH2vlMvA,244
|
|
14
14
|
cherrypy_foundation/components/Field.jinja,sha256=HuhZvHzv0MtFrcpoavbZHOLLdTdse8RcLIExXXx-Rsk,3319
|
|
@@ -16,6 +16,8 @@ cherrypy_foundation/components/Field.js,sha256=SFixZ62WlLq7SSCEazMAGhSnc9EnQ1wg6
|
|
|
16
16
|
cherrypy_foundation/components/Fields.jinja,sha256=_8Or6DOlciKjRao-sdBpLs--bx5V-K80X3hDEA1VKCQ,165
|
|
17
17
|
cherrypy_foundation/components/Flash.jinja,sha256=COy44drQsXpbZajjJS4w_9NMPYFdMfUNdfhd7SbFdYA,442
|
|
18
18
|
cherrypy_foundation/components/Icon.jinja,sha256=Z1RGYBg5xlDEoUy3glqb_k_LEjkJHeCxQXqDEvWzEF4,135
|
|
19
|
+
cherrypy_foundation/components/LocaleSelection.jinja,sha256=NHhQGI6CCJl4LWl-eteu_AIg1_qc1MusuA1fKcrjluQ,509
|
|
20
|
+
cherrypy_foundation/components/LocaleSelection.js,sha256=-iTZD5ejg1R9F3tZASTH7dkgd19upke-pzOhsZ9K_LA,670
|
|
19
21
|
cherrypy_foundation/components/SideBySideMultiSelect.css,sha256=_poMY9O8rvDsOh01pQLf9qtg1Gm4eCM2HsM_ekC5zkk,503
|
|
20
22
|
cherrypy_foundation/components/SideBySideMultiSelect.jinja,sha256=sud1WP-6JzuP7ZLRr-JQqvgMRWZRlXvxUJfguFr_klk,478
|
|
21
23
|
cherrypy_foundation/components/SideBySideMultiSelect.js,sha256=5YMz1pgkXeWC_SRRfDbQI3X-c4PuxiTIpbWJt9sY7Rc,197
|
|
@@ -91,29 +93,29 @@ cherrypy_foundation/plugins/tests/test_ldap.py,sha256=j0QUIxqeEBgN4VLvczUlw0xMM_
|
|
|
91
93
|
cherrypy_foundation/plugins/tests/test_scheduler.py,sha256=I-ZuQhMvCCvqFDwukwsyz_UkdJJ8JSLTkAanUo24GCE,3564
|
|
92
94
|
cherrypy_foundation/plugins/tests/test_scheduler_db.py,sha256=-iZnLgb3qsJzs0p7JAw6TQswRtExCmHAtYXqRNPZn7U,3101
|
|
93
95
|
cherrypy_foundation/plugins/tests/test_smtp.py,sha256=qs5yezIpSXkBmLmFlqckfPW7NmntHZxQjDSkdQG_dNE,4183
|
|
94
|
-
cherrypy_foundation/tests/__init__.py,sha256=
|
|
96
|
+
cherrypy_foundation/tests/__init__.py,sha256=VIBhEMPDIaffF7lYzA5KevcVNzALz4kykBBaO-gdKHY,2818
|
|
95
97
|
cherrypy_foundation/tests/test_error_page.py,sha256=8yLK8OGbJIdUjilFIHMNBZadLKHrXnD6KSmQ3Da4LaQ,2399
|
|
96
98
|
cherrypy_foundation/tests/test_flash.py,sha256=JqZDAgazlNnP3HcPFmFOWbPeDDMzc6z5fHNe-pBTin0,1976
|
|
97
99
|
cherrypy_foundation/tests/test_form.py,sha256=sWsPWyXwAVkCeP5t0qIHc0Oi32Zi3kztoQ_wlDR9STc,4326
|
|
98
100
|
cherrypy_foundation/tests/test_passwd.py,sha256=gC5O4yhHyU1YRYuDc0pG0T_5zvrG2qrr6P822iyK3Rg,1956
|
|
99
101
|
cherrypy_foundation/tests/test_url.py,sha256=W-RTKQuxYS2KXxCYTTtnKcxfdP9F6Fp3QKY_sBTnBmE,6434
|
|
100
|
-
cherrypy_foundation/tests/templates/test_flash.html,sha256=
|
|
101
|
-
cherrypy_foundation/tests/templates/test_form.html,sha256=
|
|
102
|
-
cherrypy_foundation/tests/templates/test_url.html,sha256=
|
|
102
|
+
cherrypy_foundation/tests/templates/test_flash.html,sha256=b1S4I9v0n-Y1yoTUh2ZKNysR1NMrqv8ldvqONtmInzw,213
|
|
103
|
+
cherrypy_foundation/tests/templates/test_form.html,sha256=ywaLKgWqvBdbdfnBgYmi1ihA3xK8X5YtjomA6atnJUg,206
|
|
104
|
+
cherrypy_foundation/tests/templates/test_url.html,sha256=427G6AnA6zUfjPoLxsVHy3U2e_XxG4ntpZX3DIjO18Q,512
|
|
103
105
|
cherrypy_foundation/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
104
106
|
cherrypy_foundation/tools/auth.py,sha256=lTSajxCiReMzm-Fl-xhTByi4yFnInEWOoNsmUMnHQhs,9761
|
|
105
107
|
cherrypy_foundation/tools/auth_mfa.py,sha256=VaLvBz9wo6jTx-2mCGqFXPxl-z14f8UMWvd6_xeXd40,9212
|
|
106
108
|
cherrypy_foundation/tools/errors.py,sha256=ELpAj0N9kIxC22QW5xDQJz60zMpCwgm-Twu2WpELM1A,1005
|
|
107
|
-
cherrypy_foundation/tools/i18n.py,sha256=
|
|
108
|
-
cherrypy_foundation/tools/jinja2.py,sha256=
|
|
109
|
+
cherrypy_foundation/tools/i18n.py,sha256=pP3QrGCHiNHVDpjdKNFKErOCm3eRYZL6W_nlz9j8eD8,17128
|
|
110
|
+
cherrypy_foundation/tools/jinja2.py,sha256=xrHfk814GjlWhfqJcXTA6I2PQ3YqsSz4eHcCh9sG05U,5969
|
|
109
111
|
cherrypy_foundation/tools/ratelimit.py,sha256=pT7vZRmjltNeuiQpdyXOmnpG9BcXjLaj-AXJ0e2x_zw,8300
|
|
110
112
|
cherrypy_foundation/tools/secure_headers.py,sha256=Yh-iA_Js4MUsx5nq4ilbc-iWy90ZC0oMb3TJJD_UwYo,3921
|
|
111
113
|
cherrypy_foundation/tools/sessions_timeout.py,sha256=6iBWJntPMk_Qt94fBSfBISf1IXInSh-1XrxLbKXFV-g,7408
|
|
112
114
|
cherrypy_foundation/tools/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
113
115
|
cherrypy_foundation/tools/tests/test_auth.py,sha256=oeM5t38M8DUC9dYn59dcf00jdGY6Ry0jZWhQd_PYQUk,2847
|
|
114
116
|
cherrypy_foundation/tools/tests/test_auth_mfa.py,sha256=911hnBbdg5CKb613uIBrlggoTAyBU9SoL7Sxd-tIKS0,15008
|
|
115
|
-
cherrypy_foundation/tools/tests/test_i18n.py,sha256=
|
|
116
|
-
cherrypy_foundation/tools/tests/test_jinja2.py,sha256=
|
|
117
|
+
cherrypy_foundation/tools/tests/test_i18n.py,sha256=9rK4_1HVTk2dtTh9egIymx4SP8EO8_igBQkCD4YiIys,10758
|
|
118
|
+
cherrypy_foundation/tools/tests/test_jinja2.py,sha256=gLwaGeQn1Wt7Ximc_5ESHSm499vCeyqhEhPYUPJnamk,5545
|
|
117
119
|
cherrypy_foundation/tools/tests/test_ratelimit.py,sha256=rrqybwMbh1GFlF2-Ut57zHPAc1uqX88aqea6VS_6p5E,3449
|
|
118
120
|
cherrypy_foundation/tools/tests/components/Button.jinja,sha256=uSLp1GpEIgZNXK_GWglu0E_a1c3jHpDLI66MRfMqGhE,95
|
|
119
121
|
cherrypy_foundation/tools/tests/locales/messages.pot,sha256=5K9piTRL7H5MxDXFIWJsCccSJRA0HwfCQQU8b8VYo30,40
|
|
@@ -121,11 +123,11 @@ cherrypy_foundation/tools/tests/locales/de/LC_MESSAGES/messages.mo,sha256=bsJTVL
|
|
|
121
123
|
cherrypy_foundation/tools/tests/locales/de/LC_MESSAGES/messages.po,sha256=glzYY96jmCaGyxYtMqmNF-1q-OYWXBIkMysvbXO4L6E,351
|
|
122
124
|
cherrypy_foundation/tools/tests/locales/fr/LC_MESSAGES/messages.mo,sha256=u3_kl_nqZ3FNaSyKVQKmu4KJzN3xOxxJNVmcdhw37jA,327
|
|
123
125
|
cherrypy_foundation/tools/tests/locales/fr/LC_MESSAGES/messages.po,sha256=6_Sk9Igqm7dtpyyS701p5qc4DvOJE7TRT0ajRZctFAQ,342
|
|
124
|
-
cherrypy_foundation/tools/tests/templates/test_jinja2.html,sha256=
|
|
125
|
-
cherrypy_foundation/tools/tests/templates/
|
|
126
|
-
cherrypy_foundation/tools/tests/templates/
|
|
127
|
-
cherrypy_foundation-1.0.
|
|
128
|
-
cherrypy_foundation-1.0.
|
|
129
|
-
cherrypy_foundation-1.0.
|
|
130
|
-
cherrypy_foundation-1.0.
|
|
131
|
-
cherrypy_foundation-1.0.
|
|
126
|
+
cherrypy_foundation/tools/tests/templates/test_jinja2.html,sha256=s1bHmy-lyf0YW0t-LOx3ugILV2kqFoBNYxziWgrZbo0,216
|
|
127
|
+
cherrypy_foundation/tools/tests/templates/test_jinjax.html,sha256=NImzIW0mUHxilFd61PSoxFC-yu1nayEVwv-5zlgD9yo,179
|
|
128
|
+
cherrypy_foundation/tools/tests/templates/test_jinjax_i18n.html,sha256=yre8j7HBjpTQZHpM0PuB3ASGD3O4vKkJ-y72Fm6STgY,771
|
|
129
|
+
cherrypy_foundation-1.0.0a15.dist-info/licenses/LICENSE.md,sha256=trSLYs5qlaow_bBwsLTRKpmTXsXzFksM_YUCMqrgAJQ,35149
|
|
130
|
+
cherrypy_foundation-1.0.0a15.dist-info/METADATA,sha256=yFHspuRqMzwk-zpZtT_j7H7WAR4KTX38sX__MvuwPdI,2023
|
|
131
|
+
cherrypy_foundation-1.0.0a15.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
132
|
+
cherrypy_foundation-1.0.0a15.dist-info/top_level.txt,sha256=B1vQPTLYhpKJ6W0JkRCWyAf8RPcnwJWdYxixv75-4ew,20
|
|
133
|
+
cherrypy_foundation-1.0.0a15.dist-info/RECORD,,
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
<html lang="{{ get_translation().locale }}">
|
|
2
|
-
<head>
|
|
3
|
-
<title>test-jinja2</title>
|
|
4
|
-
</head>
|
|
5
|
-
<body>
|
|
6
|
-
{{ get_language_name(get_translation().locale) }}<br/>
|
|
7
|
-
{% trans %}Some text to translate{% endtrans %}<br/>
|
|
8
|
-
{{ my_datetime | format_datetime(format='full') }}<br/>
|
|
9
|
-
{{ my_date | format_date(format='full') }}
|
|
10
|
-
</body>
|
|
11
|
-
</html>
|
|
File without changes
|
|
File without changes
|
{cherrypy_foundation-1.0.0a13.dist-info → cherrypy_foundation-1.0.0a15.dist-info}/top_level.txt
RENAMED
|
File without changes
|