Files
bim_engine/docs/国际化实现指南.md
yuding 8d027419e4 docs: 文档中文化重命名并添加规范文档
- 将所有英文文档重命名为中文格式
- 新增 AI代码规范模板.md(通用开发规范)
- 新增 国际化实现指南.md(i18n 规范)
- 重复文档移至 .recycle 目录
2026-01-21 10:59:23 +08:00

549 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 国际化实现指南
> 本文档详细描述项目的国际化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 = `<div>${t('myComponent.description')}</div>`;
```
---
## 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 = `
<div class="my-component-title"></div>
<div class="my-component-content"></div>
<div class="my-component-actions">
<button class="btn-save"></button>
<button class="btn-cancel"></button>
</div>
`;
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<LocaleType, TranslationDictionary> = {
'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