# 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 审查:提示词不变,审查质量不退化