kivy-garden-i18n 0.3.0__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.
- kivy_garden/i18n/__init__.py +0 -0
- kivy_garden/i18n/fontfinder.py +168 -0
- kivy_garden/i18n/localizer.py +237 -0
- kivy_garden/i18n/utils/__init__.py +1 -0
- kivy_garden/i18n/utils/_extract_msgids_from_string_literals.py +64 -0
- kivy_garden_i18n-0.3.0.dist-info/METADATA +88 -0
- kivy_garden_i18n-0.3.0.dist-info/RECORD +9 -0
- kivy_garden_i18n-0.3.0.dist-info/WHEEL +4 -0
- kivy_garden_i18n-0.3.0.dist-info/entry_points.txt +3 -0
|
File without changes
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
__all__ =(
|
|
2
|
+
"enum_pre_installed_fonts", "default_filter", "enum_langs", "register_lang",
|
|
3
|
+
"font_provides_glyphs", "font_supports_lang",
|
|
4
|
+
)
|
|
5
|
+
|
|
6
|
+
from typing import Union
|
|
7
|
+
from collections.abc import Callable, Iterator
|
|
8
|
+
from pathlib import Path, PurePath
|
|
9
|
+
from kivy.core.text import LabelBase, Label as CoreLabel
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def default_filter(font: PurePath, suffixes={".ttf", ".otf", ".ttc", ".woff", ".woff2"}) -> bool:
|
|
13
|
+
'''
|
|
14
|
+
The default filter for :func:`enum_pre_installed_fonts`.
|
|
15
|
+
|
|
16
|
+
Returns True if ``font`` has one of the specified suffixes and its filename
|
|
17
|
+
does not contain ``"fallback"``.
|
|
18
|
+
'''
|
|
19
|
+
return font.suffix in suffixes and "fallback" not in font.name.lower()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def enum_pre_installed_fonts(*, filter: Callable[[PurePath], bool]=default_filter) -> Iterator[Path]:
|
|
23
|
+
for dir in LabelBase.get_system_fonts_dir():
|
|
24
|
+
for child in Path(dir).iterdir():
|
|
25
|
+
if filter(child):
|
|
26
|
+
yield child
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def font_provides_glyphs(font: str | Path, glyphs: str) -> bool:
|
|
30
|
+
'''
|
|
31
|
+
Whether a specified ``font`` provides all of the given ``glyphs``.
|
|
32
|
+
|
|
33
|
+
:param glyphs: A string consisting of three or more unique characters.
|
|
34
|
+
|
|
35
|
+
.. code-block::
|
|
36
|
+
|
|
37
|
+
from kivy_garden.i18n.fontfinder import font_provides_glyphs as f
|
|
38
|
+
|
|
39
|
+
# Roboto, Kivy's default font, lacks CJK glyphs.
|
|
40
|
+
assert f("Roboto", "ABC")
|
|
41
|
+
assert not f("Roboto", "漢字한글ひら")
|
|
42
|
+
assert not f("Roboto", "漢字한글ひらABC")
|
|
43
|
+
|
|
44
|
+
# NotoSerifCJK provides ASCII and CJK glyphs.
|
|
45
|
+
assert f("NotoSerifCJK-Regular.ttc", "ABC")
|
|
46
|
+
assert f("NotoSerifCJK-Regular.ttc", "漢字한글ひら")
|
|
47
|
+
assert f("NotoSerifCJK-Regular.ttc", "漢字한글ひらABC")
|
|
48
|
+
|
|
49
|
+
# Fallback fonts may lack ASCII glyphs.
|
|
50
|
+
assert not f("DroidSansFallbackFull.ttf", "ABC")
|
|
51
|
+
assert f("DroidSansFallbackFull.ttf", "漢字한글ひら")
|
|
52
|
+
assert not f("DroidSansFallbackFull.ttf", "漢字한글ひらABC")
|
|
53
|
+
|
|
54
|
+
.. warning::
|
|
55
|
+
|
|
56
|
+
This function does not produce 100% accurate results.
|
|
57
|
+
Providing more glyphs improves accuracy at the cost of execution time.
|
|
58
|
+
'''
|
|
59
|
+
if not _validate_discriminant(glyphs):
|
|
60
|
+
raise ValueError(f"'glyphs' must consist of three or more unique characters (was {glyphs!r})")
|
|
61
|
+
label = CoreLabel()
|
|
62
|
+
label._size = (16, 16, )
|
|
63
|
+
label.options['font_name'] = str(font)
|
|
64
|
+
label.resolve_font_name()
|
|
65
|
+
rendered_results = set()
|
|
66
|
+
for c in glyphs:
|
|
67
|
+
label.text = c
|
|
68
|
+
label._render_begin()
|
|
69
|
+
label._render_text(c, 0, 0)
|
|
70
|
+
pixels = label._render_end().data
|
|
71
|
+
if pixels in rendered_results:
|
|
72
|
+
return False
|
|
73
|
+
rendered_results.add(pixels)
|
|
74
|
+
return True
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
DISCRIMINANTS = {
|
|
78
|
+
'ar': 'الجزيرةAB',
|
|
79
|
+
'hi': 'भारतAB',
|
|
80
|
+
'ja': (v := '経伝説あAB'),
|
|
81
|
+
'ja-JP': v,
|
|
82
|
+
'ja_JP': v,
|
|
83
|
+
'ko': (v := '안녕조AB'),
|
|
84
|
+
'ko-KR': v,
|
|
85
|
+
'ko_KR': v,
|
|
86
|
+
'zh-Hans': (v := '哪经传说AB'),
|
|
87
|
+
'zh_Hans': v,
|
|
88
|
+
'zh-CN': v,
|
|
89
|
+
'zh_CN': v,
|
|
90
|
+
'zh-SG': v,
|
|
91
|
+
'zh_SG': v,
|
|
92
|
+
'zh-Hant': (v := '哪經傳說AB'),
|
|
93
|
+
'zh_Hant': v,
|
|
94
|
+
'zh-TW': v,
|
|
95
|
+
'zh_TW': v,
|
|
96
|
+
'zh-HK': v,
|
|
97
|
+
'zh_HK': v,
|
|
98
|
+
'zh-MO': v,
|
|
99
|
+
'zh_MO': v,
|
|
100
|
+
'zh': '哪經傳說经传说AB',
|
|
101
|
+
}
|
|
102
|
+
'''
|
|
103
|
+
あるフォントがある言語に対応しているか否かを判定するために使われる辞書。
|
|
104
|
+
辞書の値に含まれている文字全てがフォントに含まれている時のみ、そのフォントは対応する鍵の言語に対応していると見做される。
|
|
105
|
+
'''
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def font_supports_lang(font: Union[str, Path], lang: str) -> bool:
|
|
109
|
+
'''
|
|
110
|
+
Whether a specified ``font`` provides the glyphs required for a specified ``lang``.
|
|
111
|
+
|
|
112
|
+
.. code-block::
|
|
113
|
+
|
|
114
|
+
from kivy_garden.i18n.fontfinder import font_supports_lang as f
|
|
115
|
+
|
|
116
|
+
assert not f("Roboto", "zh")
|
|
117
|
+
assert not f("Roboto", "ko")
|
|
118
|
+
assert not f("Roboto", "ja")
|
|
119
|
+
|
|
120
|
+
assert f("NotoSerifCJK-Regular.ttc", "zh")
|
|
121
|
+
assert f("NotoSerifCJK-Regular.ttc", "ko")
|
|
122
|
+
assert f("NotoSerifCJK-Regular.ttc", "ja")
|
|
123
|
+
|
|
124
|
+
# A font that lacks ASCII characters is considered unable to support any language.
|
|
125
|
+
assert not f("DroidSansFallbackFull.ttf", "zh")
|
|
126
|
+
assert not f("DroidSansFallbackFull.ttf", "ko")
|
|
127
|
+
assert not f("DroidSansFallbackFull.ttf", "ja")
|
|
128
|
+
|
|
129
|
+
.. warning::
|
|
130
|
+
|
|
131
|
+
This function does not produce 100% accurate results.
|
|
132
|
+
'''
|
|
133
|
+
try:
|
|
134
|
+
glyphs = DISCRIMINANTS[lang]
|
|
135
|
+
except KeyError:
|
|
136
|
+
raise ValueError(f"Unable to check language support: {lang = }.\n"
|
|
137
|
+
"Register the language first using 'register_lang' function.")
|
|
138
|
+
return font_provides_glyphs(font, glyphs)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def enum_langs() -> Iterator[str]:
|
|
142
|
+
'''
|
|
143
|
+
Available languages for :func:`font_supports_lang`.
|
|
144
|
+
'''
|
|
145
|
+
return DISCRIMINANTS.keys()
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def register_lang(lang: str, discriminant: str):
|
|
149
|
+
'''
|
|
150
|
+
Enables a language in the :func:`font_supports_lang`.
|
|
151
|
+
|
|
152
|
+
.. code-block::
|
|
153
|
+
|
|
154
|
+
register_lang('th', "ราชอAB") # Thai language
|
|
155
|
+
'''
|
|
156
|
+
if not _validate_discriminant(discriminant):
|
|
157
|
+
raise ValueError(f"'discriminant' must consist of three or more unique characters (was {discriminant!r})")
|
|
158
|
+
DISCRIMINANTS[lang] = discriminant
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _validate_discriminant(discriminant: str, len=len, set=set) -> bool:
|
|
162
|
+
l = len(discriminant)
|
|
163
|
+
return l >= 3 and len(set(discriminant)) == l
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
# Aliases for backward compatibility
|
|
167
|
+
can_render_text = font_provides_glyphs
|
|
168
|
+
can_render_lang = font_supports_lang
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
__all__ = (
|
|
2
|
+
# type hints
|
|
3
|
+
"Translator", "TranslatorFactory", "FontPicker",
|
|
4
|
+
|
|
5
|
+
# exceptions
|
|
6
|
+
"FontNotFoundError",
|
|
7
|
+
|
|
8
|
+
# concrete TranslatorFactory
|
|
9
|
+
"GettextBasedTranslatorFactory", "MappingBasedTranslatorFactory",
|
|
10
|
+
|
|
11
|
+
# concrete FontPicker
|
|
12
|
+
"DefaultFontPicker",
|
|
13
|
+
|
|
14
|
+
#
|
|
15
|
+
"Localizer",
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
from collections.abc import Callable, Mapping
|
|
19
|
+
from typing import TypeAlias, Union
|
|
20
|
+
import itertools
|
|
21
|
+
from functools import cached_property
|
|
22
|
+
|
|
23
|
+
from kivy.properties import StringProperty, ObjectProperty
|
|
24
|
+
from kivy.event import EventDispatcher
|
|
25
|
+
from kivy.logger import Logger
|
|
26
|
+
from kivy.uix.label import Label
|
|
27
|
+
|
|
28
|
+
from .fontfinder import enum_pre_installed_fonts, font_supports_lang
|
|
29
|
+
|
|
30
|
+
Msgid: TypeAlias = str
|
|
31
|
+
Msgstr: TypeAlias = str
|
|
32
|
+
Lang: TypeAlias = str
|
|
33
|
+
Translator: TypeAlias = Callable[[Msgid], Msgstr]
|
|
34
|
+
TranslatorFactory : TypeAlias = Callable[[Lang], Translator]
|
|
35
|
+
Font: TypeAlias = str
|
|
36
|
+
FontPicker: TypeAlias = Callable[[Lang], Font]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class FontNotFoundError(Exception):
|
|
40
|
+
@cached_property
|
|
41
|
+
def lang(self) -> Lang:
|
|
42
|
+
'''The language for which a localizer couldn't find a suitable font.'''
|
|
43
|
+
return self.args[0]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class Localizer(EventDispatcher):
|
|
47
|
+
lang: Lang = StringProperty()
|
|
48
|
+
'''
|
|
49
|
+
The "current language" of this localizer.
|
|
50
|
+
'''
|
|
51
|
+
|
|
52
|
+
_: Translator = ObjectProperty(lambda msgid: msgid)
|
|
53
|
+
'''
|
|
54
|
+
(read-only)
|
|
55
|
+
A callable object that takes a ``msgid``, and returns the corresponging ``msgstr`` according to the "current language".
|
|
56
|
+
|
|
57
|
+
.. code-block::
|
|
58
|
+
|
|
59
|
+
loc = Localizer(...)
|
|
60
|
+
loc.lang = "en"
|
|
61
|
+
print(loc._("app title")) # => "My First Kivy Program"
|
|
62
|
+
loc.lang = "ja"
|
|
63
|
+
print(loc._("app title")) # => "初めてのKivyプログラム"
|
|
64
|
+
|
|
65
|
+
:meta public:
|
|
66
|
+
'''
|
|
67
|
+
|
|
68
|
+
font_name: Font = StringProperty(Label.font_name.defaultvalue)
|
|
69
|
+
'''
|
|
70
|
+
(read-only)
|
|
71
|
+
A font that provides the glyphs required for rendering the "current language".
|
|
72
|
+
|
|
73
|
+
.. code-block::
|
|
74
|
+
|
|
75
|
+
loc = Localizer(...)
|
|
76
|
+
loc.lang = "en"
|
|
77
|
+
print(loc.font_name) # => "Roboto"
|
|
78
|
+
loc.lang = "ko"
|
|
79
|
+
print(loc.font_name) # => "<a pre-installed Korean font>"
|
|
80
|
+
'''
|
|
81
|
+
|
|
82
|
+
def __init__(self, translator_factory: TranslatorFactory=None, *, lang: Lang='en', font_picker: FontPicker=None):
|
|
83
|
+
if translator_factory is None:
|
|
84
|
+
Logger.warning(f"kivy_garden.i18n: No translator_factory was provided. Msgid's themselves will be displayed.")
|
|
85
|
+
translator_factory = lambda lang: lambda msgid: msgid
|
|
86
|
+
if font_picker is None:
|
|
87
|
+
font_picker = DefaultFontPicker()
|
|
88
|
+
self.translator_factory = translator_factory
|
|
89
|
+
self.font_picker = font_picker
|
|
90
|
+
super().__init__(lang=lang)
|
|
91
|
+
|
|
92
|
+
def install(self, *, name):
|
|
93
|
+
'''
|
|
94
|
+
Makes the localizer accessble from kv without any import-statements.
|
|
95
|
+
|
|
96
|
+
.. code-block::
|
|
97
|
+
|
|
98
|
+
loc = Localizer(...)
|
|
99
|
+
loc.install(name='l')
|
|
100
|
+
|
|
101
|
+
.. code-block:: yaml
|
|
102
|
+
|
|
103
|
+
Label:
|
|
104
|
+
font_name: l.font_name
|
|
105
|
+
text: l._("msgid")
|
|
106
|
+
|
|
107
|
+
:raises ValueError: if the ``name`` has already been used.
|
|
108
|
+
'''
|
|
109
|
+
from kivy.lang import global_idmap
|
|
110
|
+
if name in global_idmap:
|
|
111
|
+
raise ValueError(f"The name {name!r} has already been used.")
|
|
112
|
+
global_idmap[name] = self
|
|
113
|
+
|
|
114
|
+
def uninstall(self, *, name):
|
|
115
|
+
from kivy.lang import global_idmap
|
|
116
|
+
if name not in global_idmap:
|
|
117
|
+
raise ValueError(f"The name {name!r} not found.")
|
|
118
|
+
if global_idmap[name] is not self:
|
|
119
|
+
raise ValueError(f"The object referenced by {name!r} is not me.")
|
|
120
|
+
del global_idmap[name]
|
|
121
|
+
|
|
122
|
+
@staticmethod
|
|
123
|
+
def on_lang(self, lang):
|
|
124
|
+
''':meta private:'''
|
|
125
|
+
self._ = self.translator_factory(lang)
|
|
126
|
+
self.font_name = self.font_picker(lang)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class DefaultFontPicker:
|
|
130
|
+
PRESET = {
|
|
131
|
+
"en": (v := "Roboto"),
|
|
132
|
+
"fr": v,
|
|
133
|
+
"it": v,
|
|
134
|
+
"pt": v,
|
|
135
|
+
"ru": v,
|
|
136
|
+
}
|
|
137
|
+
''':meta private:'''
|
|
138
|
+
|
|
139
|
+
del v
|
|
140
|
+
|
|
141
|
+
def __init__(self, *, fallback: Union[Lang, None]="Roboto"):
|
|
142
|
+
self._lang2font = self.PRESET.copy()
|
|
143
|
+
self._fallback = fallback
|
|
144
|
+
|
|
145
|
+
def __call__(self, lang: Lang) -> Font:
|
|
146
|
+
try:
|
|
147
|
+
return self._lang2font[lang]
|
|
148
|
+
except KeyError:
|
|
149
|
+
pass
|
|
150
|
+
|
|
151
|
+
name = None
|
|
152
|
+
for font in enum_pre_installed_fonts():
|
|
153
|
+
if font_supports_lang(font, lang):
|
|
154
|
+
name = font.name
|
|
155
|
+
break
|
|
156
|
+
if name is None:
|
|
157
|
+
fallback = self._fallback
|
|
158
|
+
if fallback is None:
|
|
159
|
+
raise FontNotFoundError(lang)
|
|
160
|
+
Logger.warning(f"kivy_garden.i18n: Couldn't find a font for lang '{lang}'. Use {fallback} as a fallback.")
|
|
161
|
+
name = fallback
|
|
162
|
+
self._lang2font[lang] = name
|
|
163
|
+
return name
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class GettextBasedTranslatorFactory:
|
|
167
|
+
def __init__(self, domain, localedir):
|
|
168
|
+
self.domain = domain
|
|
169
|
+
self.localedir = localedir
|
|
170
|
+
|
|
171
|
+
def __call__(self, lang: Lang) -> Translator:
|
|
172
|
+
from gettext import translation
|
|
173
|
+
return translation(domain=self.domain, localedir=self.localedir, languages=(lang, )).gettext
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class MappingBasedTranslatorFactory:
|
|
177
|
+
def __init__(self, translations: Mapping[Msgid, Mapping[Lang, Msgstr]], /, strict=False):
|
|
178
|
+
'''
|
|
179
|
+
:param strict:
|
|
180
|
+
If False (default), a missing translation falls back to the ``msgid`` itself.
|
|
181
|
+
If True, a missing translation raises ``ValueError``.
|
|
182
|
+
'''
|
|
183
|
+
self._compiled_translations = self._compile_translations(translations, strict=strict)
|
|
184
|
+
|
|
185
|
+
def __call__(self, lang: Lang) -> Translator:
|
|
186
|
+
return self._compiled_translations[lang].__getitem__
|
|
187
|
+
|
|
188
|
+
@staticmethod
|
|
189
|
+
def _compile_translations(d: Mapping[Msgid, Mapping[Lang, Msgstr]], *, strict) -> dict[Lang, dict[Msgid, Msgstr]]:
|
|
190
|
+
'''
|
|
191
|
+
アプリ開発者側にとって嬉しいのは次のような形式の翻訳表だと思うが
|
|
192
|
+
|
|
193
|
+
.. code-block::
|
|
194
|
+
|
|
195
|
+
翻訳表 = {
|
|
196
|
+
"greeting": {
|
|
197
|
+
"ja": "おはよう",
|
|
198
|
+
"en": "morning",
|
|
199
|
+
},
|
|
200
|
+
"app title": {
|
|
201
|
+
"ja": "初めてのKivyプログラム",
|
|
202
|
+
"en": "My First Kivy App",
|
|
203
|
+
},
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
ライブラリが内部で持ちたいのは次の形式の翻訳表である。
|
|
207
|
+
|
|
208
|
+
.. code-block::
|
|
209
|
+
|
|
210
|
+
翻訳表 = {
|
|
211
|
+
"ja": {
|
|
212
|
+
"greeting": "おはよう",
|
|
213
|
+
"app title": "初めてのKivyプログラム",
|
|
214
|
+
},
|
|
215
|
+
"en": {
|
|
216
|
+
"greeting": "morning",
|
|
217
|
+
"app title": "My First Kivy App",
|
|
218
|
+
},
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
この関数は前者を後者に変換する。
|
|
222
|
+
'''
|
|
223
|
+
msgids = tuple(d.keys())
|
|
224
|
+
langs = set(itertools.chain.from_iterable(d.values()))
|
|
225
|
+
if strict:
|
|
226
|
+
try:
|
|
227
|
+
return {
|
|
228
|
+
lang: {msgid: d[msgid][lang] for msgid in msgids}
|
|
229
|
+
for lang in langs
|
|
230
|
+
}
|
|
231
|
+
except KeyError as e:
|
|
232
|
+
raise ValueError(f"Msgid '{e.args[0]}' is missing one or more translations") from e
|
|
233
|
+
else:
|
|
234
|
+
return {
|
|
235
|
+
lang: {msgid: d[msgid].get(lang, msgid) for msgid in msgids}
|
|
236
|
+
for lang in langs
|
|
237
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from ._extract_msgids_from_string_literals import extract_msgids_from_string_literals
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
__all__ = ('extract_msgids_from_string_literals', )
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from typing import Iterator
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def extract_string_literals(python_code: str) -> Iterator[str]:
|
|
8
|
+
from ast import parse, walk, Constant
|
|
9
|
+
str_ = str
|
|
10
|
+
isinstance_ = isinstance
|
|
11
|
+
for node in walk(parse(python_code)):
|
|
12
|
+
if isinstance_(node, Constant) and isinstance_(node.value, str_):
|
|
13
|
+
yield node.value
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
PATTERN = re.compile(r"""
|
|
17
|
+
(^|\W)_\("(.*?)"\) # _("")で括られた文字列
|
|
18
|
+
| # もしくは
|
|
19
|
+
(^|\W)_\('(.*?)'\) # _('')で括られた文字列
|
|
20
|
+
""", re.VERBOSE | re.MULTILINE)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def extract_msgid(s:str) -> Iterator[str]:
|
|
24
|
+
for m in PATTERN.finditer(s):
|
|
25
|
+
yield (m.group(2) or m.group(4))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def extract_msgids_from_string_literals(python_code: str) -> Iterator[str]:
|
|
29
|
+
'''
|
|
30
|
+
.. code-block::
|
|
31
|
+
|
|
32
|
+
msgids = extract_msgids_from_string_literals("""
|
|
33
|
+
Label:
|
|
34
|
+
font_name: _("AAA")
|
|
35
|
+
text: _("BBB")
|
|
36
|
+
""")
|
|
37
|
+
assert list(msgids) == ["AAA", "BBB"]
|
|
38
|
+
'''
|
|
39
|
+
return (
|
|
40
|
+
ls
|
|
41
|
+
for s in extract_string_literals(python_code)
|
|
42
|
+
for ls in extract_msgid(s)
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def cli_main():
|
|
47
|
+
from textwrap import dedent
|
|
48
|
+
import sys
|
|
49
|
+
from pathlib import Path
|
|
50
|
+
from io import StringIO
|
|
51
|
+
|
|
52
|
+
if len(sys.argv) == 1:
|
|
53
|
+
print(dedent("""
|
|
54
|
+
Usage:
|
|
55
|
+
extract-msgids filename1.py filename2.py ...
|
|
56
|
+
"""),
|
|
57
|
+
file=sys.stderr)
|
|
58
|
+
return
|
|
59
|
+
output = StringIO()
|
|
60
|
+
for file in sys.argv[1:]:
|
|
61
|
+
for ls in extract_msgids_from_string_literals(Path(file).read_text(encoding='utf-8')):
|
|
62
|
+
print(ls, file=output)
|
|
63
|
+
print(output.getvalue())
|
|
64
|
+
output.close()
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kivy-garden-i18n
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: i18n for Kivy
|
|
5
|
+
Keywords: kivy
|
|
6
|
+
Author: Nattōsai Mitō
|
|
7
|
+
Author-email: Nattōsai Mitō <flow4re2c@gmail.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
19
|
+
Classifier: Operating System :: OS Independent
|
|
20
|
+
Requires-Python: >=3.10, <4.0
|
|
21
|
+
Project-URL: Repository, https://github.com/gottadiveintopython/i18n
|
|
22
|
+
Project-URL: Documentation, https://gottadiveintopython.github.io/i18n/
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# kivy_garden.i18n
|
|
26
|
+
|
|
27
|
+
Internationalization support for Kivy applications.
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
from kivy_garden.i18n.localizer import MappingBasedTranslatorFactory, Localizer
|
|
31
|
+
|
|
32
|
+
translations = {
|
|
33
|
+
"greeting": {
|
|
34
|
+
"ja": "おはよう",
|
|
35
|
+
"en": "morning",
|
|
36
|
+
},
|
|
37
|
+
"app title": {
|
|
38
|
+
"ja": "初めてのKivyプログラム",
|
|
39
|
+
"en": "My First Kivy App",
|
|
40
|
+
},
|
|
41
|
+
}
|
|
42
|
+
localizer = Localizer(MappingBasedTranslatorFactory(translations))
|
|
43
|
+
|
|
44
|
+
l = localizer
|
|
45
|
+
l.lang = "en" # Set the "current language" to "en"
|
|
46
|
+
assert l.font_name == "Roboto"
|
|
47
|
+
assert l._("greeting") == "morning"
|
|
48
|
+
assert l._("app title") == "My First Kivy App"
|
|
49
|
+
|
|
50
|
+
l.lang = "ja"
|
|
51
|
+
assert l.font_name == "<a pre-installed Japanese font>"
|
|
52
|
+
assert l._("greeting") == "おはよう"
|
|
53
|
+
assert l._("app title") == "初めてのKivyプログラム"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Bindings:
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from kivy.lang import Builder
|
|
60
|
+
|
|
61
|
+
localizer.install(name="l")
|
|
62
|
+
label = Builder.load_string("""
|
|
63
|
+
Label:
|
|
64
|
+
font_name: l.font_name
|
|
65
|
+
text: l._("greeting")
|
|
66
|
+
""")
|
|
67
|
+
localizer.lang = "en"
|
|
68
|
+
assert label.font_name == "Roboto"
|
|
69
|
+
assert label.text == "morning"
|
|
70
|
+
localizer.lang = "ja"
|
|
71
|
+
assert label.font_name == "<a pre-installed Japanese font>"
|
|
72
|
+
assert label.text == "おはよう"
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
# Installation
|
|
76
|
+
|
|
77
|
+
Pin the minor version.
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
pip install "kivy-garden-i18n>=0.3,<0.4"
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
# Tested on
|
|
84
|
+
|
|
85
|
+
- CPython 3.10 + Kivy 2.3
|
|
86
|
+
- CPython 3.11 + Kivy 2.3
|
|
87
|
+
- CPython 3.12 + Kivy 2.3
|
|
88
|
+
- CPython 3.13 + Kivy 2.3
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
kivy_garden/i18n/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
kivy_garden/i18n/fontfinder.py,sha256=aQkQcKPM3dFWugwVK1qUYiY3pS2crmuTM_jGaBvIkPY,5474
|
|
3
|
+
kivy_garden/i18n/localizer.py,sha256=956llaf2yk09cN7EjWVWf7sJD9LyMGUJmgXkCh_UFCI,7350
|
|
4
|
+
kivy_garden/i18n/utils/__init__.py,sha256=EaG7vkSf72tc6GAV_v0TqA2urlMOyKaXsF48zuS4afA,86
|
|
5
|
+
kivy_garden/i18n/utils/_extract_msgids_from_string_literals.py,sha256=min0LFN3XZ9RAP-4F-c7js9EHeZxSBCDYt_IMrRVyG0,1708
|
|
6
|
+
kivy_garden_i18n-0.3.0.dist-info/WHEEL,sha256=XV0cjMrO7zXhVAIyyc8aFf1VjZ33Fen4IiJk5zFlC3g,80
|
|
7
|
+
kivy_garden_i18n-0.3.0.dist-info/entry_points.txt,sha256=1HK5Z_gLLUsXJIYxBV3Zbjk7z2rRR14OUT6us33SkQI,105
|
|
8
|
+
kivy_garden_i18n-0.3.0.dist-info/METADATA,sha256=XtJk6YimSzIRs4ApJ_NFu0nKPUp4GhiuUUsxYX1kuBU,2342
|
|
9
|
+
kivy_garden_i18n-0.3.0.dist-info/RECORD,,
|