# i18n Integration Learnings ## 2026-03-13 - Locale File Structure ### Structure Created - **Directory**: `src/renderer/src/locales/` - **Subdirectories**: `default/`, `zh-CN/`, `ja-JP/`, `en-US/` - **Files per language**: `common.json`, `domain.json`, `settings.json`, `errors.json` ### Translation Key Format - Use flat key structure (no nested objects): `"app.title"` instead of `{"app": {"title": ""}}` - Each file contains 20+ translation keys (exceeds minimum 5) - Keys are organized by feature area: - `common.json`: UI buttons, labels, status messages - `domain.json`: Domain management specific translations - `settings.json`: Settings page translations - `errors.json`: Error messages and validation errors ### Language Codes - `default/`: Source language (Chinese) - `zh-CN/`: Simplified Chinese - `en-US/`: English (US) - `ja-JP/`: Japanese ### Best Practices 1. Keep translation keys descriptive (e.g., `collapseSidebar` not `cs`) 2. Group related keys together in the same file 3. Use consistent naming convention (camelCase for keys) ## [2026-03-13] - Locale Store Implementation ### Pattern: Zustand Store with Persist Middleware Created `localeStore.ts` following the existing `domainStore.ts` pattern: ```typescript export const useLocaleStore = create()( persist( (set) => ({ locale: DEFAULT_LOCALE, setLocale: (locale) => set({ locale }), }), { name: "locale-storage", partialize: (state) => ({ locale: state.locale }), }, ), ); ``` ### Key Decisions - Use `partialize` to only persist the `locale` value, not actions - Import `DEFAULT_LOCALE` from shared types to ensure consistency - Store key is `locale-storage` in localStorage ### File Location - Store: `src/renderer/src/stores/localeStore.ts` - Types: `src/shared/types/locale.ts` (LocaleCode, DEFAULT_LOCALE) ## [2026-03-13] - i18n Instance Configuration ### Pattern: Static Import Configuration Created `i18n.ts` with static imports for all locale files: ```typescript // Import locale files statically import commonZHCN from "./locales/zh-CN/common.json"; import domainZHCN from "./locales/zh-CN/domain.json"; // ... (all 12 files imported) const resources = { "zh-CN": { common: commonZHCN, domain: domainZHCN, ... }, "ja-JP": { ... }, "en-US": { ... }, }; ``` ### Key Configuration Options - **fallbackLng**: `zh-CN` - fallback to Chinese if translation missing - **defaultNS**: `common` - default namespace for `t()` calls - **ns**: `['common', 'domain', 'settings', 'errors']` - 4 namespaces - **escapeValue**: `false` - React handles XSS protection - **detection**: localStorage + navigator order, caches to localStorage ### Language Detector Configuration ```typescript detection: { order: ["localStorage", "navigator"], caches: ["localStorage"], lookupLocalStorage: "i18nextLng", } ``` This ensures: 1. First check localStorage for saved preference 2. Fall back to browser language 3. Persist detected/selected language to localStorage ### File Location - Configuration: `src/renderer/src/i18n.ts` - Must be imported in `main.tsx` before React renders ## [2026-03-13] - Dependencies Installation ### Installed Packages - `i18next@25.8.18` - Core i18n framework - `react-i18next@16.5.8` - React bindings for i18next - `i18next-browser-languagedetector@8.2.1` - Browser language detection - `@lobehub/i18n-cli@1.26.1` - LobeHub i18n CLI tool ### Notes - React-i18next correctly dedupes i18next dependency - All packages are latest stable versions - LobeHub UI uses @emoji-mart/react which has peer dependency warning for React 19 (non-blocking) ## [2026-03-13] - @lobehub/i18n-cli Configuration ### Configuration Pattern - Binary name: `lobe-i18n` (not `@lobehub/i18n-cli`) - Config file: `.i18nrc.js` (CommonJS format) - Uses environment variable for API key: `OPENAI_API_KEY` ### Locale Structure for CLI - `locales/default/` - Source files (Chinese) - `locales/zh-CN/` - Same as default (for consistency) - `locales/en-US/` - English translations - `locales/ja-JP/` - Japanese translations ### Important Notes - entryLocale = source language (zh-CN = Chinese) - outputLocales = target languages for AI translation - Do NOT put zh-CN in outputLocales if it's the source - The CLI supports markdown translation with `--with-md` flag ### npm script ```json "i18n": "lobe-i18n" ``` Run with: `npm run i18n` ## 2026-03-14 - I18nextProvider Integration ### Changes Made - Added `I18nextProvider` import from `react-i18next` - Added `i18n` instance import from `./i18n` - Wrapped `` with `` to enable i18n throughout the app ### Integration Pattern ```tsx ``` ### Notes - The i18n instance from `./i18n` is already initialized (chained `.use().init()`) - No additional initialization needed in main.tsx - I18nextProvider wraps ThemeProvider (not the other way around) - I18nextProvider wraps ThemeProvider (not the other way around) ## [2026-03-14] - Ant Design Locale Synchronization ### Pattern: Locale Mapping - Use a mapping function to convert i18next locale codes to Ant Design locale objects - Supported mapping: - `zh-CN` → `zhCN` (antd/locale/zh_CN) - `ja-JP` → `jaJP` (antd/locale/ja_JP) - `en-US` → `enUS` (antd/locale/en_US) ### Implementation ```typescript // Import all locales import zhCN from "antd/locale/zh_CN"; import jaJP from "antd/locale/ja_JP"; import enUS from "antd/locale/en_US"; import type { Locale } from "antd/es/locale"; // Map locale code to Ant Design locale const getAntdLocale = React.useCallback((localeCode: string): Locale => { switch (localeCode) { case "ja-JP": return jaJP; case "en-US": return enUS; case "zh-CN": default: return zhCN; } }, []); // Memoize the result const antdLocale = React.useMemo( () => getAntdLocale(locale), [locale, getAntdLocale] ); // Use in ConfigProvider ``` ### Key Points - Use `React.useCallback` for the mapping function to prevent unnecessary re-renders - Use `React.useMemo` to memoize the locale result based on locale changes - Import `Locale` type from `antd/es/locale` for type safety - Default fallback to `zhCN` for unsupported locales ## [2026-03-14] - Preload Locale API Exposure ### Files Modified - `src/shared/types/ipc.ts` - Added `SetLocaleParams` interface - `src/preload/index.ts` - Added `getLocale` and `setLocale` IPC invocations - `src/preload/index.d.ts` - Added type declarations for locale API ### Pattern: IPC Exposure in Preload ```typescript // In preload/index.ts const api: SelfAPI = { // ... other APIs // Locale getLocale: () => ipcRenderer.invoke("getLocale"), setLocale: (params) => ipcRenderer.invoke("setLocale", params), }; ``` ### Type Declaration Pattern ```typescript // In preload/index.d.ts import type { SetLocaleParams } from "@shared/types/ipc"; import type { LocaleCode } from "@shared/types/locale"; export interface SelfAPI { // ... other methods // ==================== Locale ==================== getLocale: () => Promise>; setLocale: (params: SetLocaleParams) => Promise>; } ``` ### Key Points - Follow existing IPC patterns (invoke with channel name and params) - Use `Result` wrapper for return types - Import types from `@shared/types/` path alias - Add IPC type params interface to `ipc.ts` ## 2026-03-14 - Main Process IPC Locale Synchronization ### Implementation Pattern **Storage Layer** (`src/main/storage.ts`): - Added `locale?: LocaleCode` to `AppConfig` interface - Implemented `getLocale()` - reads locale from config.json, returns `DEFAULT_LOCALE` if not set - Implemented `setLocale(locale: LocaleCode)` - saves locale to config.json - Uses existing `readJsonFile`/`writeJsonFile` pattern for consistency **IPC Handlers** (`src/main/ipc-handlers.ts`): - `getLocale` handler: Returns `Promise>` - `setLocale` handler: Takes `SetLocaleParams`, returns `Promise>` - Uses existing `handle` helper wrapper for error handling **Types** (`src/shared/types/ipc.ts`): - `SetLocaleParams` interface already exists with `locale: LocaleCode` property - Follows pattern of other params objects (e.g., `CreateDomainParams`, `UpdateDomainParams`) **Preload** (`src/preload/index.ts`): - `getLocale: () => ipcRenderer.invoke("getLocale")` - `setLocale: (params) => ipcRenderer.invoke("setLocale", params)` ### Key Decisions 1. **Not using react-i18next in main process**: Main process uses simple locale string storage, not full i18n library 2. **Params object pattern**: `setLocale` uses `SetLocaleParams` object instead of `LocaleCode` directly for consistency with other IPC handlers 3. **Default fallback**: `getLocale()` returns `DEFAULT_LOCALE` ("zh-CN") when no locale is set 4. **Storage location**: Locale stored in `config.json` alongside domains array ### File Changes Summary - `src/main/storage.ts`: Added `getLocale()` and `setLocale()` functions - `src/main/ipc-handlers.ts`: Added `registerGetLocale()` and `registerSetLocale()` handlers - `src/shared/types/ipc.ts`: `SetLocaleParams` already existed - `src/preload/index.d.ts`: Locale API types already existed - `src/preload/index.ts`: Locale methods already existed ## 2026-03-14 - App.tsx Internationalization ### Pattern Used - Import: `import { useTranslation } from "react-i18next";` - Hook usage: `const { t } = useTranslation();` - Translation call: `{t('translationKey')}` ### Translation Keys Added - `deployFiles` - "部署文件" / "Deploy Files" / "ファイルをデプロイ" - `versionHistory` - "版本历史" / "Version History" / "バージョン履歴" - `settings` - "设置" / "Settings" / "設定" ### Existing Keys Used - `collapseSidebar` - "收起侧边栏" - `expandSidebar` - "展开侧边栏" ### Files Modified 1. `src/renderer/src/App.tsx` - Added useTranslation hook, replaced hardcoded text 2. `src/renderer/src/locales/zh-CN/common.json` - Added new keys 3. `src/renderer/src/locales/en-US/common.json` - Added new keys 4. `src/renderer/src/locales/ja-JP/common.json` - Added new keys 5. `src/renderer/src/locales/default/common.json` - Added new keys ### i18n Setup - Uses `react-i18next` with `I18nextProvider` wrapper in main.tsx - Default namespace: `common` - Fallback language: `zh-CN` - Locale files: `./locales/{lang}/{namespace}.json` ## [2026-03-14] - DomainManager Component Internationalization ### Files Modified - `src/renderer/src/components/DomainManager/DomainManager.tsx` - `src/renderer/src/locales/default/domain.json` ### Pattern: useTranslation Hook with Namespace ```typescript import { useTranslation } from "react-i18next"; const DomainManager: React.FC = ({...}) => { const { t } = useTranslation("domain"); // ... return

{t("domainManagement")}

; }; ``` ### Translation Keys Added - `noDomainSelected`: "未选择 Domain" - `addDomainTooltip`: "添加 Domain" - `domainManagement`: "Domain 管理" - `add`: "添加" - `noDomainConfig`: "暂无 Domain 配置" - `addFirstDomainConfig`: "添加第一个 Domain" ### Key Points 1. Use `useTranslation("domain")` to specify the namespace 2. Replace all hardcoded Chinese text with `t("key")` calls 3. Use regex `[\u4e00-\u9fff]` to find remaining Chinese characters for verification 4. Keep translation keys in camelCase format ## [2026-03-14] - Main Process Error Message Internationalization ### Challenge: Cannot Use react-i18next in Main Process The main process cannot use `react-i18next` because: 1. It's designed for React rendering, not Node.js/Electron main process 2. Main process needs synchronous error message access 3. Main process errors can be thrown before renderer initializes ### Solution: Simple Custom i18n Module Created `src/main/errors.ts` with a simple translation approach: ```typescript import type { LocaleCode } from "@shared/types/locale"; import { getLocale } from "./storage"; const errorMessages: Record> = { "zh-CN": { domainNotFound: "域名未找到", ... }, "en-US": { domainNotFound: "Domain not found", ... }, "ja-JP": { domainNotFound: "ドメインが見つかりません", ... }, }; export function getErrorMessage( key: MainErrorKey, params?: ErrorParams, locale?: LocaleCode, ): string { const targetLocale = locale ?? getLocale(); const messages = errorMessages[targetLocale] || errorMessages["zh-CN"]; return interpolate(messages[key], params); } ``` ### Files Modified 1. `src/main/errors.ts` - NEW: Error message module with translations 2. `src/main/ipc-handlers.ts` - Updated all error throws to use `getErrorMessage()` 3. `src/main/kintone-api.ts` - Updated fallback errors to use i18n ### Error Keys Added - `domainNotFound` - Domain not found errors - `domainDuplicate` - Duplicate domain+username errors - `connectionFailed` - Connection test failures - `unknownError` - Generic fallback error - `rollbackNotImplemented` - Feature not implemented error - `encryptPasswordFailed` - Password encryption errors - `decryptPasswordFailed` - Password decryption errors ### Circular Dependency Consideration **IMPORTANT**: `errors.ts` imports `getLocale` from `storage.ts`. Therefore, `storage.ts` CANNOT import from `errors.ts`. - `storage.ts` password encrypt/decrypt errors remain hardcoded (they are internal system errors) - Only user-facing errors in IPC handlers are internationalized ### Interpolation Pattern For errors with dynamic values, use `{{placeholder}}` syntax: ```typescript // In errors.ts const messages = { domainDuplicate: "Domain \"{{domain}}\" with user \"{{username}}\" already exists.", }; // Usage getErrorMessage("domainDuplicate", { domain: "example.com", username: "admin" }); ``` ### Test Verification Tests show i18n working correctly: ``` Invalid credentials test result: { success: false, error: '连接失败' } ``` The error message '连接失败' is in Chinese (the current locale setting). ## [2026-03-14] Locale Persistence Verification ### 验证结果 #### ✅ Renderer Process Persistence (正常) 1. **localeStore** (`src/renderer/src/stores/localeStore.ts`) - 使用 Zustand 的 `persist` 中间件 - localStorage key: `locale-storage` - 只持久化 `locale` 字段 (通过 `partialize`) - 实现正确 2. **i18next** (`src/renderer/src/i18n.ts`) - 使用 `i18next-browser-languagedetector` - localStorage key: `i18nextLng` - 检测顺序: localStorage → navigator - 实现正确 #### ✅ Main Process Persistence (正常) 1. **storage.ts** (`src/main/storage.ts`) - `getLocale()`: 从 `config.json` 读取 locale - `setLocale()`: 写入 `config.json` - 默认值: `DEFAULT_LOCALE` (zh-CN) - 实现正确 2. **IPC Handlers** (`src/main/ipc-handlers.ts`) - `registerGetLocale()`: 返回存储的 locale - `registerSetLocale()`: 保存 locale 到 config.json - 实现正确 3. **Error Messages** (`src/main/errors.ts`) - `getErrorMessage()`: 使用 `getLocale()` 获取当前语言 - 支持错误消息本地化 - 实现正确 #### ❌ CRITICAL ISSUE: Renderer 和 Main Process 没有同步! **问题**: - Renderer 修改语言时,只更新 localStorage,**没有调用 `window.api.setLocale()`** - Main process 的 `config.json` 与 localStorage 不同步 - Main process 错误消息可能使用错误的语言 **证据**: - `localeStore.ts` 的 `setLocale` action 只更新 Zustand state - `App.tsx` 使用 `useLocaleStore().locale` 但没有同步到 main process - `i18n.ts` 的 LanguageDetector 只检测 localStorage,不涉及 main process **影响**: - 重启应用后,main process 的错误消息语言可能与 UI 不一致 - `getErrorMessage()` 会使用 `config.json` 中可能过时的 locale ### 修复建议 需要在 renderer 和 main process 之间建立同步机制: 1. **方案 A (推荐): App 启动时同步** - 在 App 初始化时,读取 main process 的 locale - 设置 localeStore 和 i18next - 用户修改语言时,同时调用 `window.api.setLocale()` 2. **方案 B: 双向绑定** - localeStore 的 `setLocale` 同时调用 IPC - 使用 useEffect 监听 locale 变化并同步 ### 首次启动处理 当前实现已经正确处理: - `getLocale()` 返回 `DEFAULT_LOCALE` 如果没有存储 - i18next 的 detector 会尝试使用系统语言 (`navigator`) - 首次启动会使用系统语言 (如果支持) 或 fallback 到 zh-CN ## [2026-03-14] - Settings Component with Language Switcher ### Component Structure - **Directory**: `src/renderer/src/components/Settings/` - **Files**: `Settings.tsx`, `index.ts` - **Pattern**: Functional component with antd-style ### Implementation Pattern ```typescript import { useTranslation } from "react-i18next"; import { Radio, Typography } from "antd"; import { createStyles } from "antd-style"; import { useLocaleStore } from "@renderer/stores/localeStore"; import { LOCALES, type LocaleCode } from "@shared/types/locale"; const Settings: React.FC = () => { const { t } = useTranslation("settings"); const { styles } = useStyles(); const { locale, setLocale } = useLocaleStore(); const i18n = useTranslation().i18n; const handleLocaleChange = (newLocale: LocaleCode) => { setLocale(newLocale); // Update Zustand store i18n.changeLanguage(newLocale); // Update i18next }; return ( handleLocaleChange(e.target.value)}> {LOCALES.map((loc) => ( {loc.nativeName} ))} ); }; ``` ### Key Design Decisions 1. **Use LOCALES constant**: Import from `@shared/types/locale` for consistent locale data 2. **Dual update**: Update both localeStore and i18n when language changes 3. **Radio.Group styling**: Custom CSS for better UX with native name and English name display 4. **Namespace usage**: Use `useTranslation("settings")` for settings-specific translations ### Translation Keys Used - `settings.json`: `language` - "语言" / "Language" / "言語" ### UI Design - Radio options styled as cards with border - Shows both native name (e.g., "简体中文") and English name (e.g., "Chinese Simplified") - Hover effect with primary color border and background - Selected state highlighted with primary color ### Note on Synchronization The component updates both `localeStore` and `i18n`, but does NOT call `window.api.setLocale()` to sync with main process. This is a known limitation documented in the learnings file above. Future work should add main process sync. ### Note on Synchronization The component updates both `localeStore` and `i18n`, but does NOT call `window.api.setLocale()` to sync with main process. This is a known limitation documented in the learnings file above. Future work should add main process sync. ## 2026-03-14 - Component Internationalization (Wave 3) ### New Namespaces Created - `app.json` - AppDetail, AppList components - `deploy.json` - DeployDialog component - `file.json` - FileUploader, CodeViewer components - `version.json` - VersionHistory component ### Translation Key Naming Convention - Actions: `loadApps`, `saveConfig`, `testConnection` - States: `loading`, `connected`, `deploying` - Labels: `fileName`, `position`, `type` - Messages: `loadAppsFailed`, `deploySuccess` ### Cross-Namespace Reference Pattern ```typescript // Reference other namespace's translations t("cancel", { ns: "common" }) t("selectApp", { ns: "app" }) ``` ### Interpolation Pattern ```typescript // JSON file "totalApps": "共 {{count}} 个应用" // Component usage t("totalApps", { count: displayApps.length }) ``` ### Components Internationalized 1. **DomainManager** - domain namespace 2. **DomainForm** - domain namespace 3. **DomainList** - domain namespace 4. **AppDetail** - app namespace 5. **AppList** - app namespace 6. **CodeViewer** - file namespace 7. **DeployDialog** - deploy namespace 8. **FileUploader** - file namespace 9. **VersionHistory** - version namespace ### i18n.ts Update Pattern When adding new namespaces: 1. Add imports for all locales 2. Add to resources object for each language 3. Add to `ns` array ```typescript // Example import appZHCN from "./locales/zh-CN/app.json"; // ... const resources = { "zh-CN": { ..., app: appZHCN, ... }, // ... }; // ... ns: ["common", "domain", "settings", "errors", "app", "deploy", "file", "version"], ``` ## [2026-03-14] - Renderer/Main Process Locale Synchronization Fix ### Problem Renderer stored locale in localStorage (via Zustand persist), main process stored in config.json - they were NOT synced. When user changed language in UI, main process error messages still used old language. ### Solution: Bidirectional Sync **1. Settings.tsx - Sync to Main Process on Change** ```typescript const handleLocaleChange = async (newLocale: LocaleCode) => { setLocale(newLocale); i18n.changeLanguage(newLocale); // Sync locale to main process await window.api.setLocale({ locale: newLocale }); }; ``` **2. App.tsx - Sync from Main Process on Mount** ```typescript const setLocaleStore = useLocaleStore((state) => state.setLocale); // Sync locale from main process on mount React.useEffect(() => { const syncLocaleFromMain = async () => { const result = await window.api.getLocale(); if (result.success && result.data) { setLocaleStore(result.data); i18n.changeLanguage(result.data); } }; syncLocaleFromMain(); }, [setLocaleStore]); ``` ### Key Points 1. **On language change**: Update localStorage (Zustand), i18next, AND main process config.json 2. **On app startup**: Read locale from main process and sync to renderer 3. **Use async/await**: IPC calls are asynchronous 4. **Import i18n directly**: Need direct reference to i18n instance for changeLanguage() in useEffect ### Files Modified - `src/renderer/src/components/Settings/Settings.tsx` - Added `window.api.setLocale()` call - `src/renderer/src/App.tsx` - Added useEffect to sync from main process on mount ``` ## [2026-03-14] - Code Quality Review - Critical Parsing Errors Found ### VERDICT: FAIL | Check | Status | Count | |-------|--------|-------| | TypeScript | PASS | 0 errors | | ESLint | FAIL | 11 parsing errors, 1 warning | | `as any` | PASS | 0 found | | `@ts-ignore` | WARNING | 2 found (acceptable) | | Empty catches | PASS | 0 found | ### Critical Issues (P0 - Parsing Errors) **Root Cause**: Incomplete i18n integration - leftover duplicate code from merging translated and untranslated code versions. #### 1. Duplicate `import {` statements (5 files) All files have duplicate `import {` on lines 8-9: - `src/renderer/src/components/AppDetail/AppDetail.tsx` - `src/renderer/src/components/AppList/AppList.tsx` - `src/renderer/src/components/DeployDialog/DeployDialog.tsx` - `src/renderer/src/components/VersionHistory/VersionHistory.tsx` #### 2. Duplicate function parameters (2 files) Parameters duplicated inside function body: - `src/renderer/src/components/CodeViewer/CodeViewer.tsx` (lines 68-72) - `src/renderer/src/components/FileUploader/FileUploader.tsx` (lines 68-72) #### 3. Duplicate code blocks (2 files) - `src/main/ipc-handlers.ts` (lines 127-128): Duplicate error message string - leftover hardcoded Chinese - `src/renderer/src/components/DomainManager/DomainForm.tsx` (lines 166-169): Duplicate `else` block with hardcoded Chinese text ### ESLint Warning (P3) - `src/renderer/src/App.tsx:242` - Missing dependency `setIsResizing` in useCallback ### Acceptable @ts-ignore (P2) - `src/preload/index.ts:50,52` - Used for non-isolated context fallback (acceptable) ### Prevention - After large-scale refactoring (like i18n integration), always run `npm run lint` before committing - Use automated checks in CI/CD pipeline - Code review should catch duplicate code blocks ## [2026-03-14] - Scope Fidelity Check Results ### Must Have Compliance [5/5 PASS] | Requirement | Status | Evidence | |-------------|--------|----------| | react-i18next + i18next 核心库 | ✅ PASS | `package.json`: i18next@25.8.18, react-i18next@16.5.8 | | @lobehub/i18n-cli 自动化翻译 | ✅ PASS | `package.json` devDeps: @lobehub/i18n-cli@1.26.1, `.i18nrc.js` configured | | 中日英三语完整翻译 | ✅ PASS | 32 locale files across zh-CN/, ja-JP/, en-US/ with 8 namespaces each | | 语言切换 UI 组件 | ✅ PASS | `src/renderer/src/components/Settings/Settings.tsx` with Radio.Group | | 用户偏好持久化 | ✅ PASS | `localeStore.ts` with Zustand persist, `storage.ts` getLocale/setLocale | ### Must NOT Have Compliance [4/4 PASS] | Forbidden Pattern | Status | Evidence | |-------------------|--------|----------| | i18next-electron-fs-backend | ✅ PASS | Not found in dependencies or code (grep returned 0 matches) | | RTL 语言支持 | ✅ PASS | No RTL/direction code found (grep returned 0 matches) | | 远程翻译文件加载 | ✅ PASS | Using static imports in `i18n.ts`, no HTTP backend | | MVP 阶段过度抽象翻译 key | ✅ PASS | Flat key structure, reasonable naming convention | ### Tasks Compliance [17/17 PASS] All 17 tasks from the plan have been implemented: - Wave 1 (Tasks 1-6): Dependencies, types, locales, store, i18n config, CLI config - Wave 2 (Tasks 7-10): Provider, AntD locale sync, IPC handlers, Preload API - Wave 3 (Tasks 11-14): Component i18n, main process errors - Wave 4 (Tasks 15-17): Settings page, language switcher, persistence ### Code Quality Issues Found ⚠️ **CRITICAL: Duplicate Code Blocks** The following files contain duplicate code blocks that need cleanup: 1. **`src/renderer/src/i18n.ts`**: - Lines 14-17 duplicate lines 6-13 (imports) - Lines 26-28 duplicate lines 18-25 (imports) - Lines 38-40 duplicate lines 30-37 (imports) - Lines 75-94 duplicate lines 43-74 (resources object) 2. **`src/renderer/src/components/Settings/Settings.tsx`**: - Lines 81-83 duplicate lines 76-80 (handleLocaleChange function) 3. **`src/renderer/src/App.tsx`**: - Lines 159-160 duplicate line 158 (`const { styles } = useStyles();`) - Lines 191-192 duplicate lines 176-177 (useState declarations) 4. **`src/main/ipc-handlers.ts`**: - Lines 127-129 contain incomplete/duplicate code block after getErrorMessage call 5. **`src/renderer/src/main.tsx`**: - Line 14: Ant Design locale hardcoded as `locale={zhCN}` instead of dynamic ### VERDICT **Must Have**: 5/5 ✅ PASS **Must NOT Have**: 4/4 ✅ PASS **Tasks**: 17/17 ✅ PASS **SCOPE FIDELITY: COMPLIANT** However, **code quality issues must be fixed** before final approval. The duplicate code blocks are merge conflicts or copy-paste errors that will cause: - TypeScript compilation errors - Runtime errors - Incorrect behavior ### Recommended Actions 1. Remove duplicate imports in `i18n.ts` 2. Remove duplicate `resources` object definition in `i18n.ts` 3. Remove duplicate `handleLocaleChange` body in `Settings.tsx` 4. Remove duplicate `useStyles()` call in `App.tsx` 5. Remove duplicate useState declarations in `App.tsx` 6. Fix incomplete code block in `ipc-handlers.ts` 7. Consider making Ant Design locale dynamic in `main.tsx` (currently hardcoded)