790 lines
27 KiB
Markdown
790 lines
27 KiB
Markdown
# 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<LocaleState>()(
|
||
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 `<ThemeProvider>` with `<I18nextProvider i18n={i18n}>` to enable i18n throughout the app
|
||
|
||
### Integration Pattern
|
||
```tsx
|
||
<I18nextProvider i18n={i18n}>
|
||
<ThemeProvider>
|
||
<AntdApp>
|
||
<App />
|
||
</AntdApp>
|
||
</ThemeProvider>
|
||
</I18nextProvider>
|
||
```
|
||
|
||
### 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
|
||
<ConfigProvider locale={antdLocale}>
|
||
```
|
||
|
||
### 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<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?: 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<Result<LocaleCode>>`
|
||
- `setLocale` handler: Takes `SetLocaleParams`, returns `Promise<Result<void>>`
|
||
- Uses existing `handle<P, T>` 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<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
|
||
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<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
|
||
|
||
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 (
|
||
<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
|
||
|
||
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) |