Shell - Product Localization - #4194
Merged
Merged
Conversation
Install react-i18next and supporting plugins, configure i18next with chained localStorage/HTTP backend, lazy loading, and git-hash-based cache busting. Add locale file structure for all 6 supported languages and i18next-parser config for key extraction.
…e (Phase 2) Connect LocaleSwitcher to i18n.changeLanguage(), add updateUser RTK Query mutation to persist locale in user.prefs, and apply DB locale preference on boot via load-instance before first render.
…ration (Phase 3 partial) - Replace hardcoded common strings (Save, Cancel, Edit, etc.) with t() calls across ~73 files - Fix i18next-parser config defaultValue to use function form so locale files get actual English strings - Populate public/locales/*/common.json with correct English values as translation placeholders - Upgrade TypeScript 4.9 → 5.9 to support i18next v26 type definitions (uses const type params) - Add skipLibCheck to tsconfig to suppress remaining third-party type incompatibilities - Add "shell" to i18n ns array in preparation for Phase 3 shell namespace
…ate plan
- Strip temporary { defaultValue: "..." } from all common namespace t() calls
- Update LOCALIZATION_PLAN.md: mark common namespace complete, check off Phase 5 items already done, reflect shell.json placeholder files added
…space - Translate all 9 Tier 1 shell components (ResizeableContainer, InvalidUrl, UserFilter, GlobalDirtyCodeModal, ConfirmDeleteModal, DropdownMenu, Comment/index, Favicon, GlobalDomainsMenu) - Move comment, reply, prod keys from shell to common namespace - Fix module-level CHIP_TITLE constant in GlobalDomainsMenu — replaced with inline t() ternary since module scope can't call t() - Add namespace prefix to existing t() calls across shell and sub-app files - Update locale files for all 6 languages (common + shell namespaces) - Update implementation plan with conventions for functions, object maps, prop-passed strings, and cross-namespace deduplication rules
…ate plan Locale files are confirmed correct — temporary defaultValue scaffolding removed from all 9 Tier 1 shell components. Updated plan to reflect completed status.
…mit link
- Replace slug-to-name conversion in global-menu with a productLabels map
backed by t() — all 13 product names now translated across 6 locales
- Add navAppTooltip key with {{name}} interpolation for the product tooltips
- Translate GlobalSidebar "View source code commit" link title
- data-cy values preserved via separate dataCyName variable so tests are unaffected
- Update plan to reflect completed items
Resolve the UI locale authoritatively from the logged-in user and fall back to the default. Previously a user with no saved locale (or a different one) kept the prior session's language because localStorage and i18n still held it after logout and there was no fallback.
Localize AccessDenied, NoInstancePermission, ConfirmPublishModal, global-tabs Dropdown (pluralized results), InstancesList (decoupled header text from icon logic), GlobalDocsMenu (module-level doc arrays + width bump to 520), InviteMembersModal, DateFilter, and RoleAccessInfo (role names left untranslated). Adds and translates all new common/shell keys across the 6 locales and marks Tier 2 done in the plan.
Contributor
Localization Reviewer — ✅ No blockers · 🟡 3 advisory note(s)🔴 BlockingNone 🟡 Advisory
|
Contributor
Code Review — ✅ No blockers · 🟡 2 warning(s) — see inline comments |
Contributor
Localization Reviewer — ✅ No blockers · 🟡 2 advisory note(s)🔴 BlockingNone 🟡 Advisory
|
Contributor
Code Review — ✅ No blockers |
Contributor
|
@finnar-bin please run negative QA on this |
# Conflicts: # package-lock.json
Contributor
Localization Reviewer — ✅ No blockers · 🟡 2 advisory note(s)🔴 BlockingNone 🟡 Advisory
|
Contributor
Localization Reviewer — ✅ No blockers · 🟡 5 advisory note(s)🔴 BlockingNone 🟡 Advisory
|
Contributor
Code Review — ✅ No blockers · 🟡 3 warning(s) — see inline comments |
Contributor
Contributor
Localization Reviewer — ✅ No blockers · 🟡 4 advisory note(s)🔴 BlockingNone 🟡 Advisory
|
Contributor
Localization Reviewer — ✅ No blockers · 🟡 4 advisory note(s)🔴 BlockingNone 🟡 Advisory
|
Contributor
Code Review — ✅ No blockers · 🟡 1 warning(s) — see inline comments |
Contributor
Localization Reviewer — ✅ No blockers · 🟡 2 advisory note(s)🔴 BlockingNone 🟡 Advisory
|
agalin920
approved these changes
Sep 14, 2026
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.

Resolves #4148
Summary
Rolls out full i18next + react-i18next localization across manager-ui — the app shell and every sub-app — supporting 6 locales:
en-US(fallback),es-ES,hi-IN,zh-CN,ru-RU,nl-NL.Dependencies
@zesty-io/materialfor accounts-ui to consume.prefs.localeDB field.Out of scope for this PR: the language switcher UI and the DB write-back of the selected locale both live in the two dependencies above, not in manager-ui. This repo only consumes
prefs.locale(src/shell/components/load-instance/index.js) and caches the resolved value tolocalStorageclient-side — it never writes locale back to the database itself.Architecture
src/shell/i18n/index.ts, loaded before the React root renders; root is wrapped in<Suspense>.public/locales/<locale>/<namespace>.json.t("content.publishItem")) — the namespace is the first dot-segment, everything after is one flat key. No nested JSON, no second dot.<Suspense>and callsuseTranslation("<ns>")once, but that trigger is now effectively a no-op since the namespace is already resolved by init time — kept intentionally. True lazy-loading was reverted: it caused crashes from a race condition where Redux-driven code (thunks/middleware callingi18n.t()outside of React) could fire before a sub-app's namespace had loaded, and the perf gain was minimal anyway.en-USand report once per key to Sentry.localizeTheme/LocalizedThemeProvider, not throught().formatLocalized/formatDistanceToNowLocalized; machine formats (yyyy-MM-dd, API/CSV payloads, URL params) are intentionally left locale-independent.Tooling added
Workflow({ name: "localize" })— an AI-driven pipeline (Discovery → Extract & Wire → Composer → Verifier) for localizing new copy going forward. Extracts hardcoded strings, wirest()/i18n.t()calls, writesen-US+ English-placeholder locale JSON, and verifies (tsc, JSON validity, key parity, broken-key refs). See README's "Localizing new copy" section.npm run i18n:extract— a lightweighti18next-parsersafety net that statically findst()calls and flags/backfills any keys missing from locale JSON. Safe to run repeatedly..github/workflows/claude-localization-reviewer.yml("Claude Localization Reviewer") — a dedicated PR check that runs only when a PR touchessrc/**/*.{js,jsx,ts,tsx}orpublic/locales/**. Two layers:ci/scripts/check_localization_objective.js— deterministic checks: TypeScript errors (scoped to changed files), locale JSON validity, cross-locale key parity (CLDR-plural-aware per locale), and brokent()/i18n.t()key references.t()wiring for new hardcoded copy, value-formatting rule violations, and translation quality/grammar in the non-English locale files.Posts inline PR comments on each confirmed finding (
ci/scripts/post_inline_comments.js, generic/reusable — parses a report's Blocking bullets rather than relying on the model to call its own commenting tool) plus a summary comment, and fails the check on any confirmed finding. The summary comment is posted fresh on every run rather than updated in place, mirroringclaude-auto-reviewer.yml, so the PR timeline shows the review history ("FAIL" → fix commits → "PASS").ci/scripts/build_localization_diff.jskeeps the diff handed to Claude within a fixed byte budget on large PRs without starving source-file coverage in favor of locale-file coverage (or vice versa). Runs onclaude-sonnet-5.cypress/e2e/.../sub-app-translations.spec.js— Cypress coverage for locale switching across sub-apps.Screenshots / video
Screencast_20260708_094140.webm