# 国际化实现指南 > 本文档详细描述项目的国际化(i18n)实现规范,包括翻译键定义、组件实现、语言切换等。 --- ## 📋 目录 1. [国际化的重要性](#1-国际化的重要性) 2. [项目国际化架构](#2-项目国际化架构) 3. [实现步骤](#3-实现步骤) 4. [组件中的国际化实现](#4-组件中的国际化实现) 5. [注意事项](#5-注意事项) 6. [最佳实践](#6-最佳实践) 7. [添加新语言](#7-添加新语言) 8. [检查清单](#8-检查清单) --- ## 1. 国际化的重要性 **所有用户可见的文本都必须支持国际化,这是强制要求。** - 项目支持多语言(目前:中文 `zh-CN`、英文 `en-US`) - 所有 UI 文本必须通过翻译函数获取 - **严禁在代码中硬编码任何语言的文本** --- ## 2. 项目国际化架构 ### 2.1 相关文件 | 文件路径 | 作用 | |---------|------| | `src/locales/types.ts` | 翻译键类型定义 | | `src/locales/zh-CN.ts` | 中文翻译字典 | | `src/locales/en-US.ts` | 英文翻译字典 | | `src/services/locale.ts` | 语言管理服务(单例) | ### 2.2 核心 API ```typescript // 导入语言服务 import { t, localeManager } from '../../services/locale'; // 获取翻译文本 const text = t('toolbar.home'); // 返回 "首页" 或 "Home" // 订阅语言变更 const unsubscribe = localeManager.subscribe(() => { // 语言变更时的回调 this.setLocales(); }); // 取消订阅(在组件销毁时调用) unsubscribe(); // 切换语言 localeManager.setLocale('en-US'); // 获取当前语言 const currentLocale = localeManager.getLocale(); ``` --- ## 3. 实现步骤 ### 步骤 1:在类型文件中定义翻译键 **文件:`src/locales/types.ts`** ```typescript export interface TranslationDictionary { // ... 现有键 myComponent: { title: string; description: string; buttons: { save: string; cancel: string; }; }; } ``` ### 步骤 2:添加中文翻译 **文件:`src/locales/zh-CN.ts`** ```typescript export const zhCN: TranslationDictionary = { // ... 现有内容 myComponent: { title: '我的组件', description: '这是组件描述', buttons: { save: '保存', cancel: '取消', }, }, }; ``` ### 步骤 3:添加英文翻译 **文件:`src/locales/en-US.ts`** ```typescript export const enUS: TranslationDictionary = { // ... 现有内容 myComponent: { title: 'My Component', description: 'This is component description', buttons: { save: 'Save', cancel: 'Cancel', }, }, }; ``` ### 步骤 4:在代码中使用翻译函数 ```typescript import { t } from '../../services/locale'; // 获取翻译文本 const title = t('myComponent.title'); // 在 DOM 中使用 const titleEl = document.createElement('div'); titleEl.textContent = t('myComponent.title'); // 在 HTML 字符串中使用 const html = `
${t('myComponent.description')}
`; ``` --- ## 4. 组件中的国际化实现 ### 4.1 完整实现示例 ```typescript import { t, localeManager } from '../../services/locale'; import { IBimComponent } from '../../types/component'; import { ThemeConfig } from '../../themes/types'; export class MyComponent implements IBimComponent { private element: HTMLElement; private unsubscribeLocale: (() => void) | null = null; private unsubscribeTheme: (() => void) | null = null; constructor(container: HTMLElement) { this.element = this.createDOM(); container.appendChild(this.element); } /** * 创建 DOM 结构 */ private createDOM(): HTMLElement { const root = document.createElement('div'); root.className = 'my-component'; root.innerHTML = `
`; return root; } /** * 初始化组件 */ public init(): void { // 订阅语言变更 this.unsubscribeLocale = localeManager.subscribe(() => { this.setLocales(); }); // 初始设置语言 this.setLocales(); } /** * 设置/更新所有文本(语言变更时调用) */ public setLocales(): void { // 更新标题 const titleEl = this.element.querySelector('.my-component-title'); if (titleEl) { titleEl.textContent = t('myComponent.title'); } // 更新内容 const contentEl = this.element.querySelector('.my-component-content'); if (contentEl) { contentEl.textContent = t('myComponent.description'); } // 更新按钮 const saveBtn = this.element.querySelector('.btn-save'); if (saveBtn) { saveBtn.textContent = t('myComponent.buttons.save'); } const cancelBtn = this.element.querySelector('.btn-cancel'); if (cancelBtn) { cancelBtn.textContent = t('myComponent.buttons.cancel'); } } /** * 设置主题 */ public setTheme(theme: ThemeConfig): void { // 主题设置逻辑 } /** * 销毁组件 */ public destroy(): void { // 取消语言订阅 if (this.unsubscribeLocale) { this.unsubscribeLocale(); this.unsubscribeLocale = null; } // 取消主题订阅 if (this.unsubscribeTheme) { this.unsubscribeTheme(); this.unsubscribeTheme = null; } // 移除 DOM this.element.remove(); } } ``` ### 4.2 关键点说明 1. **在 `init()` 中订阅语言变更** 2. **实现 `setLocales()` 方法更新所有文本** 3. **在 `destroy()` 中取消订阅** 4. **使用 `t('key.path')` 获取翻译文本** --- ## 5. 注意事项 ### 5.1 ✅ 必须做的 #### 所有用户可见文本使用 `t(key)` ```typescript // ✅ 正确 button.textContent = t('toolbar.home'); // ❌ 错误 button.textContent = '首页'; button.textContent = 'Home'; ``` #### 翻译键使用有意义的路径 ```typescript // ✅ 正确:按功能模块组织 t('toolbar.home') t('dialog.title') t('button.save') t('message.success') // ❌ 错误:键名不清晰 t('text1') t('label') t('str') ``` #### 在所有语言文件中添加翻译 - 添加新键时,必须同时更新 `zh-CN.ts` 和 `en-US.ts` - 确保所有语言文件的结构一致 #### 实现 `setLocales()` 方法 - 所有组件必须实现 `setLocales()` 方法 - 在方法中更新所有用户可见的文本 #### 订阅语言变更 - 组件初始化时订阅 `localeManager.subscribe()` - 组件销毁时取消订阅 ### 5.2 ❌ 禁止做的 #### 禁止硬编码文本 ```typescript // ❌ 错误:硬编码中文 const title = '首页'; // ❌ 错误:硬编码英文 const title = 'Home'; // ✅ 正确:使用翻译函数 const title = t('toolbar.home'); ``` #### 禁止在翻译键中使用变量 ```typescript // ❌ 错误:动态拼接键名(难以追踪和维护) t(`toolbar.${buttonId}`); // ✅ 正确:使用完整的键名 t('toolbar.home'); t('toolbar.settings'); ``` #### 禁止忽略语言变更 - 组件必须响应语言切换 - 不能只在初始化时设置文本 --- ## 6. 最佳实践 ### 6.1 翻译键的组织结构 ```typescript // 按功能模块组织 interface TranslationDictionary { // 工具栏相关 toolbar: { home: string; settings: string; measure: string; }; // 弹窗相关 dialog: { title: string; content: string; close: string; }; // 按钮相关 button: { save: string; cancel: string; confirm: string; delete: string; }; // 消息相关 message: { success: string; error: string; warning: string; loading: string; }; // 表单相关 form: { required: string; invalid: string; placeholder: { name: string; email: string; }; }; } ``` ### 6.2 嵌套键的使用 对于复杂组件,可以使用嵌套结构: ```typescript // 类型定义 measurePanel: { modes: { distance: string; angle: string; area: string; }; labels: { currentMode: string; result: string; }; actions: { clear: string; settings: string; }; }; // 使用 t('measurePanel.modes.distance') // "距离" t('measurePanel.labels.result') // "结果" ``` ### 6.3 处理动态内容 如果需要在翻译中插入动态内容,建议: ```typescript // 方案 1:翻译后拼接(简单场景) const message = t('file.selected') + `: ${fileName}`; // 方案 2:使用模板(需要扩展 LocaleManager) // 翻译字典: "已选择 {count} 个文件" const message = t('file.selectedCount', { count: 5 }); ``` --- ## 7. 添加新语言 ### 步骤 1:扩展语言类型 **文件:`src/locales/types.ts`** ```typescript export type LocaleType = 'zh-CN' | 'en-US' | 'ja-JP'; // 添加日语 ``` ### 步骤 2:创建翻译文件 **文件:`src/locales/ja-JP.ts`** ```typescript import { TranslationDictionary } from './types'; export const jaJP: TranslationDictionary = { toolbar: { home: 'ホーム', settings: '設定', // ... 所有翻译 }, // ... 完整的翻译字典 }; ``` ### 步骤 3:注册新语言 **文件:`src/services/locale.ts`** ```typescript import { jaJP } from '../locales/ja-JP'; class LocaleManager { private messages: Record = { 'zh-CN': zhCN, 'en-US': enUS, 'ja-JP': jaJP, // 添加新语言 }; // ... } ``` --- ## 8. 检查清单 在开发新功能时,确保完成以下检查: ### 8.1 翻译键 - [ ] 在 `types.ts` 中定义了新的翻译键类型 - [ ] 在 `zh-CN.ts` 中添加了中文翻译 - [ ] 在 `en-US.ts` 中添加了英文翻译 - [ ] 翻译键名清晰、有意义 - [ ] 翻译键结构与类型定义一致 ### 8.2 组件实现 - [ ] 所有用户可见的文本都使用 `t(key)` 函数 - [ ] 组件实现了 `setLocales()` 方法 - [ ] 组件在 `init()` 中订阅了语言变更事件 - [ ] 组件在 `destroy()` 中取消了语言订阅 ### 8.3 代码质量 - [ ] 没有硬编码任何语言的文本 - [ ] 没有使用动态拼接的翻译键 - [ ] 翻译文本语法正确、表达清晰 ### 8.4 测试验证 - [ ] 在中文环境下测试文本显示正确 - [ ] 在英文环境下测试文本显示正确 - [ ] 切换语言后所有文本正确更新 - [ ] 没有遗漏的未翻译文本 --- ## 附录:现有翻译键参考 ### 工具栏 (toolbar) ```typescript toolbar: { home: '首页' | 'Home', settings: '设置' | 'Settings', info: '信息' | 'Info', location: '定位' | 'Location', measure: '测量' | 'Measure', // ... } ``` ### 弹窗 (dialog) ```typescript dialog: { title: '弹窗标题' | 'Dialog Title', close: '关闭' | 'Close', // ... } ``` ### 测量面板 (measure) ```typescript measure: { modes: { distance: '距离' | 'Distance', angle: '角度' | 'Angle', // ... }, // ... } ``` --- **文档版本**:v1.0 **最后更新**:2026-01-21