Skip to content

i18n: add Persian and Arabic, with RTL layout support - #1646

Open
andreasfoo wants to merge 3 commits into
mainfrom
claude/i18n-fa-ar-rtl-nobgmh
Open

andreasfoo wants to merge 3 commits into
mainfrom
claude/i18n-fa-ar-rtl-nobgmh

Conversation

@andreasfoo

@andreasfoo andreasfoo commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

Summary

Stacked on #1648. Adds Persian and Arabic — both right-to-left, which the UI had no support for at all, so this is more than two more locale files.

Key Changes

  • Two RTL locales: full fa.ts and ar.ts covering every key en.ts and zh.ts define; Arabic carries all six plural categories, with its one/two forms using the word rather than the numeral.
  • RTL layout: SUPPORTED_LANGUAGES gains dir, driving theme.direction, documentElement.dir/lang, and an emotion cache that mirrors the app's existing physical CSS instead of rewriting it by hand.
  • Machine text stays LTR: code, env var names, URLs and log output opt out of mirroring, so bidi cannot reorder http://host:3000/ into /http://host:3000.
  • Lazy locale chunks: only English ships eagerly, so the always-preloaded i18n chunk drops from 325 kB to 126 kB (99 → 41 kB gzip) despite two more languages.

Notes

  • Charts (recharts) do not mirror — a time series still reads left-to-right, which is conventional in RTL locales. Tables, cards and navigation do mirror.
  • resolve.dedupe: ['stylis'] in both vite configs is load-bearing: the dev dep-optimizer otherwise pre-bundled two copies and every RTL page threw. Production builds were never affected.
  • Review after i18n: add Russian, and backfill every t() key into the locale bundles #1648 merges, or read the diff against that branch — the base is set accordingly.

Adds Russian as a third UI language alongside English and Chinese.

- New `i18n/locales/ru.ts` covering every key in en.ts (the fallback
  bundle) plus the remoteControl / bots / notify namespaces that only
  zh.ts overrides, so Russian is not worse off than Chinese on the
  Remote Control surfaces. Plural keys carry the four Russian
  Intl.PluralRules categories (_one/_few/_many/_other).
- `i18n/index.ts` now exports SUPPORTED_LANGUAGES / AppLanguage /
  resolveLanguage as the single source of truth. The ActivityBar menu
  and badge, the System settings chips and the guide dialog toggle all
  render from that list instead of hard-coding language codes, so a
  fourth language is one row plus a locale file.
- resolveLanguage() replaces the `=== 'zh' ? 'zh' : 'en'` checks, which
  silently mapped every non-Chinese tag to English. Used by the MUI
  locale bundle (ruRU added), the token heatmap's date locale, and the
  co-located Record<AppLanguage, ...> copy in AgentNode, the Claude Code
  quick-config / config modal / profile overrides, and the Codex and dsh
  quick-config panels — all of which gained Russian entries.
- Russian nav and field labels are deliberately shorter than a literal
  translation where the ActivityBar rail (~11 chars) or the 180px
  config label column would otherwise ellipsise them; English fits those
  widths and Russian did not.
- New localeParity test asserts ru covers every key in en and in zh, so
  a future key added to en.ts cannot silently drop Russian back to
  English.

Verified: typecheck unchanged at 35 pre-existing errors (none in touched
files), lint clean, production build passes, and the test suite is back
to its 3 pre-existing failures with the new parity test green.
Screenshots of the Russian UI checked via the ui-preview skill.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018CAigqrGygAebgepPY2kjL
claude added 2 commits August 26, 2026 09:45
The codebase's inline-English pattern — t('some.key', { defaultValue: '…' })
— lets a key be referenced by the UI without existing in any locale file.
English still renders, so the gap is invisible in development, and 326 keys
had drifted out that way: the Bots, IM Notify and Remote Control pages were
largely English even in Chinese.

- 130 keys existed in no bundle at all: added to en, zh and ru.
- 196 more existed in zh/ru but not en; added to en so the fallback bundle
  is complete and the coverage guard below can be enforced.
- Two of those pass `count`, so they are registered as en plurals
  (bots.overview.subtitle, remoteControl.bots.configuredCount).

Also internationalizes four strings hardcoded in JSX: the ActivityBar's
Tools and Servertool nav labels, the Claude Code "Default Mode" row label,
and the config modal's preview Close button. ChatNode's Direct/Group chips
now use the notify.target.* keys its sibling NotifyTestDialog already used.

New tKeyCoverage test walks the sources via import.meta.glob, collects every
literal key passed to t(), and asserts all bundles define it — verified to
fail when a key is removed. Two Russian nav labels are shortened because the
ActivityBar rail clips at ~11 characters where English fits.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018CAigqrGygAebgepPY2kjL
Both are right-to-left, so this is more than two more locale files.

Locales
- New fa.ts and ar.ts covering every key en.ts and zh.ts define. Arabic
  plural keys carry all six Intl.PluralRules categories; its `one`/`two`
  forms use the word rather than the numeral ("حساب واحد", "حسابان"),
  which is why they omit {{count}}.
- fa/ar entries added to the co-located Record<AppLanguage, ...> copy in
  AgentNode, the Claude Code quick-config / config modal / profile
  overrides, and the Codex and dsh quick-config panels.

RTL
- SUPPORTED_LANGUAGES gains a `dir` field, the single source the rest
  reads from. It drives theme.direction (MUI's own RtlProvider handling
  for Drawer, Menu, Popper, Slider) and a new DirectionProvider that sets
  documentElement dir/lang and swaps in an emotion cache running
  stylis-plugin-rtl, mirroring the physical CSS the app already writes
  (`sx={{ ml: 2 }}` and friends) instead of rewriting it by hand. LTR
  keeps emotion's default cache, so the existing languages render through
  exactly the path they always did.
- Code, machine identifiers, URLs and log output stay LTR: a global rule
  in index.css pins code/pre/samp/kbd and anything tagged `data-ltr`, and
  the shared copyable-text and env-var-chip styles opt in directly.
  Without it bidi reorders the punctuation around a Latin run —
  "http://host:3000/" renders as "/http://host:3000".
- vite resolve.dedupe + optimizeDeps keep stylis to one instance. The dev
  dep-optimizer otherwise pre-bundled it twice, and emotion handed
  elements built by one copy to middleware from the other, throwing
  "Cannot read properties of undefined (reading 'push')" on every page.
  Production (rollup) was never affected.

Bundle
- Locale bundles become dynamic imports registered via addResourceBundle;
  only English, the fallback, ships eagerly. main.tsx awaits the detected
  locale before first paint so nothing flashes through English, and the
  switchers go through one setAppLanguage helper that loads the chunk
  before changing language. The always-preloaded i18n chunk drops from
  325 kB / 99 kB gzip on the base branch to 126 kB / 41 kB, despite two
  more languages.

Verified: typecheck unchanged at 35 pre-existing errors, lint clean, build
passes, tests back to their 3 pre-existing failures with the extended
parity and coverage guards green. All five languages render and switch
live; Persian and Arabic checked visually across five pages.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018CAigqrGygAebgepPY2kjL
@andreasfoo
andreasfoo force-pushed the claude/i18n-russian-support-nobgmh branch from 2dcb9a9 to dceb3f0 Compare August 26, 2026 09:46
@andreasfoo
andreasfoo force-pushed the claude/i18n-fa-ar-rtl-nobgmh branch from 45e162e to d57305d Compare August 26, 2026 09:46
FFengIll pushed a commit that referenced this pull request Aug 26, 2026
…#1648)

## Summary

Tingly Box shipped English and Chinese only; this adds Russian. Doing so
surfaced a second problem: 326 keys the UI references were never in any
locale file, so whole pages rendered English even in Chinese.

Implement #1641 .

## Key Changes

- **Russian locale**: full `ru.ts` covering every key `en.ts` and
`zh.ts` define, with all four Russian plural categories on the plural
keys.
- **One language source of truth**: `SUPPORTED_LANGUAGES` now feeds the
switchers, the MUI locale bundle and the heatmap's date locale,
replacing scattered `=== 'zh'` checks that mapped every other tag to
English.
- **Locale coverage backfill**: 326 keys existed only as inline `t(…, {
defaultValue })`, leaving Bots / IM Notify / Remote Control largely
English in every language — 130 were in no bundle at all, 196 were
missing from `en` itself.
- **Four hardcoded strings**: the Tools and Servertool nav labels, the
Claude Code "Default Mode" row, and the config modal's Close button now
go through `t()`.
- **Two test guards**: `localeParity` and `tKeyCoverage` fail the build
when a key is missing from a bundle, instead of silently rendering
English.

## Notes

- Two Russian nav labels use shorter synonyms than a literal
translation: the ActivityBar rail clips at ~11 characters, which English
fits and Russian did not.
- ~430 English literals remain hardcoded in JSX across 93 files;
deliberately out of scope, and `tKeyCoverage` cannot see them since they
never reach `t()`.
- Persian and Arabic, with the RTL layout work they need, are stacked on
this branch in #1646.
- Supersedes #1643, which could not be reopened after the branch was
split.

---
_Generated by [Claude
Code](https://claude.ai/code/session_018CAigqrGygAebgepPY2kjL)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
Base automatically changed from claude/i18n-russian-support-nobgmh to main August 26, 2026 10:11
FFengIll pushed a commit that referenced this pull request Sep 19, 2026
…#1648)

## Summary

Tingly Box shipped English and Chinese only; this adds Russian. Doing so
surfaced a second problem: 326 keys the UI references were never in any
locale file, so whole pages rendered English even in Chinese.

Implement #1641 .

## Key Changes

- **Russian locale**: full `ru.ts` covering every key `en.ts` and
`zh.ts` define, with all four Russian plural categories on the plural
keys.
- **One language source of truth**: `SUPPORTED_LANGUAGES` now feeds the
switchers, the MUI locale bundle and the heatmap's date locale,
replacing scattered `=== 'zh'` checks that mapped every other tag to
English.
- **Locale coverage backfill**: 326 keys existed only as inline `t(…, {
defaultValue })`, leaving Bots / IM Notify / Remote Control largely
English in every language — 130 were in no bundle at all, 196 were
missing from `en` itself.
- **Four hardcoded strings**: the Tools and Servertool nav labels, the
Claude Code "Default Mode" row, and the config modal's Close button now
go through `t()`.
- **Two test guards**: `localeParity` and `tKeyCoverage` fail the build
when a key is missing from a bundle, instead of silently rendering
English.

## Notes

- Two Russian nav labels use shorter synonyms than a literal
translation: the ActivityBar rail clips at ~11 characters, which English
fits and Russian did not.
- ~430 English literals remain hardcoded in JSX across 93 files;
deliberately out of scope, and `tKeyCoverage` cannot see them since they
never reach `t()`.
- Persian and Arabic, with the RTL layout work they need, are stacked on
this branch in #1646.
- Supersedes #1643, which could not be reopened after the branch was
split.

---
_Generated by [Claude
Code](https://claude.ai/code/session_018CAigqrGygAebgepPY2kjL)_

---------

Co-authored-by: Claude <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.

2 participants