Files
GearBox/.planning/phases/34-i18n-foundation/34-02-PLAN.md

25 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
34-i18n-foundation 02 execute 1
01
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
true
D-01
D-02
D-03
truths artifacts key_links
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)
path provides contains
src/client/components/TopNav.tsx Translated navigation useTranslation
path provides contains
src/client/components/BottomTabBar.tsx Translated tab labels useTranslation
path provides contains
src/client/routes/settings.tsx Translated settings page useTranslation
from to via pattern
src/client/components/TopNav.tsx src/client/locales/en/common.json useTranslation('common') t(
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.

<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_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 useTranslation hook pattern: ```typescript import { useTranslation } from "react-i18next";

function MyComponent() { const { t } = useTranslation("common"); // or specific namespace return {t("actions.save")}; }


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:

t("items.count", { count: 5 }) // "5 items"
t("items.count_one", { count: 1 }) // "1 item" (plural)
Task 1: Extract strings from navigation and global UI components 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/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 - 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" 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. <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> 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 Navigation and global UI components fully internationalized

Task 2: Extract strings from collection and item components 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 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 - 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() 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. <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> 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 Collection and item components fully internationalized

Task 3: Extract strings from thread and candidate components 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/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 - 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) 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. <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> 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 Thread and candidate components fully internationalized

Task 4: Extract strings from setup, modal, and route components 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 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 - 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() 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. <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> cd /home/jlmak/Projects/jlmak/GearBox && grep -rl "useTranslation" src/client/components/ src/client/routes/ | wc -l Setups, modals, dialogs, and all route pages fully internationalized

Task 5: Extract strings from onboarding and settings components 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/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 - 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() 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. <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> 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 Onboarding flow and settings page fully internationalized

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

<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>
After completion, create `.planning/phases/34-i18n-foundation/34-02-SUMMARY.md`