Fixes the 51 P0–P2 findings from the full review of the i18n branch and the issues found while checking every new string on screen in ru and es. - Translate errors and statuses that still reached ru/es in English: feed and tile errors, provider statuses, the ALPR chip, the traffic mode chip, voice plan step labels, fire feed values, receiver bands, scene save reasons and the Cesium attribution link. - Show preset city names in the UI language (src/ui/placeNames.js); data and everything sent to the model stay English. - Share layer names between the layer panel, HUD and voice card (src/ui/layerNames.js). - Keep long ru/es strings whole: voice card error lines, analyst titles, the voice tier status, the Nepal evidence button, the ALPR retry chip. English layout is unchanged. - Ask before a language switch drops unsaved work. - Catalog fixes: glossary terms (пуск, пресет), gender agreement and consistent "peticiones" in es, fewer calques in both languages. - Fix English text that was itself wrong (e.g. "Exit cockpit to fly to a weather", raw tool ids in voice plan steps). Catalogs: es/ru 2535 messages each, complete. Co-Authored-By: Claude Opus 5.5 <[email protected]>
4.3 KiB
Translating God's Eye View
Shipped languages: English (source and fallback), Spanish (es) and Russian (ru).
How it works
The English text is the key: code calls t('Save keys'), and markup marks elements with data-i18n. How developers mark text is in I18N.md.
Every other language is one flat JSON file, src/i18n/locales/<code>.json, keyed by the English text:
{
"Save keys": "Сохранить ключи",
"{count} cameras": {
"one": "{count} камера",
"few": "{count} камеры",
"many": "{count} камер",
"other": "{count} камеры"
}
}
A tn() entry is keyed by the English plural form and holds one string per Intl.PluralRules category of that language. A missing entry falls back to English.
The page picks its language from ?lang=, then the choice stored by the language switch in the Visual Presets dock, then the browser languages, then English. ?lang= is not stored, and copied share links leave it out. Switching reloads the page.
The check
npm run i18n # what is missing, unused or broken, per language
npm run i18n -- --missing ru # missing ru entries as JSON, ready to translate
npm run i18n -- --prune # drop entries the code no longer uses
npm test runs the same check (src/i18n/catalog.test.mjs). It fails when a translation is missing or empty, when an entry is no longer used, when placeholders differ from the English, when a plural entry lacks a form, or when a translation, or any form of a plural entry, is still the English text. The --missing template is not a translation: its strings are empty and its plural forms are the English, so the check fails until every one is translated. If an entry is right as it is in English (a code such as HDG, a product name, a word spelled the same in your language), add it under your language code in src/i18n/same-as-english.json.
Changing English text changes its key: the old translation becomes unused and the new text shows as missing. Translate it and prune.
Fixing a translation
Edit src/i18n/locales/<code>.json and run npm run i18n. To see your change, open the app with ?lang=<code>.
Adding a language
- Create
src/i18n/locales/<code>.json, fill it fromnpm run i18n -- --missing <code>(start from{}), and translate every entry. - Import it in
src/i18n/index.jsand add it toCATALOGS. - Add the code to
LOCALESand its own-language name toLOCALE_NAMESinsrc/i18n/locale.js. Right-to-left languages also go inRTL_LOCALES. - Add
src/i18n/locales/<code>.jsonnext toru.jsoninscripts/package-boundaries.json. - Write
docs/translations/<code>.md: glossary, plural forms, units and style (copy the structure ofru.md). - Run
npm run i18nandnpm test, then check the panels in the app at narrow widths.
Rules
- Keep every
{placeholder}name exactly as in the English: do not rename, drop or add one. Its position inside the sentence follows the grammar of your language, e.g."{accepted} of {raw} records accepted"→"принято записей: {accepted} из {raw}". - UPPERCASE English stays UPPERCASE. Short labels, chips and HUD readouts should stay short; prefer an abbreviation over a wrapped line.
- Keep the separators
·,—,→,↗,…. - Leave product, provider and data names in their original form: Google Maps, Cesium ion, OpenAI, ChatGPT, NASA FIRMS, USGS, OpenSky, ADS-B, AIS, CCTV, HUD, TomTom, OpenStreetMap, Mapillary, and keyboard keys (Space, Esc, H, 1–9).
- Units follow the language's convention; see the language notes.
Never translated
These are contracts, not copy: layer ids and registry keys, status enums and other machine values (translate only the label shown for them), share-link and hash parameters, ?lang=, storage schemas, API fields, voice tool names and schemas, provider and dataset names, data from feeds (callsigns, vessel, satellite, station and place names, headlines), attribution, console diagnostics, and user-authored content.
Language notes
Each language keeps its glossary, plural forms, units and style in its own file, docs/translations/<code>.md: Spanish, Russian. Read it before translating, and add a term there when you settle on one.