Skip to content

feat(web-ui): i18n — next-intl en/ja with locale switcher in Settings#200

Merged
ShotaroKataoka merged 9 commits into
mainfrom
feat/i18n
Jul 15, 2026
Merged

feat(web-ui): i18n — next-intl en/ja with locale switcher in Settings#200
ShotaroKataoka merged 9 commits into
mainfrom
feat/i18n

Conversation

@ShotaroKataoka

@ShotaroKataoka ShotaroKataoka commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Roadmap 3-5: internationalize the Web UI with next-intl (en / ja), switchable from Settings.

  • No i18n routing — the app is a static-export SPA (output: "export"), so next-intl's middleware/[locale]-segment mode isn't available. Instead, LocaleProvider wraps the app at RootLayout, persisting the locale in localStorage (sdpm-locale) with browser-language auto-detection (ja → 日本語, everything else → English). Both message bundles are statically imported (small).
  • Settings → Language: English / 日本語 radio toggle, applies instantly.
  • 40+ components migrated to useTranslations() — deck list, deck cards, workspace, slide carousel, outline view, chat panel/input/tools, spec navigation, dialogs, empty states, and all aria-labels.
  • Compose UI (ComposeCard / AgentCard / StopSummary) — header states, badges, activity timeline. activityLabel accepts an optional translator so parseComposeState stays a pure function.
  • Styles / Templates pages, StyleChat, style picker — reachable in cloud mode via AppShell nav, so they are in scope (upload dialog, rename/delete flows, toasts included).
  • Chat Options panel, ModelPicker, ConfirmDialog (translated Confirm/Cancel defaults), McpStatusBar aria-labels.
  • Tool activity labels (ToolCard / ToolIndicator) resolve via a tools message namespace with graceful fallback to the derived label for unknown tools.
  • formatDate is now locale-aware (en-US / ja-JP, localized Today/Yesterday/Nd ago).
  • ErrorBoundary refactored: the class boundary delegates its fallback to a functional ErrorFallback so the fallback text can use the translation hook.
  • Test infra: renderWithIntl helper wraps Testing Library render in NextIntlClientProvider.
  • Steering: .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.ts user-facing messages — guarded by a Python↔TS parity test (tests/test_agent_errors.py) that would need coordinated changes
  • Agent/model-generated dynamic text (compose purpose, backend error bodies)
  • Local-only surfaces (AgentSettingsDialog)

Test plan

  • en/ja message key parity check (345 keys)
  • tsc --noEmit clean, eslint --quiet clean
  • npm test — 38 passed (ErrorBoundary tests updated to use renderWithIntl)
  • 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 builds
  • make lint / make test — 260 passed
  • Deployed to dev account and verified both locales in the browser

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.
@ShotaroKataoka ShotaroKataoka added the blog:skip ブログ対象外 label Jul 15, 2026
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).
@ShotaroKataoka ShotaroKataoka merged commit 2415a3e into main Jul 15, 2026
12 checks passed
@ShotaroKataoka ShotaroKataoka deleted the feat/i18n branch July 15, 2026 02:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

blog:skip ブログ対象外

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant