---
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
