Skip to main content

ui/
i18n.rs

1// i18n -- languages, translation catalogues, regional formats and messages
2//
3//   set_language(LANG_*) / language()   the UI language: the catalogue column (process wide;
4//                                       default German, the source column)
5//   set_language_code(code) -> index    the best column for a BCP 47 code ("fr-CA" -> "fr",
6//                                       else English, else the source)
7//   language_code() -> "de" / "en" / ...   the code of the current column
8//   user_languages() / set_user_languages(list)   the languages the user wants, in order (ui
9//                                       boots them from --lang or jfgui_LANG, the system in
10//                                       window mode, "de-DE" in headless runs);
11//                                       negotiate(available, wanted)
12//   use_system()                        user languages and format from --lang, jfgui_LANG or
13//                                       the system (programs without ui::main!)
14//   tr(de) -> text                      a text of the library itself (file dialog, command
15//                                       bar, tree, toasts, shortcuts, date picker) in the UI
16//                                       language: German, English, French, Spanish, Italian,
17//                                       Dutch, Polish, Arabic, Hebrew (library_languages), else
18//                                       English; the German source text is the key
19//                                       (library_keys)
20//   Catalog::from_tsv(text)             an app's catalogue: a TSV file whose first line names
21//                                       the languages ("de\ten\tfr"), then one row per text,
22//                                       the source text first; catalog.tr(s) -> the text in
23//                                       the current language (the source when missing)
24//   month_name(m) / month_short(m) / weekday_short(d)   calendar names of the UI language
25//                                       (m 1..12, d 0 = Mon)
26//   Catalog::with_library_languages()   also offer the languages of the library's own texts;
27//                                       the app's texts show their English column in them
28//   set_catalog(catalog) / t(s)         the app's catalogue for the whole process: t(s) is the
29//                                       text in the current language (the library's text, then
30//                                       the English column, then the German source when the
31//                                       cell is missing).  Unless set_language came first, the
32//                                       column is negotiated with the user's languages
33//   commands(table) / menu(table)       a static command table or menu in the current language
34//                                       (a copy made once per table and language)
35//   fill(text, args)                    "{}" placeholders replaced in order
36//   tf(s, args) / list(&TABLE)          t with its "{}" filled; a table of texts through t
37//   msg(text, args)                     named / numbered placeholders, plurals, select,
38//                                       numbers and dates in the user's format (i18n_msg.rs)
39//   locale(), Locale, fmt_*, plural     regional formats and plural rules (i18n_locale.rs)
40//   collate(a, b), Collator, to_upper / to_lower / fold, eq_ignore_case   sorting by language
41//                                       and case mapping (i18n_collate.rs)
42//
43// The language index is the catalogue column: LANG_DE = 0 (the source column), LANG_EN = 1.
44
45use alloc::boxed::Box;
46use alloc::collections::BTreeMap;
47use alloc::string::String;
48use alloc::vec::Vec;
49use core::sync::atomic::{AtomicBool, AtomicU32, Ordering};
50
51use crate::{Command, Mi};
52
53#[path = "i18n_locale.rs"]
54mod regional;
55pub use regional::*;
56#[path = "i18n_msg.rs"]
57mod message;
58pub use message::{Arg, msg};
59#[path = "i18n_collate.rs"]
60mod collation;
61pub use collation::*;
62
63pub const LANG_DE: u32 = 0;
64pub const LANG_EN: u32 = 1;
65
66static LANG: AtomicU32 = AtomicU32::new(LANG_DE);
67// the language of the library's texts: an index of LIB_LANGS (0 German, 1 English, ...)
68static LIB: AtomicU32 = AtomicU32::new(0);
69// the UI language is written right to left
70static LIB_RTL: AtomicBool = AtomicBool::new(false);
71const RTL_LANGS: [&str; 10] = ["ar", "he", "iw", "fa", "ur", "ps", "sd", "ug", "yi", "dv"];
72// set_language / set_language_code was called: set_catalog keeps the column (or the code)
73static EXPLICIT: AtomicBool = AtomicBool::new(false);
74static EXPLICIT_CODE: rt::sync::SpinLock<Option<String>> = rt::sync::SpinLock::new(None);
75// the user's languages (empty: "de-DE")
76static USER: rt::sync::SpinLock<Vec<String>> = rt::sync::SpinLock::new(Vec::new());
77
78/// set the UI language: the catalogue column (LANG_DE, LANG_EN, ...)
79pub fn set_language(lang: u32) {
80    EXPLICIT.store(true, Ordering::Relaxed);
81    *EXPLICIT_CODE.lock() = None;
82    select(lang);
83}
84
85fn select(lang: u32) {
86    LANG.store(lang, Ordering::Relaxed);
87    let code = language_code();
88    let lib = if code == "iw" { "he" } else { code.as_str() };
89    LIB.store(LIB_LANGS.iter().position(|l| *l == lib).unwrap_or(1) as u32, Ordering::Relaxed);
90    LIB_RTL.store(RTL_LANGS.contains(&code.as_str()), Ordering::Relaxed);
91}
92
93// the language codes of the columns: the catalogue's, else the library's own
94fn columns() -> Vec<String> {
95    match *APP.lock() {
96        Some(c) if !c.langs.is_empty() => c.langs.clone(),
97        _ => alloc::vec![String::from("de"), String::from("en")],
98    }
99}
100
101/// (inspect_edit.rs) the codes of the languages the UI can switch to: the columns of the app's
102/// catalogue, else German and English
103#[cfg(jfgui_dev)]
104pub(crate) fn language_codes() -> Vec<String> {
105    columns()
106}
107
108/// the code of the current UI language ("de", "en", "fr"; the catalogue's header, lower case)
109pub fn language_code() -> String {
110    let cols = columns();
111    let code = cols.get(language() as usize).map_or("en", |c| c.as_str());
112    String::from(code.split(['-', '_']).next().unwrap_or(code)).to_ascii_lowercase()
113}
114
115/// select the column that fits a BCP 47 code best (the language, else English, else the
116/// source) -> its index; a later catalogue is matched with the code again
117pub fn set_language_code(code: &str) -> u32 {
118    EXPLICIT.store(true, Ordering::Relaxed);
119    *EXPLICIT_CODE.lock() = Some(String::from(code));
120    let k = pick(&[code]);
121    select(k);
122    k
123}
124
125// the best column for the wanted languages
126fn pick<W: AsRef<str>>(wanted: &[W]) -> u32 {
127    let cols = columns();
128    let k = negotiate(&cols, wanted).or_else(|| negotiate(&cols, &["en"])).unwrap_or(0);
129    k as u32
130}
131
132/// the first of `available` that fits the `wanted` languages (in their order): the same tag,
133/// else the same language ("fr-CA" takes "fr" or "fr-FR")
134pub fn negotiate<A: AsRef<str>, W: AsRef<str>>(available: &[A], wanted: &[W]) -> Option<usize> {
135    fn primary(t: &str) -> &str {
136        t.split(['-', '_']).next().unwrap_or(t)
137    }
138    for w in wanted {
139        let w = w.as_ref();
140        if let Some(k) = available.iter().position(|a| a.as_ref().replace('_', "-").eq_ignore_ascii_case(&w.replace('_', "-"))) {
141            return Some(k);
142        }
143        if let Some(k) = available.iter().position(|a| primary(a.as_ref()).eq_ignore_ascii_case(primary(w))) {
144            return Some(k);
145        }
146    }
147    None
148}
149
150/// the languages the user wants, most wanted first (BCP 47)
151pub fn user_languages() -> Vec<String> {
152    let u = USER.lock().clone();
153    if u.is_empty() { alloc::vec![String::from("de-DE")] } else { u }
154}
155
156/// set the user's languages; an installed catalogue follows unless set_language came first
157pub fn set_user_languages<W: AsRef<str>>(list: &[W]) {
158    *USER.lock() = list.iter().filter_map(|w| plat::locale_tag(w.as_ref())).collect();
159    if APP.lock().is_some() && !EXPLICIT.load(Ordering::Relaxed) {
160        select(pick(&user_languages()));
161    }
162}
163
164/// the user's languages and format from jfgui_LANG ("fr-CA" or a ':' list), else the system
165pub fn use_system() {
166    if !use_env() {
167        set_user_languages(&plat::system_languages());
168        if let Some(t) = plat::system_locale() {
169            set_locale(&t);
170        }
171    }
172}
173
174// --lang (test scripts: "#!args --lang ar"), else jfgui_LANG: languages and format -> whether
175// one was set
176fn use_env() -> bool {
177    let Some(v) = plat::arg_value("--lang").or_else(|| plat::env("jfgui_LANG")).filter(|v| !v.trim().is_empty()) else { return false };
178    let list: Vec<&str> = v.split([':', ',']).map(str::trim).filter(|t| !t.is_empty()).collect();
179    set_user_languages(&list);
180    if let Some(first) = list.first() {
181        set_locale(first);
182    }
183    true
184}
185
186/// ui boot: jfgui_LANG, else the system's languages in window mode; headless runs keep German
187/// so screenshots match on every machine
188pub(crate) fn boot(headless: bool) {
189    if headless {
190        use_env();
191    } else {
192        use_system();
193    }
194}
195
196/// whether the UI language is written right to left (Arabic, Hebrew, Persian, Urdu, ...):
197/// ui mirrors its windows then (Ui::set_rtl)
198pub fn rtl_language() -> bool {
199    LIB_RTL.load(Ordering::Relaxed)
200}
201
202/// the current UI language (LANG_*)
203pub fn language() -> u32 {
204    LANG.load(Ordering::Relaxed)
205}
206
207// the languages of the library's own texts: the columns of LIB_TEXTS and the calendar names
208const LIB_LANGS: [&str; 9] = ["de", "en", "fr", "es", "it", "nl", "pl", "ar", "he"];
209
210// the library's own texts: the German source, then the other languages of LIB_LANGS (short,
211// as the systems' dialogs say it; key names as the keyboards print them)
212static LIB_TEXTS: &[[&str; 9]] = &[
213    ["Öffnen", "Open", "Ouvrir", "Abrir", "Apri", "Openen", "Otwórz", "فتح", "פתיחה"],
214    ["Speichern", "Save", "Enregistrer", "Guardar", "Salva", "Opslaan", "Zapisz", "حفظ", "שמירה"],
215    ["Speichern unter", "Save as", "Enregistrer sous", "Guardar como", "Salva con nome", "Opslaan als", "Zapisz jako", "حفظ باسم", "שמירה בשם"],
216    ["Abbrechen", "Cancel", "Annuler", "Cancelar", "Annulla", "Annuleren", "Anuluj", "إلغاء", "ביטול"],
217    ["Schließen", "Close", "Fermer", "Cerrar", "Chiudi", "Sluiten", "Zamknij", "إغلاق", "סגירה"],
218    ["Zurücksetzen", "Reset", "Réinitialiser", "Restablecer", "Reimposta", "Opnieuw instellen", "Resetuj", "إعادة تعيين", "איפוס"],
219    ["Dateiname:", "File name:", "Nom du fichier\u{A0}:", "Nombre de archivo:", "Nome file:", "Bestandsnaam:", "Nazwa pliku:", "اسم الملف:", "שם קובץ:"],
220    ["Ordner auswählen", "Select folder", "Sélectionner un dossier", "Seleccionar carpeta", "Seleziona cartella", "Map selecteren", "Wybierz folder", "تحديد مجلد", "בחירת תיקייה"],
221    ["Neuer Ordner", "New folder", "Nouveau dossier", "Nueva carpeta", "Nuova cartella", "Nieuwe map", "Nowy folder", "مجلد جديد", "תיקייה חדשה"],
222    ["Übergeordneter Ordner", "Parent folder", "Dossier parent", "Carpeta superior", "Cartella superiore", "Bovenliggende map", "Folder nadrzędny", "المجلد الأصل", "תיקיית אב"],
223    ["Startordner", "Home folder", "Dossier personnel", "Carpeta personal", "Cartella personale", "Persoonlijke map", "Folder domowy", "المجلد الرئيسي", "תיקיית הבית"],
224    [
225        "Der Pfad ist zu lang.",
226        "The path is too long.",
227        "Le chemin d’accès est trop long.",
228        "La ruta es demasiado larga.",
229        "Il percorso è troppo lungo.",
230        "Het pad is te lang.",
231        "Ścieżka jest za długa.",
232        "المسار طويل جدًا.",
233        "הנתיב ארוך מדי.",
234    ],
235    [
236        "Die Datei wurde nicht gefunden.",
237        "The file was not found.",
238        "Le fichier est introuvable.",
239        "No se encontró el archivo.",
240        "Impossibile trovare il file.",
241        "Het bestand is niet gevonden.",
242        "Nie znaleziono pliku.",
243        "تعذر العثور على الملف.",
244        "הקובץ לא נמצא.",
245    ],
246    ["Aufwärts", "Up", "Monter", "Subir", "Su", "Omhoog", "W górę", "لأعلى", "למעלה"],
247    ["Zurück", "Back", "Précédent", "Atrás", "Indietro", "Vorige", "Wstecz", "السابق", "הקודם"],
248    ["Weiter", "Next", "Suivant", "Siguiente", "Avanti", "Volgende", "Dalej", "التالي", "הבא"],
249    ["Fertigstellen", "Finish", "Terminer", "Finalizar", "Fine", "Voltooien", "Zakończ", "إنهاء", "סיום"],
250    ["Weitere Befehle", "More commands", "Plus de commandes", "Más comandos", "Altri comandi", "Meer opdrachten", "Więcej poleceń", "مزيد من الأوامر", "פקודות נוספות"],
251    [
252        "Vorherige Register",
253        "Previous tabs",
254        "Onglets précédents",
255        "Pestañas anteriores",
256        "Schede precedenti",
257        "Vorige tabbladen",
258        "Poprzednie karty",
259        "علامات التبويب السابقة",
260        "כרטיסיות קודמות",
261    ],
262    ["Weitere Register", "More tabs", "Plus d’onglets", "Más pestañas", "Altre schede", "Meer tabbladen", "Więcej kart", "مزيد من علامات التبويب", "כרטיסיות נוספות"],
263    ["Aufklappen", "Expand", "Développer", "Expandir", "Espandi", "Uitvouwen", "Rozwiń", "توسيع", "הרחבה"],
264    ["Zuklappen", "Collapse", "Réduire", "Contraer", "Comprimi", "Samenvouwen", "Zwiń", "طي", "כיווץ"],
265    [
266        "Navigation umschalten",
267        "Toggle navigation",
268        "Afficher/masquer la navigation",
269        "Mostrar u ocultar navegación",
270        "Mostra/nascondi navigazione",
271        "Navigatie weergeven/verbergen",
272        "Pokaż/ukryj nawigację",
273        "إظهار التنقل أو إخفاؤه",
274        "הצגה/הסתרה של הניווט",
275    ],
276    ["Heute", "Today", "Aujourd’hui", "Hoy", "Oggi", "Vandaag", "Dzisiaj", "اليوم", "היום"],
277    ["Vorheriger Monat", "Previous month", "Mois précédent", "Mes anterior", "Mese precedente", "Vorige maand", "Poprzedni miesiąc", "الشهر السابق", "החודש הקודם"],
278    ["Nächster Monat", "Next month", "Mois suivant", "Mes siguiente", "Mese successivo", "Volgende maand", "Następny miesiąc", "الشهر التالي", "החודש הבא"],
279    // key names of the shortcuts (Arabic and Hebrew keyboards print the English ones)
280    ["Strg", "Ctrl", "Ctrl", "Ctrl", "Ctrl", "Ctrl", "Ctrl", "Ctrl", "Ctrl"],
281    ["Umschalt", "Shift", "Maj", "Mayús", "Maiusc", "Shift", "Shift", "Shift", "Shift"],
282    ["Alt", "Alt", "Alt", "Alt", "Alt", "Alt", "Alt", "Alt", "Alt"],
283    ["Entf", "Del", "Suppr", "Supr", "Canc", "Del", "Del", "Del", "Del"],
284    ["Einfg", "Ins", "Inser", "Insert", "Ins", "Ins", "Ins", "Ins", "Ins"],
285    ["Pos1", "Home", "Origine", "Inicio", "Home", "Home", "Home", "Home", "Home"],
286    ["Ende", "End", "Fin", "Fin", "Fine", "End", "End", "End", "End"],
287    ["Bild auf", "PgUp", "Pg préc", "Re Pág", "PgSu", "PgUp", "PgUp", "PgUp", "PgUp"],
288    ["Bild ab", "PgDn", "Pg suiv", "Av Pág", "PgGiù", "PgDn", "PgDn", "PgDn", "PgDn"],
289    ["Leertaste", "Space", "Espace", "Espacio", "Spazio", "Spatie", "Spacja", "Space", "Space"],
290    ["Eingabe", "Enter", "Entrée", "Entrar", "Invio", "Enter", "Enter", "Enter", "Enter"],
291    ["Rücktaste", "Backspace", "Retour arrière", "Retroceso", "Backspace", "Backspace", "Backspace", "Backspace", "Backspace"],
292    ["Esc", "Esc", "Échap", "Esc", "Esc", "Esc", "Esc", "Esc", "Esc"],
293    ["Nach-links", "Left", "Gauche", "Izquierda", "Sinistra", "Links", "W lewo", "Left", "Left"],
294    ["Nach-rechts", "Right", "Droite", "Derecha", "Destra", "Rechts", "W prawo", "Right", "Right"],
295    ["Nach-oben", "Up", "Haut", "Arriba", "Su", "Omhoog", "W górę", "Up", "Up"],
296    ["Nach-unten", "Down", "Bas", "Abajo", "Giù", "Omlaag", "W dół", "Down", "Down"],
297    // the settings page (settings_page.rs)
298    ["Allgemein", "General", "Général", "General", "Generale", "Algemeen", "Ogólne", "عام", "כללי"],
299    [
300        "Wird nach einem Neustart wirksam.",
301        "Takes effect after a restart.",
302        "Prend effet après un redémarrage.",
303        "Surte efecto después de reiniciar.",
304        "Ha effetto dopo il riavvio.",
305        "Wordt van kracht na een herstart.",
306        "Zacznie obowiązywać po ponownym uruchomieniu.",
307        "يسري مفعوله بعد إعادة التشغيل.",
308        "ייכנס לתוקף לאחר הפעלה מחדש.",
309    ],
310    // fonts (fontpick.rs)
311    ["OK", "OK", "OK", "Aceptar", "OK", "OK", "OK", "موافق", "אישור"],
312    ["Suchen", "Search", "Rechercher", "Buscar", "Cerca", "Zoeken", "Szukaj", "بحث", "חיפוש"],
313    ["Schriftart", "Font", "Police", "Fuente", "Carattere", "Lettertype", "Czcionka", "الخط", "גופן"],
314    [
315        "Schriftschnitt",
316        "Font style",
317        "Style de police",
318        "Estilo de fuente",
319        "Stile carattere",
320        "Tekenstijl",
321        "Styl czcionki",
322        "نمط الخط",
323        "סגנון גופן",
324    ],
325    ["Größe", "Size", "Taille", "Tamaño", "Dimensione", "Grootte", "Rozmiar", "الحجم", "גודל"],
326    ["Vorschau", "Preview", "Aperçu", "Vista previa", "Anteprima", "Voorbeeld", "Podgląd", "معاينة", "תצוגה מקדימה"],
327    ["Achsen", "Axes", "Axes", "Ejes", "Assi", "Assen", "Osie", "المحاور", "צירים"],
328    ["Standard", "Regular", "Normal", "Normal", "Normale", "Normaal", "Normalny", "عادي", "רגיל"],
329    ["Kursiv", "Italic", "Italique", "Cursiva", "Corsivo", "Cursief", "Kursywa", "مائل", "נטוי"],
330    ["Fett", "Bold", "Gras", "Negrita", "Grassetto", "Vet", "Pogrubienie", "غامق", "מודגש"],
331    [
332        "Fett Kursiv",
333        "Bold Italic",
334        "Gras italique",
335        "Negrita cursiva",
336        "Grassetto corsivo",
337        "Vet cursief",
338        "Pogrubiona kursywa",
339        "غامق مائل",
340        "מודגש נטוי",
341    ],
342    [
343        "Keine passende Schrift installiert",
344        "No matching font installed",
345        "Aucune police correspondante installée",
346        "No hay ninguna fuente adecuada instalada",
347        "Nessun carattere adatto installato",
348        "Geen passend lettertype geïnstalleerd",
349        "Brak zainstalowanej pasującej czcionki",
350        "لا يوجد خط مطابق مثبت",
351        "לא מותקן גופן מתאים",
352    ],
353    [
354        "Angezeigt in:",
355        "Shown in:",
356        "Affiché avec\u{A0}:",
357        "Se muestra en:",
358        "Visualizzato in:",
359        "Weergegeven in:",
360        "Wyświetlane czcionką:",
361        "معروض بخط:",
362        "מוצג בגופן:",
363    ],
364    // docking (dock.rs)
365    [
366        "Weitere Optionen",
367        "More options",
368        "Plus d’options",
369        "Más opciones",
370        "Altre opzioni",
371        "Meer opties",
372        "Więcej opcji",
373        "مزيد من الخيارات",
374        "אפשרויות נוספות",
375    ],
376    ["Einklappen", "Collapse", "Réduire", "Contraer", "Comprimi", "Samenvouwen", "Zwiń", "طي", "כיווץ"],
377    ["Ausblenden", "Hide", "Masquer", "Ocultar", "Nascondi", "Verbergen", "Ukryj", "إخفاء", "הסתרה"],
378    ["Lösen", "Float", "Détacher", "Desacoplar", "Sgancia", "Losmaken", "Odepnij", "فصل", "ניתוק"],
379    ["Andocken", "Dock", "Ancrer", "Acoplar", "Ancora", "Vastzetten", "Zadokuj", "إرساء", "עגינה"],
380    [
381        "Links andocken",
382        "Dock left",
383        "Ancrer à gauche",
384        "Acoplar a la izquierda",
385        "Ancora a sinistra",
386        "Links vastzetten",
387        "Zadokuj po lewej",
388        "إرساء إلى اليسار",
389        "עגינה לשמאל",
390    ],
391    [
392        "Rechts andocken",
393        "Dock right",
394        "Ancrer à droite",
395        "Acoplar a la derecha",
396        "Ancora a destra",
397        "Rechts vastzetten",
398        "Zadokuj po prawej",
399        "إرساء إلى اليمين",
400        "עגינה לימין",
401    ],
402    [
403        "Unten andocken",
404        "Dock at the bottom",
405        "Ancrer en bas",
406        "Acoplar abajo",
407        "Ancora in basso",
408        "Onderaan vastzetten",
409        "Zadokuj na dole",
410        "إرساء في الأسفل",
411        "עגינה למטה",
412    ],
413    ["Als Register", "As a tab", "Comme onglet", "Como pestaña", "Come scheda", "Als tabblad", "Jako karta", "كعلامة تبويب", "ככרטיסייה"],
414    ["Darüber", "Above", "Au-dessus", "Encima", "Sopra", "Erboven", "Powyżej", "فوق", "מעל"],
415    ["Darunter", "Below", "En dessous", "Debajo", "Sotto", "Eronder", "Poniżej", "تحت", "מתחת"],
416    ["Davor", "Before", "Avant", "Antes", "Prima", "Ervoor", "Przed", "قبل", "לפני"],
417    ["Dahinter", "After", "Après", "Después", "Dopo", "Erna", "Za", "بعد", "אחרי"],
418    // spell checking (edit_spell.rs)
419    ["Keine Vorschläge", "No suggestions", "Aucune suggestion", "No hay sugerencias", "Nessun suggerimento", "Geen suggesties", "Brak sugestii", "لا توجد اقتراحات", "אין הצעות"],
420    [
421        "Vorschläge werden gesucht …",
422        "Looking for suggestions…",
423        "Recherche de suggestions…",
424        "Buscando sugerencias…",
425        "Ricerca dei suggerimenti…",
426        "Suggesties zoeken…",
427        "Wyszukiwanie sugestii…",
428        "جارٍ البحث عن اقتراحات…",
429        "חיפוש הצעות…",
430    ],
431    [
432        "Zum Wörterbuch hinzufügen",
433        "Add to dictionary",
434        "Ajouter au dictionnaire",
435        "Agregar al diccionario",
436        "Aggiungi al dizionario",
437        "Toevoegen aan woordenlijst",
438        "Dodaj do słownika",
439        "إضافة إلى القاموس",
440        "הוספה למילון",
441    ],
442    ["Ignorieren", "Ignore", "Ignorer", "Ignorar", "Ignora", "Negeren", "Ignoruj", "تجاهل", "התעלמות"],
443    // permission questions and the permissions page (perm.rs)
444    ["Zugriff erlauben?", "Allow access?", "Autoriser l’accès\u{A0}?", "¿Permitir el acceso?", "Consentire l’accesso?", "Toegang toestaan?", "Zezwolić na dostęp?", "السماح بالوصول؟", "לאפשר גישה?"],
445    ["Dateien lesen", "Read files", "Lire des fichiers", "Leer archivos", "Leggere file", "Bestanden lezen", "Odczytywać pliki", "قراءة الملفات", "קריאת קבצים"],
446    ["Dateien schreiben", "Write files", "Écrire des fichiers", "Escribir archivos", "Scrivere file", "Bestanden schrijven", "Zapisywać pliki", "كتابة الملفات", "כתיבת קבצים"],
447    ["Ein Programm starten", "Start a program", "Lancer un programme", "Iniciar un programa", "Avviare un programma", "Een programma starten", "Uruchomić program", "تشغيل برنامج", "הפעלת תוכנית"],
448    ["Eine Adresse öffnen", "Open an address", "Ouvrir une adresse", "Abrir una dirección", "Aprire un indirizzo", "Een adres openen", "Otworzyć adres", "فتح عنوان", "פתיחת כתובת"],
449    ["Ein Plugin laden", "Load a plug-in", "Charger un plug-in", "Cargar un complemento", "Caricare un plug-in", "Een plug-in laden", "Wczytać wtyczkę", "تحميل مكوّن إضافي", "טעינת תוסף"],
450    ["Drucken", "Print", "Imprimer", "Imprimir", "Stampare", "Afdrukken", "Drukować", "طباعة", "הדפסה"],
451    [
452        "Gespeicherte Kennwörter verwenden",
453        "Use saved passwords",
454        "Utiliser les mots de passe enregistrés",
455        "Usar contraseñas guardadas",
456        "Usare le password salvate",
457        "Opgeslagen wachtwoorden gebruiken",
458        "Używać zapisanych haseł",
459        "استخدام كلمات المرور المحفوظة",
460        "שימוש בסיסמאות שמורות",
461    ],
462    [
463        "Beim Anmelden starten",
464        "Start at sign-in",
465        "Démarrer à l’ouverture de session",
466        "Iniciar al iniciar sesión",
467        "Avviare all’accesso",
468        "Starten bij aanmelden",
469        "Uruchamiać przy logowaniu",
470        "التشغيل عند تسجيل الدخول",
471        "הפעלה בעת הכניסה",
472    ],
473    ["Klänge abspielen", "Play sounds", "Jouer des sons", "Reproducir sonidos", "Riprodurre suoni", "Geluiden afspelen", "Odtwarzać dźwięki", "تشغيل الأصوات", "השמעת צלילים"],
474    ["Den Standort abfragen", "Get your location", "Connaître votre position", "Consultar su ubicación", "Rilevare la posizione", "Uw locatie opvragen", "Odczytać lokalizację", "معرفة موقعك", "קבלת המיקום שלך"],
475    ["Die Bewegungssensoren lesen", "Read the motion sensors", "Lire les capteurs de mouvement", "Leer los sensores de movimiento", "Leggere i sensori di movimento", "De bewegingssensoren lezen", "Odczytać czujniki ruchu", "قراءة مستشعرات الحركة", "קריאת חיישני התנועה"],
476    [
477        "Eine Netzwerkverbindung aufbauen",
478        "Connect to the network",
479        "Se connecter au réseau",
480        "Conectarse a la red",
481        "Connettersi alla rete",
482        "Verbinding maken met het netwerk",
483        "Łączyć się z siecią",
484        "الاتصال بالشبكة",
485        "התחברות לרשת",
486    ],
487    [
488        "Netzwerkverbindungen annehmen",
489        "Accept network connections",
490        "Accepter des connexions réseau",
491        "Aceptar conexiones de red",
492        "Accettare connessioni di rete",
493        "Netwerkverbindingen accepteren",
494        "Przyjmować połączenia sieciowe",
495        "قبول اتصالات الشبكة",
496        "קבלת חיבורי רשת",
497    ],
498    [
499        "Eine Aktion für den Agenten ausführen",
500        "Run an action for the agent",
501        "Exécuter une action pour l’agent",
502        "Ejecutar una acción para el agente",
503        "Eseguire un’azione per l’agente",
504        "Een actie voor de agent uitvoeren",
505        "Wykonać akcję dla agenta",
506        "تنفيذ إجراء للوكيل",
507        "ביצוע פעולה עבור הסוכן",
508    ],
509    ["Für das Plugin: {}", "For the plug-in: {}", "Pour le plug-in\u{A0}: {}", "Para el complemento: {}", "Per il plug-in: {}", "Voor de plug-in: {}", "Dla wtyczki: {}", "للمكوّن الإضافي: {}", "עבור התוסף: {}"],
510    ["Grund: {}", "Reason: {}", "Motif\u{A0}: {}", "Motivo: {}", "Motivo: {}", "Reden: {}", "Powód: {}", "السبب: {}", "סיבה: {}"],
511    ["Regel: {}", "Rule: {}", "Règle\u{A0}: {}", "Regla: {}", "Regola: {}", "Regel: {}", "Reguła: {}", "القاعدة: {}", "כלל: {}"],
512    ["Einmal erlauben", "Allow once", "Autoriser une fois", "Permitir una vez", "Consenti una volta", "Eenmaal toestaan", "Zezwól raz", "السماح مرة واحدة", "אפשר פעם אחת"],
513    ["Immer erlauben", "Always allow", "Toujours autoriser", "Permitir siempre", "Consenti sempre", "Altijd toestaan", "Zawsze zezwalaj", "السماح دائمًا", "אפשר תמיד"],
514    ["Ablehnen", "Deny", "Refuser", "Denegar", "Nega", "Weigeren", "Odmów", "رفض", "דחה"],
515    ["Nie erlauben", "Never allow", "Ne jamais autoriser", "No permitir nunca", "Non consentire mai", "Nooit toestaan", "Nigdy nie zezwalaj", "عدم السماح أبدًا", "לעולם אל תאפשר"],
516    [
517        "Wird beim nächsten Zugriff gefragt",
518        "Asked on the next access",
519        "Demandé au prochain accès",
520        "Se preguntará en el próximo acceso",
521        "Richiesto al prossimo accesso",
522        "Wordt bij de volgende toegang gevraagd",
523        "Pytanie przy następnym dostępie",
524        "سيُطلب عند الوصول التالي",
525        "תוצג שאלה בגישה הבאה",
526    ],
527    [
528        "Bis zum Programmende erlaubt",
529        "Allowed until the program ends",
530        "Autorisé jusqu’à la fermeture du programme",
531        "Permitido hasta que se cierre el programa",
532        "Consentito fino alla chiusura del programma",
533        "Toegestaan tot het programma sluit",
534        "Dozwolone do zamknięcia programu",
535        "مسموح حتى إغلاق البرنامج",
536        "מותר עד לסגירת התוכנית",
537    ],
538    ["Immer erlaubt", "Always allowed", "Toujours autorisé", "Siempre permitido", "Sempre consentito", "Altijd toegestaan", "Zawsze dozwolone", "مسموح دائمًا", "מותר תמיד"],
539    [
540        "Bis zum Programmende abgelehnt",
541        "Denied until the program ends",
542        "Refusé jusqu’à la fermeture du programme",
543        "Denegado hasta que se cierre el programa",
544        "Negato fino alla chiusura del programma",
545        "Geweigerd tot het programma sluit",
546        "Odmówiono do zamknięcia programu",
547        "مرفوض حتى إغلاق البرنامج",
548        "נדחה עד לסגירת התוכנית",
549    ],
550    ["Nie erlaubt", "Never allowed", "Jamais autorisé", "Nunca permitido", "Mai consentito", "Nooit toegestaan", "Nigdy niedozwolone", "غير مسموح أبدًا", "אסור תמיד"],
551    [
552        "Keine Rückfragen",
553        "No permission questions",
554        "Aucune demande d’autorisation",
555        "No hay solicitudes de permiso",
556        "Nessuna richiesta di autorizzazione",
557        "Geen toestemmingsvragen",
558        "Brak pytań o uprawnienia",
559        "لا توجد طلبات أذونات",
560        "אין בקשות הרשאה",
561    ],
562    ["Alle zurücksetzen", "Reset all", "Tout réinitialiser", "Restablecer todo", "Reimposta tutto", "Alles opnieuw instellen", "Resetuj wszystko", "إعادة تعيين الكل", "איפוס הכול"],
563    // the parts of the colour picker the keyboard reaches (color.rs)
564    ["Sättigung und Helligkeit", "Saturation and brightness", "Saturation et luminosité", "Saturación y brillo", "Saturazione e luminosità", "Verzadiging en helderheid", "Nasycenie i jasność", "التشبع والسطوع", "רוויה ובהירות"],
565    ["Farbton", "Hue", "Teinte", "Matiz", "Tonalità", "Tint", "Odcień", "تدرج اللون", "גוון"],
566    ["Deckkraft", "Opacity", "Opacité", "Opacidad", "Opacità", "Dekking", "Krycie", "العتامة", "אטימות"],
567    // the colour picker's pipette (CP_PIPETTE): a colour from anywhere on the screen (screen.rs)
568    ["Farbe vom Bildschirm aufnehmen", "Pick a color from the screen", "Prélever une couleur à l'écran", "Tomar un color de la pantalla", "Preleva un colore dallo schermo", "Kleur van het scherm kiezen", "Pobierz kolor z ekranu", "التقاط لون من الشاشة", "דגימת צבע מהמסך"],
569    // a splitter grip (shell.rs split views, dock.rs): F8 focuses it, the arrows move it
570    ["Teiler", "Splitter", "Séparateur", "Divisor", "Divisore", "Splitser", "Rozdzielacz", "فاصل", "מפריד"],
571    // (ROADMAP §6) the rich-text and code editors (rich_edit.rs, rich_doc.rs, code_edit.rs, code_doc.rs)
572    ["Einfügen", "Paste", "Coller", "Pegar", "Incolla", "Plakken", "Wklej", "لصق", "הדבקה"],
573    ["Löschen", "Delete", "Supprimer", "Eliminar", "Elimina", "Verwijderen", "Usuń", "حذف", "מחיקה"],
574    ["Liste", "List", "Liste", "Lista", "Elenco", "Lijst", "Lista", "قائمة", "רשימה"],
575    ["Absatz", "Paragraph", "Paragraphe", "Párrafo", "Paragrafo", "Alinea", "Akapit", "فقرة", "פסקה"],
576    ["Unterstrichen", "Underline", "Souligné", "Subrayado", "Sottolineato", "Onderstrepen", "Podkreślenie", "تسطير", "קו תחתון"],
577    ["Durchgestrichen", "Strikethrough", "Barré", "Tachado", "Barrato", "Doorhalen", "Przekreślenie", "يتوسطه خط", "קו חוצה"],
578    ["Format", "Format", "Format", "Formato", "Formato", "Opmaak", "Formatowanie", "تنسيق", "עיצוב"],
579    ["Textfarbe", "Text color", "Couleur du texte", "Color de texto", "Colore testo", "Tekstkleur", "Kolor tekstu", "لون النص", "צבע טקסט"],
580    ["Hervorhebung", "Highlight", "Surlignage", "Resaltado", "Evidenziazione", "Markering", "Wyróżnienie", "تمييز", "הדגשה"],
581    ["Schriftgröße", "Font size", "Taille de police", "Tamaño de fuente", "Dimensione carattere", "Tekengrootte", "Rozmiar czcionki", "حجم الخط", "גודל גופן"],
582    ["Link", "Link", "Lien", "Enlace", "Collegamento", "Koppeling", "Link", "ارتباط", "קישור"],
583    ["Format löschen", "Clear formatting", "Effacer la mise en forme", "Borrar formato", "Cancella formattazione", "Opmaak wissen", "Wyczyść formatowanie", "مسح التنسيق", "ניקוי עיצוב"],
584    ["Ausrichtung", "Alignment", "Alignement", "Alineación", "Allineamento", "Uitlijning", "Wyrównanie", "محاذاة", "יישור"],
585    ["Einzug", "Indent", "Retrait", "Sangría", "Rientro", "Inspringing", "Wcięcie", "مسافة بادئة", "כניסה"],
586    ["Formatvorlage", "Style", "Style", "Estilo", "Stile", "Stijl", "Styl", "نمط", "סגנון"],
587    ["Ersetzen", "Replace", "Remplacer", "Reemplazar", "Sostituisci", "Vervangen", "Zamień", "استبدال", "החלפה"],
588    ["Link-Adresse", "Link address", "Adresse du lien", "Dirección del enlace", "Indirizzo collegamento", "Koppelingsadres", "Adres linku", "عنوان الارتباط", "כתובת קישור"],
589    ["Link entfernen", "Remove link", "Supprimer le lien", "Quitar enlace", "Rimuovi collegamento", "Koppeling verwijderen", "Usuń link", "إزالة الارتباط", "הסרת קישור"],
590    ["Rückgängig", "Undo", "Annuler", "Deshacer", "Annulla", "Ongedaan maken", "Cofnij", "تراجع", "ביטול פעולה"],
591    ["Wiederholen", "Redo", "Rétablir", "Rehacer", "Ripeti", "Opnieuw", "Ponów", "إعادة", "ביצוע חוזר"],
592    ["Überschrift 1", "Heading 1", "Titre 1", "Título 1", "Titolo 1", "Kop 1", "Nagłówek 1", "عنوان 1", "כותרת 1"],
593    ["Überschrift 2", "Heading 2", "Titre 2", "Título 2", "Titolo 2", "Kop 2", "Nagłówek 2", "عنوان 2", "כותרת 2"],
594    ["Überschrift 3", "Heading 3", "Titre 3", "Título 3", "Titolo 3", "Kop 3", "Nagłówek 3", "عنوان 3", "כותרת 3"],
595    ["Zitat", "Quote", "Citation", "Cita", "Citazione", "Citaat", "Cytat", "اقتباس", "ציטוט"],
596    ["Keine", "None", "Aucune", "Ninguno", "Nessuno", "Geen", "Brak", "بلا", "ללא"],
597    ["Automatisch", "Automatic", "Automatique", "Automático", "Automatico", "Automatisch", "Automatyczny", "تلقائي", "אוטומטי"],
598    ["Linksbündig", "Align left", "Aligner à gauche", "Alinear a la izquierda", "Allinea a sinistra", "Links uitlijnen", "Wyrównaj do lewej", "محاذاة لليسار", "יישור לשמאל"],
599    ["Zentriert", "Center", "Centrer", "Centrar", "Centra", "Centreren", "Wyśrodkuj", "توسيط", "מרכוז"],
600    ["Rechtsbündig", "Align right", "Aligner à droite", "Alinear a la derecha", "Allinea a destra", "Rechts uitlijnen", "Wyrównaj do prawej", "محاذاة لليمين", "יישור לימין"],
601    ["Blocksatz", "Justify", "Justifier", "Justificar", "Giustifica", "Uitvullen", "Wyjustuj", "ضبط", "יישור לשני הצדדים"],
602    ["Aufzählung", "Bullets", "Puces", "Viñetas", "Elenco puntato", "Opsommingstekens", "Punktory", "تعداد نقطي", "תבליטים"],
603    ["Nummerierung", "Numbering", "Numérotation", "Numeración", "Elenco numerato", "Nummering", "Numerowanie", "ترقيم", "מספור"],
604    ["Einzug verkleinern", "Decrease indent", "Diminuer le retrait", "Disminuir sangría", "Riduci rientro", "Inspringing verkleinen", "Zmniejsz wcięcie", "إنقاص المسافة البادئة", "הקטנת כניסה"],
605    ["Einzug vergrößern", "Increase indent", "Augmenter le retrait", "Aumentar sangría", "Aumenta rientro", "Inspringing vergroten", "Zwiększ wcięcie", "زيادة المسافة البادئة", "הגדלת כניסה"],
606    ["Zeilenumbruch", "Line break", "Saut de ligne", "Salto de línea", "Interruzione di riga", "Regeleinde", "Podział wiersza", "فاصل أسطر", "מעבר שורה"],
607    ["Kommentar", "Comment", "Commentaire", "Comentario", "Commento", "Opmerking", "Komentarz", "تعليق", "הערה"],
608    ["Zeilen verschieben", "Move lines", "Déplacer les lignes", "Mover líneas", "Sposta righe", "Regels verplaatsen", "Przenieś wiersze", "نقل الأسطر", "הזזת שורות"],
609    ["Alle ersetzen", "Replace all", "Remplacer tout", "Reemplazar todo", "Sostituisci tutto", "Alles vervangen", "Zamień wszystko", "استبدال الكل", "החלפת הכול"],
610    ["Groß-/Kleinschreibung", "Match case", "Respecter la casse", "Coincidir mayúsculas y minúsculas", "Maiuscole/minuscole", "Hoofdlettergevoelig", "Uwzględnij wielkość liter", "مطابقة حالة الأحرف", "התאמת רישיות"],
611    ["Ganzes Wort", "Whole word", "Mot entier", "Palabra completa", "Parola intera", "Heel woord", "Całe wyrazy", "الكلمة بأكملها", "מילה שלמה"],
612    ["Vorheriger Treffer", "Previous match", "Résultat précédent", "Coincidencia anterior", "Risultato precedente", "Vorige overeenkomst", "Poprzednie wystąpienie", "التطابق السابق", "ההתאמה הקודמת"],
613    ["Nächster Treffer", "Next match", "Résultat suivant", "Coincidencia siguiente", "Risultato successivo", "Volgende overeenkomst", "Następne wystąpienie", "التطابق التالي", "ההתאמה הבאה"],
614    ["Keine Treffer", "No results", "Aucun résultat", "Sin resultados", "Nessun risultato", "Geen resultaten", "Brak wyników", "لا توجد نتائج", "אין תוצאות"],
615    // (L6) the severities of the code editor's diagnostics (tooltips, the record at the caret)
616    ["Fehler", "Error", "Erreur", "Error", "Errore", "Fout", "Błąd", "خطأ", "שגיאה"],
617    ["Warnung", "Warning", "Avertissement", "Advertencia", "Avviso", "Waarschuwing", "Ostrzeżenie", "تحذير", "אזהרה"],
618    ["Hinweis", "Info", "Info", "Información", "Informazione", "Info", "Informacja", "معلومة", "מידע"],
619    ["Schreiben", "Typing", "Saisie", "Escritura", "Digitazione", "Typen", "Pisanie", "كتابة", "הקלדה"],
620    ["Fließtext", "Normal text", "Texte normal", "Texto normal", "Testo normale", "Normale tekst", "Zwykły tekst", "نص عادي", "טקסט רגיל"],
621    ["Standardschrift", "Default font", "Police par défaut", "Fuente predeterminada", "Carattere predefinito", "Standaardlettertype", "Domyślna czcionka", "الخط الافتراضي", "גופן ברירת מחדל"],
622    // (ROADMAP §6) the Markdown / HTML viewer's name for screen readers (md_view.rs)
623    ["Dokument", "Document", "Document", "Documento", "Documento", "Document", "Dokument", "مستند", "מסמך"],
624    // (ROADMAP §7) the caption of a form's submit button that has none (md_doc.rs)
625    ["Senden", "Submit", "Envoyer", "Enviar", "Invia", "Verzenden", "Wyślij", "إرسال", "שליחה"],
626    // (ROADMAP §12) the live inspector's tool window (inspect.rs)
627    ["Inspektor", "Inspector", "Inspecteur", "Inspector", "Ispettore", "Inspector", "Inspektor", "أداة الفحص", "כלי בדיקה"],
628    ["Auswählen", "Pick", "Choisir", "Elegir", "Scegli", "Kiezen", "Wskaż", "انتقاء", "בחירה"],
629    ["Umrisse", "Outlines", "Contours", "Contornos", "Contorni", "Omtrekken", "Obrysy", "الحدود", "קווי מתאר"],
630    ["Tab-Reihenfolge", "Tab order", "Ordre de tabulation", "Orden de tabulación", "Ordine di tabulazione", "Tabvolgorde", "Kolejność tabulacji", "ترتيب التنقل", "סדר מעבר"],
631    ["Neu gezeichnet", "Repaints", "Zones redessinées", "Zonas repintadas", "Aree ridisegnate", "Hertekend", "Odświeżone obszary", "المناطق المعاد رسمها", "אזורים שצוירו מחדש"],
632    ["Widgets", "Widgets", "Widgets", "Widgets", "Widget", "Widgets", "Widżety", "عناصر الواجهة", "רכיבים"],
633    ["Bildaufbau", "Frame", "Image", "Fotograma", "Fotogramma", "Frame", "Klatka", "الإطار", "פריים"],
634    ["Farben", "Colors", "Couleurs", "Colores", "Colori", "Kleuren", "Kolory", "الألوان", "צבעים"],
635    ["Nichts ausgewählt", "Nothing selected", "Aucune sélection", "Nada seleccionado", "Nessuna selezione", "Niets geselecteerd", "Nic nie zaznaczono", "لم يُحدَّد شيء", "לא נבחר דבר"],
636    [
637        "Klicken Sie auf ein Widget (Esc beendet)",
638        "Click a widget (Esc ends)",
639        "Cliquez sur un widget (Échap pour terminer)",
640        "Haga clic en un widget (Esc para terminar)",
641        "Fai clic su un widget (Esc per terminare)",
642        "Klik op een widget (Esc stopt)",
643        "Kliknij widżet (Esc kończy)",
644        "انقر عنصرًا (Esc للإنهاء)",
645        "לחצו על רכיב (Esc לסיום)",
646    ],
647    ["Kopieren", "Copy", "Copier", "Copiar", "Copia", "Kopiëren", "Kopiuj", "نسخ", "העתקה"],
648    ["Art", "Kind", "Type", "Tipo", "Tipo", "Soort", "Rodzaj", "النوع", "סוג"],
649    ["Kennung", "ID", "Identifiant", "Identificador", "Identificatore", "ID", "Identyfikator", "المعرّف", "מזהה"],
650    ["Beschriftung", "Label", "Libellé", "Etiqueta", "Etichetta", "Label", "Etykieta", "التسمية", "תווית"],
651    ["Rechteck", "Rectangle", "Rectangle", "Rectángulo", "Rettangolo", "Rechthoek", "Prostokąt", "المستطيل", "מלבן"],
652    ["Wert", "Value", "Valeur", "Valor", "Valore", "Waarde", "Wartość", "القيمة", "ערך"],
653    ["Zustand", "State", "État", "Estado", "Stato", "Status", "Stan", "الحالة", "מצב"],
654    ["Ebene", "Layer", "Calque", "Capa", "Livello", "Laag", "Warstwa", "الطبقة", "שכבה"],
655    ["Fenster", "Window", "Fenêtre", "Ventana", "Finestra", "Venster", "Okno", "النافذة", "חלון"],
656    ["Selektor", "Selector", "Sélecteur", "Selector", "Selettore", "Selector", "Selektor", "المحدِّد", "בורר"],
657    ["Durchgänge", "Passes", "Passes", "Pasadas", "Passate", "Passes", "Przebiegi", "التمريرات", "מעברים"],
658    ["Aufbau", "Build", "Construction", "Construcción", "Costruzione", "Opbouw", "Budowanie", "البناء", "בנייה"],
659    ["Zeichnen", "Render", "Rendu", "Renderizado", "Rendering", "Weergave", "Renderowanie", "الرسم", "ציור"],
660    ["Neu gezeichnete Fläche", "Repainted area", "Surface redessinée", "Área repintada", "Area ridisegnata", "Hertekend gebied", "Odświeżony obszar", "المساحة المعاد رسمها", "שטח שצויר מחדש"],
661    ["Zeichenbefehle", "Draw commands", "Commandes de dessin", "Comandos de dibujo", "Comandi di disegno", "Tekenopdrachten", "Polecenia rysowania", "أوامر الرسم", "פקודות ציור"],
662    ["Glyphen-Cache", "Glyph cache", "Cache des glyphes", "Caché de glifos", "Cache dei glifi", "Glyphcache", "Pamięć podręczna glifów", "ذاكرة الحروف المؤقتة", "מטמון גליפים"],
663    ["Hauptfenster", "Main window", "Fenêtre principale", "Ventana principal", "Finestra principale", "Hoofdvenster", "Okno główne", "النافذة الرئيسية", "חלון ראשי"],
664    // (ROADMAP §11) hot reload: a failed build in the inspector (hotreload.rs)
665    ["Bau fehlgeschlagen", "Build failed", "Échec de la compilation", "Error de compilación", "Compilazione non riuscita", "Build mislukt", "Kompilacja nie powiodła się", "فشل البناء", "הבנייה נכשלה"],
666    [
667        "Die laufende Fassung bleibt.",
668        "The running version stays.",
669        "La version en cours reste active.",
670        "La versión en ejecución se mantiene.",
671        "La versione in esecuzione resta attiva.",
672        "De draaiende versie blijft actief.",
673        "Działająca wersja pozostaje.",
674        "تبقى النسخة الجارية قيد التشغيل.",
675        "הגרסה הפועלת נשארת.",
676    ],
677    // (ROADMAP §11) the inspector's editing (inspect_edit.rs, development builds)
678    ["Hell", "Light", "Clair", "Claro", "Chiaro", "Licht", "Jasny", "فاتح", "בהיר"],
679    ["Dunkel", "Dark", "Sombre", "Oscuro", "Scuro", "Donker", "Ciemny", "داكن", "כהה"],
680    ["Hoher Kontrast", "High contrast", "Contraste élevé", "Contraste alto", "Contrasto elevato", "Hoog contrast", "Wysoki kontrast", "تباين عالٍ", "ניגודיות גבוהה"],
681    ["Akzent", "Accent", "Accent", "Acento", "Accento", "Accent", "Akcent", "لون التمييز", "צבע הדגשה"],
682    ["Eckenradius", "Corner radius", "Rayon des coins", "Radio de las esquinas", "Raggio degli angoli", "Hoekradius", "Promień narożników", "نصف قطر الزوايا", "רדיוס פינות"],
683    ["Abstände", "Spacing", "Espacement", "Espaciado", "Spaziatura", "Afstand", "Odstępy", "التباعد", "ריווח"],
684    ["Steuerelementhöhe", "Control height", "Hauteur des contrôles", "Altura de los controles", "Altezza dei controlli", "Hoogte van besturingselementen", "Wysokość kontrolek", "ارتفاع عناصر التحكم", "גובה פקדים"],
685    ["Familie", "Family", "Famille", "Familia", "Famiglia", "Familie", "Rodzina", "العائلة", "משפחה"],
686    ["Modus", "Mode", "Mode", "Modo", "Modalità", "Modus", "Tryb", "الوضع", "מצב תצוגה"],
687    ["Stil", "Style", "Style", "Estilo", "Stile", "Stijl", "Styl", "النمط", "סגנון"],
688    ["Sprache", "Language", "Langue", "Idioma", "Lingua", "Taal", "Język", "اللغة", "שפה"],
689    ["Skalierung", "Scale", "Échelle", "Escala", "Scala", "Schaal", "Skala", "التحجيم", "קנה מידה"],
690    ["Textskalierung", "Text scale", "Échelle du texte", "Escala del texto", "Scala del testo", "Tekstschaal", "Skala tekstu", "تحجيم النص", "קנה מידה של טקסט"],
691    ["vom Stil", "from the style", "du style", "del estilo", "dello stile", "van de stijl", "ze stylu", "من النمط", "מהסגנון"],
692    ["Alles zurücksetzen", "Reset all", "Tout réinitialiser", "Restablecer todo", "Reimposta tutto", "Alles opnieuw instellen", "Resetuj wszystko", "إعادة تعيين الكل", "איפוס הכול"],
693    ["Gespeichert: {}", "Saved: {}", "Enregistré\u{A0}: {}", "Guardado: {}", "Salvato: {}", "Opgeslagen: {}", "Zapisano: {}", "تم الحفظ: {}", "נשמר: {}"],
694    ["Nicht gespeichert: {}", "Not saved: {}", "Non enregistré\u{A0}: {}", "No guardado: {}", "Non salvato: {}", "Niet opgeslagen: {}", "Nie zapisano: {}", "لم يُحفظ: {}", "לא נשמר: {}"],
695    ["Das hast du geändert", "What you changed", "Ce que vous avez modifié", "Lo que has cambiado", "Cosa hai modificato", "Wat je hebt gewijzigd", "Co zostało zmienione", "ما غيّرته", "מה ששינית"],
696    ["Theme-Datei", "Theme file", "Fichier de thème", "Archivo de tema", "File del tema", "Themabestand", "Plik motywu", "ملف السمة", "קובץ ערכת נושא"],
697    ["Im Code", "In the code", "Dans le code", "En el código", "Nel codice", "In de code", "W kodzie", "في الشيفرة", "בקוד"],
698    ["Kopiert", "Copied", "Copié", "Copiado", "Copiato", "Gekopieerd", "Skopiowano", "تم النسخ", "הועתק"],
699    ["Änderungen: {}", "Changes: {}", "Modifications\u{A0}: {}", "Cambios: {}", "Modifiche: {}", "Wijzigingen: {}", "Zmiany: {}", "التغييرات: {}", "שינויים: {}"],
700    ["Zurücksetzen: {}", "Reset: {}", "Réinitialiser\u{A0}: {}", "Restablecer: {}", "Reimposta: {}", "Opnieuw instellen: {}", "Resetuj: {}", "إعادة تعيين: {}", "איפוס: {}"],
701    ["Farbwähler", "Color picker", "Sélecteur de couleur", "Selector de color", "Selettore colore", "Kleurkiezer", "Selektor kolorów", "منتقي الألوان", "בורר צבעים"],
702    ["geändert", "changed", "modifié", "cambiado", "modificato", "gewijzigd", "zmieniono", "مُعدَّل", "שונה"],
703    ["Darstellung", "Appearance", "Apparence", "Apariencia", "Aspetto", "Weergave", "Wygląd", "المظهر", "מראה"],
704    ["Maße", "Metrics", "Dimensions", "Medidas", "Misure", "Afmetingen", "Wymiary", "الأبعاد", "מידות"],
705    ["Schrift", "Font", "Police", "Fuente", "Carattere", "Lettertype", "Czcionka", "الخط", "גופן"],
706    ["Änderungen", "Changes", "Modifications", "Cambios", "Modifiche", "Wijzigingen", "Zmiany", "التغييرات", "שינויים"],
707    ["Keine Änderungen", "No changes", "Aucune modification", "Sin cambios", "Nessuna modifica", "Geen wijzigingen", "Brak zmian", "لا توجد تغييرات", "אין שינויים"],
708    ["Pfad der Theme-Datei", "Path of the theme file", "Chemin du fichier de thème", "Ruta del archivo de tema", "Percorso del file del tema", "Pad van het themabestand", "Ścieżka pliku motywu", "مسار ملف السمة", "נתיב קובץ ערכת הנושא"],
709    ["Als Theme speichern", "Save as theme", "Enregistrer comme thème", "Guardar como tema", "Salva come tema", "Opslaan als thema", "Zapisz jako motyw", "حفظ كسمة", "שמירה כערכת נושא"],
710    ["Liste kopieren", "Copy list", "Copier la liste", "Copiar lista", "Copia elenco", "Lijst kopiëren", "Kopiuj listę", "نسخ القائمة", "העתקת הרשימה"],
711    ["Bedienen", "Operate", "Manipuler", "Manejar", "Azionare", "Bedienen", "Obsłuż", "تشغيل", "הפעלה"],
712    ["Skript: {}", "Script: {}", "Script\u{A0}: {}", "Script: {}", "Script: {}", "Script: {}", "Skrypt: {}", "البرنامج النصي: {}", "סקריפט: {}"],
713    ["Klicken", "Click", "Cliquer", "Hacer clic", "Fai clic", "Klikken", "Kliknij", "نقر", "לחיצה"],
714    ["Ziehen", "Drag", "Glisser", "Arrastrar", "Trascina", "Slepen", "Przeciągnij", "سحب", "גרירה"],
715    ["Setzen", "Set", "Définir", "Establecer", "Imposta", "Instellen", "Ustaw", "تعيين", "הגדרה"],
716    ["Text", "Text", "Texte", "Texto", "Testo", "Tekst", "Tekst", "النص", "טקסט"],
717    ["Tippen", "Type", "Saisir", "Escribir", "Digita", "Typen", "Wpisz", "كتابة", "הקלדה"],
718    ["Befehl", "Command", "Commande", "Comando", "Comando", "Opdracht", "Polecenie", "الأمر", "פקודה"],
719    ["Ausführen", "Run", "Exécuter", "Ejecutar", "Esegui", "Uitvoeren", "Uruchom", "تنفيذ", "הרצה"],
720    // (ROADMAP §11) the inspector's sources (inspect_src.rs, development builds)
721    ["Quelle", "Source", "Source", "Fuente", "Sorgente", "Bron", "Źródło", "المصدر", "מקור"],
722    ["Quelle kopieren", "Copy source", "Copier la source", "Copiar fuente", "Copia sorgente", "Bron kopiëren", "Kopiuj źródło", "نسخ المصدر", "העתקת המקור"],
723    ["Im Editor öffnen", "Open in editor", "Ouvrir dans l'éditeur", "Abrir en el editor", "Apri nell'editor", "Openen in editor", "Otwórz w edytorze", "فتح في المحرر", "פתיחה בעורך"],
724    ["Geöffnet mit {}", "Opened with {}", "Ouvert avec {}", "Abierto con {}", "Aperto con {}", "Geopend met {}", "Otwarto za pomocą {}", "فُتح باستخدام {}", "נפתח באמצעות {}"],
725    [
726        "Kein Editor gefunden: jfgui_EDITOR, VISUAL oder EDITOR setzen",
727        "No editor found: set jfgui_EDITOR, VISUAL or EDITOR",
728        "Aucun éditeur trouvé\u{A0}: définissez jfgui_EDITOR, VISUAL ou EDITOR",
729        "No se encontró ningún editor: defina jfgui_EDITOR, VISUAL o EDITOR",
730        "Nessun editor trovato: impostare jfgui_EDITOR, VISUAL o EDITOR",
731        "Geen editor gevonden: stel jfgui_EDITOR, VISUAL of EDITOR in",
732        "Nie znaleziono edytora: ustaw jfgui_EDITOR, VISUAL lub EDITOR",
733        "لم يُعثر على محرر: اضبط jfgui_EDITOR أو VISUAL أو EDITOR",
734        "לא נמצא עורך: יש להגדיר jfgui_EDITOR, VISUAL או EDITOR",
735    ],
736    [
737        "Die App darf {} nicht starten (PERMISSIONS: process). Quelle kopieren und selbst öffnen.",
738        "The app may not start {} (PERMISSIONS: process). Copy the source and open it yourself.",
739        "L'application ne peut pas lancer {} (PERMISSIONS\u{A0}: process). Copiez la source et ouvrez-la vous-même.",
740        "La aplicación no puede iniciar {} (PERMISSIONS: process). Copie la fuente y ábrala usted mismo.",
741        "L'app non può avviare {} (PERMISSIONS: process). Copia la sorgente e aprila manualmente.",
742        "De app mag {} niet starten (PERMISSIONS: process). Kopieer de bron en open die zelf.",
743        "Aplikacja nie może uruchomić {} (PERMISSIONS: process). Skopiuj źródło i otwórz je samodzielnie.",
744        "لا يُسمح للتطبيق بتشغيل {} (PERMISSIONS: process). انسخ المصدر وافتحه بنفسك.",
745        "לאפליקציה אסור להפעיל את {} (PERMISSIONS: process). יש להעתיק את המקור ולפתוח אותו ידנית.",
746    ],
747    [
748        "Die App fragt, ob sie {} starten darf: nach der Antwort noch einmal öffnen.",
749        "The app asks whether it may start {}: open again after the answer.",
750        "L'application demande si elle peut lancer {}\u{A0}: ouvrez à nouveau après la réponse.",
751        "La aplicación pregunta si puede iniciar {}: vuelva a abrir tras la respuesta.",
752        "L'app chiede se può avviare {}: riaprire dopo la risposta.",
753        "De app vraagt of hij {} mag starten: open opnieuw na het antwoord.",
754        "Aplikacja pyta, czy może uruchomić {}: po odpowiedzi otwórz ponownie.",
755        "يسأل التطبيق عمّا إذا كان يمكنه تشغيل {}: افتح مرة أخرى بعد الإجابة.",
756        "האפליקציה שואלת אם מותר לה להפעיל את {}: יש לפתוח שוב לאחר התשובה.",
757    ],
758    [
759        "Start fehlgeschlagen: {} (Fehler {})",
760        "Could not start {} (error {})",
761        "Impossible de lancer {} (erreur {})",
762        "No se pudo iniciar {} (error {})",
763        "Impossibile avviare {} (errore {})",
764        "Kan {} niet starten (fout {})",
765        "Nie można uruchomić {} (błąd {})",
766        "تعذّر تشغيل {} (الخطأ {})",
767        "לא ניתן להפעיל את {} (שגיאה {})",
768    ],
769    // (ROADMAP §6) the image viewer (imgview.rs): the accessible name of ui.image_view
770    ["Bild", "Image", "Image", "Imagen", "Immagine", "Afbeelding", "Obraz", "صورة", "תמונה"],
771    // (L5) the line chart (chart.rs): its empty state and the words of its summary
772    ["Keine Daten", "No data", "Aucune donnée", "Sin datos", "Nessun dato", "Geen gegevens", "Brak danych", "لا توجد بيانات", "אין נתונים"],
773    ["aktuell", "current", "actuel", "actual", "attuale", "huidig", "bieżąca", "الحالية", "נוכחי"],
774    ["min.", "min", "min", "mín.", "min", "min.", "min.", "الأدنى", "מינימום"],
775    ["max.", "max", "max", "máx.", "max", "max.", "maks.", "الأقصى", "מקסימום"],
776    // the terminal (term.rs): the button that jumps down to new output
777    ["Neue Ausgabe", "New output", "Nouvelle sortie", "Nueva salida", "Nuovo output", "Nieuwe uitvoer", "Nowe dane wyjściowe", "مخرجات جديدة", "פלט חדש"],
778    // the names of the roles screen readers hear (plat::a11y::ROLE_TEXTS: UIA
779    // LocalizedControlType, AT-SPI GetLocalizedRoleName), English as AT-SPI names them
780    ["Bereich", "panel", "panneau", "panel", "pannello", "paneel", "panel", "لوحة", "חלונית"],
781    ["Bezeichnung", "label", "étiquette", "etiqueta", "etichetta", "label", "etykieta", "تسمية", "תווית"],
782    ["Schaltfläche", "push button", "bouton", "botón", "pulsante", "knop", "przycisk", "زر", "לחצן"],
783    ["Kontrollkästchen", "check box", "case à cocher", "casilla", "casella di controllo", "selectievakje", "pole wyboru", "خانة اختيار", "תיבת סימון"],
784    ["Optionsfeld", "radio button", "bouton radio", "botón de opción", "pulsante di opzione", "keuzerondje", "przycisk opcji", "زر اختيار", "לחצן אפשרות"],
785    ["Umschalter", "toggle switch", "interrupteur", "interruptor", "interruttore", "schakelaar", "przełącznik", "مفتاح تبديل", "מתג"],
786    ["Schieberegler", "slider", "curseur", "control deslizante", "dispositivo di scorrimento", "schuifregelaar", "suwak", "شريط تمرير", "מחוון"],
787    ["Fortschrittsanzeige", "progress bar", "barre de progression", "barra de progreso", "barra di avanzamento", "voortgangsbalk", "pasek postępu", "شريط التقدم", "סרגל התקדמות"],
788    ["Eingabefeld", "entry", "zone de saisie", "campo de texto", "campo di testo", "invoerveld", "pole tekstowe", "حقل إدخال", "שדה קלט"],
789    ["Verweis", "link", "lien", "enlace", "collegamento", "koppeling", "łącze", "ارتباط", "קישור"],
790    ["Listenfeld", "list box", "zone de liste", "cuadro de lista", "casella di riepilogo", "keuzelijst", "pole listy", "مربع قائمة", "תיבת רשימה"],
791    ["Listeneintrag", "list item", "élément de liste", "elemento de lista", "elemento dell'elenco", "lijstitem", "element listy", "عنصر قائمة", "פריט ברשימה"],
792    ["Menüleiste", "menu bar", "barre de menus", "barra de menús", "barra dei menu", "menubalk", "pasek menu", "شريط القوائم", "סרגל תפריטים"],
793    ["Menü", "menu", "menu", "menú", "menu", "menu", "menu", "قائمة", "תפריט"],
794    ["Menüeintrag", "menu item", "élément de menu", "elemento de menú", "voce di menu", "menu-item", "element menu", "بند قائمة", "פריט תפריט"],
795    ["Registerleiste", "page tab list", "liste d'onglets", "lista de pestañas", "elenco schede", "tabbladenlijst", "lista kart", "قائمة علامات التبويب", "רשימת כרטיסיות"],
796    ["Registerkarte", "page tab", "onglet", "pestaña", "scheda", "tabblad", "karta", "علامة تبويب", "כרטיסייה"],
797    ["Kombinationsfeld", "combo box", "zone de liste déroulante", "cuadro combinado", "casella combinata", "keuzelijst met invoervak", "pole kombi", "مربع تحرير وسرد", "תיבה משולבת"],
798    ["Grafik", "image", "image", "imagen", "immagine", "afbeelding", "obraz", "صورة", "תמונה"],
799    ["Zeichenfläche", "canvas", "zone de dessin", "lienzo", "area di disegno", "tekenvlak", "obszar rysowania", "لوحة رسم", "משטח ציור"],
800    ["Dialog", "dialog", "boîte de dialogue", "cuadro de diálogo", "finestra di dialogo", "dialoogvenster", "okno dialogowe", "مربع حوار", "תיבת דו-שיח"],
801    ["Überschrift", "heading", "titre", "encabezado", "intestazione", "kop", "nagłówek", "عنوان", "כותרת"],
802    ["Rahmen", "frame", "cadre", "marco", "cornice", "kader", "ramka", "إطار", "מסגרת"],
803    ["Anwendung", "application", "application", "aplicación", "applicazione", "toepassing", "aplikacja", "تطبيق", "יישום"],
804    ["unbekannt", "unknown", "inconnu", "desconocido", "sconosciuto", "onbekend", "nieznany", "غير معروف", "לא ידוע"],
805    // app themes (theme_app.rs): mistakes of a theme file, a file or font that cannot be read
806    ["Zeile {}: unbekannter Abschnitt [{}]", "Line {}: unknown section [{}]", "Ligne {}\u{A0}: section inconnue [{}]",
807     "Línea {}: sección desconocida [{}]", "Riga {}: sezione sconosciuta [{}]", "Regel {}: onbekende sectie [{}]",
808     "Wiersz {}: nieznana sekcja [{}]", "السطر {}: قسم غير معروف [{}]", "שורה {}: מקטע לא ידוע [{}]"],
809    ["Zeile {}: unbekannter Schlüssel „{}“ in [{}]", "Line {}: unknown key “{}” in [{}]",
810     "Ligne {}\u{A0}: clé inconnue «\u{A0}{}\u{A0}» dans [{}]", "Línea {}: clave desconocida «{}» en [{}]",
811     "Riga {}: chiave sconosciuta «{}» in [{}]", "Regel {}: onbekende sleutel ‘{}’ in [{}]", "Wiersz {}: nieznany klucz „{}” w [{}]",
812     "السطر {}: مفتاح غير معروف «{}» في [{}]", "שורה {}: מפתח לא ידוע „{}” ב-[{}]"],
813    ["Zeile {}: „{}“ steht zweimal in [{}]", "Line {}: “{}” appears twice in [{}]",
814     "Ligne {}\u{A0}: «\u{A0}{}\u{A0}» apparaît deux fois dans [{}]", "Línea {}: «{}» aparece dos veces en [{}]",
815     "Riga {}: «{}» compare due volte in [{}]", "Regel {}: ‘{}’ staat twee keer in [{}]", "Wiersz {}: „{}” występuje dwa razy w [{}]",
816     "السطر {}: «{}» مكرر في [{}]", "שורה {}: „{}” מופיע פעמיים ב-[{}]"],
817    ["Zeile {}: „Schlüssel = Wert“ erwartet", "Line {}: expected “key = value”", "Ligne {}\u{A0}: «\u{A0}clé = valeur\u{A0}» attendu",
818     "Línea {}: se esperaba «clave = valor»", "Riga {}: atteso «chiave = valore»", "Regel {}: ‘sleutel = waarde’ verwacht",
819     "Wiersz {}: oczekiwano „klucz = wartość”", "السطر {}: المتوقع «مفتاح = قيمة»", "שורה {}: נדרש „מפתח = ערך”"],
820    ["Zeile {}: „{}“ steht vor dem ersten Abschnitt", "Line {}: “{}” comes before the first section",
821     "Ligne {}\u{A0}: «\u{A0}{}\u{A0}» précède la première section", "Línea {}: «{}» está antes de la primera sección",
822     "Riga {}: «{}» precede la prima sezione", "Regel {}: ‘{}’ staat vóór de eerste sectie", "Wiersz {}: „{}” stoi przed pierwszą sekcją",
823     "السطر {}: «{}» قبل القسم الأول", "שורה {}: „{}” מופיע לפני המקטע הראשון"],
824    ["Zeile {}: „{}“ ist keine Farbe (#RRGGBB oder #AARRGGBB)", "Line {}: “{}” is not a colour (#RRGGBB or #AARRGGBB)",
825     "Ligne {}\u{A0}: «\u{A0}{}\u{A0}» n’est pas une couleur (#RRGGBB ou #AARRGGBB)", "Línea {}: «{}» no es un color (#RRGGBB o #AARRGGBB)",
826     "Riga {}: «{}» non è un colore (#RRGGBB o #AARRGGBB)", "Regel {}: ‘{}’ is geen kleur (#RRGGBB of #AARRGGBB)",
827     "Wiersz {}: „{}” nie jest kolorem (#RRGGBB lub #AARRGGBB)", "السطر {}: «{}» ليس لونًا (#RRGGBB أو #AARRGGBB)",
828     "שורה {}: „{}” אינו צבע (#RRGGBB או #AARRGGBB)"],
829    ["Zeile {}: „{}“ ist keine Zahl von {} bis {}", "Line {}: “{}” is not a number from {} to {}",
830     "Ligne {}\u{A0}: «\u{A0}{}\u{A0}» n’est pas un nombre de {} à {}", "Línea {}: «{}» no es un número de {} a {}",
831     "Riga {}: «{}» non è un numero da {} a {}", "Regel {}: ‘{}’ is geen getal van {} tot {}", "Wiersz {}: „{}” nie jest liczbą od {} do {}",
832     "السطر {}: «{}» ليس رقمًا من {} إلى {}", "שורה {}: „{}” אינו מספר בין {} ל-{}"],
833    ["Zeile {}: „{}“ ist keine Schriftstärke (regular, semibold, bold, 100 bis 900)",
834     "Line {}: “{}” is not a font weight (regular, semibold, bold, 100 to 900)",
835     "Ligne {}\u{A0}: «\u{A0}{}\u{A0}» n’est pas une graisse (regular, semibold, bold, 100 à 900)",
836     "Línea {}: «{}» no es un grosor de fuente (regular, semibold, bold, 100 a 900)",
837     "Riga {}: «{}» non è uno spessore del carattere (regular, semibold, bold, da 100 a 900)",
838     "Regel {}: ‘{}’ is geen letterdikte (regular, semibold, bold, 100 tot 900)",
839     "Wiersz {}: „{}” nie jest grubością czcionki (regular, semibold, bold, 100 do 900)",
840     "السطر {}: «{}» ليس سُمك خط (regular أو semibold أو bold أو 100 إلى 900)",
841     "שורה {}: „{}” אינו משקל גופן (regular, semibold, bold, 100 עד 900)"],
842    ["Theme-Datei nicht lesbar: {}", "Theme file not readable: {}", "Fichier de thème illisible\u{A0}: {}",
843     "No se puede leer el archivo de tema: {}", "File del tema non leggibile: {}", "Themabestand niet leesbaar: {}",
844     "Nie można odczytać pliku motywu: {}", "تعذرت قراءة ملف السمة: {}", "לא ניתן לקרוא את קובץ ערכת הנושא: {}"],
845    ["Zeile {}: „{}“ sind keine Achsenwerte (wght=650, wdth=87.5 …)",
846     "Line {}: “{}” are no axis values (wght=650, wdth=87.5 …)",
847     "Ligne {}\u{A0}: «\u{A0}{}\u{A0}» ne sont pas des valeurs d’axes (wght=650, wdth=87.5 …)",
848     "Línea {}: «{}» no son valores de ejes (wght=650, wdth=87.5 …)",
849     "Riga {}: «{}» non sono valori di assi (wght=650, wdth=87.5 …)",
850     "Regel {}: ‘{}’ zijn geen aswaarden (wght=650, wdth=87.5 …)",
851     "Wiersz {}: „{}” to nie wartości osi (wght=650, wdth=87.5 …)",
852     "السطر {}: «{}» ليست قيم محاور (wght=650، wdth=87.5 …)",
853     "שורה {}: „{}” אינם ערכי צירים (wght=650, wdth=87.5 …)"],
854    ["Schrift nicht ladbar: {}", "Font cannot be loaded: {}", "Police impossible à charger\u{A0}: {}",
855     "No se puede cargar la fuente: {}", "Impossibile caricare il carattere: {}", "Lettertype kan niet worden geladen: {}",
856     "Nie można wczytać czcionki: {}", "تعذر تحميل الخط: {}", "לא ניתן לטעון את הגופן: {}"],
857    // (L7) windows inside an area (desk.rs): their buttons and menus, the taskbar
858    ["Minimieren", "Minimize", "Réduire", "Minimizar", "Riduci a icona", "Minimaliseren", "Minimalizuj", "تصغير", "מזעור"],
859    ["Maximieren", "Maximize", "Agrandir", "Maximizar", "Ingrandisci", "Maximaliseren", "Maksymalizuj", "تكبير", "הגדלה"],
860    ["Wiederherstellen", "Restore", "Restaurer", "Restaurar", "Ripristina", "Herstellen", "Przywróć", "استعادة", "שחזור"],
861    ["minimiert", "minimized", "réduite", "minimizada", "ridotta a icona", "geminimaliseerd", "zminimalizowane", "مصغّرة", "ממוזער"],
862    ["maximiert", "maximized", "agrandie", "maximizada", "ingrandita", "gemaximaliseerd", "zmaksymalizowane", "مكبّرة", "מוגדל"],
863    ["Taskleiste", "Taskbar", "Barre des tâches", "Barra de tareas", "Barra delle applicazioni", "Taakbalk", "Pasek zadań", "شريط المهام", "שורת המשימות"],
864    ["Weitere Fenster", "More windows", "Plus de fenêtres", "Más ventanas", "Altre finestre", "Meer vensters", "Więcej okien", "مزيد من النوافذ", "חלונות נוספים"],
865    // (SK6) the code editor's completion list: its undo step and its name for screen readers
866    ["Vervollständigen", "Complete", "Compléter", "Completar", "Completa", "Aanvullen", "Uzupełnij", "إكمال", "השלמה"],
867    ["Vorschläge", "Suggestions", "Suggestions", "Sugerencias", "Suggerimenti", "Suggesties", "Podpowiedzi", "اقتراحات", "הצעות"],
868];
869
870/// a library text in the current language: the UI language when the library has it
871/// (library_languages), else English; the German source when the text is not in the table
872pub fn tr(de: &'static str) -> &'static str {
873    let l = li();
874    if l == 0 {
875        return de;
876    }
877    LIB_TEXTS.iter().find(|r| r[0] == de).map_or(de, |r| r[l])
878}
879
880/// the languages of the library's own texts (tr, month_name, weekday_short): "de", "en", "fr",
881/// "es", "it", "nl", "pl", "ar", "he"
882pub fn library_languages() -> &'static [&'static str] {
883    &LIB_LANGS
884}
885
886/// the German source texts of the library's own texts (the keys of tr)
887pub fn library_keys() -> impl Iterator<Item = &'static str> {
888    LIB_TEXTS.iter().map(|r| r[0])
889}
890
891// calendar names: stand-alone forms (the date picker's title "Oktober 2026"), in the order of
892// LIB_LANGS
893static MONTHS: [[&str; 12]; 9] = [
894    ["Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"],
895    ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
896    ["janvier", "février", "mars", "avril", "mai", "juin", "juillet", "août", "septembre", "octobre", "novembre", "décembre"],
897    ["enero", "febrero", "marzo", "abril", "mayo", "junio", "julio", "agosto", "septiembre", "octubre", "noviembre", "diciembre"],
898    ["gennaio", "febbraio", "marzo", "aprile", "maggio", "giugno", "luglio", "agosto", "settembre", "ottobre", "novembre", "dicembre"],
899    ["januari", "februari", "maart", "april", "mei", "juni", "juli", "augustus", "september", "oktober", "november", "december"],
900    ["styczeń", "luty", "marzec", "kwiecień", "maj", "czerwiec", "lipiec", "sierpień", "wrzesień", "październik", "listopad", "grudzień"],
901    ["يناير", "فبراير", "مارس", "أبريل", "مايو", "يونيو", "يوليو", "أغسطس", "سبتمبر", "أكتوبر", "نوفمبر", "ديسمبر"],
902    ["ינואר", "פברואר", "מרץ", "אפריל", "מאי", "יוני", "יולי", "אוגוסט", "ספטמבר", "אוקטובר", "נובמבר", "דצמבר"],
903];
904static MONTHS_SHORT: [[&str; 12]; 9] = [
905    ["Jan", "Feb", "Mär", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez"],
906    ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
907    ["janv.", "févr.", "mars", "avr.", "mai", "juin", "juil.", "août", "sept.", "oct.", "nov.", "déc."],
908    ["ene", "feb", "mar", "abr", "may", "jun", "jul", "ago", "sept", "oct", "nov", "dic"],
909    ["gen", "feb", "mar", "apr", "mag", "giu", "lug", "ago", "set", "ott", "nov", "dic"],
910    ["jan", "feb", "mrt", "apr", "mei", "jun", "jul", "aug", "sep", "okt", "nov", "dec"],
911    ["sty", "lut", "mar", "kwi", "maj", "cze", "lip", "sie", "wrz", "paź", "lis", "gru"],
912    ["يناير", "فبراير", "مارس", "أبريل", "مايو", "يونيو", "يوليو", "أغسطس", "سبتمبر", "أكتوبر", "نوفمبر", "ديسمبر"],
913    ["ינו׳", "פבר׳", "מרץ", "אפר׳", "מאי", "יוני", "יולי", "אוג׳", "ספט׳", "אוק׳", "נוב׳", "דצמ׳"],
914];
915// the calendar's column heads (Arabic: the one-letter forms of its calendars)
916static WEEKDAYS_SHORT: [[&str; 7]; 9] = [
917    ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"],
918    ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"],
919    ["lu", "ma", "me", "je", "ve", "sa", "di"],
920    ["lu", "ma", "mi", "ju", "vi", "sá", "do"],
921    ["lu", "ma", "me", "gi", "ve", "sa", "do"],
922    ["ma", "di", "wo", "do", "vr", "za", "zo"],
923    ["pn", "wt", "śr", "cz", "pt", "so", "nd"],
924    ["ن", "ث", "ر", "خ", "ج", "س", "ح"],
925    ["ב׳", "ג׳", "ד׳", "ה׳", "ו׳", "ש׳", "א׳"],
926];
927static WEEKDAYS_ABBR: [[&str; 7]; 9] = [
928    ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"],
929    ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
930    ["lun.", "mar.", "mer.", "jeu.", "ven.", "sam.", "dim."],
931    ["lun", "mar", "mié", "jue", "vie", "sáb", "dom"],
932    ["lun", "mar", "mer", "gio", "ven", "sab", "dom"],
933    ["ma", "di", "wo", "do", "vr", "za", "zo"],
934    ["pon.", "wt.", "śr.", "czw.", "pt.", "sob.", "niedz."],
935    ["الاثنين", "الثلاثاء", "الأربعاء", "الخميس", "الجمعة", "السبت", "الأحد"],
936    ["יום ב׳", "יום ג׳", "יום ד׳", "יום ה׳", "יום ו׳", "שבת", "יום א׳"],
937];
938
939fn li() -> usize {
940    LIB.load(Ordering::Relaxed) as usize
941}
942
943/// the name of month m (1..12) in the current language
944pub fn month_name(m: u32) -> &'static str {
945    MONTHS[li()][((m.max(1) - 1) % 12) as usize]
946}
947
948/// the short name of month m (1..12)
949pub fn month_short(m: u32) -> &'static str {
950    MONTHS_SHORT[li()][((m.max(1) - 1) % 12) as usize]
951}
952
953/// the two-letter name of weekday d (0 = Monday .. 6 = Sunday)
954pub fn weekday_short(d: u32) -> &'static str {
955    WEEKDAYS_SHORT[li()][(d % 7) as usize]
956}
957
958/// the usual abbreviation of weekday d (0 = Monday) in running text: "Sa" / "Sat"
959pub fn weekday_abbr(d: u32) -> &'static str {
960    WEEKDAYS_ABBR[li()][(d % 7) as usize]
961}
962
963/// a translation catalogue of an app (see the module comment)
964pub struct Catalog {
965    langs: Vec<String>,
966    rows: BTreeMap<&'static str, Vec<&'static str>>,
967}
968
969impl Catalog {
970    /// parse a TSV catalogue (first line: the language codes); "\n" and "\t" escapes in the
971    /// cells are not interpreted
972    pub fn from_tsv(text: &'static str) -> Catalog {
973        let mut lines = text.lines();
974        let langs: Vec<String> = lines.next().map(|h| h.split('\t').map(|s| String::from(s.trim())).collect()).unwrap_or_default();
975        let mut rows = BTreeMap::new();
976        for line in lines {
977            if line.is_empty() || line.starts_with('#') {
978                continue;
979            }
980            let mut cols = line.split('\t');
981            let Some(key) = cols.next() else { continue };
982            let rest: Vec<&'static str> = cols.collect();
983            rows.insert(key, rest);
984        }
985        Catalog { langs, rows }
986    }
987
988    /// also offer the languages of the library's own texts (library_languages): there the
989    /// library's texts follow the user and Arabic and Hebrew mirror the window, while the app's
990    /// own texts show their English column.  The browser bundle has no font for Arabic and
991    /// Hebrew: there they stay out, the user gets English instead of empty boxes
992    pub fn with_library_languages(mut self) -> Catalog {
993        for l in LIB_LANGS {
994            let no_font = cfg!(target_arch = "wasm32") && matches!(l, "ar" | "he");
995            if !no_font && !self.langs.iter().any(|c| c == l) {
996                self.langs.push(String::from(l));
997            }
998        }
999        self
1000    }
1001
1002    /// the language codes of the columns ("de", "en", ...)
1003    pub fn languages(&self) -> &[String] {
1004        &self.langs
1005    }
1006
1007    /// the text in the current language (the source text when the cell is missing or empty)
1008    pub fn tr<'a>(&'a self, s: &'a str) -> &'a str {
1009        let l = language() as usize;
1010        if l == 0 {
1011            return s;
1012        }
1013        match self.rows.get(s).and_then(|r| r.get(l - 1)) {
1014            Some(t) if !t.is_empty() => t,
1015            _ => s,
1016        }
1017    }
1018
1019    /// whether the catalogue has a row for this source text
1020    pub fn has(&self, s: &str) -> bool {
1021        self.rows.contains_key(s)
1022    }
1023}
1024
1025static APP: rt::sync::SpinLock<Option<&'static Catalog>> = rt::sync::SpinLock::new(None);
1026
1027/// install the app's catalogue for the whole process (t() reads it); its column follows the
1028/// user's languages unless set_language (the same column) or set_language_code (the best
1029/// column for that code) came first
1030pub fn set_catalog(c: Catalog) {
1031    let r: &'static Catalog = Box::leak(Box::new(c));
1032    *APP.lock() = Some(r);
1033    let code = EXPLICIT_CODE.lock().clone();
1034    if let Some(code) = code {
1035        select(pick(&[code]));
1036    } else if EXPLICIT.load(Ordering::Relaxed) {
1037        select(language());
1038    } else {
1039        select(pick(&user_languages()));
1040    }
1041}
1042
1043/// the app's text in the current language: the installed catalogue, then the library's own
1044/// texts, then the catalogue's English column, then the German source
1045pub fn t(s: &'static str) -> &'static str {
1046    if language() == LANG_DE {
1047        return s;
1048    }
1049    let cat = *APP.lock();
1050    if let Some(x) = cat.and_then(|c| c.get_static(s)) {
1051        return x;
1052    }
1053    let lib = tr(s);
1054    if !core::ptr::eq(lib, s) {
1055        return lib;
1056    }
1057    cat.and_then(|c| c.english(s)).unwrap_or(s)
1058}
1059
1060// the translated copies of the static tables: (the table, the language, the copy)
1061static COMMANDS: rt::sync::SpinLock<Vec<(usize, u32, &'static [Command])>> = rt::sync::SpinLock::new(Vec::new());
1062static MENUS: rt::sync::SpinLock<Vec<(usize, u32, &'static [Mi])>> = rt::sync::SpinLock::new(Vec::new());
1063
1064/// a command table (static, German) in the current language, made once per table and language
1065pub fn commands(src: &'static [Command]) -> &'static [Command] {
1066    let l = language();
1067    if l == LANG_DE || src.is_empty() {
1068        return src;
1069    }
1070    let key = src.as_ptr() as usize;
1071    if let Some(&(_, _, done)) = COMMANDS.lock().iter().find(|e| e.0 == key && e.1 == l) {
1072        return done;
1073    }
1074    let done: &'static [Command] = Vec::leak(src.iter().map(|c| Command { label: t(c.label), ..*c }).collect());
1075    COMMANDS.lock().push((key, l, done));
1076    done
1077}
1078
1079/// a menu (static, German) in the current language, made once per menu and language
1080pub fn menu(src: &'static [Mi]) -> &'static [Mi] {
1081    let l = language();
1082    if l == LANG_DE || src.is_empty() {
1083        return src;
1084    }
1085    let key = src.as_ptr() as usize;
1086    if let Some(&(_, _, done)) = MENUS.lock().iter().find(|e| e.0 == key && e.1 == l) {
1087        return done;
1088    }
1089    let v: Vec<Mi> = src
1090        .iter()
1091        .map(|it| match *it {
1092            Mi::Menu(label, sub) => Mi::Menu(t(label), menu(sub)),
1093            Mi::Sub(label, sub) => Mi::Sub(t(label), menu(sub)),
1094            Mi::Item(id, label, icon) => Mi::Item(id, t(label), icon),
1095            Mi::List(id, n, label) => Mi::List(id, n, t(label)),
1096            other => other,
1097        })
1098        .collect();
1099    let done: &'static [Mi] = Vec::leak(v);
1100    MENUS.lock().push((key, l, done));
1101    done
1102}
1103
1104/// (perm.rs) a text that is not 'static (a manifest's reason): the app's catalogue, else the
1105/// text itself
1106pub(crate) fn t_text(s: &str) -> String {
1107    let cat = if language() == LANG_DE { None } else { *APP.lock() };
1108    String::from(cat.and_then(|c| c.get_static(s).or_else(|| c.english(s))).unwrap_or(s))
1109}
1110
1111/// the app's text in the current language with its "{}" placeholders filled in order
1112pub fn tf(s: &'static str, args: &[&str]) -> String {
1113    fill(t(s), args)
1114}
1115
1116/// a table of the app's texts in the current language (combo boxes, tabs)
1117pub fn list(items: &[&'static str]) -> Vec<&'static str> {
1118    items.iter().map(|s| t(s)).collect()
1119}
1120
1121/// replace the "{}" placeholders of a text in order
1122pub fn fill(text: &str, args: &[&str]) -> String {
1123    let mut out = String::with_capacity(text.len() + 16);
1124    let mut rest = text;
1125    let mut i = 0;
1126    while let Some(p) = rest.find("{}") {
1127        out.push_str(&rest[..p]);
1128        out.push_str(args.get(i).copied().unwrap_or(""));
1129        i += 1;
1130        rest = &rest[p + 2..];
1131    }
1132    out.push_str(rest);
1133    out
1134}
1135
1136impl Catalog {
1137    /// the translation of a source text in the current language, when the catalogue has one
1138    pub fn get_static(&self, s: &str) -> Option<&'static str> {
1139        let l = language() as usize;
1140        if l == 0 {
1141            return None;
1142        }
1143        self.rows.get(s).and_then(|r| r.get(l - 1)).copied().filter(|t| !t.is_empty())
1144    }
1145
1146    // the text's English cell: what a language without a cell of its own shows
1147    fn english(&self, s: &str) -> Option<&'static str> {
1148        let k = self.langs.iter().position(|c| c == "en").filter(|k| *k > 0)?;
1149        self.rows.get(s).and_then(|r| r.get(k - 1)).copied().filter(|t| !t.is_empty())
1150    }
1151}