Skip to content

Latest commit

 

History

History
166 lines (131 loc) · 7.18 KB

File metadata and controls

166 lines (131 loc) · 7.18 KB

Internationalisation (i18n) pipeline

The FS25 Farm Dashboard ships with 27 user-selectable languages. This document describes how the catalog is structured, how to edit a translation, and how to refill missing strings through Google Cloud Translation v3.

Locales

en  (source)
de fr es it pl nl pt sv da fi cs el hu ro bg hr sk sl
et lv lt ga mt is nb uk

These appear in both the in-app Settings → Language picker (web/assests/js/i18n/i18n.js) and the first-run setup wizard (web/assests/js/setup-i18n.js).

Source layout

web/locales/
  messages/
    en.json              ← single source of truth (every key the app uses)
    de.json … uk.json    ← per-locale overrides; only keys that differ from en
    .mt-cache.json       ← MT cache, sha1(en)|lang → translation, gitignored
  audit-keys.mjs         ← npm run i18n:audit
  find-hardcoded-strings.mjs ← npm run i18n:scan
  build-translations.mjs ← npm run i18n:build  (writes translations.json)
  mt-fill.mjs            ← npm run i18n:fill   (calls Google Translate)
  verify-i18n.mjs        ← npm run i18n:verify (coverage gate)
  sync-keys-from-en.mjs  ← npm run i18n:sync (copy missing keys from en.json into every locale — English fallback until hand-translated)
  translations.json      ← compiled catalog, what `i18n/i18n.js` + `setup-i18n.js` load

translations.json is generated; never edit it by hand. Every change goes into messages/en.json (for new English copy) or messages/<code>.json (for a hand-corrected translation).

Working with a single string

  1. Add or change English copy in messages/en.json.
    • Use {{name}} for runtime placeholders. They survive translation as long as they keep the {{ … }} form; the MT script wraps them in <span translate="no">…</span> before calling Google.
  2. Reference it from code:
    • In dashboard JS: import { t } from "./i18n/i18n.js" and call t("namespace.key", { name: value }).
    • In dashboard HTML: <span data-i18n="namespace.key"></span> (or data-i18n-attr="placeholder" for input placeholders, etc.).
    • In setup.html: setupT("namespace.key", { name: value }) or data-setup-i18n="namespace.key".
  3. Rebuild with npm run i18n:build to refresh translations.json.
  4. If you added many keys to en.json, run npm run i18n:sync so every messages/<lang>.json gets the new keys (copied from English), then npm run i18n:verify must pass before merge.
  5. Refill non-English locales with npm run i18n:fill (Google Translate API — optional polish after sync).

Hand-correcting a locale

Just edit messages/<code>.json and add or replace keys. The build script validates that:

  • every key in a locale file also exists in en.json,
  • values are non-empty strings,
  • the set of {{placeholder}} tokens matches en.json.

A mismatch fails npm run i18n:build with an actionable error. npm run i18n:sync copies missing keys from en.json into each locale so npm run i18n:verify can pass in CI; replace copied English with a proper translation when you have one.

Machine-fill with Google Cloud Translation v3

mt-fill.mjs walks each locale, identifies keys where the translation is missing, empty, or identical to English, and translates them via Google's REST API in HTML mode (so inline <strong>, <code>, <br> are preserved).

Placeholders are protected by substituting {{token}} with XPH<n>X sentinels before sending and restoring them after. This is more reliable than wrapping in <span translate="no"> because Google occasionally collapses short trailing words next to a span (we have observed {{count}} animal becoming just {{count}} for some locales when wrapped in span). The sentinel scheme keeps context intact.

Authentication

Pick one:

  • Service account (preferred for v3) — set GOOGLE_APPLICATION_CREDENTIALS=/abs/path/to/sa.json. The script signs a short-lived JWT and exchanges it for an OAuth2 token automatically and uses Translation v3 (projects/<id>/locations/global:translateText). The service account needs the Cloud Translation API User role (roles/cloudtranslate.user) on the GCP project.
  • API key (uses v2) — set GOOGLE_TRANSLATE_API_KEY=AIza…. Translation v3 does not accept API keys for translateText, so the script transparently routes API-key auth through Translation v2 (/language/translate/v2), which fully supports HTML mode and is functionally equivalent for our use case. Restrict the key to the Cloud Translation API in the GCP console.

Never commit either credential. They are read at runtime from the environment only.

Commands

# Fill only what is missing across every locale (cheap to re-run, cached)
npm run i18n:fill

# Force-re-translate every key (uses cache for repeats; expensive without cache)
npm run i18n:fill:force

# Limit scope while testing
node web/locales/mt-fill.mjs --langs de,fr --limit 20

# Show the work plan without calling the API
node web/locales/mt-fill.mjs --dry-run

After a fill, run:

npm run i18n:build      # regenerate translations.json
npm run i18n:verify     # report per-locale coverage; non-zero exit if gaps

Cache

Successful translations are stored in messages/.mt-cache.json keyed by sha1(srcString)|targetLang. Re-running npm run i18n:fill after no English changes is essentially free; deleting the cache and re-running will re-call Google for every string.

Validation safety net

  • Placeholder tokens are checked round-trip; entries that lost a {{token}} during translation are dropped (logged as dropped "<key>" — placeholder drift) and fall back to English instead of being saved.
  • Empty translations are dropped.
  • Lost-content guard: if the non-placeholder text in a translation is more than 75 % shorter than the source's non-placeholder text (e.g. Google swallowed a trailing noun), the entry is dropped with lost meaningful content next to placeholder so it can be re-tried.
  • npm run i18n:verify exits non-zero if any locale is below 100 % coverage or contains placeholder drift, making it suitable for CI.

Cost expectation

The catalog is currently around 600 keys; with 26 non-English locales that is roughly 15 600 strings on a first run. Google Cloud Translation v3 charges per character and a one-off fill of the entire catalog should land well under $10 USD on standard pricing. The cache eliminates repeat cost.

Troubleshooting

  • [mt-fill] OAuth token exchange failed — the service account JSON is malformed or private_key is missing newlines. Re-download from the GCP console.
  • Translate API error 403 — the Translation API is disabled for the project, or the service account lacks roles/cloudtranslate.user.
  • Translate API error 429 — quota hit. The script retries with exponential backoff up to 5 attempts; rerun later if it still fails.
  • placeholder drift warnings — Google moved or escaped a {{token}}. Edit messages/<code>.json by hand for those keys; the rest of the locale is unaffected.
  • npm run i18n:build complains about an unknown key — the locale file contains a key that no longer exists in en.json. Remove it (or rename the English key first).