docs: add i18n internationalization design spec

This commit is contained in:
范智鹏
2026-07-26 00:31:03 +08:00
parent 3661c80db5
commit d96e4dd866
@@ -0,0 +1,422 @@
# i18n 国际化 — Extension Design Spec
## 1. 概述
为 vscode-code-reviewer 实现运行时动态语言切换。用户在设置面板选择语言后,所有 webview 面板、通知、QuickPick、报告等 UI 文字立即生效,无需重载 VS Code 窗口。
语言源:复用现有 `ai.outputLanguage` 配置项(`zh-CN` / `en` / `ja`)。
### 范围
| 区域 | 是否纳入 | 说明 |
|------|---------|------|
| Webview 面板(设置/审查报告/导入预览) | 是 | 动态重渲染 |
| 通知(showInformationMessage 等) | 是 | 下次调用用新语言 |
| QuickPick | 是 | 下次调用用新语言 |
| 进度条(withProgress | 是 | 下次调用用新语言 |
| 报告导出(Markdown) | 是 | 导出时取当前语言 |
| 适配器错误消息 | 是 | 每次调用取当前语言 |
| `package.json` contributes | 否 | 跟随 VS Code 机制,不影响 |
| AI 提示词(engine.ts / fixer.ts | 否 | 保持中文,保证 AI 输出质量稳定 |
---
## 2. 核心模块:`src/i18n/messages.ts`
### 2.1 类型定义
```typescript
export type Language = 'zh-CN' | 'en' | 'ja';
const defaultLang: Language = 'zh-CN';
```
### 2.2 消息注册
单文件注册所有文案,每条包含三种语言的值。命名采用 `模块.场景.含义`,点分隔,全小写。
```typescript
const messages = {
'review.noEditor': {
'zh-CN': '请先打开一个文件',
en: 'Please open a file first',
ja: '最初にファイルを開いてください',
},
// ...
};
`Record<string, Record<Language, string>>`
### 2.3
```typescript
let currentLang: Language = defaultLang;
```
### 2.4 `t()` 函数
```typescript
export function t(key: string): string {
const msg = messages[key]?.[currentLang] ?? messages[key]?.[defaultLang];
return msg ?? key;
}
```
插件内所有文案都通过 `t()` 获取,不再硬编码。无模板插值参数,简单直接。
### 2.5 语言切换
```typescript
const languageChangeEmitter = new vscode.EventEmitter<Language>();
export function setLanguage(lang: Language): void {
if (currentLang === lang) return;
currentLang = lang;
languageChangeEmitter.fire(lang);
}
export function onLanguageChange(listener: (lang: Language) => void): vscode.Disposable {
return languageChangeEmitter.event(listener);
}
export function getLanguage(): Language {
return currentLang;
}
```
---
## 3. 语言切换流程
采用"写配置 → 事件驱动"的单一入口模式:
```
用户在设置面板改语言下拉
webview postMessage 'setLanguage'
extension 端 handleMessage:
vscode.workspace.getConfiguration('vscode-code-reviewer')
.update('ai.outputLanguage', lang, true)
onDidChangeConfiguration 监听到变化(ext.activate 中注册)
if (changed.key === 'vscode-code-reviewer.ai.outputLanguage')
setLanguage(newLang)
onLanguageChange 触发
所有 webview 回调: postMessage 推送新翻译 → 全量重渲染
所有通知/QuickPick: 下次调用时 t() 取新语言
```
### 3.1 为什么不用同步 setLanguage + update
- 用户可能通过 VS Code 原生设置界面改语言,`onDidChangeConfiguration` 能自动捕获
- 单一入口、无分支、逻辑内聚
### 3.2 初始化
`extension.ts``activate()` 中:
```typescript
// 从当前配置初始化语言
const lang = getAIOutputLanguage() as Language;
setLanguage(lang);
// 监听配置变更
context.subscriptions.push(
vscode.workspace.onDidChangeConfiguration(e => {
if (e.affectsConfiguration('vscode-code-reviewer.ai.outputLanguage')) {
const newLang = getAIOutputLanguage() as Language;
setLanguage(newLang);
}
})
);
```
---
## 4. Webview 刷新策略
### 4.1 消息推送
扩展端构建翻译后的消息 map 通过 `postMessage` 发给 webview
```typescript
// 只推送当前 webview 需要的 key
function pushLanguage(webview: vscode.Webview) {
const translated: Record<string, string> = {};
for (const key of webviewI18nKeys) {
translated[key] = t(key);
}
webview.postMessage({ type: 'lang', lang: currentLang, messages: translated });
}
```
### 4.2 Webview 端处理
```javascript
window.addEventListener('message', e => {
if (e.data.type === 'lang') {
render(e.data.messages, e.data.lang);
}
});
```
### 4.3 重渲染
全量重渲染。每个 webview 在收到 `lang` 消息后重建 HTML。
设置面板特殊处理:
- 保存表单当前值到 JS 变量中
- 语言下拉变化时:先持久化当前表单值(API Key 等),再改语言,最后重建
- 重建后用保存的变量回填表单
### 4.4 Webview HTML 生成方式
当前 webview 在扩展端生成 HTML 字符串。改造后:
- 被翻译的文案在生成 HTML 时用 `t()` 注入(扩展端调用)
- 生成完整 HTML 时文案已经翻译好,webview 端 JS 无需再翻译
- webview 端 JS 只是接收到 `lang` 消息时重新请求全集 HTML(或接受新 HTML 字符串替换)
三种面板处理方式:
| 面板 | 策略 |
|------|------|
| 审查报告 | 重新生成 HTML → postMessage → 替换 `document.body.innerHTML` |
| 设置面板 | 保留表单 JS 变量,重新生成 HTML 后回填 |
| 导入预览 | 重新生成 HTML → postMessage → 替换 |
---
## 5. 消息清单设计
预估约 60 条文案,按命名空间分组。
### 5.1 review.* (审查类)
| Key | zh-CN | en (参考) |
|-----|-------|-----------|
| `review.noEditor` | 请先打开一个文件 | Please open a file first |
| `review.running` | 正在审查... | Reviewing... |
| `review.staticAnalysis` | 运行静态分析... | Running static analysis... |
| `review.aiReview` | 运行 AI 审查... | Running AI review... |
| `review.completeSelection` | 选中代码审查完成: {0} 个问题 | Selection review: {0} issues |
| `review.needRunFirst` | 请先运行完整审查生成报告 | Run a full review first |
| `review.noSelection` | 请先选中要审查的代码 | Select code to review first |
| `review.needApiKey` | 请先在设置面板中配置 API Key | Configure API Key in Setup first |
| `review.fixNotAvailable` | 单条修复功能开发中 | Single fix under development |
| `review.fixAllNotAvailable` | 批量修复功能开发中 | Batch fix under development |
### 5.2 export.* (导出类)
| Key | Description |
|-----|-------------|
| `export.selectMethod` | 选择导出方式 |
| `export.copyToClipboard` | 复制到剪贴板 |
| `export.downloadMarkdown` | 下载 Markdown 文件 |
| `export.copied` | 报告已复制到剪贴板 |
| `export.saveDialogTitle` | 保存审查报告 |
| `export.saved` | 报告已保存到 {0} |
### 5.3 setup.* (设置面板)
| Key | Description |
|-----|-------------|
| `setup.header` | 净码特工 · 代码审查 · 设置 |
| `setup.quickStart` | 快速开始 |
| `setup.gettingStarted` | 三步启用代码审核 |
| `setup.step1` | 安装插件后,配置 AI 模型及 API Key... |
| `setup.step2` | 启用自定义规则,补充团队特有的编码规范 |
| `setup.step3` | 保存并测试连接,验证配置无误后即可触发审核 |
| `setup.step3Hint` | 按 Ctrl + Shift + R 快捷键触发审核... |
| `setup.engineSection` | 审核引擎 |
| `setup.commonRules` | 共通规则 |
| `setup.linterStatic` | Linter 静态分析 |
| `setup.customRules` | 自定义规则 |
| `setup.teamCoding` | 团队编码规范 |
| `setup.aiReview` | AI 审核 |
| `setup.deepReview` | 深度代码审查 |
| `setup.aiConfig` | AI 模型配置 |
| `setup.provider` | 模型提供商 |
| `setup.notConfigured` | 未配置 |
| `setup.model` | 模型名称 |
| `setup.modelHint` | 建议使用支持结构化输出的模型 |
| `setup.apiKey` | API Key |
| `setup.baseUrl` | Base URL |
| `setup.keyStorageHint` | Key 仅存储在本地 VS Code 安全存储中 |
| `setup.outputLang` | 输出语言 |
| `setup.outputLangHint` | AI 审查结果输出语言 |
| `setup.customRulesSection` | 自定义规则 |
| `setup.ruleList` | 规则列表 |
| `setup.ruleCount` | {0} 条 |
| `setup.ruleNamePlaceholder` | 输入规则名称... |
| `setup.add` | + 添加 |
| `setup.ruleNameHint` | 建议使用英文名称... |
| `setup.reset` | 重置 |
| `setup.saveAndTest` | 保存并测试连接 |
| `setup.testSuccess` | ✓ 连接成功 |
| `setup.testFail` | ✗ 连接失败: {0} |
| `setup.setApiKeyFirst` | 请先设置 API Key |
| `setup.setBaseUrlFirst` | 请先设置 Base URL |
| `setup.selectRuleFile` | 选择规则文件 |
| `setup.fileExists` | 文件 {0} 已存在 |
| `setup.importSuccess` | 规则文件已导入: {0} |
| `setup.importCancelled` | 导入已取消 |
| `setup.importDedupResult` | 规则已导入: ... ({0} 条,{1} 条重复已注释,{2} 条重叠已标注) |
| `setup.importFail` | 规则导入失败: {0} |
| `setup.openSetupFail` | 无法打开设置面板 |
| `setup.openSettingsJson` | 打开设置 (JSON) |
| `setup.noWorkspace` | 请先打开工作区 |
| `setup.manageRulesHint` | 请在设置面板中管理自定义规则 |
| `setup.languageLabels` | 中文(简体)/ English / 日本語 |
### 5.4 report.* (审查报告)
| Key | Description |
|-----|-------------|
| `report.title` | 代码审查报告 |
| `report.panelTitle` | 净码特工 · 代码审查报告 |
| `report.totalIssues` | 总计问题 |
| `report.errors` | 错误 |
| `report.warnings` | 警告 |
| `report.info` | 建议 |
| `report.fixAll` | 全部修复 |
| `report.rerun` | 重新审查 |
| `report.export` | 导出报告 |
| `report.sourceLinter` | Linter |
| `report.sourceCustom` | 自定义 |
| `report.sourceAI` | AI |
| `report.noIssues` | 未发现任何问题 |
| `report.noRuleViolations` | 未发现规则违规 |
| `report.noAIFindings` | 无 AI 审查建议 |
| `report.skipCustomRules` | 当前文件语言无匹配的自定义规则 |
| `report.executionErrors` | 执行错误 |
| `report.degraded` | AI 审查未完成,报告仅包含部分结果 |
| `report.degradedBanner` | 部分 AI 功能不可用,报告已降级 |
| `report.file` | 文件 |
| `report.language` | 语言 |
| `report.duration` | 耗时 |
| `report.tools` | 分析工具 |
| `report.totalSummary` | 总计: {0} \| 错误: {1} \| 警告: {2} \| 建议: {3} |
| `report.staticSection` | 静态分析 · {0} 个问题 |
| `report.customSection` | 自定义规则 · {0} 个问题 |
| `report.aiSection` | AI 审查 · {0} 条建议 |
| `report.suggestion` | 建议 |
| `report.noProblems` | 未发现问题 |
### 5.5 import.* (规则导入)
| Key | Description |
|-----|-------------|
| `import.previewTitle` | 规则导入预览 |
| `import.source` | 来源:{0} · 检测到 {1} 条规则 |
| `import.fullDuplicate` | 完全重复 {0} 条 |
| `import.partialOverlap` | 部分重叠 {0} 条 |
| `import.noDuplicate` | 无重复 {0} 条 |
| `import.willKeep` | 将保留 {0} 条规则,注释 {1} 条规则 |
| `import.keep` | 保留 |
| `import.comment` | 注释 |
| `import.cancel` | 取消 |
| `import.confirm` | 确认导入 |
| `import.emptyFile` | 所选文件为空 |
| `import.needApiKey` | 请先在设置面板中配置 API Key |
| `import.timeout` | AI 生成规则超时,请检查网络或增大 ai.timeout 配置 |
| `import.aiFail` | AI 生成规则失败: {0} |
| `import.emptyResponse` | AI 返回内容为空 |
| `import.excelReadFail` | 读取 Excel 文件失败: {0} |
| `import.excelEmpty` | Excel 文件没有工作表 |
| `import.excelNoData` | Excel 工作表中没有数据 |
| `import.docxReadFail` | 读取 Word 文件失败: {0} |
| `import.docxEmpty` | Word 文件中没有可提取的文本内容 |
| `import.pptxReadFail` | 读取 PowerPoint 文件失败: {0} |
| `import.pptxEmpty` | PowerPoint 文件中没有可提取的文本内容 |
### 5.6 adapter.* (适配器错误)
| Key | Description |
|-----|-------------|
| `adapter.javaNotInstalled` | Java 11+ 未安装或不在 PATH 中 |
| `adapter.sqlfluffNotInstalled` | sqlfluff 未安装,请执行 pip install sqlfluff |
| `adapter.invalidApiKey` | API Key 无效,请重新设置 |
| `adapter.noApiKey` | 未配置 API Key |
| `adapter.createProviderFail` | 创建 Provider 失败: {0} |
| `adapter.customRuleParseFail` | 自定义规则响应解析失败: {0} |
| `adapter.customRuleRequestFail` | 自定义规则请求失败: {0} |
| `adapter.aiReviewParseFail` | AI 审查响应解析失败: {0} |
| `adapter.aiReviewRequestFail` | AI 审查请求失败: {0} |
### 5.7 extension.* (插件级)
| Key | Description |
|-----|-------------|
| `extension.activated` | 净码特工 · Code Purifier 已激活 |
### 5.8 命令标题(仅作参考,不修改 package.json
命令标题在 `package.json` 保持中文不变,不纳入本次范围。
---
## 6. 文件变更清单
| 变更类型 | 文件 | 说明 |
|----------|------|------|
| 新增 | `src/i18n/messages.ts` | 核心 i18n 模块 |
| 新增 | `src/i18n/messages.test.ts` | 消息完整性测试 |
| 修改 | `src/extension.ts` | activate 中初始化语言 + 监听配置变更 |
| 修改 | `src/activation/commands.ts` | 通知/进度条/QuickPick 文案替换为 t() |
| 修改 | `src/views/setupView.ts` | 设置面板 HTML 生成用 t(),语言下拉即时生效 |
| 修改 | `src/panel/webview.ts` | 审查报告 HTML 生成用 t(),监听语言变化重渲染 |
| 修改 | `src/views/import-preview.ts` | 导入预览 HTML 用 t() |
| 修改 | `src/utils/report.ts` | 报告生成用 t() |
| 修改 | `src/adapters/pmd.ts` | 错误消息替换 |
| 修改 | `src/adapters/sql-lint.ts` | 错误消息替换 |
| 修改 | `src/ai/engine.ts` | 错误消息替换(提示词不修改) |
| 修改 | `src/ai/providers/openai-compatible.ts` | 错误消息替换 |
| 修改 | `src/rules/import-service.ts` | 错误消息替换 |
| 修改 | `src/rules/converters/excel-converter.ts` | 错误消息替换 |
| 修改 | `src/rules/converters/docx-converter.ts` | 错误消息替换 |
| 修改 | `src/rules/converters/pptx-converter.ts` | 错误消息替换 |
---
## 7. 实现顺序
1. 创建 `src/i18n/messages.ts`(核心模块 + 全部消息注册)
2. 编写 `messages.test.ts`(验证三种语言完整性)
3. 改造 `src/extension.ts`init + onDidChangeConfiguration
4. 改造 `src/activation/commands.ts`
5. 改造 `src/panel/webview.ts`
6. 改造 `src/views/setupView.ts`
7. 改造 `src/views/import-preview.ts`
8. 改造 `src/utils/report.ts`
9. 改造 `src/adapters/pmd.ts``sql-lint.ts`
10. 改造 `src/ai/engine.ts``openai-compatible.ts`
11. 改造各 converter 错误消息
12. 运行 `npm test` 验证
---
## 8. 边界处理
| 场景 | 处理 |
|------|------|
| 某个 key 不存在于 messages | fallback zh-CN,再 fallback 返回 key 本身 |
| 某条消息缺某种语言翻译 | fallback zh-CN |
| 传入非法语言值 | TypeScript 类型系统 `Language` 防止编译时错误 |
| setLanguage 同语言重复调 | 短路 return,不触发事件 |
| 切换语言时 webview 未打开 | 下次打开 webview 时取当前语言渲染,无影响 |
| 切换语言时正在显示通知 | 已显示的通知不更新,下次通知用新语言 |
---
## 9. 验证标准
- 在设置面板切语言后:设置面板文字立即变,审查报告文字立即变
- 重新触发审查:通知/进度条用新语言
- 导出报告:报告标题和标签用新语言
- 重启 VS Code:语言保持上次设置的值
- AI 审查:提示词不变,审查质量不退化