feat(web-ui): i18n — next-intl en/ja with locale switcher in Settings#200
Merged
Conversation
Introduce next-intl (4.13) without i18n routing (compatible with static export). All user-facing strings in the main UI are externalized to messages/en.json and messages/ja.json. Key changes: - LocaleProvider (NextIntlClientProvider + localStorage persistence) wired at RootLayout level; locale auto-detected from browser language - Settings: Language section with English / 日本語 radio toggle - formatDate: locale-aware (en-US / ja-JP) - 30+ components migrated to useTranslations() - ToolCard / ToolIndicator: tool labels resolve via "tools" namespace with fallback to the existing label map for unknown tools - renderWithIntl test helper for components under the intl provider - ErrorBoundary refactored: class delegates render to functional ErrorFallback so it can call useTranslations() Verified: tsc clean, vitest 38 passed, Playwright E2E passed, build:cloud static export passed, make lint + make test (260) all green. Not yet externalized (lower priority): styles page, templates page, agentErrors.ts user messages (these have a Python parity test that would need parallel updates).
CI runs Node 20 / npm 10, which validates wasm32-wasi optional deps' nested @emnapi entries differently from npm 11. Full regeneration with npm@10 produces a lockfile valid under both npm 10 and 11.
Remaining hardcoded English in the main flow: - ComposeCard / AgentCard / StopSummary — header states, badges, activity labels (activityLabel now accepts a translator), aria-labels - ModelPicker — Select model / Recommended / search placeholder / empty - ConfirmDialog — translated Confirm/Cancel defaults - McpStatusBar — aria-labels
These surfaces exist in cloud mode too (nav links in AppShell), so they were wrongly deferred as local-only. Also revise ja translations for naturalness — match existing conventions (spacing around latin words, 「〜しますか?」 confirm titles, 「〜できませんでした」 errors) and fix two English leftovers (chat.chatMessages, chatInput.inputLabel).
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
Roadmap 3-5: internationalize the Web UI with next-intl (en / ja), switchable from Settings.
output: "export"), so next-intl's middleware/[locale]-segment mode isn't available. Instead,LocaleProviderwraps the app at RootLayout, persisting the locale inlocalStorage(sdpm-locale) with browser-language auto-detection (ja → 日本語, everything else → English). Both message bundles are statically imported (small).useTranslations()— deck list, deck cards, workspace, slide carousel, outline view, chat panel/input/tools, spec navigation, dialogs, empty states, and all aria-labels.ComposeCard/AgentCard/StopSummary) — header states, badges, activity timeline.activityLabelaccepts an optional translator soparseComposeStatestays a pure function.ToolCard/ToolIndicator) resolve via atoolsmessage namespace with graceful fallback to the derived label for unknown tools.formatDateis now locale-aware (en-US/ja-JP, localized Today/Yesterday/Nd ago).ErrorFallbackso the fallback text can use the translation hook.renderWithIntlhelper wraps Testing Library render inNextIntlClientProvider..kiro/steering/i18n.md(public) documents architecture, message-file rules, Japanese translation conventions, and verification commands.Japanese translation quality
ja strings follow product-UI conventions rather than literal translation: half-width space between Japanese and Latin words (「AI で作成」), 「〜しますか?」 confirm titles, 「〜できませんでした」 errors, established terms (再試行 / ピン留め / お気に入り).
Not in scope (follow-ups)
agentErrors.tsuser-facing messages — guarded by a Python↔TS parity test (tests/test_agent_errors.py) that would need coordinated changespurpose, backend error bodies)AgentSettingsDialog)Test plan
tsc --noEmitclean,eslint --quietcleannpm test— 38 passed (ErrorBoundary tests updated to userenderWithIntl)npm run test:e2e— Playwright deck-creation E2E passed (default locale stays en, so role/aria selectors are unchanged)npm run build:cloud— static export buildsmake lint/make test— 260 passed