feat(web): i18n operator UX — settings, pilot copy, Intl, a11y, API errors (#909) #923

Merged
dev merged 2 commits from dev/909 into main 2026-05-07 16:53:33 +00:00
Collaborator

Summary

  • US-B1/settings/language locale picker: Intl.DisplayNames native labels, immediate redirect on selection, localStorage + server /me sync best-effort
  • US-B2login, onboarding, settings-appearance, settings-language fully migrated to Paraglide messages (m.*); ICU plural proof via Intl.PluralRules in the language count subtitle
  • US-B3fmtTime uses getLocale() so pilot-screen timestamps honour the active locale
  • US-B4$locale layout syncs document.documentElement.lang / .dir on every navigation; RTL detection via getTextDirection(); 5 a11y contract tests
  • US-B5localizedApiError() maps HTTP 401/403/404/5xx to Paraglide keys; network errors use error_network; unknown errors carry raw server detail as err.cause

Test plan

  • 989 tests pass
  • TypeScript strict clean
  • Biome: 0 errors
  • 5 new a11y contract tests ($locale.test.tsx)
  • 7 new language picker tests (settings.language.test.tsx)

🤖 Generated with Claude Code

## Summary - **US-B1** — `/settings/language` locale picker: `Intl.DisplayNames` native labels, immediate redirect on selection, `localStorage` + server `/me` sync best-effort - **US-B2** — `login`, `onboarding`, `settings-appearance`, `settings-language` fully migrated to Paraglide messages (`m.*`); ICU plural proof via `Intl.PluralRules` in the language count subtitle - **US-B3** — `fmtTime` uses `getLocale()` so pilot-screen timestamps honour the active locale - **US-B4** — `$locale` layout syncs `document.documentElement.lang` / `.dir` on every navigation; RTL detection via `getTextDirection()`; 5 a11y contract tests - **US-B5** — `localizedApiError()` maps HTTP 401/403/404/5xx to Paraglide keys; network errors use `error_network`; unknown errors carry raw server detail as `err.cause` ## Test plan - [x] 989 tests pass - [x] TypeScript strict clean - [x] Biome: 0 errors - [x] 5 new a11y contract tests (`$locale.test.tsx`) - [x] 7 new language picker tests (`settings.language.test.tsx`) 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(web): i18n operator UX — settings, pilot copy, Intl, a11y, API errors (#909)
Some checks failed
qa / qa-1 (pull_request) Failing after 13s
qa / dockerfile (pull_request) Successful in 14s
qa / qa (pull_request) Failing after 0s
qa / sql-layer-check (pull_request) Failing after 14s
qa / db-schema (pull_request) Failing after 43s
5b2dce6912
Implements US-B1 through US-B5:

- US-B1: `/settings/language` locale picker with Intl.DisplayNames native
  labels, immediate redirect on select, localStorage + server /me sync
- US-B2: login, onboarding, settings-appearance and settings-language
  migrated to Paraglide messages (m.*); ICU plural proof via
  Intl.PluralRules in the language count subtitle
- US-B3: fmtTime uses getLocale() so timestamps honour the active locale
- US-B4: $locale layout syncs document.documentElement.lang/.dir on every
  navigation; RTL detection via getTextDirection(); 5 a11y contract tests
- US-B5: localizedApiError() maps HTTP 401/403/404/5xx to Paraglide keys;
  network errors use error_network; unknown errors carry raw detail as cause

Also:
- routeTree.gen.ts updated with /$locale/settings/language entry
- vitest.setup.tsx: Outlet stub (prevents router.isServer crash in layout
  tests), Route.useParams mock pattern documented
- login.test.tsx: assertions updated for message-key mock format

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix(ci): indent codegen recipe body so just can parse the justfile
All checks were successful
qa / sql-layer-check (pull_request) Successful in 14s
qa / dockerfile (pull_request) Successful in 16s
qa / db-schema (pull_request) Successful in 42s
qa / qa-1 (pull_request) Successful in 2m4s
qa / qa (pull_request) Successful in 0s
bca0bbb650
The `bun --eval "..."` JS block in the new `codegen` recipe landed with
lines at column 0. Just treats unindented lines as recipe boundaries, so
it bailed with "Unknown start of token ';'" on every invocation — making
every CI job that shells out to `just` fail at parse time.

Add 4-space indent to all 21 JS lines (plus the closing `"`); just strips
the common indent before writing the shebang script to a temp file, so
the executed code is byte-for-byte identical.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
dev requested review from reviewer 2026-05-07 16:11:08 +00:00
reviewer approved these changes 2026-05-07 16:12:51 +00:00
reviewer left a comment

US-B1–B5 all implemented correctly. localizedApiError, fmtTime locale wiring, lang/dir sync, language picker with Intl.DisplayNames + ICU plural, 12 new tests — all green CI.

Nit (not blocking): language-description <p> sits outside the <section> card — cosmetically fine but semantically it reads as a page footer rather than section copy.

US-B1–B5 all implemented correctly. `localizedApiError`, `fmtTime` locale wiring, `lang`/`dir` sync, language picker with `Intl.DisplayNames` + ICU plural, 12 new tests — all green CI. Nit (not blocking): `language-description` `<p>` sits outside the `<section>` card — cosmetically fine but semantically it reads as a page footer rather than section copy.
Collaborator

PR has conflicts — please rebase on main before merging.

PR has conflicts — please rebase on main before merging.
dev force-pushed dev/909 from bca0bbb650
All checks were successful
qa / sql-layer-check (pull_request) Successful in 14s
qa / dockerfile (pull_request) Successful in 16s
qa / db-schema (pull_request) Successful in 42s
qa / qa-1 (pull_request) Successful in 2m4s
qa / qa (pull_request) Successful in 0s
to 707f287746
All checks were successful
qa / sql-layer-check (pull_request) Successful in 10s
qa / dockerfile (pull_request) Successful in 14s
qa / db-schema (pull_request) Successful in 41s
qa / qa-1 (pull_request) Successful in 1m36s
qa / qa (pull_request) Successful in 0s
2026-05-07 16:51:52 +00:00
Compare
dev merged commit 92ec02f1e0 into main 2026-05-07 16:53:33 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
charles/claude-hooks!923
No description provided.