27 KiB
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 messagesdomain.json: Domain management specific translationssettings.json: Settings page translationserrors.json: Error messages and validation errors
Language Codes
default/: Source language (Chinese)zh-CN/: Simplified Chineseen-US/: English (US)ja-JP/: Japanese
Best Practices
- Keep translation keys descriptive (e.g.,
collapseSidebarnotcs) - Group related keys together in the same file
- 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:
export const useLocaleStore = create<LocaleState>()(
persist(
(set) => ({
locale: DEFAULT_LOCALE,
setLocale: (locale) => set({ locale }),
}),
{
name: "locale-storage",
partialize: (state) => ({ locale: state.locale }),
},
),
);
Key Decisions
- Use
partializeto only persist thelocalevalue, not actions - Import
DEFAULT_LOCALEfrom shared types to ensure consistency - Store key is
locale-storagein 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:
// 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 fort()calls - ns:
['common', 'domain', 'settings', 'errors']- 4 namespaces - escapeValue:
false- React handles XSS protection - detection: localStorage + navigator order, caches to localStorage
Language Detector Configuration
detection: {
order: ["localStorage", "navigator"],
caches: ["localStorage"],
lookupLocalStorage: "i18nextLng",
}
This ensures:
- First check localStorage for saved preference
- Fall back to browser language
- Persist detected/selected language to localStorage
File Location
- Configuration:
src/renderer/src/i18n.ts - Must be imported in
main.tsxbefore React renders
[2026-03-13] - Dependencies Installation
Installed Packages
i18next@25.8.18- Core i18n frameworkreact-i18next@16.5.8- React bindings for i18nexti18next-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 translationslocales/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-mdflag
npm script
"i18n": "lobe-i18n"
Run with: npm run i18n
2026-03-14 - I18nextProvider Integration
Changes Made
- Added
I18nextProviderimport fromreact-i18next - Added
i18ninstance import from./i18n - Wrapped
<ThemeProvider>with<I18nextProvider i18n={i18n}>to enable i18n throughout the app
Integration Pattern
<I18nextProvider i18n={i18n}>
<ThemeProvider>
<AntdApp>
<App />
</AntdApp>
</ThemeProvider>
</I18nextProvider>
Notes
-
The i18n instance from
./i18nis 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
// 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
<ConfigProvider locale={antdLocale}>
Key Points
- Use
React.useCallbackfor the mapping function to prevent unnecessary re-renders - Use
React.useMemoto memoize the locale result based on locale changes - Import
Localetype fromantd/es/localefor type safety - Default fallback to
zhCNfor unsupported locales
[2026-03-14] - Preload Locale API Exposure
Files Modified
src/shared/types/ipc.ts- AddedSetLocaleParamsinterfacesrc/preload/index.ts- AddedgetLocaleandsetLocaleIPC invocationssrc/preload/index.d.ts- Added type declarations for locale API
Pattern: IPC Exposure in Preload
// In preload/index.ts
const api: SelfAPI = {
// ... other APIs
// Locale
getLocale: () => ipcRenderer.invoke("getLocale"),
setLocale: (params) => ipcRenderer.invoke("setLocale", params),
};
Type Declaration Pattern
// 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<Result<LocaleCode>>;
setLocale: (params: SetLocaleParams) => Promise<Result<void>>;
}
Key Points
- Follow existing IPC patterns (invoke with channel name and params)
- Use
Result<T>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?: LocaleCodetoAppConfiginterface - Implemented
getLocale()- reads locale from config.json, returnsDEFAULT_LOCALEif not set - Implemented
setLocale(locale: LocaleCode)- saves locale to config.json - Uses existing
readJsonFile/writeJsonFilepattern for consistency
IPC Handlers (src/main/ipc-handlers.ts):
getLocalehandler: ReturnsPromise<Result<LocaleCode>>setLocalehandler: TakesSetLocaleParams, returnsPromise<Result<void>>- Uses existing
handle<P, T>helper wrapper for error handling
Types (src/shared/types/ipc.ts):
SetLocaleParamsinterface already exists withlocale: LocaleCodeproperty- 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
- Not using react-i18next in main process: Main process uses simple locale string storage, not full i18n library
- Params object pattern:
setLocaleusesSetLocaleParamsobject instead ofLocaleCodedirectly for consistency with other IPC handlers - Default fallback:
getLocale()returnsDEFAULT_LOCALE("zh-CN") when no locale is set - Storage location: Locale stored in
config.jsonalongside domains array
File Changes Summary
src/main/storage.ts: AddedgetLocale()andsetLocale()functionssrc/main/ipc-handlers.ts: AddedregisterGetLocale()andregisterSetLocale()handlerssrc/shared/types/ipc.ts:SetLocaleParamsalready existedsrc/preload/index.d.ts: Locale API types already existedsrc/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
src/renderer/src/App.tsx- Added useTranslation hook, replaced hardcoded textsrc/renderer/src/locales/zh-CN/common.json- Added new keyssrc/renderer/src/locales/en-US/common.json- Added new keyssrc/renderer/src/locales/ja-JP/common.json- Added new keyssrc/renderer/src/locales/default/common.json- Added new keys
i18n Setup
- Uses
react-i18nextwithI18nextProviderwrapper 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.tsxsrc/renderer/src/locales/default/domain.json
Pattern: useTranslation Hook with Namespace
import { useTranslation } from "react-i18next";
const DomainManager: React.FC<DomainManagerProps> = ({...}) => {
const { t } = useTranslation("domain");
// ...
return <h2>{t("domainManagement")}</h2>;
};
Translation Keys Added
noDomainSelected: "未选择 Domain"addDomainTooltip: "添加 Domain"domainManagement: "Domain 管理"add: "添加"noDomainConfig: "暂无 Domain 配置"addFirstDomainConfig: "添加第一个 Domain"
Key Points
- Use
useTranslation("domain")to specify the namespace - Replace all hardcoded Chinese text with
t("key")calls - Use regex
[\u4e00-\u9fff]to find remaining Chinese characters for verification - 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:
- It's designed for React rendering, not Node.js/Electron main process
- Main process needs synchronous error message access
- Main process errors can be thrown before renderer initializes
Solution: Simple Custom i18n Module
Created src/main/errors.ts with a simple translation approach:
import type { LocaleCode } from "@shared/types/locale";
import { getLocale } from "./storage";
const errorMessages: Record<LocaleCode, Record<MainErrorKey, string>> = {
"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
src/main/errors.ts- NEW: Error message module with translationssrc/main/ipc-handlers.ts- Updated all error throws to usegetErrorMessage()src/main/kintone-api.ts- Updated fallback errors to use i18n
Error Keys Added
domainNotFound- Domain not found errorsdomainDuplicate- Duplicate domain+username errorsconnectionFailed- Connection test failuresunknownError- Generic fallback errorrollbackNotImplemented- Feature not implemented errorencryptPasswordFailed- Password encryption errorsdecryptPasswordFailed- Password decryption errors
Circular Dependency Consideration
IMPORTANT: errors.ts imports getLocale from storage.ts. Therefore, storage.ts CANNOT import from errors.ts.
storage.tspassword 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:
// 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 (正常)
-
localeStore (
src/renderer/src/stores/localeStore.ts)- 使用 Zustand 的
persist中间件 - localStorage key:
locale-storage - 只持久化
locale字段 (通过partialize) - 实现正确
- 使用 Zustand 的
-
i18next (
src/renderer/src/i18n.ts)- 使用
i18next-browser-languagedetector - localStorage key:
i18nextLng - 检测顺序: localStorage → navigator
- 实现正确
- 使用
✅ Main Process Persistence (正常)
-
storage.ts (
src/main/storage.ts)getLocale(): 从config.json读取 localesetLocale(): 写入config.json- 默认值:
DEFAULT_LOCALE(zh-CN) - 实现正确
-
IPC Handlers (
src/main/ipc-handlers.ts)registerGetLocale(): 返回存储的 localeregisterSetLocale(): 保存 locale 到 config.json- 实现正确
-
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的setLocaleaction 只更新 Zustand stateApp.tsx使用useLocaleStore().locale但没有同步到 main processi18n.ts的 LanguageDetector 只检测 localStorage,不涉及 main process
影响:
- 重启应用后,main process 的错误消息语言可能与 UI 不一致
getErrorMessage()会使用config.json中可能过时的 locale
修复建议
需要在 renderer 和 main process 之间建立同步机制:
-
方案 A (推荐): App 启动时同步
- 在 App 初始化时,读取 main process 的 locale
- 设置 localeStore 和 i18next
- 用户修改语言时,同时调用
window.api.setLocale()
-
方案 B: 双向绑定
- localeStore 的
setLocale同时调用 IPC - 使用 useEffect 监听 locale 变化并同步
- localeStore 的
首次启动处理
当前实现已经正确处理:
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
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 (
<Radio.Group value={locale} onChange={(e) => handleLocaleChange(e.target.value)}>
{LOCALES.map((loc) => (
<Radio key={loc.code} value={loc.code}>
{loc.nativeName}
</Radio>
))}
</Radio.Group>
);
};
Key Design Decisions
- Use LOCALES constant: Import from
@shared/types/localefor consistent locale data - Dual update: Update both localeStore and i18n when language changes
- Radio.Group styling: Custom CSS for better UX with native name and English name display
- 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 componentsdeploy.json- DeployDialog componentfile.json- FileUploader, CodeViewer componentsversion.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
// Reference other namespace's translations
t("cancel", { ns: "common" })
t("selectApp", { ns: "app" })
Interpolation Pattern
// JSON file
"totalApps": "共 {{count}} 个应用"
// Component usage
t("totalApps", { count: displayApps.length })
Components Internationalized
- DomainManager - domain namespace
- DomainForm - domain namespace
- DomainList - domain namespace
- AppDetail - app namespace
- AppList - app namespace
- CodeViewer - file namespace
- DeployDialog - deploy namespace
- FileUploader - file namespace
- VersionHistory - version namespace
i18n.ts Update Pattern
When adding new namespaces:
- Add imports for all locales
- Add to resources object for each language
- Add to
nsarray
// 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
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
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
- On language change: Update localStorage (Zustand), i18next, AND main process config.json
- On app startup: Read locale from main process and sync to renderer
- Use async/await: IPC calls are asynchronous
- Import i18n directly: Need direct reference to i18n instance for changeLanguage() in useEffect
Files Modified
src/renderer/src/components/Settings/Settings.tsx- Addedwindow.api.setLocale()callsrc/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)