Internationalisation¶
switch UI language and watch plural rules update live.
▶ Run in browserTags: ui i18n localisation text plural
Translations is the engine’s translation table: load_dict({locale: {key: text}})
(or load_json / load_csv) fills it, and setting Translations.instance().locale
switches the active language. tr(key, **kwargs) looks a key up in the active
locale, falls back to the default locale when it is missing, and fills {name}
placeholders from the keyword arguments. translate_plural(key, count) picks
key_<category> by the locale’s CLDR plural rule: English has one and other,
Japanese only other, and Arabic six categories, zero, one, two, few,
many and other, each with its own phrase here.
The panel shows a greeting, a pluralised item count and the language name in English, French, Japanese and Arabic. Press 1-4 or click the EN / FR / JA / AR buttons to switch; press Left / Right or click - / + to change the count and watch the plural phrase change with it. Japanese and Arabic glyphs come from the engine’s font fallback chain, so no font is loaded here.
A label shows whatever string it was given, so refresh re-runs every lookup
after a change. The engine has no text-direction data either, so the example
keeps its own set of right-to-left locales and right-aligns the Arabic lines.
The locale buttons share a ButtonGroup, which keeps the active one checked.
Source¶
1# /// simvx
2# tags = ["ui", "i18n", "localisation", "text", "plural"]
3# web = { width = 720, height = 480, responsive = true }
4# ///
5"""Internationalisation: switch UI language and watch plural rules update live.
6
7`Translations` is the engine's translation table: `load_dict({locale: {key: text}})`
8(or `load_json` / `load_csv`) fills it, and setting `Translations.instance().locale`
9switches the active language. `tr(key, **kwargs)` looks a key up in the active
10locale, falls back to the default locale when it is missing, and fills `{name}`
11placeholders from the keyword arguments. `translate_plural(key, count)` picks
12`key_<category>` by the locale's CLDR plural rule: English has `one` and `other`,
13Japanese only `other`, and Arabic six categories, `zero`, `one`, `two`, `few`,
14`many` and `other`, each with its own phrase here.
15
16The panel shows a greeting, a pluralised item count and the language name in
17English, French, Japanese and Arabic. Press 1-4 or click the EN / FR / JA / AR
18buttons to switch; press Left / Right or click - / + to change the count and watch
19the plural phrase change with it. Japanese and Arabic glyphs come from the
20engine's font fallback chain, so no font is loaded here.
21
22A label shows whatever string it was given, so `refresh` re-runs every lookup
23after a change. The engine has no text-direction data either, so the example
24keeps its own set of right-to-left locales and right-aligns the Arabic lines.
25The locale buttons share a `ButtonGroup`, which keeps the active one checked.
26"""
27
28from simvx.core import Anchor, Colour, HAlign, Input, Key, Label, Node, Panel
29from simvx.core.i18n import Translations, tr
30from simvx.core.ui import Button, ButtonGroup, HBoxContainer, VBoxContainer
31from simvx.graphics import App
32
33TRANSLATIONS = {
34 "en": {
35 "greeting": "Hello, {name}!",
36 "item_one": "You have {count} item.",
37 "item_other": "You have {count} items.",
38 "language": "Language: English",
39 "count_label": "Items: {count}",
40 "instructions": "1=EN 2=FR 3=JA 4=AR Left/Right = count",
41 },
42 "fr": {
43 "greeting": "Bonjour, {name} !",
44 "item_one": "Vous avez {count} objet.",
45 "item_other": "Vous avez {count} objets.",
46 "language": "Langue : Français",
47 "count_label": "Objets : {count}",
48 "instructions": "1=EN 2=FR 3=JA 4=AR Gauche/Droite = nombre",
49 },
50 "ja": {
51 "greeting": "こんにちは、{name}さん!",
52 "item_other": "アイテムが{count}個あります。",
53 "language": "言語: 日本語",
54 "count_label": "個数: {count}",
55 "instructions": "1=EN 2=FR 3=JA 4=AR ←/→ = 数",
56 },
57 "ar": {
58 "greeting": "مرحبا، {name}!",
59 "item_zero": "ليس لديك عناصر.",
60 "item_one": "لديك عنصر واحد.",
61 "item_two": "لديك عنصران.",
62 "item_few": "لديك {count} عناصر.",
63 "item_many": "لديك {count} عنصرا.",
64 "item_other": "لديك {count} عنصر.",
65 "language": "اللغة: العربية",
66 "count_label": "العدد: {count}",
67 "instructions": "1=EN 2=FR 3=JA 4=AR ←/→",
68 },
69}
70LOCALE_KEYS = {"en": Key.KEY_1, "fr": Key.KEY_2, "ja": Key.KEY_3, "ar": Key.KEY_4}
71RIGHT_TO_LEFT = {"ar"}
72
73
74class I18nDemo(Node):
75 input_context = {f"locale_{code}": [key] for code, key in LOCALE_KEYS.items()} | {
76 "fewer": [Key.LEFT],
77 "more": [Key.RIGHT],
78 }
79
80 def on_ready(self):
81 Translations.instance().load_dict(TRANSLATIONS)
82 self.count = 5
83
84 panel = self.add_child(Panel(anchor=Anchor.CENTER, size=(560, 340)))
85 panel.bg_colour = Colour.hex("#1A1A2E")
86 column = panel.add_child(VBoxContainer(anchor=Anchor.FULL_RECT, inset=(26, 22), separation=14))
87
88 self.title = column.add_child(Label(font_size=20, alignment=HAlign.CENTRE))
89 self.greeting = column.add_child(Label(name="Greeting", font_size=18))
90 self.greeting.text_colour = Colour.hex("#FFD166")
91 self.plural = column.add_child(Label(name="Plural", font_size=16))
92 self.language = column.add_child(Label(name="Language"))
93 self.language.text_colour = Colour.LIGHT_GRAY
94
95 count_row = column.add_child(HBoxContainer(separation=10))
96 count_row.add_child(Button("-", name="Fewer", size=(40, 30), on_press=lambda: self.change_count(-1)))
97 self.count_label = count_row.add_child(Label(name="Count", size=(120, 30), alignment=HAlign.CENTRE))
98 count_row.add_child(Button("+", name="More", size=(40, 30), on_press=lambda: self.change_count(1)))
99
100 locale_row = column.add_child(HBoxContainer(separation=8))
101 group = ButtonGroup()
102 self.locale_buttons = {
103 code: locale_row.add_child(
104 Button(code.upper(), name=code, size=(108, 34), group=group, on_press=lambda c=code: self.set_locale(c))
105 )
106 for code in LOCALE_KEYS
107 }
108
109 self.help = column.add_child(Label(font_size=12, alignment=HAlign.CENTRE))
110 self.help.text_colour = Colour.GRAY
111 self.set_locale("en")
112
113 def on_update(self, dt):
114 for code in LOCALE_KEYS:
115 if Input.just_pressed(f"locale_{code}"):
116 self.set_locale(code)
117 if Input.just_pressed("fewer"):
118 self.change_count(-1)
119 if Input.just_pressed("more"):
120 self.change_count(1)
121
122 def set_locale(self, code: str):
123 Translations.instance().locale = code
124 self.refresh()
125
126 def change_count(self, delta: int):
127 self.count = max(0, self.count + delta)
128 self.refresh()
129
130 def refresh(self):
131 ts = Translations.instance()
132 self.title.text = f"SimVX i18n Demo [{ts.locale.upper()}]"
133 self.greeting.text = tr("greeting", name="Player")
134 self.plural.text = ts.translate_plural("item", self.count)
135 self.language.text = tr("language")
136 self.count_label.text = tr("count_label", count=self.count)
137 self.help.text = tr("instructions")
138 align = HAlign.RIGHT if ts.locale in RIGHT_TO_LEFT else HAlign.LEFT
139 for label in (self.greeting, self.plural, self.language):
140 label.alignment = align
141 self.locale_buttons[ts.locale].checked = True
142
143
144if __name__ == "__main__":
145 App(title="SimVX i18n Demo", width=720, height=480).run(I18nDemo())