docs(34): create gap closure plans for missing i18n wiring and German umlauts

This commit is contained in:
2026-04-17 20:09:47 +02:00
parent 459a4ed4b0
commit b21ba0d97b
2 changed files with 412 additions and 0 deletions

View File

@@ -0,0 +1,240 @@
---
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\\("
---
<objective>
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.
</objective>
<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.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
<interfaces>
useTranslation hook pattern (already established in codebase):
```typescript
import { useTranslation } from "react-i18next";
function MyComponent() {
const { t } = useTranslation("common"); // or specific namespace
return <button>{t("actions.save")}</button>;
}
// 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
</interfaces>
</context>
<tasks>
<task type="auto">
<name>Task 1: Wire useTranslation into routes and settings currency suggestion</name>
<files>src/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</files>
<read_first>src/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</read_first>
<action>
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.
</action>
<verify>
<automated>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.json</automated>
</verify>
<done>All 3 route pages and settings currency suggestion use useTranslation with t() calls, locale files updated for both en and de</done>
</task>
<task type="auto">
<name>Task 2: Wire useTranslation into remaining 11 components</name>
<files>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/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.json</files>
<read_first>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/collection.json, src/client/locales/en/threads.json, src/client/locales/en/setups.json, src/client/locales/en/common.json</read_first>
<action>
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.
</action>
<verify>
<automated>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 -3</automated>
</verify>
<done>All 11 components use useTranslation with t() calls, no hardcoded English UI chrome remains, locale files updated for both en and de, build passes</done>
</task>
</tasks>
<threat_model>
## 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. |
</threat_model>
<verification>
- `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)
</verification>
<success_criteria>
- 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
</success_criteria>
<output>
After completion, create `.planning/phases/34-i18n-foundation/34-06-SUMMARY.md`
</output>

View File

@@ -0,0 +1,172 @@
---
phase: 34-i18n-foundation
plan: 07
type: execute
wave: 4
depends_on: [05]
files_modified:
- src/client/locales/de/common.json
- src/client/locales/de/collection.json
- src/client/locales/de/threads.json
- src/client/locales/de/setups.json
- src/client/locales/de/onboarding.json
- src/client/locales/de/settings.json
autonomous: true
gap_closure: true
requirements: [D-13, D-14]
must_haves:
truths:
- "All German locale files use proper umlauts (ä, ö, ü, Ä, Ö, Ü, ß) instead of ASCII fallbacks (ae, oe, ue)"
- "No instances of 'Loeschen', 'Zurueck', 'Bestaetigen', 'Schliessen', 'Gegenstaende', 'Ausruestung', 'Waehrung', 'Schluessel' remain"
- "German translations read naturally to a German speaker"
- "Key parity test still passes after corrections"
artifacts:
- path: "src/client/locales/de/common.json"
provides: "German common translations with proper umlauts"
contains: "Löschen"
- path: "src/client/locales/de/collection.json"
provides: "German collection translations with proper umlauts"
contains: "Gegenstände"
- path: "src/client/locales/de/settings.json"
provides: "German settings translations with proper umlauts"
contains: "Währung"
key_links:
- from: "src/client/lib/i18n.ts"
to: "src/client/locales/de/common.json"
via: "import deCommon"
pattern: "deCommon"
---
<objective>
Fix all German locale files to use proper Unicode umlauts instead of ASCII fallbacks.
Purpose: UAT test 4 reported that German text uses "ae" instead of "ä", "oe" instead of "ö", "ue" instead of "ü", and similar. All 6 German JSON files were generated with ASCII approximations instead of proper German characters. This plan does a complete pass through every German locale file and replaces every ASCII fallback with the correct Unicode character.
Output: All 6 de/*.json files with proper German umlauts (ä, ö, ü, Ä, Ö, Ü, ß).
</objective>
<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.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
</context>
<tasks>
<task type="auto">
<name>Task 1: Replace ASCII fallbacks with proper umlauts in all 6 German locale files</name>
<files>src/client/locales/de/common.json, src/client/locales/de/collection.json, src/client/locales/de/threads.json, src/client/locales/de/setups.json, src/client/locales/de/onboarding.json, src/client/locales/de/settings.json</files>
<read_first>src/client/locales/de/common.json, src/client/locales/de/collection.json, src/client/locales/de/threads.json, src/client/locales/de/setups.json, src/client/locales/de/onboarding.json, src/client/locales/de/settings.json</read_first>
<action>
Read each German locale file fully. For every string value, replace ASCII umlaut approximations with proper Unicode characters. This is NOT a simple find-and-replace — you must check each word in context because "ae", "oe", "ue" are not always umlauts (e.g., "Israel" should not become "Israöl").
**Replacement rules (apply to German words only):**
- `ae``ä` when it represents an umlaut (Loeschen → Löschen, Gegenstaende → Gegenstände, Waehrung → Währung, Aenderung → Änderung)
- `oe``ö` when it represents an umlaut (Loeschen → Löschen, Groesse → Größe)
- `ue``ü` when it represents an umlaut (Zurueck → Zurück, Ausruestung → Ausrüstung, Ueberpruefen → Überprüfen, Stueck → Stück, hinzufuegen → hinzufügen)
- `Ae``Ä` at word start (Aenderung → Änderung)
- `Oe``Ö` at word start
- `Ue``Ü` at word start (Ueberpruefen → Überprüfen)
- `ss``ß` where appropriate in German (Schliessen → Schließen, Groesse → Größe, Strasse → Straße, weiss → weiß) — but NOT in compounds like "Impressum" or "Pressemitteilung"
**Known corrections (from UAT report and file inspection):**
- `Loeschen``Löschen`
- `Zurueck``Zurück`
- `Bestaetigen``Bestätigen`
- `Schliessen``Schließen`
- `Gegenstaende``Gegenstände`
- `Ausruestung``Ausrüstung`
- `Waehrung``Währung`
- `Schluessel``Schlüssel`
- `hinzufuegen``hinzufügen`
- `Hinzufuegen``Hinzufügen`
- `Ueberpruefen``Überprüfen`
- `verfuegbar``verfügbar`
- `Stueck``Stück`
- `Groesse``Größe`
- `aendern``ändern`
- `Aendern``Ändern`
- `aehnlich``ähnlich`
- `haeufig``häufig`
- `unterstuetzen``unterstützen`
- `Ernaehrung``Ernährung`
- `Geraet``Gerät`
- `Geraete``Geräte`
- `gewuenscht``gewünscht`
- `moeglich``möglich`
- `moeglicherweise``möglicherweise`
- `natuerlich``natürlich`
- `pruefen``prüfen`
- `Uebersicht``Übersicht`
- `Veroeffentlichen``Veröffentlichen`
- `oeffentlich``öffentlich`
- `Oeffentlich``Öffentlich`
- `wuenschen``wünschen`
- `fuer``für`
- `Fuer``Für`
- `ueber``über`
- `Ueber``Über`
**Process for each file:**
1. Read the entire file
2. Go through every string value
3. Identify every German word that uses ASCII umlaut approximation
4. Replace with proper Unicode umlaut
5. Write the corrected file
6. Ensure the file is valid JSON after corrections
**Also review for natural German phrasing.** While fixing umlauts, if you notice awkward or unnatural German translations, improve them. The goal (per D-14) is natural German, not word-for-word translation.
**Do NOT change:**
- JSON key names (only values)
- Interpolation variables: {{count}}, {{name}}, etc. must remain exactly as-is
- English loanwords used intentionally in German context (e.g., "Setup", "Thread", "Export", "Import", "CSV")
</action>
<verify>
<automated>cd /home/jlmak/Projects/jlmak/GearBox && echo "=== Checking for remaining ASCII fallbacks ===" && grep -r "Loeschen\|Zurueck\|Bestaetigen\|Schliessen\|Gegenstaende\|Ausruestung\|Waehrung\|Schluessel" src/client/locales/de/ && echo "FAIL: ASCII fallbacks still present" || echo "PASS: No known ASCII fallbacks found" && echo "=== Checking for proper umlauts ===" && grep -c "ä\|ö\|ü\|Ä\|Ö\|Ü\|ß" src/client/locales/de/common.json && echo "=== Key parity ===" && bun test tests/i18n/locales.test.ts 2>&1 | tail -5</automated>
</verify>
<done>All 6 German locale files use proper Unicode umlauts, no ASCII approximations remain, key parity test passes, German reads naturally</done>
</task>
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| locale JSON→i18n | Static bundled files — trusted, no runtime injection vector |
## STRIDE Threat Register
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-34-08 | Information Disclosure | de locale files | accept | Translation files contain only UI strings, no secrets. Same disposition as T-34-02 and T-34-06. |
</threat_model>
<verification>
- `grep -r "Loeschen\|Zurueck\|Bestaetigen\|Schliessen" src/client/locales/de/` returns no matches
- `grep -c "ä\|ö\|ü\|ß" src/client/locales/de/common.json` returns > 0
- All 6 de/*.json files are valid JSON
- `bun test tests/i18n/locales.test.ts` passes (key parity maintained)
- `bun run build` succeeds
</verification>
<success_criteria>
- Every German locale file uses proper Unicode umlauts (ä, ö, ü, Ä, Ö, Ü, ß)
- Zero instances of known ASCII fallback patterns remain
- German translations read naturally
- Key parity test passes
- Build passes
</success_criteria>
<output>
After completion, create `.planning/phases/34-i18n-foundation/34-07-SUMMARY.md`
</output>