Files
GearBox/.planning/phases/34-i18n-foundation/34-REVIEW.md

7.8 KiB

phase, reviewed, depth, files_reviewed, files_reviewed_list, findings, status
phase reviewed depth files_reviewed files_reviewed_list findings status
34-i18n-foundation 2026-04-18T14:30:00Z standard 40
package.json
src/client/components/AddToCollectionModal.tsx
src/client/components/ClassificationBadge.tsx
src/client/components/ImageUpload.tsx
src/client/components/ImpactDeltaBadge.tsx
src/client/components/PlanningView.tsx
src/client/components/PublicSetupCard.tsx
src/client/components/SetupImpactSelector.tsx
src/client/components/ThreadCard.tsx
src/client/components/ThreadTabs.tsx
src/client/components/TotalsBar.tsx
src/client/hooks/useFormatters.ts
src/client/hooks/useLanguage.ts
src/client/lib/formatters.ts
src/client/lib/i18n.ts
src/client/locales/de/catalog.json
src/client/locales/de/collection.json
src/client/locales/de/common.json
src/client/locales/de/onboarding.json
src/client/locales/de/settings.json
src/client/locales/de/setups.json
src/client/locales/de/threads.json
src/client/locales/en/catalog.json
src/client/locales/en/collection.json
src/client/locales/en/common.json
src/client/locales/en/onboarding.json
src/client/locales/en/settings.json
src/client/locales/en/setups.json
src/client/locales/en/threads.json
src/client/main.tsx
src/client/routes/__root.tsx
src/client/routes/collection/index.tsx
src/client/routes/global-items/index.tsx
src/client/routes/index.tsx
src/client/routes/items/$itemId.tsx
src/client/routes/profile.tsx
src/client/routes/settings.tsx
src/client/routes/setups/$setupId.tsx
src/client/routes/threads/$threadId/index.tsx
src/client/routes/users/$userId.tsx
tests/formatters.test.ts
critical warning info total
1 3 3 7
issues_found

Phase 34: Code Review Report

Reviewed: 2026-04-18T14:30:00Z Depth: standard Files Reviewed: 40 Status: issues_found

Summary

This review covers the i18n foundation implementation across 40 files: the i18n library setup, locale JSON files (English and German), React components and routes using useTranslation, formatting utilities, and associated tests. The i18n architecture is well-structured with namespace separation, proper fallback language configuration, and locale-aware formatting.

One critical bug was found where the account deletion confirmation check is hardcoded to English ("DELETE") but the German locale instructs users to type "LOSCHEN", making account deletion impossible for German-language users. Several warnings address incomplete i18n adoption (hardcoded locale in date formatting, hardcoded English placeholder strings) and a subtle falsy-value bug. Info items cover variable shadowing and a debug console.error statement.

Critical Issues

CR-01: Account Deletion Confirmation Hardcoded to English

File: src/client/routes/profile.tsx:380 Issue: The delete account confirmation checks confirmation !== "DELETE" but the German locale (de/common.json:118-119) instructs users to type "LOSCHEN". German-language users cannot delete their accounts because the hardcoded string comparison will never match their input. Fix: Use a locale-aware confirmation word, or always use "DELETE" in both locale files:

Option A -- Use a translation key for the confirmation word:

// Add to common.json: "deleteConfirmWord": "DELETE" (en), "deleteConfirmWord": "LOSCHEN" (de)
const confirmWord = t("profile.deleteConfirmWord");
// ...
disabled={confirmation !== confirmWord || deleteAccount.isPending}

Option B -- Standardize on "DELETE" in both locales:

// de/common.json
"deleteConfirmMessage": "Diese Aktion ist dauerhaft. Geben Sie DELETE ein, um zu bestätigen.",
"deleteConfirmPlaceholder": "Geben Sie DELETE ein, um zu bestätigen"

Warnings

WR-01: Hardcoded Locale in ThreadCard Date Formatting

File: src/client/components/ThreadCard.tsx:20 Issue: The formatDate function hardcodes "en-US" locale: d.toLocaleDateString("en-US", { month: "short", day: "numeric" }). This ignores the user's language preference and will always display English month abbreviations (e.g., "Apr 18" instead of "18. Apr." for German users). Fix: Accept and use the locale from useLanguage() or pass undefined to use the browser default:

function formatDate(iso: string, locale?: string): string {
    const d = new Date(iso);
    return d.toLocaleDateString(locale, { month: "short", day: "numeric" });
}

// In the component:
const { locale } = useFormatters();
// ...
{formatDate(createdAt, locale)}

WR-02: Hardcoded English Placeholder Strings in Item Edit Form

File: src/client/routes/items/$itemId.tsx:432-440 Issue: Two input placeholders are hardcoded English strings instead of using translation keys:

  • Line 432: placeholder="Brand / Manufacturer (optional)"
  • Line 440: placeholder="Item name / Model"

These will display in English regardless of the user's language setting. Fix: Add translation keys and use them:

placeholder={t("collection:form.brandPlaceholder")}
// ...
placeholder={t("collection:form.modelPlaceholder")}

WR-03: Falsy Check on purchasePriceCents Discards Zero Values

File: src/client/components/AddToCollectionModal.tsx:67 Issue: purchasePriceCents || undefined uses a falsy check. If a user enters a purchase price of $0.00, purchasePriceCents will be 0, which is falsy. The value will be silently discarded and not sent to the API. While $0.00 purchase price is uncommon, it is valid (e.g., a gifted item). Fix: Use a nullish check instead:

purchasePriceCents: purchasePriceCents ?? undefined,

Or keep the existing purchasePrice string check and only convert when present:

purchasePriceCents: purchasePrice
    ? Math.round(Number.parseFloat(purchasePrice) * 100)
    : undefined,

Info

IN-01: Variable Shadowing of Translation Function t

File: src/client/components/PlanningView.tsx:31-32 Issue: The .filter() callbacks use t as the parameter name, shadowing the t translation function from useTranslation. While this does not cause a runtime bug (the callbacks access object properties, not call the translation function), it is confusing and could lead to future bugs if someone tries to use translation inside the filter. Fix: Rename the filter parameter to a more descriptive name:

const filteredThreads = (threads ?? [])
    .filter((thread) => thread.status === activeTab)
    .filter((thread) => (categoryFilter ? thread.categoryId === categoryFilter : true));

IN-02: console.error Left in Production Code

File: src/client/routes/profile.tsx:331 Issue: console.error("Account deletion failed:", err) is left in the DangerZoneSection's error handler. While error logging can be useful, this appears to be a debug artifact since the error is not surfaced to the user. Fix: Either show the error to the user via a state message, or remove the console.error:

} catch (err) {
    setMessage({ type: "error", text: (err as Error).message });
}

IN-03: Missing German Locale-Aware Date Formatting in PublicSetupCard

File: src/client/components/PublicSetupCard.tsx:16-22 Issue: toLocaleDateString(undefined, ...) delegates to browser locale detection rather than the app's language setting. This means a German-language user on an English-locale browser will see English date formats. This is a minor inconsistency with the rest of the i18n implementation which explicitly passes locale. Fix: Use the useLanguage() hook to pass the app's language:

const language = useLanguage();
const formattedDate = new Date(setup.createdAt).toLocaleDateString(language, {
    year: "numeric",
    month: "short",
    day: "numeric",
});

Reviewed: 2026-04-18T14:30:00Z Reviewer: Claude (gsd-code-reviewer) Depth: standard