From d96e4dd866873a1f660d71b4aacbd7ee3268416d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8C=83=E6=99=BA=E9=B9=8F?= Date: Sun, 26 Jul 2026 00:31:03 +0800 Subject: [PATCH] docs: add i18n internationalization design spec --- .../specs/2026-07-26-i18n-design.md | 422 ++++++++++++++++++ 1 file changed, 422 insertions(+) create mode 100644 docs/superpowers/specs/2026-07-26-i18n-design.md diff --git a/docs/superpowers/specs/2026-07-26-i18n-design.md b/docs/superpowers/specs/2026-07-26-i18n-design.md new file mode 100644 index 0000000..e2fcea7 --- /dev/null +++ b/docs/superpowers/specs/2026-07-26-i18n-design.md @@ -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>`,确保所有消息三种语言都有值。 + +### 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(); + +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 = {}; + 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 审查:提示词不变,审查质量不退化