Skip to content

feat(i18n): localize the extension UI in 18 languages (V2-537)#8

Open
Nic-dorman wants to merge 6 commits into
mainfrom
feat/i18n-v2-537
Open

feat(i18n): localize the extension UI in 18 languages (V2-537)#8
Nic-dorman wants to merge 6 commits into
mainfrom
feat/i18n-v2-537

Conversation

@Nic-dorman

Copy link
Copy Markdown
Contributor

What

Adds multi-language support to ant-webex. Previously the extension was English-only, with every user-facing string hardcoded in the popup/onboarding HTML and TS. Now the UI localizes into 18 languages, with a user-selectable language picker and RTL support.

Closes V2-537.

Approach — Hybrid (runtime dictionary + native _locales)

The extension is dependency-free, so this is a ~130-line hand-rolled runtime, not a framework:

  • Runtime dictionary (src/i18n/index.ts + src/i18n/locales/<lang>.json) powers all in-UI strings — t() with {name} interpolation, _one/_many plurals, and fallback (active locale → English → key).
  • Native _locales/__MSG__ localizes the one thing a runtime dictionary can't: the store-listing description (Chrome Web Store / Firefox AMO), read from the manifest before any JS runs. The product name "Autonomi" stays untranslated.
  • Language picker in popup Settings (System default + all 18 in native script), persisted to chrome.storage.local — independent of the browser UI language.

Languages

en (source) + ja ko nl fr bg es ar he ru uk zh-CN zh-TW pt-BR tr vi id de.

The 17 non-English locales ship as machine-translated baselines, each flagged with _translator_notes for native-speaker polish (see CONTRIBUTING-i18n.md). Shared UI atoms (Download, Save, Settings, Connected, Downloads, …) are copied verbatim from the sibling ant-ui desktop app so the two products read identically.

RTL

Arabic + Hebrew ship with <html dir="rtl"> wiring and the popup's physical margin-left/right converted to logical properties. (One decorative progress-shimmer keyframe is a noted minor follow-up.)

Notable

  • Per-OS "how to open a terminal" copy moved out of constants.ts into the catalog (it's user-facing prose).
  • Content-script strings are bundled as a small subset (regenerated from the locale files on every build) — it injects into arbitrary pages and can't fetch packaged assets without widening web_accessible_resources. Content-script labels follow the browser UI language.
  • Backend/daemon error detail stays English (leading label localized) — matches the same carve-out ant-ui tracks; documented in CONTRIBUTING-i18n.md.

Verification

  • npm run typecheck — clean
  • npm run build:all — Chrome + Firefox dists build
  • Structural validation 18/18 PASS — key parity with en.json, all {placeholder} tokens preserved, shared atoms verbatim, native descriptions present
  • web-ext lint on the Firefox dist: 0 errors (confirms __MSG__/_locales resolve). The 2 Chrome-dist lint errors are pre-existing (verified identical on base v0.1.2; web-ext is a Firefox tool flagging the intentional service_worker) and unrelated to this change.

For reviewers / testers

  • Load dist/ (Chrome) or dist-firefox/ (Firefox) as an unpacked extension.
  • Switch languages via Settings → Language, or change the browser UI language.
  • Try Arabic/Hebrew to confirm RTL mirrors.
  • Native speakers: locale files with _translator_notes are open for polish — see CONTRIBUTING-i18n.md.

🤖 Generated with Claude Code

Add multi-language support to ant-webex. The extension was previously
English-only with every string hardcoded in the popup/onboarding HTML and TS.

Approach: Hybrid — a hand-rolled runtime dictionary (no framework; the
extension stays dependency-free) for all in-UI strings, plus native
`_locales`/`__MSG__` for the one thing a runtime dictionary can't localize:
the store-listing description (Chrome Web Store / Firefox AMO). The product
name "Autonomi" stays untranslated.

- src/i18n/index.ts: t() with {name} interpolation + _one/_many plurals,
  locale resolution (chrome.storage override -> navigator.language -> en),
  normalizeLocale / NATIVE_LOCALE_NAMES / RTL_LOCALES ported from ant-ui.
- 18 runtime catalogs (en source of truth; 17 machine-translated baselines
  flagged _translator_notes for native-speaker review). Shared UI atoms are
  copied verbatim from the ant-ui desktop app so the two read identically.
- Popup + onboarding marked with data-i18n*; dynamic strings routed through
  t()/tc(). Per-OS terminal copy moved from constants.ts into the catalog.
- Language picker in popup Settings (System default + all 18 in native script).
- Content script: bundled string subset (follows browser UI language),
  regenerated from the locale files on every build.
- RTL: <html dir="rtl"> for ar/he + logical CSS properties.
- build.mjs copies both string stores into dist/ and dist-firefox/.
- CONTRIBUTING-i18n.md for translators.

Verified: typecheck clean; build:all (Chrome + Firefox) green; structural
validation 18/18 (key parity, placeholders, atoms); web-ext lint on the
Firefox dist has 0 errors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
readdirSync order is unspecified and differs across filesystems (macOS vs
Linux/CI), which could make the generated content-locales.json vary between
machines and trip the reproducible-build check. Sort for a stable key order.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Nic-dorman added a commit that referenced this pull request Jul 21, 2026
…source (V2-688) (#10)

On a `v*` tag push (or manual workflow_dispatch), build from a clean checkout and
produce the distributable artifacts, then attach them to the GitHub Release:

- ant-webex-chrome-vX.Y.Z.zip  — CWS upload package (manifest at zip root).
- ant-webex-firefox-vX.Y.Z.xpi — signed, unlisted via `web-ext sign` when the
  AMO API secrets (WEB_EXT_API_KEY/SECRET) are set; installable by testers.
  Falls back to an unsigned .zip (with a warning) when creds are absent, so the
  rest of the release still succeeds.
- ant-webex-source-vX.Y.Z.zip  — `git archive` source tree for AMO's source-code
  submission requirement.

Guards: version must agree across tag / manifest.json / package.json; web-ext
lint runs before signing so a broken build doesn't burn an AMO version number.
Runs in a `release` GitHub Environment so signing can be gated behind a required
reviewer and secrets aren't exposed to fork-PR runs. RELEASING.md documents the
tag flow + one-time AMO credential setup.

Branched off main; independent of the i18n (#8) and CI-hardening (#9) PRs.

Co-authored-by: Nic <nic@nics-mbp.home>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Nic and others added 4 commits July 21, 2026 10:12
The onboarding page's <title> stayed hardcoded "Welcome to Autonomi" in
every locale — the in-body heading was localized (onboarding.welcome_title)
but the browser tab title was not. Mark it data-i18n so
applyStaticTranslations() fills it from the active catalog. Reuses the
existing key: no new strings, key parity preserved across all 18 locales.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…English

normalizeLocale mapped any tag we don't ship verbatim to English. For the two
languages we ship only regionally — Chinese (zh-CN/zh-TW) and Portuguese
(pt-BR) — that silently dropped common real-world tags to English: pt-PT,
zh-HK, and the script subtags zh-Hant/zh-Hans (which some systems send).

Add a regional fallback: pt* → pt-BR; zh* → zh-TW when the tag carries a
Traditional script (Hant) or region (TW/HK/MO), else zh-CN. Exact matches and
base-language folding (fr-CA → fr) are unchanged. Adds index.test.ts covering
the resolution table.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Settings language <select> added for i18n had no style rule, so it
rendered as a white native control against the dark popup. Theme it like the
text inputs (dark fill, slate border), strip the native appearance, and add
an inline-SVG caret plus dark <option> colours. (On macOS the opened menu is
drawn natively and ignores option colours — platform limitation.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The downloads list filtered by `byExtensionId === chrome.runtime.id`, which
is unreliable: it's undefined on Firefox, and if chrome.runtime.id ever reads
falsy the `=== id` test inverts and lets every unowned browser download
through — so the box showed the user's entire download history.

Filter instead to the download ids the background already records for
autonomi:// content (STORAGE_KEYS.DOWNLOADS, address → id) — authoritative and
browser-agnostic. Side-effect: the antd installer download no longer shows in
the box (it has its own progress panel during setup); the box is now purely
autonomi:// content.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant