21 KiB
21 KiB
ClawX 项目国际化 (i18n) 实现参考
本文档分析了 ClawX 项目的国际化实现架构,为 zn-ai 项目的国际化改进提供参考和开发计划。
概述
ClawX 使用 React + i18next 框架实现国际化,具有以下特点:
- 模块化语言文件:按功能模块组织翻译内容
- 多语言支持:支持英文 (en)、中文 (zh)、日文 (ja)
- 智能语言检测:自动根据系统语言和用户偏好选择语言
- 状态管理集成:与 Zustand 状态存储深度集成,支持持久化
- 类型安全:完整的 TypeScript 类型支持
架构设计
核心文件结构
src/
├── i18n/
│ ├── locales/
│ │ ├── en/ # 英文翻译
│ │ │ ├── common.json # 通用翻译
│ │ │ ├── settings.json # 设置页面翻译
│ │ │ ├── dashboard.json # 仪表盘翻译
│ │ │ ├── chat.json # 聊天页面翻译
│ │ │ ├── channels.json # 频道页面翻译
│ │ │ ├── agents.json # 代理页面翻译
│ │ │ ├── skills.json # 技能页面翻译
│ │ │ ├── cron.json # 定时任务翻译
│ │ │ └── setup.json # 设置向导翻译
│ │ ├── zh/ # 中文翻译 (同上结构)
│ │ └── ja/ # 日文翻译 (同上结构)
│ └── index.ts # i18n 配置入口
├── shared/
│ └── language.ts # 语言解析工具
└── stores/
└── settings.ts # 语言状态管理
技术栈
{
"dependencies": {
"i18next": "^25.8.11",
"react-i18next": "^16.5.4"
}
}
核心代码解析
1. 语言解析工具 (shared/language.ts)
export const SUPPORTED_LANGUAGE_CODES = ['en', 'zh', 'ja'] as const;
export type LanguageCode = (typeof SUPPORTED_LANGUAGE_CODES)[number];
// 标准化语言代码(处理 zh-CN、zh_TW 等变体)
function normalizeLocale(locale: string | null | undefined): string {
return locale?.trim().toLowerCase().replaceAll('_', '-') ?? '';
}
// 解析支持的语言代码
export function resolveSupportedLanguage(
locale: string | null | undefined,
fallback: LanguageCode = 'en',
): LanguageCode {
const normalizedLocale = normalizeLocale(locale);
if (!normalizedLocale) return fallback;
const [baseLanguage] = normalizedLocale.split('-');
return SUPPORTED_LANGUAGE_CODE_SET.has(baseLanguage)
? (baseLanguage as LanguageCode)
: fallback;
}
关键特性:
- 自动处理语言变体(如
zh-CN→zh,en-US→en) - 安全的默认值回退机制
- 类型安全的语言代码枚举
2. i18n 配置入口 (src/i18n/index.ts)
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import { SUPPORTED_LANGUAGE_CODES, resolveSupportedLanguage } from '../../shared/language';
// 导入所有语言文件(按模块)
import enCommon from './locales/en/common.json';
import enSettings from './locales/en/settings.json';
// ... 其他模块导入
// 定义支持的语言列表(包含显示标签)
export const SUPPORTED_LANGUAGES = [
{ code: 'en', label: 'English' },
{ code: 'zh', label: '中文' },
{ code: 'ja', label: '日本語' },
] as const;
// 构建 i18next 资源结构
const resources = {
en: {
common: enCommon,
settings: enSettings,
// ... 其他命名空间
},
zh: { /* 同上结构 */ },
ja: { /* 同上结构 */ },
};
// 初始化 i18next
i18n
.use(initReactI18next)
.init({
resources,
lng: resolveSupportedLanguage(
typeof navigator !== 'undefined' ? navigator.language : undefined
),
fallbackLng: 'en',
supportedLngs: [...SUPPORTED_LANGUAGE_CODES],
defaultNS: 'common', // 默认命名空间
ns: ['common', 'settings', 'dashboard', 'chat', 'channels', 'agents', 'skills', 'cron', 'setup'],
interpolation: { escapeValue: false }, // React 已处理转义
react: { useSuspense: false },
});
export default i18n;
配置亮点:
- 多命名空间:按功能模块分离,避免单个文件过大
- 智能语言检测:自动根据浏览器语言选择
- 安全的回退机制:确保始终有可用的翻译
- React 优化:禁用 Suspense 避免渲染问题
3. 状态管理集成 (src/stores/settings.ts)
import i18n from '@/i18n';
import { resolveSupportedLanguage } from '../../shared/language';
interface SettingsState {
language: string;
// ... 其他设置
}
const defaultSettings = {
language: resolveSupportedLanguage(
typeof navigator !== 'undefined' ? navigator.language : undefined
),
// ... 其他默认值
};
export const useSettingsStore = create<SettingsState>()(
persist(
(set) => ({
...defaultSettings,
// 初始化时加载远程设置并同步 i18n
init: async () => {
try {
const settings = await hostApiFetch<Partial<typeof defaultSettings>>('/api/settings');
const resolvedLanguage = settings.language
? resolveSupportedLanguage(settings.language)
: undefined;
set((state) => ({
...state,
...settings,
...(resolvedLanguage ? { language: resolvedLanguage } : {}),
}));
if (resolvedLanguage) {
i18n.changeLanguage(resolvedLanguage); // 同步 i18n 实例
}
} catch { /* 降级处理 */ }
},
// 设置语言时同步更新 i18n 和远程存储
setLanguage: (language) => {
const resolvedLanguage = resolveSupportedLanguage(language);
i18n.changeLanguage(resolvedLanguage); // 1. 更新 i18n 实例
set({ language: resolvedLanguage }); // 2. 更新本地状态
void hostApiFetch('/api/settings/language', { // 3. 同步到主进程
method: 'PUT',
body: JSON.stringify({ value: resolvedLanguage }),
}).catch(() => { });
},
}),
{ name: 'clawx-settings' } // Zustand 持久化配置
)
);
集成特点:
- 三级同步:i18n 实例 ↔ 本地状态 ↔ 主进程存储
- 持久化:使用 localStorage 持久化用户偏好
- 错误处理:网络失败时降级到本地存储
4. 应用集成 (src/App.tsx)
import i18n from './i18n';
import { useSettingsStore } from './stores/settings';
function App() {
const language = useSettingsStore((state) => state.language);
// 挂载时同步 i18n 语言与持久化设置
useEffect(() => {
if (language && language !== i18n.language) {
i18n.changeLanguage(language);
}
}, [language]);
return <>{/* 应用内容 */}</>;
}
5. 组件使用示例 (src/pages/Setup/index.tsx)
import { useTranslation } from 'react-i18next';
import type { TFunction } from 'i18next';
import { SUPPORTED_LANGUAGES } from '@/i18n';
export function Setup() {
// 使用多个命名空间
const { t, i18n } = useTranslation(['setup', 'channels']);
// 获取语言设置操作
const { language, setLanguage } = useSettingsStore();
// 动态翻译函数(用于生成动态内容)
const getSteps = (t: TFunction): SetupStep[] => [
{
id: 'welcome',
title: t('steps.welcome.title'), // setup 命名空间
description: t('steps.welcome.description'),
},
// ...
];
return (
<div>
{/* 直接翻译 */}
<h1>{t(`steps.${step.id}.title`)}</h1>
{/* 语言选择器 */}
<div className="flex justify-center gap-2 py-2">
{SUPPORTED_LANGUAGES.map((lang) => (
<Button
key={lang.code}
variant={language === lang.code ? 'secondary' : 'ghost'}
onClick={() => setLanguage(lang.code)}
>
{lang.label}
</Button>
))}
</div>
{/* 带参数的翻译 */}
<p>{t('runtime.status.gatewayRunning', { port: gatewayStatus.port })}</p>
</div>
);
}
使用模式:
- 命名空间:
useTranslation(['setup', 'channels'])加载多个模块 - 动态键:
t(steps.${step.id}.title)支持动态翻译键 - 参数插值:
t('key', { param: value })支持动态内容 - 类型安全:通过
TFunction类型提供类型提示
语言文件结构
模块化设计
// locales/zh/common.json
{
"sidebar": {
"chat": "聊天",
"newChat": "新对话",
"cronTasks": "定时任务",
"skills": "技能",
"agents": "Agents",
"channels": "频道",
"dashboard": "仪表盘",
"settings": "设置",
"devConsole": "开发者控制台",
"models": "模型",
"deleteSessionConfirm": "确定要删除对话 \"{{label}}\" 吗?",
"openClawPage": "OpenClaw 页面"
},
"actions": {
"save": "保存",
"cancel": "取消",
"delete": "删除",
"edit": "编辑",
"refresh": "刷新",
"close": "关闭",
"copy": "复制",
"search": "搜索",
"confirm": "确认",
"dismiss": "忽略"
},
"status": {
"running": "运行中",
"stopped": "已停止",
"error": "错误",
"connected": "已连接",
"disconnected": "已断开"
}
}
// locales/zh/setup.json (设置向导专用)
{
"steps": {
"welcome": {
"title": "欢迎使用 ClawX",
"description": "让我们快速完成初始设置"
},
"runtime": {
"title": "运行环境检查",
"description": "确保所有依赖项已就绪"
}
},
"welcome": {
"title": "欢迎!",
"description": "ClawX 是一个基于 OpenClaw 的图形化 AI 助手...",
"features": {
"noCommand": "无需命令行经验",
"modernUI": "现代化的图形界面",
"bundles": "预置技能包",
"crossPlatform": "跨平台支持"
}
}
}
组织原则:
- 按功能模块分离:避免单个文件过大
- 嵌套结构:使用对象嵌套提高可读性
- 参数化:支持
{{variable}}插值 - 上下文清晰:键名反映使用场景
构建配置
package.json 依赖
{
"devDependencies": {
"i18next": "^25.8.11",
"react-i18next": "^16.5.4"
}
}
Vite 配置
无需特殊配置,i18next 与构建工具无关。
为 zn-ai 项目的改进建议
当前 zn-ai i18n 状态分析
zn-ai 项目目前使用 Vue 3 + vue-i18n,但实现较为简单:
优势:
- ✅ 已集成 vue-i18n 基础框架
- ✅ 基本的中英文翻译已就绪
- ✅ 目录结构已优化 (
src/i18n/)
不足:
- ❌ 缺少模块化语言文件组织
- ❌ 缺乏智能语言检测和解析
- ❌ 未与状态管理集成(持久化)
- ❌ 没有类型安全的语言代码
- ❌ 缺少多命名空间支持
推荐架构迁移
鉴于 zn-ai 使用 Vue 3,建议采用以下架构:
src/i18n/
├── locales/
│ ├── en/ # 英文模块
│ │ ├── common.json # 通用翻译
│ │ ├── login.json # 登录页面
│ │ ├── dashboard.json # 仪表盘
│ │ ├── task.json # 任务管理
│ │ ├── rate.json # 费率管理
│ │ ├── knowledge.json # 知识库
│ │ ├── setting.json # 设置页面
│ │ └── component.json # 组件翻译
│ ├── zh/ # 中文模块(同上结构)
│ └── ja/ # 日文模块(可选)
├── constants.ts # 语言常量定义
├── resolver.ts # 语言解析工具
├── store.ts # 语言状态管理
└── index.ts # vue-i18n 配置
技术选择
- 核心库:继续使用
vue-i18n@9.x(与 Vue 3 兼容) - 状态管理:集成到现有的 Pinia store
- 类型安全:使用 TypeScript 增强类型提示
- 持久化:通过
electron-store或localStorage
当前进展
语言选择器 UI 试点实现
已在设置页面的版本信息组件 (src/pages/setting/components/Version/index.vue) 中实现语言选择器 UI,作为国际化改进的试点。
实现内容:
- 添加了语言设置区域,包含三个按钮:中文、English、日本語
- 默认语言为中文,当前选中语言高亮显示
- 集成了现有的
vue-i18n实例,使用setLanguage/getLanguageAPI - 支持日语语言文件占位 (
ja.json)
技术细节:
- 使用 Element Plus 按钮组件 (
el-button) - 响应式当前语言状态管理
- 类型安全的语言代码 (
LanguageType) - 遵循 ClawX 项目的 UI 设计模式
下一步:
- 将语言选择器集成到全局设置页面
- 添加语言状态持久化(Pinia Store)
- 完善日语翻译内容
- 推广到其他组件
开发计划
第一阶段:基础架构升级 (预计: 2-3 天)
目标:建立模块化、类型安全的 i18n 基础架构
-
语言文件重组
- 创建模块化目录结构 (
src/i18n/locales/en/,src/i18n/locales/zh/) - 按功能拆分现有翻译:
common,login,dashboard,task,rate,knowledge,setting,component - 保持向后兼容,逐步迁移
- 创建模块化目录结构 (
-
核心工具开发
- 创建
src/i18n/constants.ts:定义SUPPORTED_LANGUAGES等常量 - 创建
src/i18n/resolver.ts:语言检测和解析工具 - 增强类型定义:
LanguageCode,NamespaceKey等
- 创建
-
vue-i18n 配置升级
- 重写
src/i18n/index.ts:支持多命名空间、智能语言检测 - 配置回退链:
zh-CN→zh→en - 添加开发调试工具
- 重写
第二阶段:状态管理集成 (预计: 1-2 天)
目标:实现语言设置的持久化和全应用同步
-
Pinia Store 创建
- 创建
src/store/locale.ts:管理语言状态 - 集成语言解析工具
- 实现与 vue-i18n 实例的同步
- 创建
-
持久化机制
- 集成
electron-store或使用localStorage - 实现设置保存/加载
- 添加错误处理和降级方案
- 集成
-
主进程同步(可选)
- 通过 IPC 与主进程设置同步
- 系统语言变化监听
第三阶段:组件集成优化 (预计: 2-3 天)
目标:更新所有组件使用新的 i18n 架构
-
工具函数创建
- 创建
useLocale()composable:提供类型安全的翻译函数 - 添加
t()函数的 TypeScript 增强 - 开发批量翻译更新脚本
- 创建
-
组件迁移
- 按页面逐步更新组件:Login, Dashboard, Task, Rate, Knowledge, Setting
- 更新路由和菜单的国际化
- 验证所有动态插值功能
-
开发体验优化
- 添加 VSCode 扩展建议 (i18n-ally)
- 配置提取工具
- 添加缺失翻译标记
第四阶段:高级功能与测试 (预计: 1-2 天)
目标:添加高级功能和确保质量
-
语言切换界面
- 在设置页面添加语言选择器
- 实时预览功能
- 语言包元信息显示(作者、版本)
-
测试与验证
- 单元测试:语言解析工具
- 集成测试:语言切换流程
- E2E 测试:多语言场景
-
构建优化
- 按需加载语言包
- 构建时提取和验证
- 生产环境优化
第五阶段:维护与扩展 (持续)
目标:建立可持续的国际化工作流
-
工作流建立
- 翻译提取脚本
- 与翻译平台集成方案
- 版本控制和协作流程
-
扩展准备
- 支持 RTL 语言(阿拉伯语等)
- 日期、数字、货币格式化
- 复数规则支持
-
文档完善
- 开发者指南
- 翻译贡献指南
- 最佳实践文档
实施优先级
高优先级(立即执行)
- 模块化语言文件重组
- 语言解析工具开发
- vue-i18n 配置升级
- Pinia store 集成
中优先级(本周内)
- 组件迁移(按页面)
- 语言切换界面
- 持久化实现
低优先级(后续迭代)
- 主进程同步
- 高级格式化功能
- 自动化翻译流程
预期收益
- 可维护性:模块化结构使翻译更易管理
- 开发者体验:类型安全、智能提示、调试工具
- 用户体验:智能语言检测、无缝切换、持久化偏好
- 扩展性:轻松添加新语言、支持高级 i18n 功能
- 代码质量:统一模式、减少硬编码、便于测试
具体实施任务清单(第一阶段)
任务 0:语言选择器 UI 试点(已完成)
- 在设置页面版本组件中添加语言选择器 UI (
src/pages/setting/components/Version/index.vue) - 实现三个语言按钮:中文、English、日本語,默认选中中文
- 集成现有
vue-i18nAPI (setLanguage/getLanguage) - 添加日语语言文件占位 (
src/i18n/locales/ja.json) - 导出
LanguageType类型供组件使用
任务 1:语言文件重组
- 创建目录结构:
src/i18n/locales/en/和src/i18n/locales/zh/ - 分析现有翻译内容,按功能模块拆分:
common.json:通用翻译(按钮、状态、提示)login.json:登录页面相关dashboard.json:仪表盘相关task.json:任务管理相关rate.json:费率管理相关knowledge.json:知识库相关setting.json:设置页面相关component.json:组件专用翻译
- 迁移现有翻译内容到新结构
- 验证 JSON 格式正确性
任务 2:核心工具开发
- 创建
src/i18n/constants.ts:export const SUPPORTED_LANGUAGE_CODES = ['en', 'zh', 'ja'] as const; export type LanguageCode = (typeof SUPPORTED_LANGUAGE_CODES)[number]; export const SUPPORTED_LANGUAGES = [ { code: 'en', label: 'English' }, { code: 'zh', label: '中文' }, { code: 'ja', label: '日本語' }, ] as const; - 创建
src/i18n/resolver.ts:normalizeLocale():标准化语言代码resolveSupportedLanguage():解析支持的语言- 处理
zh-CN、zh-TW、en-US等变体
- 添加类型定义:
LanguageCode、Namespace、TranslationKey等
任务 3:vue-i18n 配置升级
- 重写
src/i18n/index.ts:- 支持多命名空间导入
- 集成语言解析工具
- 配置智能回退链:
zh-CN→zh→en - 添加开发调试选项
- 验证配置正确性:
- 运行
npm run typecheck检查类型 - 运行
npm run build:vite验证构建 - 启动开发服务器测试基础功能
- 运行
任务 4:Pinia Store 集成
- 创建
src/store/locale.ts:- 状态:
language: LanguageCode - 动作:
setLanguage(lang: LanguageCode) - 持久化:集成
localStorage或electron-store
- 状态:
- 实现与 vue-i18n 实例的同步:
- Store 初始化时读取持久化设置
- 语言切换时更新 i18n 实例
- 监听 i18n 语言变化更新 Store
- 在
src/main.ts中集成 Store
任务 5:基础组件迁移(试点)
- 创建
src/composables/useLocale.ts:- 提供类型安全的
t()函数 - 集成命名空间支持
- 添加语言切换功能
- 提供类型安全的
- 迁移登录页面 (
src/pages/login/index.vue):- 使用新的
useLocale()composable - 更新所有静态文本为翻译键
- 验证动态插值功能
- 使用新的
- 迁移侧边栏菜单:
- 更新菜单项文本
- 验证语言切换实时生效
验收标准
- 类型检查通过 (
npm run typecheck) - 生产构建通过 (
npm run build:vite) - 开发服务器正常启动 (
npm run dev) - 中英文切换功能正常
- 语言设置持久化生效
- 控制台无 i18n 相关错误
第一阶段完成状态
任务完成情况
- ✅ 任务 0:语言选择器 UI 试点 - 已完成(设置页面版本组件)
- ✅ 任务 1:语言文件重组 - 已完成(创建模块化目录结构,拆分现有翻译)
- ✅ 任务 2:核心工具开发 - 已完成(创建 constants.ts 和 resolver.ts)
- ✅ 任务 3:vue-i18n配置升级 - 已完成(重写 index.ts,支持多命名空间)
- ✅ 任务 4:Pinia Store集成 - 已完成(创建 locale.ts,实现状态管理)
- ✅ 任务 5:基础组件迁移试点 - 已完成(创建 useLocale composable,迁移登录页面)
验证结果
- ✅ 类型检查通过 (
npm run typecheck) - ✅ 生产构建通过 (
npm run build:vite) - ✅ 无代码缺失,保持向后兼容
下一步建议
第一阶段基础架构已成功建立,建议继续推进第二阶段(状态管理集成优化)和第三阶段(组件集成优化),逐步将新的国际化架构推广到全应用。
结论
ClawX 的 i18n 实现提供了一个优秀的参考架构,其核心思想——模块化、类型安全、状态集成、智能解析——完全适用于 zn-ai 项目。通过分阶段实施上述开发计划,zn-ai 可以在 1-2 周内建立专业级的国际化系统,为多语言用户提供更好的体验,同时提高代码的可维护性和开发效率。
建议立即开始第二阶段实施,逐步推广到全应用。