|
1 | | -import copy |
| 1 | +import json |
2 | 2 | import os |
| 3 | +import sys |
| 4 | +from typing import Dict |
3 | 5 |
|
4 | 6 | from babel.messages.catalog import Catalog |
5 | 7 | from babel.messages.mofile import write_mo |
6 | 8 | from babel.messages.pofile import write_po |
7 | 9 |
|
8 | | -ROOT = os.path.join("sqladmin", "translations") |
| 10 | +DEFAULT_MAPPINGS = os.path.join("scripts", "translations.json") |
| 11 | +OUTPUT_ROOT = os.path.join("sqladmin", "translations") |
| 12 | +DOMAIN = "admin" |
9 | 13 |
|
10 | | -# Every translatable UI string (the msgids marked with _("...") in templates). |
11 | | -MESSAGES = [ |
12 | | - "Logout", |
13 | | - "Login", |
14 | | - "Login to", |
15 | | - "Username", |
16 | | - "Enter username", |
17 | | - "Password", |
18 | | - "Search", |
19 | | - "Filters", |
20 | | - "Apply Filter", |
21 | | - "Save", |
22 | | - "Save and continue editing", |
23 | | - "Save and add another", |
24 | | - "Save as new", |
25 | | - "Column", |
26 | | - "Value", |
27 | | - "Please confirm", |
28 | | - "Cancel", |
29 | | - "Delete", |
30 | | - "Delete selected items", |
31 | | - "Showing", |
32 | | - "Language", |
33 | | - "Export", |
34 | | - "Import CSV", |
35 | | - "New", |
36 | | - "Edit", |
37 | | - "Actions", |
38 | | - "View", |
39 | | - "to", |
40 | | - "of", |
41 | | - "items", |
42 | | - "prev", |
43 | | - "next", |
44 | | - "Show", |
45 | | - "Page", |
46 | | - "Go Back", |
47 | | - "Yes", |
48 | | - "Close", |
49 | | - "No file selected", |
50 | | - "Skip invalid rows and continue import", |
51 | | - "Import result", |
52 | | - "Missed rows", |
53 | | - "Import", |
54 | | - "Download CSV", |
55 | | - "Download TXT", |
56 | | - "Refresh list", |
57 | | - "Clear filter", |
58 | | - "Done", |
59 | | - "Select all", |
60 | | - "Select item", |
61 | | - "Import progress", |
62 | | -] |
63 | 14 |
|
64 | | -# locale -> {msgid: msgstr}. English is the source language (empty catalog). |
65 | | -TRANSLATIONS: dict[str, dict[str, str]] = { |
66 | | - "en": {}, |
67 | | - "az": { |
68 | | - "Logout": "Çıxış", |
69 | | - "Login": "Daxil ol", |
70 | | - "Login to": "Daxil ol:", |
71 | | - "Username": "İstifadəçi adı", |
72 | | - "Enter username": "İstifadəçi adını daxil edin", |
73 | | - "Password": "Şifrə", |
74 | | - "Search": "Axtarış", |
75 | | - "Filters": "Filtrlər", |
76 | | - "Apply Filter": "Filtri tətbiq et", |
77 | | - "Save": "Yadda saxla", |
78 | | - "Save and continue editing": "Yadda saxla və redaktəyə davam et", |
79 | | - "Save and add another": "Yadda saxla və başqasını əlavə et", |
80 | | - "Save as new": "Yeni kimi yadda saxla", |
81 | | - "Column": "Sütun", |
82 | | - "Value": "Dəyər", |
83 | | - "Please confirm": "Təsdiq edin", |
84 | | - "Cancel": "Ləğv et", |
85 | | - "Delete": "Sil", |
86 | | - "Delete selected items": "Seçilmiş elementləri sil", |
87 | | - "Showing": "Göstərilir", |
88 | | - "Language": "Dil", |
89 | | - "Export": "İxrac et", |
90 | | - "Import CSV": "CSV idxal et", |
91 | | - "New": "Yeni", |
92 | | - "Edit": "Redaktə et", |
93 | | - "Actions": "Əməliyyatlar", |
94 | | - "View": "Bax", |
95 | | - "to": "-", |
96 | | - "of": "/", |
97 | | - "items": "element", |
98 | | - "prev": "əvvəlki", |
99 | | - "next": "növbəti", |
100 | | - "Show": "Göstər", |
101 | | - "Page": "Səhifə", |
102 | | - "Go Back": "Geri qayıt", |
103 | | - "Yes": "Bəli", |
104 | | - "Close": "Bağla", |
105 | | - "No file selected": "Fayl seçilməyib", |
106 | | - "Skip invalid rows and continue import": "Yanlış sətirləri ötür və idxalı davam etdir", # noqa: E501 |
107 | | - "Import result": "İdxal nəticəsi", |
108 | | - "Missed rows": "Buraxılmış sətirlər", |
109 | | - "Import": "İdxal et", |
110 | | - "Download CSV": "CSV yüklə", |
111 | | - "Download TXT": "TXT yüklə", |
112 | | - "Refresh list": "Siyahını yenilə", |
113 | | - "Clear filter": "Filtri təmizlə", |
114 | | - "Done": "Hazır", |
115 | | - "Select all": "Hamısını seç", |
116 | | - "Select item": "Elementi seç", |
117 | | - "Import progress": "İdxal gedişatı", |
118 | | - }, |
119 | | - "de": { |
120 | | - "Logout": "Abmelden", |
121 | | - "Login": "Anmelden", |
122 | | - "Login to": "Anmelden bei", |
123 | | - "Username": "Benutzername", |
124 | | - "Enter username": "Benutzername eingeben", |
125 | | - "Password": "Passwort", |
126 | | - "Search": "Suche", |
127 | | - "Filters": "Filter", |
128 | | - "Apply Filter": "Filter anwenden", |
129 | | - "Save": "Speichern", |
130 | | - "Save and continue editing": "Speichern und weiter bearbeiten", |
131 | | - "Save and add another": "Speichern und weiteres hinzufügen", |
132 | | - "Save as new": "Als neu speichern", |
133 | | - "Column": "Spalte", |
134 | | - "Value": "Wert", |
135 | | - "Please confirm": "Bitte bestätigen", |
136 | | - "Cancel": "Abbrechen", |
137 | | - "Delete": "Löschen", # noqa: E501 |
138 | | - "Delete selected items": "Ausgewählte Einträge löschen", |
139 | | - "Showing": "Anzeige", |
140 | | - "Language": "Sprache", |
141 | | - "Export": "Exportieren", |
142 | | - "Import CSV": "CSV importieren", |
143 | | - "New": "Neu", |
144 | | - "Edit": "Bearbeiten", |
145 | | - "Actions": "Aktionen", |
146 | | - "View": "Ansehen", |
147 | | - "to": "bis", |
148 | | - "of": "von", |
149 | | - "items": "Einträge", |
150 | | - "prev": "zurück", |
151 | | - "next": "weiter", # noqa: E501 |
152 | | - "Show": "Zeige", |
153 | | - "Page": "Seite", |
154 | | - "Go Back": "Zurück", |
155 | | - "Yes": "Ja", |
156 | | - "Close": "Schließen", |
157 | | - "No file selected": "Keine Datei ausgewählt", |
158 | | - "Skip invalid rows and continue import": "Ungültige Zeilen überspringen und Import fortsetzen", # noqa: E501 |
159 | | - "Import result": "Importergebnis", |
160 | | - "Missed rows": "Übersprungene Zeilen", |
161 | | - "Import": "Importieren", |
162 | | - "Download CSV": "CSV herunterladen", |
163 | | - "Download TXT": "TXT herunterladen", |
164 | | - "Refresh list": "Liste aktualisieren", |
165 | | - "Clear filter": "Filter löschen", |
166 | | - "Done": "Fertig", |
167 | | - "Select all": "Alle auswählen", |
168 | | - "Select item": "Eintrag auswählen", |
169 | | - "Import progress": "Importfortschritt", |
170 | | - }, |
171 | | - "ru": { |
172 | | - "Logout": "Выход", |
173 | | - "Login": "Вход", |
174 | | - "Login to": "Вход в", |
175 | | - "Username": "Имя пользователя", |
176 | | - "Enter username": "Введите имя пользователя", |
177 | | - "Password": "Пароль", |
178 | | - "Search": "Поиск", |
179 | | - "Filters": "Фильтры", |
180 | | - "Apply Filter": "Применить фильтр", |
181 | | - "Save": "Сохранить", |
182 | | - "Save and continue editing": "Сохранить и продолжить редактирование", |
183 | | - "Save and add another": "Сохранить и добавить ещё", |
184 | | - "Save as new": "Сохранить как новое", |
185 | | - "Column": "Столбец", |
186 | | - "Value": "Значение", |
187 | | - "Please confirm": "Подтвердите", |
188 | | - "Cancel": "Отмена", |
189 | | - "Delete": "Удалить", |
190 | | - "Delete selected items": "Удалить выбранные элементы", |
191 | | - "Showing": "Показано", |
192 | | - "Language": "Язык", |
193 | | - "Export": "Экспорт", |
194 | | - "Import CSV": "Импорт CSV", |
195 | | - "New": "Создать", |
196 | | - "Edit": "Изменить", |
197 | | - "Actions": "Действия", |
198 | | - "View": "Просмотр", |
199 | | - "to": "–", |
200 | | - "of": "из", |
201 | | - "items": "элементов", |
202 | | - "prev": "назад", |
203 | | - "next": "вперёд", |
204 | | - "Show": "Показать", |
205 | | - "Page": "страница", |
206 | | - "Go Back": "Назад", |
207 | | - "Yes": "Да", |
208 | | - "Close": "Закрыть", |
209 | | - "No file selected": "Файл не выбран", |
210 | | - "Skip invalid rows and continue import": "Пропустить неверные строки и продолжить импорт", # noqa: E501 |
211 | | - "Import result": "Результат импорта", |
212 | | - "Missed rows": "Пропущенные строки", |
213 | | - "Import": "Импорт", |
214 | | - "Download CSV": "Скачать CSV", |
215 | | - "Download TXT": "Скачать TXT", # noqa: E501 |
216 | | - "Refresh list": "Обновить список", |
217 | | - "Clear filter": "Очистить фильтр", |
218 | | - "Done": "Готово", |
219 | | - "Select all": "Выбрать всё", |
220 | | - "Select item": "Выбрать элемент", |
221 | | - "Import progress": "Ход импорта", |
222 | | - }, |
223 | | - "tr": { |
224 | | - "Logout": "Çıkış", |
225 | | - "Login": "Giriş", |
226 | | - "Login to": "Giriş:", |
227 | | - "Username": "Kullanıcı adı", |
228 | | - "Enter username": "Kullanıcı adını girin", |
229 | | - "Password": "Parola", |
230 | | - "Search": "Ara", |
231 | | - "Filters": "Filtreler", |
232 | | - "Apply Filter": "Filtreyi uygula", |
233 | | - "Save": "Kaydet", |
234 | | - "Save and continue editing": "Kaydet ve düzenlemeye devam et", |
235 | | - "Save and add another": "Kaydet ve başka ekle", |
236 | | - "Save as new": "Yeni olarak kaydet", |
237 | | - "Column": "Sütun", |
238 | | - "Value": "Değer", |
239 | | - "Please confirm": "Lütfen onaylayın", |
240 | | - "Cancel": "İptal", |
241 | | - "Delete": "Sil", |
242 | | - "Delete selected items": "Seçili öğeleri sil", |
243 | | - "Showing": "Gösteriliyor", |
244 | | - "Language": "Dil", |
245 | | - "Export": "Dışa aktar", |
246 | | - "Import CSV": "CSV içe aktar", |
247 | | - "New": "Yeni", |
248 | | - "Edit": "Düzenle", |
249 | | - "Actions": "İşlemler", |
250 | | - "View": "Görüntüle", |
251 | | - "to": "-", |
252 | | - "of": "/", |
253 | | - "items": "öğe", |
254 | | - "prev": "önceki", |
255 | | - "next": "sonraki", |
256 | | - "Show": "Göster", |
257 | | - "Page": "Sayfa", |
258 | | - "Go Back": "Geri dön", |
259 | | - "Yes": "Evet", |
260 | | - "Close": "Kapat", |
261 | | - "No file selected": "Dosya seçilmedi", |
262 | | - "Skip invalid rows and continue import": "Geçersiz satırları atla ve içe aktarmaya devam et", # noqa: E501 |
263 | | - "Import result": "İçe aktarma sonucu", |
264 | | - "Missed rows": "Atlanan satırlar", |
265 | | - "Import": "İçe aktar", |
266 | | - "Download CSV": "CSV indir", |
267 | | - "Download TXT": "TXT indir", |
268 | | - "Refresh list": "Listeyi yenile", |
269 | | - "Clear filter": "Filtreyi temizle", |
270 | | - "Done": "Tamam", |
271 | | - "Select all": "Tümünü seç", |
272 | | - "Select item": "Öğeyi seç", |
273 | | - "Import progress": "İçe aktarma ilerlemesi", |
274 | | - }, |
275 | | -} |
| 15 | +def build(mappings_path: str, output_root: str = OUTPUT_ROOT) -> None: |
| 16 | + """Write ``.po``/``.mo`` catalogs (and the ``.pot`` template) from a mapping. |
276 | 17 |
|
| 18 | + Args: |
| 19 | + mappings_path: Path to the JSON file of ``{locale: {source: translation}}``. |
| 20 | + output_root: Directory the ``gettext`` tree is written to. |
| 21 | + """ |
| 22 | + with open(mappings_path, encoding="utf-8") as file: |
| 23 | + mappings: Dict[str, Dict[str, str]] = json.load(file) |
277 | 24 |
|
278 | | -def build() -> None: |
279 | | - os.makedirs(ROOT, exist_ok=True) |
| 25 | + # The full source-string set is the union of every locale's keys, so a |
| 26 | + # partially translated locale still lists all messages. |
| 27 | + messages = sorted({message for entries in mappings.values() for message in entries}) |
| 28 | + os.makedirs(output_root, exist_ok=True) |
280 | 29 |
|
281 | | - template = Catalog(project="SQLAdmin", domain="admin") |
282 | | - for message in MESSAGES: |
| 30 | + template = Catalog(project="SQLAdmin", domain=DOMAIN) |
| 31 | + for message in messages: |
283 | 32 | template.add(message, string="") |
284 | | - with open(os.path.join(ROOT, "admin.pot"), "wb") as f: |
285 | | - write_po(f, template, omit_header=False) |
| 33 | + with open(os.path.join(output_root, "admin.pot"), "wb") as file: |
| 34 | + write_po(file, template, omit_header=False) |
286 | 35 |
|
287 | | - for locale, mapping in TRANSLATIONS.items(): |
288 | | - catalog = copy.deepcopy(template) |
289 | | - catalog.locale = locale |
290 | | - catalog.fuzzy = False |
291 | | - for message in catalog: # type: ignore[assignment] |
292 | | - if message.id and message.id in mapping: # type: ignore[attr-defined] |
293 | | - message.string = mapping[message.id] # type: ignore[attr-defined] |
| 36 | + for locale, entries in mappings.items(): |
| 37 | + catalog = Catalog(locale=locale, project="SQLAdmin", domain=DOMAIN) |
| 38 | + for message in messages: |
| 39 | + catalog.add(message, string=entries.get(message, "")) |
294 | 40 |
|
295 | | - directory = os.path.join(ROOT, locale, "LC_MESSAGES") |
| 41 | + directory = os.path.join(output_root, locale, "LC_MESSAGES") |
296 | 42 | os.makedirs(directory, exist_ok=True) |
297 | | - with open(os.path.join(directory, "admin.po"), "wb") as f: |
298 | | - write_po(f, catalog, omit_header=False, sort_output=False) |
299 | | - with open(os.path.join(directory, "admin.mo"), "wb") as f: |
300 | | - write_mo(f, catalog) |
301 | | - translated = sum(1 for m in catalog if m.id and m.string) |
302 | | - print(f"{locale}: {translated}/{len(MESSAGES)} translated") |
| 43 | + with open(os.path.join(directory, "admin.po"), "wb") as file: |
| 44 | + write_po(file, catalog, omit_header=False, sort_output=False) |
| 45 | + with open(os.path.join(directory, "admin.mo"), "wb") as file: |
| 46 | + write_mo(file, catalog) |
| 47 | + |
| 48 | + translated = sum(1 for message in messages if entries.get(message)) |
| 49 | + print(f"{locale}: {translated}/{len(messages)} translated") |
303 | 50 |
|
304 | 51 |
|
305 | 52 | if __name__ == "__main__": |
306 | | - build() |
| 53 | + path = sys.argv[1] if len(sys.argv) > 1 else DEFAULT_MAPPINGS |
| 54 | + build(path) |
0 commit comments