184 lines
7.8 KiB
Markdown
184 lines
7.8 KiB
Markdown
---
|
|
phase: 34-i18n-foundation
|
|
reviewed: 2026-04-18T14:30:00Z
|
|
depth: standard
|
|
files_reviewed: 40
|
|
files_reviewed_list:
|
|
- 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
|
|
findings:
|
|
critical: 1
|
|
warning: 3
|
|
info: 3
|
|
total: 7
|
|
status: 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:
|
|
```tsx
|
|
// 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:
|
|
```json
|
|
// 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:
|
|
```tsx
|
|
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:
|
|
```tsx
|
|
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:
|
|
```tsx
|
|
purchasePriceCents: purchasePriceCents ?? undefined,
|
|
```
|
|
Or keep the existing `purchasePrice` string check and only convert when present:
|
|
```tsx
|
|
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:
|
|
```tsx
|
|
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:
|
|
```tsx
|
|
} 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:
|
|
```tsx
|
|
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_
|