diff --git a/.planning/phases/34-i18n-foundation/34-06-SUMMARY.md b/.planning/phases/34-i18n-foundation/34-06-SUMMARY.md new file mode 100644 index 0000000..99e1a4c --- /dev/null +++ b/.planning/phases/34-i18n-foundation/34-06-SUMMARY.md @@ -0,0 +1,136 @@ +--- +phase: 34-i18n-foundation +plan: "06" +subsystem: client/i18n +tags: [i18n, react-i18next, localization, german, components, routes] +dependency_graph: + requires: ["34-01", "34-02", "34-05"] + provides: ["fully-wired-i18n-components", "translated-routes"] + affects: ["client/routes/index", "client/routes/profile", "client/routes/settings", "client/components/*"] +tech_stack: + added: [] + patterns: ["useTranslation with namespace arrays", "plural keys with count interpolation", "t() with defaultValue fallback"] +key_files: + created: [] + modified: + - src/client/routes/index.tsx + - src/client/routes/profile.tsx + - src/client/routes/settings.tsx + - src/client/components/ThreadTabs.tsx + - src/client/components/PlanningView.tsx + - src/client/components/TotalsBar.tsx + - src/client/components/ThreadCard.tsx + - src/client/components/PublicSetupCard.tsx + - src/client/components/SetupImpactSelector.tsx + - src/client/components/ClassificationBadge.tsx + - src/client/components/ImpactDeltaBadge.tsx + - src/client/components/ImageUpload.tsx + - src/client/locales/en/common.json + - src/client/locales/en/collection.json + - src/client/locales/en/setups.json + - src/client/locales/en/settings.json + - src/client/locales/en/threads.json + - src/client/locales/de/common.json + - src/client/locales/de/collection.json + - src/client/locales/de/setups.json + - src/client/locales/de/settings.json + - src/client/locales/de/threads.json +decisions: + - "DashboardCard skipped: component renders only props (title, stats, emptyText) with no hardcoded UI strings — caller is responsible for translation" + - "ClassificationBadge uses t() with defaultValue fallback instead of static lookup map — handles unknown classification values gracefully" + - "Intl.DateTimeFormat locale changed from hardcoded 'en-US' to undefined in profile.tsx — uses browser locale for member-since date formatting" + - "threads.empty.noThreads changed from 'No research threads yet' to 'No threads found' to match PlanningView filtered-results context" +metrics: + duration: "~30 minutes" + completed: "2026-04-17T18:26:54Z" + tasks_completed: 2 + files_modified: 22 +requirements: [D-01, D-02, D-03] +--- + +# Phase 34 Plan 06: i18n Gap Closure — Routes and Components Summary + +Wired `useTranslation` into all routes and components that had hardcoded English strings, closing the UAT-identified gap where only the settings page, nav bar, and FAB were translated. + +## What Was Built + +**Task 1 — Routes and settings currency suggestion (commit 755c0ab):** +- `routes/index.tsx`: Section headings (Popular Setups, Recently Added, Trending Categories) now use `t("home.*")` from `common` namespace +- `routes/profile.tsx`: All sections (Account, Security, Danger Zone) fully translated — email management, password change, account deletion flow +- `routes/settings.tsx`: Currency suggestion banner text uses `t("currency.suggestion", { symbol, code })` with interpolation; Switch button and Dismiss aria-label use t() +- Added `home`, `profile`, `imageUpload` sections to en/de common.json +- Added `currency.suggestion`, `currency.switch`, `showConversions` to en/de settings.json + +**Task 2 — 10 remaining components (commit 480abdd):** +- `ThreadTabs.tsx`: Tab labels (My Gear → Gear, Planning, Setups) via `collection` namespace +- `PlanningView.tsx`: Section heading, active/resolved tabs, full empty state (title + 3 steps + CTA), "No threads found" — via `threads` namespace +- `TotalsBar.tsx`: "Sign in" link via `common.auth.signIn` +- `ThreadCard.tsx`: "Resolved" badge and candidate count with plural form (`{{count}} candidates` / `{{count}} candidate`) +- `PublicSetupCard.tsx`: "by {{name}}" and "Anonymous" fallback; item count with plural form +- `SetupImpactSelector.tsx`: "Compare with setup..." placeholder option +- `ClassificationBadge.tsx`: base/worn/consumable labels via `collection.classificationBadge.*` with defaultValue fallback +- `ImpactDeltaBadge.tsx`: "(add)" mode label via `setups.impact.adding` +- `ImageUpload.tsx`: "Click to add photo", invalid type error, file too large error, upload failed error +- `DashboardCard.tsx`: Correctly skipped — all strings are props from caller + +## New Locale Keys Added + +**en/de common.json:** `home.{popularSetups,recentlyAdded,trendingCategories}`, `imageUpload.{clickToAdd,invalidType,tooLarge,uploadFailed}`, `profile.{title,account,accountInfo,email,noEmail,change,newEmailPlaceholder,updating,updateEmail,emailUpdated,memberSince,security,managePassword,currentPassword,newPassword,password,confirmPassword,passwordRequirements,passwordUpdated,changingPassword,changePassword,setPassword,dangerZone,dangerZoneDescription,deleteAccount,deleteConfirmMessage,deleteConfirmPlaceholder}` + +**en/de settings.json:** `currency.{suggestion,switch}`, `showConversions.{title,description}` + +**en/de collection.json:** `tabs.setups`, `totals.{totalWeight,totalCost}`, `classificationBadge.{base,worn,consumable}` + +**en/de setups.json:** `card.{by,anonymous}`, `impact.compareWith` + +**en/de threads.json:** `card.{candidates,candidates_one}`, `planning.{title,emptyTitle,createFirst,step1Title,step1Description,step2Title,step2Description,step3Title,step3Description}` + +## Deviations from Plan + +### Auto-fixed Issues + +**1. [Rule 1 - Bug] Fixed hardcoded `en-US` locale in profile.tsx date formatting** +- **Found during:** Task 1 +- **Issue:** `Intl.DateTimeFormat("en-US", ...)` for "Member since" date used hardcoded locale +- **Fix:** Changed to `Intl.DateTimeFormat(undefined, ...)` to use browser's locale +- **Files modified:** src/client/routes/profile.tsx + +**2. [Rule 2 - Missing] Fixed ASCII fallbacks in de/common.json filter section** +- **Found during:** Task 1 locale update +- **Issue:** Existing de/common.json had `"Gegenstaenden"` instead of `"Gegenständen"` in filter.showing +- **Fix:** Updated to use proper umlauts when touching those strings +- **Files modified:** src/client/locales/de/common.json + +## Verification + +- `grep -c "useTranslation" src/client/routes/index.tsx` → 4 +- `grep -c "useTranslation" src/client/routes/profile.tsx` → 5 +- `grep -c "useTranslation" src/client/routes/settings.tsx` → 1 (already had it) +- All 10 components (minus DashboardCard which has no hardcoded strings) have useTranslation +- `bun run build` passes with no errors +- `bun test tests/i18n/locales.test.ts` → 19 pass, 0 fail + +## Commits + +| Task | Commit | Description | +|------|--------|-------------| +| Task 1 | 755c0ab | feat(34-06): wire useTranslation into routes and settings currency suggestion | +| Task 2 | 480abdd | feat(34-06): wire useTranslation into 10 remaining components | + +## Known Stubs + +None — all translated strings are wired to real locale data. + +## Threat Flags + +None — translation strings are static bundled content, not user input. React JSX escaping prevents XSS per T-34-07. + +## Self-Check: PASSED + +- src/client/routes/index.tsx: exists, contains useTranslation +- src/client/routes/profile.tsx: exists, contains useTranslation +- src/client/routes/settings.tsx: exists, contains useTranslation +- All 10 components modified: confirmed via grep +- Commits 755c0ab and 480abdd: confirmed in git log +- Build: passed +- i18n parity tests: 19/19 passed