1
|
|
|
""" |
2
|
|
|
byceps.util.l10n |
3
|
|
|
~~~~~~~~~~~~~~~~ |
4
|
|
|
|
5
|
|
|
Localization. |
6
|
|
|
|
7
|
|
|
:Copyright: 2014-2024 Jochen Kupperschmidt |
8
|
|
|
:License: Revised BSD (see `LICENSE` file for details) |
9
|
|
|
""" |
10
|
|
|
|
11
|
1 |
|
from collections.abc import Iterator |
12
|
|
|
from contextlib import contextmanager |
13
|
1 |
|
|
14
|
1 |
|
from babel import Locale |
15
|
|
|
from flask import current_app, g, request |
16
|
1 |
|
from flask_babel import force_locale, format_currency, get_locale |
17
|
1 |
|
from moneyed import Money |
18
|
1 |
|
from wtforms import Form |
19
|
1 |
|
|
20
|
1 |
|
from byceps.services.user.models.user import User |
21
|
|
|
|
22
|
1 |
|
|
23
|
|
|
def get_current_user_locale() -> str | None: |
24
|
|
|
"""Return the locale for the current user, if available.""" |
25
|
1 |
|
# Look for a locale on the current user object. |
26
|
|
|
user = g.user |
27
|
|
|
if (user is not None) and (user.locale is not None): |
28
|
1 |
|
return user.locale |
29
|
1 |
|
|
30
|
1 |
|
if request: |
31
|
|
|
# Try to match user agent's accepted languages. |
32
|
1 |
|
languages = [locale.language for locale in g.locales] |
33
|
|
|
return request.accept_languages.best_match(languages) |
34
|
1 |
|
|
35
|
1 |
|
return None |
36
|
|
|
|
37
|
|
|
|
38
|
|
|
@contextmanager |
39
|
|
|
def force_user_locale(user: User) -> Iterator[None]: |
40
|
1 |
|
"""Execute code with the user's preferred locale.""" |
41
|
1 |
|
locale = get_user_locale(user) |
42
|
|
|
with force_locale(locale): |
43
|
1 |
|
yield |
44
|
1 |
|
|
45
|
1 |
|
|
46
|
|
|
def get_default_locale() -> str: |
47
|
|
|
"""Return the application's default locale.""" |
48
|
1 |
|
return current_app.config['LOCALE'] |
49
|
|
|
|
50
|
1 |
|
|
51
|
|
|
def get_user_locale(user: User) -> str: |
52
|
|
|
"""Return the user's preferred locale. |
53
|
1 |
|
|
54
|
|
|
If no preference is set for the user, return the app's default |
55
|
|
|
locale. |
56
|
|
|
""" |
57
|
|
|
return user.locale or get_default_locale() |
58
|
|
|
|
59
|
1 |
|
|
60
|
|
|
BASE_LOCALE = Locale('en') |
61
|
|
|
|
62
|
1 |
|
|
63
|
|
|
def get_locales() -> list[Locale]: |
64
|
|
|
"""List available locales.""" |
65
|
1 |
|
return [BASE_LOCALE] + current_app.babel_instance.list_translations() |
66
|
|
|
|
67
|
1 |
|
|
68
|
|
|
def get_locale_str() -> str | None: |
69
|
|
|
"""Return the current locale as a string. |
70
|
1 |
|
|
71
|
|
|
Return `None` outside of a request. |
72
|
|
|
""" |
73
|
|
|
locale = get_locale() |
74
|
|
|
if locale is None: |
75
|
1 |
|
return None |
76
|
1 |
|
|
77
|
|
|
locale_str = locale.language |
78
|
|
|
if locale.territory: |
79
|
1 |
|
locale_str += '_' + locale.territory |
80
|
1 |
|
|
81
|
|
|
return locale_str |
82
|
|
|
|
83
|
1 |
|
|
84
|
|
|
class LocalizedForm(Form): |
85
|
|
|
def __init__(self, *args, **kwargs): |
86
|
1 |
|
locales = current_app.config['LOCALES_FORMS'] |
87
|
1 |
|
kwargs['meta'] = {'locales': locales} |
88
|
1 |
|
super().__init__(*args, **kwargs) |
89
|
1 |
|
|
90
|
1 |
|
|
91
|
|
|
def format_money(money: Money) -> str: |
92
|
|
|
"""Format monetary value with amount and currency.""" |
93
|
|
|
return format_currency(money.amount, money.currency.code) |
94
|
|
|
|