-
-
Notifications
You must be signed in to change notification settings - Fork 803
/
i18n.py
154 lines (123 loc) · 4.3 KB
/
i18n.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
import gettext
import os
from contextvars import ContextVar
from typing import Any, Dict, Tuple, Optional
from babel import Locale
from babel.support import LazyProxy
from ... import types
from ...dispatcher.middlewares import BaseMiddleware
class I18nMiddleware(BaseMiddleware):
"""
I18n middleware based on gettext util
>>> dp = Dispatcher(bot)
>>> i18n = I18nMiddleware(DOMAIN, LOCALES_DIR)
>>> dp.middleware.setup(i18n)
and then
>>> _ = i18n.gettext
or
>>> _ = i18n = I18nMiddleware(DOMAIN_NAME, LOCALES_DIR)
"""
ctx_locale = ContextVar('ctx_user_locale', default=None)
def __init__(self, domain, path=None, default='en'):
"""
:param domain: domain
:param path: path where located all *.mo files
:param default: default locale name
"""
super(I18nMiddleware, self).__init__()
if path is None:
path = os.path.join(os.getcwd(), 'locales')
self.domain = domain
self.path = path
self.default = default
self.locales = self.find_locales()
def find_locales(self) -> Dict[str, gettext.GNUTranslations]:
"""
Load all compiled locales from path
:return: dict with locales
"""
translations = {}
for name in os.listdir(self.path):
if not os.path.isdir(os.path.join(self.path, name)):
continue
mo_path = os.path.join(self.path, name, 'LC_MESSAGES', self.domain + '.mo')
if os.path.exists(mo_path):
with open(mo_path, 'rb') as fp:
translations[name] = gettext.GNUTranslations(fp)
elif os.path.exists(mo_path[:-2] + 'po'):
raise RuntimeError(f"Found locale '{name}' but this language is not compiled!")
return translations
def reload(self):
"""
Hot reload locales
"""
self.locales = self.find_locales()
@property
def available_locales(self) -> Tuple[str]:
"""
list of loaded locales
:return:
"""
return tuple(self.locales.keys())
def __call__(self, singular, plural=None, n=1, locale=None) -> str:
return self.gettext(singular, plural, n, locale)
def gettext(self, singular, plural=None, n=1, locale=None) -> str:
"""
Get text
:param singular:
:param plural:
:param n:
:param locale:
:return:
"""
if locale is None:
locale = self.ctx_locale.get()
if locale not in self.locales:
if n == 1:
return singular
return plural
translator = self.locales[locale]
if plural is None:
return translator.gettext(singular)
return translator.ngettext(singular, plural, n)
def lazy_gettext(self, singular, plural=None, n=1, locale=None, enable_cache=False) -> LazyProxy:
"""
Lazy get text
:param singular:
:param plural:
:param n:
:param locale:
:param enable_cache:
:return:
"""
return LazyProxy(self.gettext, singular, plural, n, locale, enable_cache=enable_cache)
# noinspection PyMethodMayBeStatic,PyUnusedLocal
async def get_user_locale(self, action: str, args: Tuple[Any]) -> Optional[str]:
"""
User locale getter
You can override the method if you want to use different way of
getting user language.
:param action: event name
:param args: event arguments
:return: locale name or None
"""
user: Optional[types.User] = types.User.get_current()
locale: Optional[Locale] = user.locale if user else None
if locale and locale.language in self.locales:
*_, data = args
language = data['locale'] = locale.language
return language
return self.default
async def trigger(self, action, args):
"""
Event trigger
:param action: event name
:param args: event arguments
:return:
"""
if 'update' not in action \
and 'error' not in action \
and action.startswith('pre_process'):
locale = await self.get_user_locale(action, args)
self.ctx_locale.set(locale)
return True