i18n: add Persian and Arabic, with RTL layout support - #1646
Open
andreasfoo wants to merge 3 commits into
Open
andreasfoo wants to merge 3 commits into
andreasfoo wants to merge 3 commits into
Conversation
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
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
force-pushed
the
claude/i18n-russian-support-nobgmh
branch
from
August 26, 2026 09:46
2dcb9a9 to
dceb3f0
Compare
andreasfoo
force-pushed
the
claude/i18n-fa-ar-rtl-nobgmh
branch
from
August 26, 2026 09:46
45e162e to
d57305d
Compare
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>
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
fa.tsandar.tscovering every keyen.tsandzh.tsdefine; Arabic carries all six plural categories, with itsone/twoforms using the word rather than the numeral.SUPPORTED_LANGUAGESgainsdir, drivingtheme.direction,documentElement.dir/lang, and an emotion cache that mirrors the app's existing physical CSS instead of rewriting it by hand.http://host:3000/into/http://host:3000.Notes
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.