---
phase: 34-i18n-foundation
plan: 06
type: execute
wave: 4
depends_on: [01, 02, 05]
files_modified:
- src/client/routes/index.tsx
- src/client/routes/setups/index.tsx
- src/client/routes/profile.tsx
- src/client/routes/settings.tsx
- src/client/components/DashboardCard.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/de/common.json
- src/client/locales/de/collection.json
- src/client/locales/de/setups.json
- src/client/locales/de/settings.json
autonomous: true
gap_closure: true
requirements: [D-01, D-02, D-03]
must_haves:
truths:
- "Home page (routes/index.tsx) uses useTranslation and all UI chrome renders via t() calls"
- "Setups list page (routes/setups/index.tsx) uses useTranslation and all UI chrome renders via t() calls"
- "Profile page (routes/profile.tsx) uses useTranslation and all UI chrome renders via t() calls"
- "Settings currency suggestion banner text renders via t() calls"
- "All 14 components listed in the gap have useTranslation imports and t() calls for every hardcoded English string"
- "Switching to German locale translates all these pages and components"
artifacts:
- path: "src/client/routes/index.tsx"
provides: "Translated home page"
contains: "useTranslation"
- path: "src/client/routes/setups/index.tsx"
provides: "Translated setups list page"
contains: "useTranslation"
- path: "src/client/routes/profile.tsx"
provides: "Translated profile page"
contains: "useTranslation"
- path: "src/client/components/DashboardCard.tsx"
provides: "Translated dashboard card"
contains: "useTranslation"
key_links:
- from: "src/client/routes/index.tsx"
to: "src/client/locales/en/common.json"
via: "useTranslation('common')"
pattern: "t\\("
- from: "src/client/components/TotalsBar.tsx"
to: "src/client/locales/en/collection.json"
via: "useTranslation('collection')"
pattern: "t\\("
---
Wire useTranslation into all routes and components that still have hardcoded English strings.
Purpose: UAT test 4 revealed that only the settings page, nav bar, and FAB were translated. The home page, collection components, setups, profile, and many other components were never wired to i18n. This plan closes that gap by adding useTranslation to every remaining file.
Output: All 14 components and 3 routes fully internationalized, with new locale keys added to both en and de JSON files.
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/34-i18n-foundation/34-CONTEXT.md
@.planning/phases/34-i18n-foundation/34-UAT.md
useTranslation hook pattern (already established in codebase):
```typescript
import { useTranslation } from "react-i18next";
function MyComponent() {
const { t } = useTranslation("common"); // or specific namespace
return ;
}
// For multiple namespaces:
const { t } = useTranslation(["collection", "common"]);
// Access: t("collection:totals.totalWeight"), t("common:actions.save")
```
For interpolation:
```typescript
t("items.count", { count: 5 }) // "5 items"
```
Existing namespace structure:
- `common` — nav, actions, errors, auth, shared strings
- `collection` — collection page, item cards, forms, weight summary, totals, classifications
- `threads` — thread list, candidates, comparison, status badges
- `setups` — setup list, setup detail, share, impact
- `onboarding` — onboarding flow screens
- `settings` — settings page sections
Task 1: Wire useTranslation into routes and settings currency suggestionsrc/client/routes/index.tsx, src/client/routes/setups/index.tsx, src/client/routes/profile.tsx, src/client/routes/settings.tsx, src/client/locales/en/common.json, src/client/locales/en/setups.json, src/client/locales/en/settings.json, src/client/locales/de/common.json, src/client/locales/de/setups.json, src/client/locales/de/settings.jsonsrc/client/routes/index.tsx, src/client/routes/setups/index.tsx, src/client/routes/profile.tsx, src/client/routes/settings.tsx, src/client/locales/en/common.json, src/client/locales/en/setups.json, src/client/locales/en/settings.json, src/client/locales/de/common.json, src/client/locales/de/setups.json, src/client/locales/de/settings.json
For each route file, read it fully, then:
1. Add `import { useTranslation } from "react-i18next"` if not already present
2. Add `const { t } = useTranslation(...)` with the appropriate namespace at the top of the component function body
3. Replace every hardcoded English string with the corresponding `t()` call
4. Add any new keys needed to both en and de locale JSON files
**src/client/routes/index.tsx (home/discovery page):**
- Use `const { t } = useTranslation("common")` (or `["common", "collection"]` if it shows collection-related text)
- Replace all section headings (e.g., "Popular Setups", "Recently Added", "Trending Categories", etc.) with t() calls
- Replace empty states, loading text, CTAs like "Go to Collection" with t() calls
- Add new keys to en/common.json under a `home` or `discovery` section, e.g.: `"home": { "popularSetups": "Popular Setups", "recentlyAdded": "Recently Added", "trendingCategories": "Trending Categories", "goToCollection": "Go to Collection" }`
- Add corresponding German translations to de/common.json: `"home": { "popularSetups": "Beliebte Setups", "recentlyAdded": "Kürzlich hinzugefügt", "trendingCategories": "Trend-Kategorien", "goToCollection": "Zur Sammlung" }`
- Do NOT translate user-generated content (setup names, item names, user names)
**src/client/routes/setups/index.tsx (setups list page):**
- Use `const { t } = useTranslation(["setups", "common"])`
- Replace headings like "Setups", "Your Setups", empty state text, CTA buttons
- Add new keys to en/setups.json and de/setups.json as needed
**src/client/routes/profile.tsx:**
- Use `const { t } = useTranslation("common")`
- Replace headings like "Profile", "Your Gear", "Public Setups", any labels or descriptions
- Add new keys under a `profile` section in en/common.json and de/common.json
**src/client/routes/settings.tsx (currency suggestion banner only):**
- The file already has useTranslation. Find the currency suggestion banner (around line 298) that shows "Based on your region, we suggest {symbol} ({code})" and the "Switch" and "Dismiss" buttons.
- Add new keys to en/settings.json: `"currency": { ..., "suggestion": "Based on your region, we suggest {{symbol}} ({{code}})", "switch": "Switch" }`
- Add German translations: `"currency": { ..., "suggestion": "Basierend auf Ihrer Region empfehlen wir {{symbol}} ({{code}})", "switch": "Wechseln" }`
- Replace the hardcoded banner text with `t("currency.suggestion", { symbol: ..., code: suggestedCurrency })`
- Replace "Switch" button text with `t("currency.switch")`
- The "Dismiss" button's aria-label should also use t()
**CRITICAL:** For every new key added to an en/*.json file, add the corresponding German translation to the de/*.json file. Use proper German umlauts (ä, ö, ü, Ä, Ö, Ü, ß) — NOT ASCII fallbacks.
cd /home/jlmak/Projects/jlmak/GearBox && for f in src/client/routes/index.tsx src/client/routes/setups/index.tsx src/client/routes/profile.tsx; do echo -n "$(basename $f): "; grep -c "useTranslation" "$f"; done && grep -c "suggestion" src/client/locales/en/settings.jsonAll 3 route pages and settings currency suggestion use useTranslation with t() calls, locale files updated for both en and deTask 2: Wire useTranslation into remaining 11 componentssrc/client/components/DashboardCard.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/threads.json, src/client/locales/en/setups.json, src/client/locales/de/common.json, src/client/locales/de/collection.json, src/client/locales/de/threads.json, src/client/locales/de/setups.jsonsrc/client/components/DashboardCard.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/collection.json, src/client/locales/en/threads.json, src/client/locales/en/setups.json, src/client/locales/en/common.json
For EACH of the 11 components listed below, read the file fully, then:
1. Add `import { useTranslation } from "react-i18next"`
2. Add `const { t } = useTranslation(...)` with the appropriate namespace
3. Replace every hardcoded English string with the corresponding t() call
4. Add any new keys to both en and de locale JSON files
**Namespace assignments:**
- `DashboardCard.tsx` → `useTranslation("collection")` — labels like "Total Weight", "Total Price", "Items", stat labels
- `ThreadTabs.tsx` → `useTranslation("threads")` — tab labels like "All", "Active", "Resolved", "Archived"
- `PlanningView.tsx` → `useTranslation(["threads", "common"])` — "Planning" heading, "Research Threads" section title, empty states, "Start a Thread" CTA
- `TotalsBar.tsx` → `useTranslation("collection")` — "Total Weight", "Total Cost", weight/price summary labels
- `ThreadCard.tsx` → `useTranslation("threads")` — thread card labels, candidate count text, status text
- `PublicSetupCard.tsx` → `useTranslation("setups")` — "items", "by", setup card labels
- `SetupImpactSelector.tsx` → `useTranslation("setups")` — "Compare with setup", "Select a setup", impact labels
- `ClassificationBadge.tsx` → `useTranslation("collection")` — "Ultralight", "Light", "Medium", "Heavy" classification labels
- `ImpactDeltaBadge.tsx` → `useTranslation("setups")` — delta labels like "lighter", "heavier", "+", "-" prefix text if any
- `ImageUpload.tsx` → `useTranslation("common")` — "Upload image", "Click to upload", "Drop image here", file size/type error messages
**For each component:** Read it fully. Find every string literal that is user-visible UI chrome (not CSS classes, not data attributes, not code identifiers). Replace with the matching t() key. If the key does not exist in the en locale file, add it in the appropriate namespace JSON.
**CRITICAL:** For every new key added to an en/*.json file, add the corresponding German translation to the de/*.json file. Use proper German umlauts (ä, ö, ü, Ä, Ö, Ü, ß) — NOT ASCII fallbacks. Examples:
- "Ultralight" → "Ultraleicht"
- "Items" → "Gegenstände"
- "Upload image" → "Bild hochladen"
- "lighter" → "leichter"
- "heavier" → "schwerer"
**Do NOT translate:** User-generated content (item names, setup names, thread titles, category names created by users).
**If a component has NO hardcoded translatable strings** (e.g., it only renders numeric data or user content), skip it — do not add unnecessary imports.
cd /home/jlmak/Projects/jlmak/GearBox && for f in DashboardCard ThreadTabs PlanningView TotalsBar ThreadCard PublicSetupCard SetupImpactSelector ClassificationBadge ImpactDeltaBadge ImageUpload; do echo -n "$f: "; grep -c "useTranslation" src/client/components/$f.tsx; done && bun run build 2>&1 | tail -3All 11 components use useTranslation with t() calls, no hardcoded English UI chrome remains, locale files updated for both en and de, build passes
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| translation files→DOM | Translation strings rendered in JSX — React escapes by default |
## STRIDE Threat Register
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-34-07 | Injection | t() output in JSX | accept | i18next interpolation escapeValue is false BUT React's JSX escaping prevents XSS. Translation strings are bundled static content, not user input. Same mitigation as T-34-03 from Plan 02. |
- `bun run build` succeeds with no errors
- `grep -c "useTranslation" src/client/routes/index.tsx` returns >= 1
- `grep -c "useTranslation" src/client/routes/setups/index.tsx` returns >= 1
- `grep -c "useTranslation" src/client/routes/profile.tsx` returns >= 1
- All 11 components (DashboardCard, ThreadTabs, PlanningView, TotalsBar, ThreadCard, PublicSetupCard, SetupImpactSelector, ClassificationBadge, ImpactDeltaBadge, ImageUpload) contain useTranslation
- Settings currency suggestion uses t() instead of hardcoded "Based on your region"
- `bun test tests/i18n/locales.test.ts` passes (key parity still holds after new keys added)
- Every file listed in the UAT gap has useTranslation wired in
- No hardcoded English UI chrome strings remain in these files
- All new en keys have corresponding de translations with proper umlauts
- Key parity test still passes
- Build passes