448 lines
25 KiB
Markdown
448 lines
25 KiB
Markdown
---
|
|
phase: 34-i18n-foundation
|
|
plan: 02
|
|
type: execute
|
|
wave: 1
|
|
depends_on: [01]
|
|
files_modified:
|
|
- src/client/components/TopNav.tsx
|
|
- src/client/components/BottomTabBar.tsx
|
|
- src/client/components/FabMenu.tsx
|
|
- src/client/components/ConfirmDialog.tsx
|
|
- src/client/components/AuthPromptModal.tsx
|
|
- src/client/components/ExternalLinkDialog.tsx
|
|
- src/client/components/CatalogSearchOverlay.tsx
|
|
- src/client/components/AddToCollectionModal.tsx
|
|
- src/client/components/AddToThreadModal.tsx
|
|
- src/client/components/UserMenu.tsx
|
|
- src/client/components/CollectionView.tsx
|
|
- src/client/components/ItemCard.tsx
|
|
- src/client/components/ItemForm.tsx
|
|
- src/client/components/CategoryPicker.tsx
|
|
- src/client/components/CategoryHeader.tsx
|
|
- src/client/components/WeightSummaryCard.tsx
|
|
- src/client/components/PlanningView.tsx
|
|
- src/client/components/TotalsBar.tsx
|
|
- src/client/components/DashboardCard.tsx
|
|
- src/client/components/ThreadCard.tsx
|
|
- src/client/components/ThreadTabs.tsx
|
|
- src/client/components/CandidateCard.tsx
|
|
- src/client/components/CandidateForm.tsx
|
|
- src/client/components/CandidateListItem.tsx
|
|
- src/client/components/ComparisonTable.tsx
|
|
- src/client/components/CreateThreadModal.tsx
|
|
- src/client/components/StatusBadge.tsx
|
|
- src/client/components/ClassificationBadge.tsx
|
|
- src/client/components/SetupsView.tsx
|
|
- src/client/components/SetupCard.tsx
|
|
- src/client/components/SetupImpactSelector.tsx
|
|
- src/client/components/ShareModal.tsx
|
|
- src/client/components/ImpactDeltaBadge.tsx
|
|
- src/client/components/ItemPicker.tsx
|
|
- src/client/components/ImageUpload.tsx
|
|
- src/client/components/GearImage.tsx
|
|
- src/client/components/GlobalItemCard.tsx
|
|
- src/client/components/ManualEntryForm.tsx
|
|
- src/client/components/LinkToGlobalItem.tsx
|
|
- src/client/components/ProfileSection.tsx
|
|
- src/client/components/PublicSetupCard.tsx
|
|
- src/client/routes/__root.tsx
|
|
- src/client/routes/index.tsx
|
|
- src/client/routes/login.tsx
|
|
- src/client/routes/profile.tsx
|
|
- src/client/routes/settings.tsx
|
|
- src/client/routes/collection/index.tsx
|
|
- src/client/routes/collection/gear.tsx
|
|
- src/client/routes/items/$itemId.tsx
|
|
- src/client/routes/threads/index.tsx
|
|
- src/client/routes/threads/$threadId.tsx
|
|
- src/client/routes/setups/$setupId.tsx
|
|
- src/client/routes/global-items/index.tsx
|
|
- src/client/routes/global-items/$itemId.tsx
|
|
- src/client/routes/users/$userId.tsx
|
|
autonomous: true
|
|
requirements: [D-01, D-02, D-03]
|
|
|
|
must_haves:
|
|
truths:
|
|
- "Every component with hardcoded English strings uses useTranslation() hook"
|
|
- "t() function calls reference keys that exist in the English locale JSON files from Plan 01"
|
|
- "User-generated content (item names, category names, thread titles, setup names) is NOT wrapped in t()"
|
|
- "All components import useTranslation from react-i18next"
|
|
- "No hardcoded English strings remain in UI chrome elements (buttons, labels, headings, nav items, empty states, error messages, toasts)"
|
|
artifacts:
|
|
- path: "src/client/components/TopNav.tsx"
|
|
provides: "Translated navigation"
|
|
contains: "useTranslation"
|
|
- path: "src/client/components/BottomTabBar.tsx"
|
|
provides: "Translated tab labels"
|
|
contains: "useTranslation"
|
|
- path: "src/client/routes/settings.tsx"
|
|
provides: "Translated settings page"
|
|
contains: "useTranslation"
|
|
key_links:
|
|
- from: "src/client/components/TopNav.tsx"
|
|
to: "src/client/locales/en/common.json"
|
|
via: "useTranslation('common')"
|
|
pattern: "t\\("
|
|
---
|
|
|
|
<objective>
|
|
Replace all hardcoded English strings in UI components with i18n t() calls.
|
|
|
|
Purpose: Complete string extraction — after this plan, all UI chrome text comes from translation files instead of hardcoded strings.
|
|
Output: Every component uses useTranslation() hook, all strings reference keys from en/*.json.
|
|
</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-RESEARCH.md
|
|
|
|
<interfaces>
|
|
useTranslation hook pattern:
|
|
```typescript
|
|
import { useTranslation } from "react-i18next";
|
|
|
|
function MyComponent() {
|
|
const { t } = useTranslation("common"); // or specific namespace
|
|
return <button>{t("actions.save")}</button>;
|
|
}
|
|
```
|
|
|
|
For multiple namespaces in one component:
|
|
```typescript
|
|
const { t } = useTranslation(["common", "collection"]);
|
|
// Access: t("common:actions.save"), t("collection:itemCard.title")
|
|
// Or with default namespace: t("actions.save") uses first in array
|
|
```
|
|
|
|
For interpolation:
|
|
```typescript
|
|
t("items.count", { count: 5 }) // "5 items"
|
|
t("items.count_one", { count: 1 }) // "1 item" (plural)
|
|
```
|
|
</interfaces>
|
|
</context>
|
|
|
|
<tasks>
|
|
|
|
<task type="auto">
|
|
<name>Task 1: Extract strings from navigation and global UI components</name>
|
|
<files>src/client/components/TopNav.tsx, src/client/components/BottomTabBar.tsx, src/client/components/FabMenu.tsx, src/client/components/UserMenu.tsx, src/client/routes/__root.tsx</files>
|
|
<read_first>src/client/components/TopNav.tsx, src/client/components/BottomTabBar.tsx, src/client/components/FabMenu.tsx, src/client/components/UserMenu.tsx, src/client/routes/__root.tsx, src/client/locales/en/common.json</read_first>
|
|
<behavior>
|
|
- TopNav.tsx uses t() for "Collection", "Setups", "Discover" nav labels and search placeholder
|
|
- BottomTabBar.tsx uses t() for "Home", "Collection", "Search", "Setups" tab labels
|
|
- FabMenu.tsx uses t() for all menu item labels
|
|
- UserMenu.tsx uses t() for menu items like "Settings", "Sign out", profile-related labels
|
|
- __root.tsx uses t() for "Something went wrong", "Try again", "Delete Candidate", "Pick Winner" dialog text
|
|
- All components import { useTranslation } from "react-i18next"
|
|
</behavior>
|
|
<action>
|
|
For each component listed in files, add `import { useTranslation } from "react-i18next"` and destructure `const { t } = useTranslation("common")` at the top of the component function body (or use the appropriate namespace).
|
|
|
|
**TopNav.tsx:**
|
|
- Replace "Collection" with `t("nav.collection")`
|
|
- Replace "Setups" with `t("nav.setups")`
|
|
- Replace "Discover" with `t("nav.discover")`
|
|
- Replace search input placeholder with `t("nav.searchPlaceholder")`
|
|
- Replace "GearBox" brand text — leave as-is (brand name, not translatable)
|
|
|
|
**BottomTabBar.tsx:**
|
|
- Replace "Home" with `t("nav.home")`
|
|
- Replace "Collection" with `t("nav.collection")`
|
|
- Replace "Search" with `t("nav.search")`
|
|
- Replace "Setups" with `t("nav.setups")`
|
|
|
|
**FabMenu.tsx:**
|
|
- Replace all menu item labels with `t("fab.addItem")`, `t("fab.newThread")`, `t("fab.newSetup")` etc. (read the component to find exact labels)
|
|
|
|
**UserMenu.tsx:**
|
|
- Replace "Settings" with `t("nav.settings")`
|
|
- Replace "Sign out" / "Log out" with `t("auth.signOut")`
|
|
- Replace other menu text with appropriate t() keys
|
|
|
|
**__root.tsx:**
|
|
- Replace "Something went wrong" with `t("errors.somethingWentWrong")`
|
|
- Replace "Try again" with `t("actions.tryAgain")`
|
|
- Replace "Delete Candidate" dialog title/text with `t("common:actions.deleteCandidate")` etc.
|
|
- Replace "Pick Winner" dialog with `t("threads:resolve.title")` etc.
|
|
- Replace "Cancel" buttons with `t("actions.cancel")`
|
|
- Replace "Delete" buttons with `t("actions.delete")`
|
|
|
|
**IMPORTANT:** Do NOT wrap user-generated content (candidateName, thread title, etc.) in t() — only UI chrome.
|
|
|
|
If any new keys are needed that were not included in Plan 01's locale files, add them to the appropriate en/*.json file as part of this task.
|
|
</action>
|
|
<acceptance_criteria>
|
|
- TopNav.tsx contains `useTranslation` import and `t(` calls
|
|
- BottomTabBar.tsx contains `useTranslation` import and `t(` calls for all tab labels
|
|
- FabMenu.tsx contains `useTranslation` import and `t(` calls
|
|
- UserMenu.tsx contains `useTranslation` import and `t(` calls
|
|
- __root.tsx contains `useTranslation` import and `t(` calls for dialog text
|
|
- No hardcoded English nav/tab labels remain in these 5 files
|
|
- `bun run build` succeeds
|
|
</acceptance_criteria>
|
|
<verify>
|
|
<automated>cd /home/jlmak/Projects/jlmak/GearBox && for f in TopNav BottomTabBar FabMenu UserMenu; do grep -c "useTranslation" src/client/components/$f.tsx; done && grep -c "useTranslation" src/client/routes/__root.tsx</automated>
|
|
</verify>
|
|
<done>Navigation and global UI components fully internationalized</done>
|
|
</task>
|
|
|
|
<task type="auto">
|
|
<name>Task 2: Extract strings from collection and item components</name>
|
|
<files>src/client/components/CollectionView.tsx, src/client/components/ItemCard.tsx, src/client/components/ItemForm.tsx, src/client/components/CategoryPicker.tsx, src/client/components/CategoryHeader.tsx, src/client/components/WeightSummaryCard.tsx, src/client/components/PlanningView.tsx, src/client/components/TotalsBar.tsx, src/client/components/DashboardCard.tsx, src/client/components/ClassificationBadge.tsx, src/client/components/ImageUpload.tsx, src/client/components/GearImage.tsx, src/client/components/GlobalItemCard.tsx, src/client/components/ManualEntryForm.tsx, src/client/components/LinkToGlobalItem.tsx, src/client/components/CategoryFilterDropdown.tsx, src/client/components/ItemPicker.tsx, src/client/components/ProfileSection.tsx</files>
|
|
<read_first>src/client/components/CollectionView.tsx, src/client/components/ItemCard.tsx, src/client/components/ItemForm.tsx, src/client/components/CategoryPicker.tsx, src/client/components/CategoryHeader.tsx, src/client/components/WeightSummaryCard.tsx, src/client/components/PlanningView.tsx, src/client/components/TotalsBar.tsx, src/client/components/DashboardCard.tsx, src/client/components/ClassificationBadge.tsx, src/client/components/ImageUpload.tsx, src/client/components/GlobalItemCard.tsx, src/client/components/ManualEntryForm.tsx, src/client/components/LinkToGlobalItem.tsx, src/client/components/CategoryFilterDropdown.tsx, src/client/components/ItemPicker.tsx, src/client/components/ProfileSection.tsx, src/client/locales/en/collection.json, src/client/locales/en/common.json</read_first>
|
|
<behavior>
|
|
- All listed components import { useTranslation } from "react-i18next"
|
|
- Each component uses const { t } = useTranslation("collection") (or "common" for shared strings)
|
|
- Headings like "Your Collection", "Items", "Weight Summary" use t() calls
|
|
- Form labels like "Name", "Brand", "Model", "Weight", "Price", "Notes" use t() calls
|
|
- Empty states like "No items yet" use t() calls
|
|
- Action buttons already covered by common namespace t() calls
|
|
- Weight classification labels ("Ultralight", "Light", "Medium", "Heavy") use t() calls
|
|
- User-generated content (item names, category names) is NOT wrapped in t()
|
|
</behavior>
|
|
<action>
|
|
For each component in the files list:
|
|
1. Add `import { useTranslation } from "react-i18next"`
|
|
2. Add `const { t } = useTranslation("collection")` (or `["collection", "common"]` for components that need both namespaces)
|
|
3. Replace every hardcoded English string with the corresponding `t()` call
|
|
|
|
**Key mappings (use namespace-prefixed keys when mixing namespaces):**
|
|
|
|
Collection components use `collection` namespace:
|
|
- Headings: `t("title")`, `t("gear")`, `t("planning")`
|
|
- Empty states: `t("empty.noItems")`, `t("empty.noCategories")`
|
|
- Item form labels: `t("form.name")`, `t("form.brand")`, `t("form.model")`, `t("form.weight")`, `t("form.price")`, `t("form.notes")`, `t("form.category")`
|
|
- Item form placeholders
|
|
- Weight summary labels
|
|
- Classification badges: `t("classification.ultralight")`, etc.
|
|
- Totals: `t("totals.totalWeight")`, `t("totals.totalPrice")`, `t("totals.itemCount")`
|
|
|
|
Common-namespace strings (buttons, actions) accessed via `t("common:actions.save")` or by passing array `["collection", "common"]`.
|
|
|
|
**For components that only use common strings** (like ImageUpload, GearImage): use `useTranslation("common")`.
|
|
|
|
**If a component has no translatable strings** (purely renders user data with no UI chrome), skip it — do NOT add unnecessary imports.
|
|
|
|
Add any new keys needed to `src/client/locales/en/collection.json` and `src/client/locales/en/common.json`.
|
|
</action>
|
|
<acceptance_criteria>
|
|
- CollectionView.tsx contains useTranslation import and t() calls
|
|
- ItemForm.tsx uses t() for all form labels (name, brand, model, weight, price, notes)
|
|
- CategoryPicker.tsx uses t() for search placeholder and "Create category" text
|
|
- WeightSummaryCard.tsx uses t() for summary labels
|
|
- ClassificationBadge.tsx uses t() for classification names
|
|
- No hardcoded English strings remain in UI chrome of these components
|
|
- `bun run build` succeeds
|
|
</acceptance_criteria>
|
|
<verify>
|
|
<automated>cd /home/jlmak/Projects/jlmak/GearBox && for f in CollectionView ItemCard ItemForm CategoryPicker WeightSummaryCard; do echo -n "$f: "; grep -c "useTranslation" src/client/components/$f.tsx; done</automated>
|
|
</verify>
|
|
<done>Collection and item components fully internationalized</done>
|
|
</task>
|
|
|
|
<task type="auto">
|
|
<name>Task 3: Extract strings from thread and candidate components</name>
|
|
<files>src/client/components/ThreadCard.tsx, src/client/components/ThreadTabs.tsx, src/client/components/CandidateCard.tsx, src/client/components/CandidateForm.tsx, src/client/components/CandidateListItem.tsx, src/client/components/ComparisonTable.tsx, src/client/components/CreateThreadModal.tsx, src/client/components/StatusBadge.tsx, src/client/components/AddToThreadModal.tsx</files>
|
|
<read_first>src/client/components/ThreadCard.tsx, src/client/components/ThreadTabs.tsx, src/client/components/CandidateCard.tsx, src/client/components/CandidateForm.tsx, src/client/components/CandidateListItem.tsx, src/client/components/ComparisonTable.tsx, src/client/components/CreateThreadModal.tsx, src/client/components/StatusBadge.tsx, src/client/components/AddToThreadModal.tsx, src/client/locales/en/threads.json</read_first>
|
|
<behavior>
|
|
- All listed components import useTranslation from react-i18next
|
|
- Thread components use "threads" namespace
|
|
- Status labels ("Active", "Resolved", "Archived") use t() calls
|
|
- Thread creation modal labels use t() calls
|
|
- Candidate form labels use t() calls
|
|
- Comparison table headers use t() calls
|
|
- Thread/candidate names are NOT wrapped in t() (user-generated content)
|
|
</behavior>
|
|
<action>
|
|
For each component:
|
|
1. Add `import { useTranslation } from "react-i18next"`
|
|
2. Add `const { t } = useTranslation("threads")` (or `["threads", "common"]`)
|
|
3. Replace all hardcoded English UI chrome strings with t() calls
|
|
|
|
**Key mappings for threads namespace:**
|
|
- Status: `t("status.active")`, `t("status.resolved")`, `t("status.archived")`
|
|
- Create modal: `t("create.title")`, `t("create.namePlaceholder")`, `t("create.description")`
|
|
- Candidate form: `t("candidate.name")`, `t("candidate.price")`, `t("candidate.weight")`, `t("candidate.url")`, `t("candidate.pros")`, `t("candidate.cons")`, `t("candidate.notes")`
|
|
- Comparison headers: `t("comparison.weight")`, `t("comparison.price")`, `t("comparison.pros")`, `t("comparison.cons")`
|
|
- Actions: use common namespace for buttons
|
|
|
|
Add any new keys to `src/client/locales/en/threads.json`.
|
|
</action>
|
|
<acceptance_criteria>
|
|
- ThreadCard.tsx contains useTranslation import and t() calls
|
|
- CandidateForm.tsx uses t() for all form labels
|
|
- ComparisonTable.tsx uses t() for column headers
|
|
- StatusBadge.tsx uses t() for status labels
|
|
- CreateThreadModal.tsx uses t() for modal title and form labels
|
|
- No hardcoded English status labels remain
|
|
- `bun run build` succeeds
|
|
</acceptance_criteria>
|
|
<verify>
|
|
<automated>cd /home/jlmak/Projects/jlmak/GearBox && for f in ThreadCard CandidateForm ComparisonTable StatusBadge CreateThreadModal; do echo -n "$f: "; grep -c "useTranslation" src/client/components/$f.tsx; done</automated>
|
|
</verify>
|
|
<done>Thread and candidate components fully internationalized</done>
|
|
</task>
|
|
|
|
<task type="auto">
|
|
<name>Task 4: Extract strings from setup, modal, and route components</name>
|
|
<files>src/client/components/SetupsView.tsx, src/client/components/SetupCard.tsx, src/client/components/SetupImpactSelector.tsx, src/client/components/ShareModal.tsx, src/client/components/PublicSetupCard.tsx, src/client/components/ImpactDeltaBadge.tsx, src/client/components/ConfirmDialog.tsx, src/client/components/ExternalLinkDialog.tsx, src/client/components/AddToCollectionModal.tsx, src/client/components/SlideOutPanel.tsx, src/client/routes/index.tsx, src/client/routes/login.tsx, src/client/routes/profile.tsx, src/client/routes/collection/index.tsx, src/client/routes/collection/gear.tsx, src/client/routes/items/$itemId.tsx, src/client/routes/threads/index.tsx, src/client/routes/threads/$threadId.tsx, src/client/routes/setups/$setupId.tsx, src/client/routes/global-items/index.tsx, src/client/routes/global-items/$itemId.tsx, src/client/routes/users/$userId.tsx</files>
|
|
<read_first>src/client/components/SetupsView.tsx, src/client/components/SetupCard.tsx, src/client/components/SetupImpactSelector.tsx, src/client/components/ShareModal.tsx, src/client/components/PublicSetupCard.tsx, src/client/components/ConfirmDialog.tsx, src/client/components/ExternalLinkDialog.tsx, src/client/components/AddToCollectionModal.tsx, src/client/routes/index.tsx, src/client/routes/login.tsx, src/client/routes/profile.tsx, src/client/routes/collection/index.tsx, src/client/routes/items/$itemId.tsx, src/client/routes/threads/index.tsx, src/client/routes/threads/$threadId.tsx, src/client/routes/setups/$setupId.tsx, src/client/routes/global-items/index.tsx, src/client/routes/global-items/$itemId.tsx, src/client/routes/users/$userId.tsx, src/client/locales/en/setups.json, src/client/locales/en/common.json</read_first>
|
|
<behavior>
|
|
- Setup components use "setups" namespace for setup-specific strings
|
|
- Modal/dialog components use "common" namespace
|
|
- Route pages use their respective namespace (collection routes use "collection", thread routes use "threads", etc.)
|
|
- Landing page (index.tsx) strings use "common" namespace
|
|
- Login page strings use "common" namespace
|
|
- All user-generated content (setup names, thread titles, user names) is NOT wrapped in t()
|
|
</behavior>
|
|
<action>
|
|
For each component/route:
|
|
1. Add `import { useTranslation } from "react-i18next"`
|
|
2. Add `const { t } = useTranslation(...)` with appropriate namespace
|
|
3. Replace all hardcoded English UI chrome strings with t() calls
|
|
|
|
**Setup namespace keys:**
|
|
- `t("title")`, `t("create")`, `t("empty")`, `t("card.items")`, `t("card.weight")`, `t("card.price")`
|
|
- Share: `t("share.title")`, `t("share.copyLink")`, `t("share.copied")`
|
|
- Impact: `t("impact.title")`, `t("impact.adding")`, `t("impact.removing")`
|
|
|
|
**Common namespace for modals/dialogs:**
|
|
- ConfirmDialog: `t("confirm.title")`, `t("confirm.message")`
|
|
- ExternalLinkDialog: `t("externalLink.title")`, `t("externalLink.message")`
|
|
- AddToCollectionModal: `t("addToCollection.title")`
|
|
|
|
**Route pages:** Use the matching namespace. Page-level headings and descriptions get t() calls. Links back ("Back") use `t("common:actions.back")`.
|
|
|
|
Add any new keys to the appropriate en/*.json files.
|
|
</action>
|
|
<acceptance_criteria>
|
|
- SetupsView.tsx contains useTranslation import
|
|
- SetupCard.tsx uses t() for card labels
|
|
- ShareModal.tsx uses t() for share dialog text
|
|
- ConfirmDialog.tsx uses t() for confirmation dialog text
|
|
- Login page (routes/login.tsx) uses t() for login page text
|
|
- Landing page (routes/index.tsx) uses t() for discovery page text
|
|
- Route pages use appropriate namespaces
|
|
- `bun run build` succeeds
|
|
</acceptance_criteria>
|
|
<verify>
|
|
<automated>cd /home/jlmak/Projects/jlmak/GearBox && grep -rl "useTranslation" src/client/components/ src/client/routes/ | wc -l</automated>
|
|
</verify>
|
|
<done>Setups, modals, dialogs, and all route pages fully internationalized</done>
|
|
</task>
|
|
|
|
<task type="auto">
|
|
<name>Task 5: Extract strings from onboarding and settings components</name>
|
|
<files>src/client/components/onboarding/OnboardingWelcome.tsx, src/client/components/onboarding/OnboardingHobbyPicker.tsx, src/client/components/onboarding/OnboardingItemBrowser.tsx, src/client/components/onboarding/OnboardingReview.tsx, src/client/components/onboarding/OnboardingDone.tsx, src/client/components/onboarding/OnboardingFlow.tsx, src/client/components/onboarding/StepIndicator.tsx, src/client/components/onboarding/HobbyCard.tsx, src/client/components/onboarding/SelectableItemCard.tsx, src/client/routes/settings.tsx</files>
|
|
<read_first>src/client/components/onboarding/OnboardingWelcome.tsx, src/client/components/onboarding/OnboardingHobbyPicker.tsx, src/client/components/onboarding/OnboardingItemBrowser.tsx, src/client/components/onboarding/OnboardingReview.tsx, src/client/components/onboarding/OnboardingDone.tsx, src/client/components/onboarding/OnboardingFlow.tsx, src/client/components/onboarding/StepIndicator.tsx, src/client/components/onboarding/HobbyCard.tsx, src/client/components/onboarding/SelectableItemCard.tsx, src/client/routes/settings.tsx, src/client/locales/en/onboarding.json, src/client/locales/en/settings.json</read_first>
|
|
<behavior>
|
|
- Onboarding components use "onboarding" namespace
|
|
- Welcome screen: title, subtitle, CTA button text use t()
|
|
- Hobby picker: heading, description use t() (hobby names MAY be translatable if they are system-defined)
|
|
- Item browser: heading, description, search placeholder use t()
|
|
- Review: heading, description use t()
|
|
- Done: heading, description, CTA button use t()
|
|
- Settings page uses "settings" namespace
|
|
- Settings labels (Weight Unit, Currency, API Keys, Import/Export) use t()
|
|
- Settings descriptions use t()
|
|
</behavior>
|
|
<action>
|
|
For each onboarding component:
|
|
1. Add `import { useTranslation } from "react-i18next"`
|
|
2. Add `const { t } = useTranslation("onboarding")`
|
|
3. Replace all hardcoded strings with t() calls
|
|
|
|
**Onboarding namespace keys:**
|
|
- Welcome: `t("welcome.title")`, `t("welcome.subtitle")`, `t("welcome.cta")`
|
|
- HobbyPicker: `t("hobby.title")`, `t("hobby.subtitle")`, `t("hobby.next")`
|
|
- ItemBrowser: `t("items.title")`, `t("items.subtitle")`, `t("items.searchPlaceholder")`, `t("items.next")`
|
|
- Review: `t("review.title")`, `t("review.subtitle")`
|
|
- Done: `t("done.title")`, `t("done.subtitle")`, `t("done.cta")`
|
|
- Step indicators: `t("step.of", { current: 1, total: 5 })`
|
|
|
|
For settings.tsx:
|
|
1. Add `import { useTranslation } from "react-i18next"`
|
|
2. Add `const { t } = useTranslation("settings")`
|
|
3. Replace settings labels:
|
|
- "Settings" heading: `t("title")`
|
|
- "Back": use `t("common:actions.back")`
|
|
- "Weight Unit" label: `t("weightUnit.title")`
|
|
- "Choose the unit used to display weights across the app": `t("weightUnit.description")`
|
|
- "Currency" label: `t("currency.title")`
|
|
- "Changes the currency symbol displayed. This does not convert values.": `t("currency.description")`
|
|
- "API Keys" heading: `t("apiKeys.title")`
|
|
- "API keys allow programmatic access...": `t("apiKeys.description")`
|
|
- "Copy this key now — it won't be shown again:": `t("apiKeys.copyWarning")`
|
|
- "Dismiss": `t("common:actions.dismiss")`
|
|
- "Key name (e.g., claude-desktop)": `t("apiKeys.namePlaceholder")`
|
|
- "Create": `t("common:actions.create")`
|
|
- "Revoke": `t("apiKeys.revoke")`
|
|
- "Import / Export" heading: `t("importExport.title")`
|
|
- "Export your gear collection as a CSV...": `t("importExport.description")`
|
|
- "Export CSV": `t("importExport.export")`
|
|
- "Import CSV": `t("importExport.import")`
|
|
- "Importing...": `t("importExport.importing")`
|
|
- Import result messages
|
|
|
|
Add any new keys to the appropriate en/*.json files.
|
|
</action>
|
|
<acceptance_criteria>
|
|
- All 9 onboarding component files contain useTranslation import
|
|
- OnboardingWelcome.tsx uses t() for title, subtitle, and CTA
|
|
- OnboardingDone.tsx uses t() for done screen text
|
|
- settings.tsx uses t() for all section headings, labels, descriptions
|
|
- settings.tsx "Weight Unit" label uses `t("weightUnit.title")`
|
|
- settings.tsx "API Keys" section uses `t("apiKeys.title")`
|
|
- No hardcoded English strings remain in onboarding or settings UI chrome
|
|
- `bun run build` succeeds
|
|
</acceptance_criteria>
|
|
<verify>
|
|
<automated>cd /home/jlmak/Projects/jlmak/GearBox && grep -c "useTranslation" src/client/routes/settings.tsx && for f in OnboardingWelcome OnboardingHobbyPicker OnboardingItemBrowser OnboardingReview OnboardingDone; do echo -n "$f: "; grep -c "useTranslation" src/client/components/onboarding/$f.tsx; done</automated>
|
|
</verify>
|
|
<done>Onboarding flow and settings page fully internationalized</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-03 | 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. |
|
|
</threat_model>
|
|
|
|
<verification>
|
|
- `bun run build` succeeds
|
|
- grep -rl "useTranslation" finds matches in all major component and route files
|
|
- No hardcoded English UI chrome strings remain in extracted components
|
|
</verification>
|
|
|
|
<success_criteria>
|
|
- All UI components use useTranslation() hook
|
|
- All hardcoded English strings replaced with t() calls
|
|
- User-generated content is NOT wrapped in t()
|
|
- Build passes
|
|
</success_criteria>
|
|
|
|
<output>
|
|
After completion, create `.planning/phases/34-i18n-foundation/34-02-SUMMARY.md`
|
|
</output>
|