From c4dfee6e5c1504f15f7e26d667399c310c8fd066 Mon Sep 17 00:00:00 2001 From: German Khokhlov Date: Wed, 28 Oct 2026 12:51:02 +0400 Subject: [PATCH] i18n(it): ship Italian - Register it: catalog in index.js, LOCALES and LOCALE_NAMES (Italiano); it-IT, it-CH and other it tags select it. - List it.json with the other catalogs in package-boundaries.json. - docs/translations/it.md: style, plurals and glossary; Italian in the shipped lists of TRANSLATORS.md and CURRENT-STATE.md. Co-Authored-By: Claude Opus 5.5 --- docs/CURRENT-STATE.md | 2 +- docs/TRANSLATORS.md | 4 +- docs/translations/it.md | 90 +++++++++++++++++++++++++++++++++ scripts/package-boundaries.json | 43 ++++++++++++++++ src/i18n/i18n.test.mjs | 2 + src/i18n/index.js | 9 +++- src/i18n/locale.js | 10 +++- src/ui/localePicker.test.mjs | 6 +-- 8 files changed, 158 insertions(+), 8 deletions(-) create mode 100644 docs/translations/it.md diff --git a/docs/CURRENT-STATE.md b/docs/CURRENT-STATE.md index c55031a6a..b65f48193 100644 --- a/docs/CURRENT-STATE.md +++ b/docs/CURRENT-STATE.md @@ -3733,7 +3733,7 @@ test:track` 43 tracking invariants · headless QA harnesses under ### Interface localization (October 2026) -Shipped locales: **en** (source and fallback), **es**, **ru**, **zh-CN** and **zh-Hant**. The code lives in +Shipped locales: **en** (source and fallback), **es**, **it**, **ru**, **zh-CN** and **zh-Hant**. The code lives in `src/i18n/`. How to mark UI text: [`docs/I18N.md`](./I18N.md); translator workflow: [`docs/TRANSLATORS.md`](./TRANSLATORS.md). diff --git a/docs/TRANSLATORS.md b/docs/TRANSLATORS.md index 830ca155e..01ed1a92e 100644 --- a/docs/TRANSLATORS.md +++ b/docs/TRANSLATORS.md @@ -1,6 +1,6 @@ # Translating God's Eye View -Shipped languages: **English** (source and fallback), **Spanish** (`es`), **Russian** (`ru`), **Simplified Chinese** (`zh-CN`) and **Traditional Chinese** (`zh-Hant`). +Shipped languages: **English** (source and fallback), **Spanish** (`es`), **Italian** (`it`), **Russian** (`ru`), **Simplified Chinese** (`zh-CN`) and **Traditional Chinese** (`zh-Hant`). ## How it works @@ -63,4 +63,4 @@ These are contracts, not copy: layer ids and registry keys, status enums and oth ## Language notes -Each language keeps its glossary, plural forms, units and style in its own file, `docs/translations/.md`: [Spanish](./translations/es.md), [Russian](./translations/ru.md), [Simplified Chinese](./translations/zh-CN.md), [Traditional Chinese](./translations/zh-Hant.md). Read it before translating, and add a term there when you settle on one. +Each language keeps its glossary, plural forms, units and style in its own file, `docs/translations/.md`: [Spanish](./translations/es.md), [Italian](./translations/it.md), [Russian](./translations/ru.md), [Simplified Chinese](./translations/zh-CN.md), [Traditional Chinese](./translations/zh-Hant.md). Read it before translating, and add a term there when you settle on one. diff --git a/docs/translations/it.md b/docs/translations/it.md new file mode 100644 index 000000000..155c0e563 --- /dev/null +++ b/docs/translations/it.md @@ -0,0 +1,90 @@ +# Italian (`it`) + +Catalog: `src/i18n/locales/it.json`. General rules: [TRANSLATORS.md](../TRANSLATORS.md). + +Translation source: abiyeenzo (#912). + +## Style + +- Standard Italian as in Italian software UIs. Address the user with informal "tu" imperatives: «Aggiungi», «Attiva», «Scegli», «Incolla», «Riprova». Tooltips that name an action use the same imperative: «Espandi il pannello», «Ripristina la camera». +- Sentence case, not Title Case: "Live Flights" → «Voli live», "Space Missions" → «Missioni spaziali». UPPERCASE English stays UPPERCASE: «LIVE», «NON DISPONIBILE». +- Quotes inside Italian text are straight double quotes: «Eliminare lo scatto "{shot}"?». Use the accented capitals (È, À) in UPPERCASE text: «VELOCITÀ», «CITTÀ». +- Errors read as a statement, not a past participle on its own: "Search failed" → «Ricerca non riuscita»; "Could not …" → «Impossibile …». A dash that joins a cause and a hint becomes a colon: «Chiave API rifiutata: controlla AISSTREAM_API_KEY». +- Short labels, chips and HUD readouts stay short. A standard abbreviation with a period beats a wrapped line: REGOLAZ., RILEV., INCL., ALT., VEL., STIM.; PREC / SUCC for PREV / NEXT. +- The HUD codes HDG, BRG, CRS, FOV and DEST stay in English, as in cockpit avionics. RANGE is «PORTATA». Words used in a sentence are translated: heading → «prua» (aircraft) or «direzione» (camera), course → «rotta». +- Keyboard keys stay in English: Space, Enter, Esc, Tab, Shift, Backspace, H, 1–9 («Tieni premuto Space per parlare», «Premi Enter per riprovare»). +- Decimal comma in numbers written in the text: «28,2710° N». Numbers formatted by the code follow `Intl` and need nothing. +- Units: km, m, km/h, m/s, kn (knots), ft (feet), h, min, s; in UPPERCASE text KM, KM/S, KT, FT, MIN, H. "m" is always metres, so minutes are «min» («5 min fa»). MW stays MW. `°` and `%` stay. +- "ago" goes after the time: «{count} min fa». When a placeholder already holds a localized "ago" text (`{age}`, `{ago}`), do not add «fa» again. +- English loanwords that Italian UIs use as they are stay in English: feed, live, cockpit, briefing, preset, overlay, layout, snapshot, tile, provider, timeout, payload, bloom, scope. + +## Plurals + +A `tn()` entry needs three forms. `Intl.PluralRules('it')` picks: + +| Form | Counts | Example | +| ------- | -------------------------------------- | ----------------------- | +| `one` | 1 | 1 telecamera | +| `many` | 1 000 000, 2 000 000… (exact millions) | 1 000 000 di telecamere | +| `other` | everything else: 0, 2–999 999, 1,5… | 3 telecamere | + +The `many` form is the `other` text with «di» after the number when a noun follows: «{count} di telecamere caricate», «{count} DI CHIAVI IN ATTESA». When no noun follows the number («{count} vicine», «{count} RICEVUTI»), `many` repeats `other`. Nouns that do not change in the plural keep one form for all three (`{count} file`, `{count} data center`), with «di» in `many`. + +## Glossary + +| English | Italiano | +| --------------------------------------------- | ------------------------------------------------------------ | +| layer / data layer | livello / livello dati | +| feed / source / data source | feed / fonte / fonte dati | +| LIVE (status) / live data | LIVE / dati live | +| SIMULATED | SIMULATO | +| stale / degraded / unavailable | obsoleto / degradato / non disponibile | +| retry / retrying | riprova / nuovo tentativo | +| rate limited | limite di richieste (raggiunto) | +| key (API key) / POWER UP / POWERED UP | chiave / ACCENDI / ACCESO | +| Provider Settings | Impostazioni provider | +| ON / OFF (state chips) | ON / OFF | +| enable / disable | attivare / disattivare | +| globe / map / map source / map stack | globo / mappa / fonte mappa / stack mappa | +| basemap / box (Recent Imagery area) | mappa di base / riquadro | +| aircraft, plane / flight / military flights | aereo / volo / voli militari | +| vessel, ship | nave | +| satellite / shell (Starlink) / DENSE | satellite / shell / DENSO | +| launch (rocket) / launch site / space mission | lancio / sito di lancio / missione spaziale | +| ascent / replay / payload / stage | salita / replay / payload / stadio | +| contact / tracking | contatto / tracciamento | +| installation / site | installazione / sito | +| mapped / observed | mappato / osservato | +| camera (view) / camera, CCTV camera | camera / telecamera, telecamera CCTV | +| earthquake / active fire / fire perimeter | terremoto / incendio attivo / perimetro dell'incendio | +| advisory (NHC/CPHC cyclone) / cone | bollettino / cono di incertezza | +| radio station / broadcaster | stazione radio / emittente | +| road / flow (traffic) | strada / flusso | +| transit / bike share | trasporto pubblico / bike sharing | +| scene / shot / scene recipe | scena / scatto / ricetta della scena | +| anchor / data pack / bundle (scene) | ancora / pacchetto dati / pacchetto | +| story beat | tappa del racconto | +| briefing / panel / dock | briefing / pannello / dock | +| cockpit | cockpit | +| preset / visual preset | preset / preset visivo | +| Tactical / Operator / Minimal / Cyber | Tattico / Operatore / Minimale / Cyber | +| Scope (circular viewport mask) / keyhole | scope / foro | +| feather / fade | sfumatura / dissolvenza | +| detection overlay | overlay di rilevamento | +| pin (Draw tool) / mark | segnaposto / segno | +| pin (Recent Imagery) | fissare | +| Draw / Directions (tools) | Disegno / Indicazioni | +| Street Level (layer) / Street View (Google) | Vista stradale / Street View | +| Recent Imagery | Immagini recenti | +| Global Context / Context (panel) | Contesto globale / Contesto | +| heading, course / altitude / ground speed | prua, rotta / altitudine / velocità al suolo (VEL. AL SUOLO) | +| HDG / BRG / CRS / FOV / DEST | HDG / BRG / CRS / FOV / DEST (unchanged) | +| RANGE / PITCH / HGT (CCTV calibration) | PORTATA / INCL. / ALT. | +| PREV / NEXT | PREC / SUCC | +| Previous / Next (button text) | Precedente / Successivo | +| L / R (relative bearing) | S / D | +| SIG / NEWS / LOCAL (briefing tabs) | SIG / NOTIZIE / LOCALE | +| compass points (N, NNE, …, W, NW) | N, NNE, …, O, NO (west is «O», ovest) | +| NOT AN ALL-CLEAR | NON È UN VIA LIBERA | +| weather field / model / valid / issued | campo / modello / valida / emessa | +| click / click to … | fai clic / clic per … | diff --git a/scripts/package-boundaries.json b/scripts/package-boundaries.json index 9f06f73f6..991ab9498 100644 --- a/scripts/package-boundaries.json +++ b/scripts/package-boundaries.json @@ -440,6 +440,7 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", + "src/i18n/locales/it.json", "src/i18n/locales/ru.json", "src/i18n/locales/zh-CN.json", "src/i18n/locales/zh-Hant.json" @@ -480,6 +481,7 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", + "src/i18n/locales/it.json", "src/i18n/locales/ru.json", "src/i18n/locales/zh-CN.json", "src/i18n/locales/zh-Hant.json" @@ -510,6 +512,7 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", + "src/i18n/locales/it.json", "src/i18n/locales/ru.json", "src/i18n/locales/zh-CN.json", "src/i18n/locales/zh-Hant.json", @@ -532,6 +535,7 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", + "src/i18n/locales/it.json", "src/i18n/locales/ru.json", "src/i18n/locales/zh-CN.json", "src/i18n/locales/zh-Hant.json" @@ -549,6 +553,7 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", + "src/i18n/locales/it.json", "src/i18n/locales/ru.json", "src/i18n/locales/zh-CN.json", "src/i18n/locales/zh-Hant.json" @@ -569,6 +574,7 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", + "src/i18n/locales/it.json", "src/i18n/locales/ru.json", "src/i18n/locales/zh-CN.json", "src/i18n/locales/zh-Hant.json", @@ -596,6 +602,7 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", + "src/i18n/locales/it.json", "src/i18n/locales/ru.json", "src/i18n/locales/zh-CN.json", "src/i18n/locales/zh-Hant.json", @@ -631,6 +638,7 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", + "src/i18n/locales/it.json", "src/i18n/locales/ru.json", "src/i18n/locales/zh-CN.json", "src/i18n/locales/zh-Hant.json", @@ -648,6 +656,7 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", + "src/i18n/locales/it.json", "src/i18n/locales/ru.json", "src/i18n/locales/zh-CN.json", "src/i18n/locales/zh-Hant.json", @@ -807,6 +816,7 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", + "src/i18n/locales/it.json", "src/i18n/locales/ru.json", "src/i18n/locales/zh-CN.json", "src/i18n/locales/zh-Hant.json", @@ -929,6 +939,7 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", + "src/i18n/locales/it.json", "src/i18n/locales/ru.json", "src/i18n/locales/zh-CN.json", "src/i18n/locales/zh-Hant.json", @@ -997,6 +1008,7 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", + "src/i18n/locales/it.json", "src/i18n/locales/ru.json", "src/i18n/locales/zh-CN.json", "src/i18n/locales/zh-Hant.json", @@ -1046,6 +1058,7 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", + "src/i18n/locales/it.json", "src/i18n/locales/ru.json", "src/i18n/locales/zh-CN.json", "src/i18n/locales/zh-Hant.json" @@ -1109,6 +1122,7 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", + "src/i18n/locales/it.json", "src/i18n/locales/ru.json", "src/i18n/locales/zh-CN.json", "src/i18n/locales/zh-Hant.json" @@ -1139,6 +1153,7 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", + "src/i18n/locales/it.json", "src/i18n/locales/ru.json", "src/i18n/locales/zh-CN.json", "src/i18n/locales/zh-Hant.json", @@ -1170,6 +1185,7 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", + "src/i18n/locales/it.json", "src/i18n/locales/ru.json", "src/i18n/locales/zh-CN.json", "src/i18n/locales/zh-Hant.json" @@ -1201,6 +1217,7 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", + "src/i18n/locales/it.json", "src/i18n/locales/ru.json", "src/i18n/locales/zh-CN.json", "src/i18n/locales/zh-Hant.json", @@ -1235,6 +1252,7 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", + "src/i18n/locales/it.json", "src/i18n/locales/ru.json", "src/i18n/locales/zh-CN.json", "src/i18n/locales/zh-Hant.json" @@ -1278,6 +1296,7 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", + "src/i18n/locales/it.json", "src/i18n/locales/ru.json", "src/i18n/locales/zh-CN.json", "src/i18n/locales/zh-Hant.json", @@ -1315,6 +1334,7 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", + "src/i18n/locales/it.json", "src/i18n/locales/ru.json", "src/i18n/locales/zh-CN.json", "src/i18n/locales/zh-Hant.json", @@ -1366,6 +1386,7 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", + "src/i18n/locales/it.json", "src/i18n/locales/ru.json", "src/i18n/locales/zh-CN.json", "src/i18n/locales/zh-Hant.json" @@ -1396,6 +1417,7 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", + "src/i18n/locales/it.json", "src/i18n/locales/ru.json", "src/i18n/locales/zh-CN.json", "src/i18n/locales/zh-Hant.json" @@ -1445,6 +1467,7 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", + "src/i18n/locales/it.json", "src/i18n/locales/ru.json", "src/i18n/locales/zh-CN.json", "src/i18n/locales/zh-Hant.json" @@ -1509,6 +1532,7 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", + "src/i18n/locales/it.json", "src/i18n/locales/ru.json", "src/i18n/locales/zh-CN.json", "src/i18n/locales/zh-Hant.json", @@ -1539,6 +1563,7 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", + "src/i18n/locales/it.json", "src/i18n/locales/ru.json", "src/i18n/locales/zh-CN.json", "src/i18n/locales/zh-Hant.json" @@ -1580,6 +1605,7 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", + "src/i18n/locales/it.json", "src/i18n/locales/ru.json", "src/i18n/locales/zh-CN.json", "src/i18n/locales/zh-Hant.json" @@ -1603,6 +1629,7 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", + "src/i18n/locales/it.json", "src/i18n/locales/ru.json", "src/i18n/locales/zh-CN.json", "src/i18n/locales/zh-Hant.json" @@ -1660,6 +1687,7 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", + "src/i18n/locales/it.json", "src/i18n/locales/ru.json", "src/i18n/locales/zh-CN.json", "src/i18n/locales/zh-Hant.json" @@ -1723,6 +1751,7 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", + "src/i18n/locales/it.json", "src/i18n/locales/ru.json", "src/i18n/locales/zh-CN.json", "src/i18n/locales/zh-Hant.json", @@ -2606,6 +2635,7 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", + "src/i18n/locales/it.json", "src/i18n/locales/ru.json", "src/i18n/locales/zh-CN.json", "src/i18n/locales/zh-Hant.json", @@ -3191,6 +3221,7 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", + "src/i18n/locales/it.json", "src/i18n/locales/ru.json", "src/i18n/locales/zh-CN.json", "src/i18n/locales/zh-Hant.json", @@ -3302,6 +3333,7 @@ "src/data/cameraArrival.js", "src/i18n/locale.js", "src/i18n/locales/es.json", + "src/i18n/locales/it.json", "src/i18n/locales/ru.json", "src/i18n/locales/zh-CN.json", "src/i18n/locales/zh-Hant.json", @@ -3326,6 +3358,7 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", + "src/i18n/locales/it.json", "src/i18n/locales/ru.json", "src/i18n/locales/zh-CN.json", "src/i18n/locales/zh-Hant.json" @@ -3363,6 +3396,7 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", + "src/i18n/locales/it.json", "src/i18n/locales/ru.json", "src/i18n/locales/zh-CN.json", "src/i18n/locales/zh-Hant.json", @@ -3390,6 +3424,7 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", + "src/i18n/locales/it.json", "src/i18n/locales/ru.json", "src/i18n/locales/zh-CN.json", "src/i18n/locales/zh-Hant.json" @@ -3482,6 +3517,7 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", + "src/i18n/locales/it.json", "src/i18n/locales/ru.json", "src/i18n/locales/zh-CN.json", "src/i18n/locales/zh-Hant.json", @@ -3538,6 +3574,7 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", + "src/i18n/locales/it.json", "src/i18n/locales/ru.json", "src/i18n/locales/zh-CN.json", "src/i18n/locales/zh-Hant.json", @@ -3793,6 +3830,7 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", + "src/i18n/locales/it.json", "src/i18n/locales/ru.json", "src/i18n/locales/zh-CN.json", "src/i18n/locales/zh-Hant.json" @@ -3837,6 +3875,7 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", + "src/i18n/locales/it.json", "src/i18n/locales/ru.json", "src/i18n/locales/zh-CN.json", "src/i18n/locales/zh-Hant.json" @@ -3891,6 +3930,7 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", + "src/i18n/locales/it.json", "src/i18n/locales/ru.json", "src/i18n/locales/zh-CN.json", "src/i18n/locales/zh-Hant.json" @@ -3908,6 +3948,7 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", + "src/i18n/locales/it.json", "src/i18n/locales/ru.json", "src/i18n/locales/zh-CN.json", "src/i18n/locales/zh-Hant.json", @@ -3968,6 +4009,7 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", + "src/i18n/locales/it.json", "src/i18n/locales/ru.json", "src/i18n/locales/zh-CN.json", "src/i18n/locales/zh-Hant.json", @@ -4165,6 +4207,7 @@ "src/i18n/index.js", "src/i18n/locale.js", "src/i18n/locales/es.json", + "src/i18n/locales/it.json", "src/i18n/locales/ru.json", "src/i18n/locales/zh-CN.json", "src/i18n/locales/zh-Hant.json", diff --git a/src/i18n/i18n.test.mjs b/src/i18n/i18n.test.mjs index 1b421f2ff..ae491dc93 100644 --- a/src/i18n/i18n.test.mjs +++ b/src/i18n/i18n.test.mjs @@ -35,6 +35,8 @@ test('normalizeLocale folds regional variants and rejects unshipped tags', () => assert.equal(normalizeLocale(' RU '), 'ru'); assert.equal(normalizeLocale('en_GB'), 'en'); assert.equal(normalizeLocale('xx'), null); + assert.equal(normalizeLocale('it-IT'), 'it'); + assert.equal(normalizeLocale('it_CH'), 'it'); // Explicit script tags take precedence over regional defaults. assert.equal(normalizeLocale('zh'), 'zh-CN'); assert.equal(normalizeLocale('zh-CN'), 'zh-CN'); diff --git a/src/i18n/index.js b/src/i18n/index.js index 6fc4becc9..e7954ed2a 100644 --- a/src/i18n/index.js +++ b/src/i18n/index.js @@ -17,11 +17,18 @@ import { writeStoredLocale, } from './locale.js'; import es from './locales/es.json' with { type: 'json' }; +import it from './locales/it.json' with { type: 'json' }; import ru from './locales/ru.json' with { type: 'json' }; import zhCN from './locales/zh-CN.json' with { type: 'json' }; import zhHant from './locales/zh-Hant.json' with { type: 'json' }; -const CATALOGS = Object.freeze({ es, ru, 'zh-CN': zhCN, 'zh-Hant': zhHant }); +const CATALOGS = Object.freeze({ + es, + it, + ru, + 'zh-CN': zhCN, + 'zh-Hant': zhHant, +}); let currentLocale = DEFAULT_LOCALE; diff --git a/src/i18n/locale.js b/src/i18n/locale.js index 55ed71163..fda8dacd3 100644 --- a/src/i18n/locale.js +++ b/src/i18n/locale.js @@ -8,12 +8,20 @@ export const LOCALE_STORAGE_KEY = 'gev:locale:v1'; export const DEFAULT_LOCALE = 'en'; /** Shipped locales; order does not matter. Adding one: docs/TRANSLATORS.md. */ -export const LOCALES = Object.freeze(['en', 'es', 'ru', 'zh-CN', 'zh-Hant']); +export const LOCALES = Object.freeze([ + 'en', + 'es', + 'it', + 'ru', + 'zh-CN', + 'zh-Hant', +]); /** Each locale named in its own language, for the selector. */ export const LOCALE_NAMES = Object.freeze({ en: 'English', es: 'Español', + it: 'Italiano', ru: 'Русский', 'zh-CN': '中文(简体)', 'zh-Hant': '中文(繁體)', diff --git a/src/ui/localePicker.test.mjs b/src/ui/localePicker.test.mjs index 057642827..47062f08c 100644 --- a/src/ui/localePicker.test.mjs +++ b/src/ui/localePicker.test.mjs @@ -40,7 +40,7 @@ test('the current language comes first, the rest by their own names', () => { nativeNames: LOCALE_NAMES, }), ), - ['ru', 'en', 'es', 'zh-CN', 'zh-Hant'], + ['ru', 'en', 'es', 'it', 'zh-CN', 'zh-Hant'], ); assert.deepEqual( codes( @@ -50,7 +50,7 @@ test('the current language comes first, the rest by their own names', () => { nativeNames: LOCALE_NAMES, }), ), - ['es', 'en', 'ru', 'zh-CN', 'zh-Hant'], + ['es', 'en', 'it', 'ru', 'zh-CN', 'zh-Hant'], ); assert.deepEqual( codes( @@ -60,7 +60,7 @@ test('the current language comes first, the rest by their own names', () => { nativeNames: LOCALE_NAMES, }), ), - ['en', 'es', 'ru', 'zh-CN', 'zh-Hant'], + ['en', 'es', 'it', 'ru', 'zh-CN', 'zh-Hant'], ); const rows = buildLocaleOptions({ locales: MANY,