Files
gods-eye-view/docs/TRANSLATORS.md
T
German Khokhlov 5d28d614e0 i18n: generate zh-Hant from zh-CN
zh-Hant is the zh-CN text in Traditional characters, but contributors
still translated it by hand from `--missing zh-Hant`, and a model asked
for Traditional Chinese writes Taiwan vocabulary (影片, 伺服器, 載入).

`npm run i18n -- --zh-hant` now rewrites zh-Hant.json from zh-CN.json
with OpenCC s2tw (opencc-js 1.4.2, cn -> tw, characters only), then the
fixed list of characters it picks wrongly. The check that `npm test`
runs fails when zh-Hant differs from that conversion, naming the key,
and `--missing zh-Hant` points to the command instead of printing a
template.

The conversion reproduces every one of the 3346 entries on main. 25
keys were out of zh-CN order and move; no value changes.

The converter loads on first use, so a copy of the script without
node_modules (stale-index.test.mjs) still extracts messages.
2026-11-13 18:46:27 +04:00

5.4 KiB
Raw Blame History

Translating God's Eye View

Shipped languages: English (source and fallback), Arabic (ar), German (de), Spanish (es), French (fr), Hebrew (he), Italian (it), Japanese (ja), Brazilian Portuguese (pt), Russian (ru), Swedish (sv), Ukrainian (uk), Simplified Chinese (zh-CN) and Traditional Chinese (zh-Hant).

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 picker (the globe button in the bottom-right corner), then the browser languages, then English. ?lang= is not stored, and copied share links leave it out. Switching applies at once, without a reload.

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. It fails on a missing, empty or unused entry, on placeholders that differ from the English, on a missing plural form, and on text or plural forms still in English. The --missing template fails until every entry is translated. If an entry is right in English (a code such as CRT, a product name), 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.

Do not translate zh-Hant: it is generated. Translate zh-CN, then run npm run i18n -- --zh-hant (see zh-Hant.md).

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

  1. Create src/i18n/locales/<code>.json, fill it from npm run i18n -- --missing <code> (start from {}), and translate every entry.
  2. Import it in src/i18n/index.js and add it to CATALOGS.
  3. Add the code to LOCALES and its own-language name to LOCALE_NAMES in src/i18n/locale.js. Right-to-left languages also go in RTL_LOCALES; the chrome mirrors through logical CSS properties (inset-inline-*, margin-inline-*), and src/ui/styles/rtl.css holds what those cannot express (see the Arabic notes). If the language should keep Western digits, map it in INTL_TAGS in locale.js.
  4. Add src/i18n/locales/<code>.json next to ru.json in scripts/package-boundaries.json and in scripts/format-scope.json.
  5. Write docs/translations/<code>.md: glossary, plural forms, units and style (copy the structure of ru.md).
  6. If the language is not written in Latin letters, also add it to NON_LATIN_LOCALES in src/hudSummaryResponse.js — that guard falls back to the translated text when a summary provider answers in English.
  7. Run npm run i18n and npm 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: Arabic, German, Spanish, French, Hebrew, Italian, Japanese, Brazilian Portuguese, Russian, Swedish, Ukrainian, Simplified Chinese, Traditional Chinese. Read it before translating, and add a term there when you settle on one.