From 1c12c6ded4a099faeb4f32592a2dce4994ccf02b Mon Sep 17 00:00:00 2001 From: emikeliu Date: Sun, 2 Aug 2026 19:46:58 +0800 Subject: [PATCH 1/2] feat(ui): init l10n --- l10n/bundle.l10n.json | 144 ++++++++++++++++++ l10n/bundle.l10n.zh-cn.json | 143 +++++++++++++++++ package.json | 105 ++++++------- package.nls.json | 57 +++++++ package.nls.zh-cn.json | 57 +++++++ src/agent/agentRunner.ts | 8 +- src/controller.ts | 14 +- src/debugLogger.ts | 3 +- src/extension.ts | 49 +++--- src/i18n.ts | 36 +++++ src/notebook/commands/compressConversation.ts | 21 +-- src/notebook/commands/debugContext.ts | 11 +- src/notebook/commands/pruneGhostBlocks.ts | 9 +- src/notebook/commands/renameSession.ts | 15 +- src/notebook/commands/selectModel.ts | 31 ++-- src/notebook/commands/testRagSearch.ts | 13 +- src/notebook/commands/toggleAutoApprove.ts | 13 +- src/notebook/completionProvider.ts | 11 +- src/notebook/serializer.ts | 5 +- src/notifications.ts | 5 +- src/sidebar/agentTreeItem.ts | 11 +- src/sidebar/approvalTreeItem.ts | 17 ++- src/sidebar/contextTreeItem.ts | 31 ++-- src/sidebar/shellTaskTreeItem.ts | 27 ++-- src/tools.d/edit_file.ts | 3 +- src/tools.d/permission.ts | 9 +- src/tools.d/toolsLogger.ts | 3 +- 27 files changed, 661 insertions(+), 190 deletions(-) create mode 100644 l10n/bundle.l10n.json create mode 100644 l10n/bundle.l10n.zh-cn.json create mode 100644 package.nls.json create mode 100644 package.nls.zh-cn.json create mode 100644 src/i18n.ts diff --git a/l10n/bundle.l10n.json b/l10n/bundle.l10n.json new file mode 100644 index 0000000..4482cc0 --- /dev/null +++ b/l10n/bundle.l10n.json @@ -0,0 +1,144 @@ +{ + "Mutsumi Debug": "Mutsumi Debug", + "Mutsumi Tools": "Mutsumi Tools", + "context.readError": "Error reading: {0}", + "renderer.parseError": "Error: Failed to parse render data\n reason: {0}\n", + + "httpServer.emptyPassword.warning": "Mutsumi HTTP Server is enabled but no password is set (mutsumi.httpServer.password). The server refuses to start.", + "httpServer.emptyPassword.openSettings": "Open Settings", + "httpServer.emptyPassword.generate": "Generate Random Password", + "httpServer.passwordGenerated": "Generated a random HTTP Server password, saved it to your user settings, and copied it to the clipboard.", + + "newAgent.noWorkspace": "Please open a workspace folder first.", + "newAgent.noEntryTypes": "No entry agent types available. Please check your configuration.", + "newAgent.quickPickPlaceHolder": "Select an agent type to create", + "newAgent.quickPickTitle": "Mutsumi: New Agent", + "newAgent.detail": "Model: {0} | Rules: {1} | Skills: {2}", + "newAgent.created": "Created {0} agent with {1} rules and {2} skills", + + "copyReference.noFile": "No file selected or active.", + "copyReference.notInWorkspace": "File is not in the workspace.", + "copyReference.statusBar": "Copied reference: {0}", + + "clearToolCache.done": "Tool result cache cleared.", + + "controller.copyDetails": "Copy Details", + "controller.mutsumiError": "Mutsumi Error: {0}", + + "agentRunner.llmError": "Mutsumi LLM Error: {0}", + + "status.running": "Running", + "status.pending": "Pending", + "status.finished": "Finished", + "status.standby": "Standby", + + "approval.pending": "⏳ Pending", + "approval.approved": "✅ Approved", + "approval.rejected": "❌ Rejected", + "approval.target": "📁 Target: `{0}`", + "approval.customActionAvailable": "🔍 **Custom Action Available**: {0}", + "approval.details": "📝 Details:\n```\n{0}\n```\n\n", + "approval.time": "🕐 Time: {0}", + "approval.status": "Status: {0}", + + "shellTask.bg": "bg {0}s", + "shellTask.fg": "fg {0}s", + "shellTask.stopped": "stopped", + "shellTask.exit": "exit {0}{1}", + "shellTask.cwd": "📁 CWD: `{0}`", + "shellTask.task": "🆔 Task: `{0}`", + "shellTask.agentSession": "🖥️ Agent Session: `{0}`", + "shellTask.mode": "Mode: {0}\n\n", + "shellTask.modeBackground": "background", + "shellTask.modeForeground": "foreground", + "shellTask.statusStopped": "Status: 🛑 stopped\n\n", + "shellTask.statusRunning": "Status: ▶️ running\n\n", + "shellTask.statusExit": "Status: ✅ exit {0}{1}\n\n", + "shellTask.signal": " (signal: {0})", + "shellTask.output": "Output:\n```\n{0}\n```", + + "context.category.rules": "Rules: Active context rules for Agents", + "context.category.skills": "Skills: Active context skills for Agents", + "context.category.macros": "Macros: Reusable text snippets", + "context.category.files": "Files: Referenced context files", + "context.category.default": "Category", + "context.active": " (Active)", + "context.inactive": " (Inactive)", + "context.refreshed": "Context tree refreshed", + "context.ruleActivated": "Rule \"{0}\" activated", + "context.ruleDeactivated": "Rule \"{0}\" deactivated", + "context.skillActivated": "Skill \"{0}\" activated", + "context.skillDeactivated": "Skill \"{0}\" deactivated", + "context.noOlderVersions": "No older versions of \"{0}\" to prune", + "context.fileNotFound": "File not found: {0}", + + "notebook.noEditor": "No active notebook editor.", + "notebook.onlyMutsumi": "This command only works with Mutsumi notebooks.", + "notebook.noCodeCell": "No code cell found in notebook.", + + "selectModel.noModels": "No models configured in settings.", + "selectModel.placeHolder": "Select model or reasoning effort (switching model resets effort to default; current: {0} / {1})", + "selectModel.modelChanged": "Model changed to: {0}; reasoning effort reset to default.", + "selectModel.effortChanged": "Reasoning effort changed to: {0}", + "selectModel.separatorModels": "Models", + "selectModel.separatorReasoning": "Reasoning efforts", + "selectModel.effortDefault": "Default", + "selectModel.effort.none": "Disable reasoning", + "selectModel.effort.minimal": "Minimal reasoning", + "selectModel.effort.low": "Low reasoning", + "selectModel.effort.medium": "Medium reasoning", + "selectModel.effort.high": "High reasoning", + "selectModel.effort.xhigh": "Extra-high reasoning", + "selectModel.effort.max": "Maximum reasoning", + + "renameSession.prompt": "Enter new session title (leave empty to auto-generate)", + "renameSession.renamed": "Session renamed: {0}", + "renameSession.failed": "Failed to rename session: {0}", + "renameSession.regenerated": "Title regenerated: {0}", + "renameSession.regenerateFailed": "Failed to regenerate title: {0}", + + "debugContext.displayed": "Debug context displayed. Total messages: {0}", + "debugContext.failed": "Failed to debug context: {0}", + + "pruneGhostBlocks.alreadyLatest": "All file references are already at their latest versions.", + "pruneGhostBlocks.done": "Pruned old ghost versions from {0} cell(s).", + + "toggleAutoApprove.on": "Auto-approve mode is now ON. Tools will be executed without confirmation.", + "toggleAutoApprove.off": "Auto-approve mode is now OFF. Tools will require confirmation.", + "toggleAutoApprove.failed": "Failed to toggle auto-approve: {0}", + + "testRag.prompt": "Enter natural language query for RAG search", + "testRag.placeHolder": "e.g., \"how to handle file operations\"", + "testRag.cancelled": "Search cancelled or empty query.", + "testRag.noWorkspaces": "No workspace folders open.", + "testRag.completed": "RAG search completed across {0} workspace(s).", + "testRag.failed": "RAG search failed: {0}", + + "compress.progress": "Compressing conversation...", + "compress.noModel": "Please configure mutsumi.compressModel or mutsumi.defaultModel in settings.", + "compress.failed": "Compression failed: {0}", + "compress.notEnough": "Not enough conversation content to compress.", + "compress.done": "Conversation compressed and saved to: {0}", + "compress.failedOverall": "Failed to compress conversation: {0}", + "compress.nameSuffix": "{0} (Compressed)", + + "completion.fileReference": "File Reference", + "completion.directoryReference": "Directory Reference", + "completion.tool": "Tool", + "completion.toolCall": "Tool Call", + "completion.referenceDoc": "Reference content of `{0}`", + + "permission.rejectPrompt": "Reason for rejecting {0}:", + "permission.rejectPlaceHolder": "Enter reason (ESC to abort generation)", + "permission.rejected": "[Rejected] The {0} operation was rejected by user.", + "permission.rejectedWithReason": "[Rejected with Reason] The {0} operation was rejected by user. Reason: {1}", + + "agentFile.openFailed": "Failed to open agent file: {0}", + + "editFile.reopenFailed": "Failed to reopen editor: {0}", + + "serializer.newAgent": "New Agent", + + "notify.approvalNeededSubtitle": "Approval needed", + "notify.approvalNeededMessage": "{0} — review in the sidebar" +} diff --git a/l10n/bundle.l10n.zh-cn.json b/l10n/bundle.l10n.zh-cn.json new file mode 100644 index 0000000..3507408 --- /dev/null +++ b/l10n/bundle.l10n.zh-cn.json @@ -0,0 +1,143 @@ +{ + "Mutsumi Debug": "Mutsumi 调试", + "Mutsumi Tools": "Mutsumi 工具", + "context.readError": "读取错误: {0}", + + "httpServer.emptyPassword.warning": "Mutsumi HTTP Server 已启用但未设置密码(mutsumi.httpServer.password)。服务器拒绝启动。", + "httpServer.emptyPassword.openSettings": "打开设置", + "httpServer.emptyPassword.generate": "生成随机密码", + "httpServer.passwordGenerated": "已生成随机 HTTP Server 密码,保存到用户设置并复制到剪贴板。", + + "newAgent.noWorkspace": "请先打开一个工作区文件夹。", + "newAgent.noEntryTypes": "没有可用的入口 Agent 类型。请检查配置。", + "newAgent.quickPickPlaceHolder": "选择要创建的 Agent 类型", + "newAgent.quickPickTitle": "Mutsumi: 新建 Agent", + "newAgent.detail": "模型: {0} | 规则: {1} | 技能: {2}", + "newAgent.created": "已创建 {0} Agent,包含 {1} 条规则和 {2} 个技能", + + "copyReference.noFile": "未选中或未打开文件。", + "copyReference.notInWorkspace": "文件不在工作区中。", + "copyReference.statusBar": "已复制引用: {0}", + + "clearToolCache.done": "工具结果缓存已清除。", + + "controller.copyDetails": "复制详情", + "controller.mutsumiError": "Mutsumi 错误: {0}", + + "agentRunner.llmError": "Mutsumi LLM 错误: {0}", + + "status.running": "运行中", + "status.pending": "等待中", + "status.finished": "已完成", + "status.standby": "待命", + + "approval.pending": "⏳ 等待中", + "approval.approved": "✅ 已批准", + "approval.rejected": "❌ 已拒绝", + "approval.target": "📁 目标: `{0}`", + "approval.customActionAvailable": "🔍 **自定义操作可用**: {0}", + "approval.details": "📝 详情:\n```\n{0}\n```\n\n", + "approval.time": "🕐 时间: {0}", + "approval.status": "状态: {0}", + + "shellTask.bg": "后台 {0}秒", + "shellTask.fg": "前台 {0}秒", + "shellTask.stopped": "已停止", + "shellTask.exit": "退出 {0}{1}", + "shellTask.cwd": "📁 工作目录: `{0}`", + "shellTask.task": "🆔 任务: `{0}`", + "shellTask.agentSession": "🖥️ Agent 会话: `{0}`", + "shellTask.mode": "模式: {0}\n\n", + "shellTask.modeBackground": "后台", + "shellTask.modeForeground": "前台", + "shellTask.statusStopped": "状态: 🛑 已停止\n\n", + "shellTask.statusRunning": "状态: ▶️ 运行中\n\n", + "shellTask.statusExit": "状态: ✅ 退出 {0}{1}\n\n", + "shellTask.signal": " (信号: {0})", + "shellTask.output": "输出:\n```\n{0}\n```", + + "context.category.rules": "规则: Agent 的活动上下文规则", + "context.category.skills": "技能: Agent 的活动上下文技能", + "context.category.macros": "宏: 可复用的文本片段", + "context.category.files": "文件: 引用的上下文文件", + "context.category.default": "类别", + "context.active": " (已启用)", + "context.inactive": " (已禁用)", + "context.refreshed": "上下文树已刷新", + "context.ruleActivated": "规则 \"{0}\" 已启用", + "context.ruleDeactivated": "规则 \"{0}\" 已禁用", + "context.skillActivated": "技能 \"{0}\" 已启用", + "context.skillDeactivated": "技能 \"{0}\" 已禁用", + "context.noOlderVersions": "\"{0}\" 没有可裁剪的旧版本", + "context.fileNotFound": "未找到文件: {0}", + + "notebook.noEditor": "没有活动的笔记本编辑器。", + "notebook.onlyMutsumi": "此命令仅适用于 Mutsumi 笔记本。", + + "selectModel.noModels": "设置中未配置任何模型。", + "selectModel.placeHolder": "选择模型或推理强度(切换模型会将强度重置为默认;当前: {0} / {1})", + "selectModel.modelChanged": "模型已切换为: {0};推理强度已重置为默认。", + "selectModel.effortChanged": "推理强度已切换为: {0}", + "selectModel.separatorModels": "模型", + "selectModel.separatorReasoning": "推理强度", + "selectModel.effortDefault": "默认", + "selectModel.effort.none": "禁用推理", + "selectModel.effort.minimal": "最小推理", + "selectModel.effort.low": "低推理", + "selectModel.effort.medium": "中等推理", + "selectModel.effort.high": "高推理", + "selectModel.effort.xhigh": "超高推理", + "selectModel.effort.max": "最大推理", + + "renameSession.prompt": "输入新的会话标题(留空则自动生成)", + "renameSession.renamed": "会话已重命名: {0}", + "renameSession.failed": "重命名会话失败: {0}", + "renameSession.regenerated": "标题已重新生成: {0}", + "renameSession.regenerateFailed": "重新生成标题失败: {0}", + + "debugContext.displayed": "调试上下文已显示。消息总数: {0}", + "debugContext.failed": "调试上下文失败: {0}", + + "pruneGhostBlocks.alreadyLatest": "所有文件引用均已为最新版本。", + "pruneGhostBlocks.done": "已从 {0} 个 Cell 中裁剪旧 Ghost 版本。", + + "toggleAutoApprove.on": "自动批准模式已开启。工具将在无需确认的情况下执行。", + "toggleAutoApprove.off": "自动批准模式已关闭。工具将需要确认。", + "toggleAutoApprove.failed": "切换自动批准失败: {0}", + + "testRag.prompt": "输入用于 RAG 搜索的自然语言查询", + "testRag.placeHolder": "例如:\"如何处理文件操作\"", + "testRag.cancelled": "搜索已取消或查询为空。", + "testRag.noWorkspaces": "没有打开的工作区文件夹。", + "testRag.completed": "RAG 搜索已完成,涵盖 {0} 个工作区。", + "testRag.failed": "RAG 搜索失败: {0}", + + "compress.progress": "正在压缩对话...", + "compress.noModel": "请在设置中配置 mutsumi.compressModel 或 mutsumi.defaultModel。", + "compress.failed": "压缩失败: {0}", + "compress.notEnough": "对话内容不足,无法压缩。", + "compress.done": "对话已压缩并保存到: {0}", + "compress.failedOverall": "压缩对话失败: {0}", + "compress.nameSuffix": "{0} (已压缩)", + + "completion.fileReference": "文件引用", + "completion.directoryReference": "目录引用", + "completion.tool": "工具", + "completion.toolCall": "工具调用", + "completion.referenceDoc": "引用 `{0}` 的内容", + + "permission.rejectPrompt": "拒绝 {0} 的原因:", + "permission.rejectPlaceHolder": "输入原因(ESC 中止生成)", + "permission.rejected": "[已拒绝] {0} 操作已被用户拒绝。", + "permission.rejectedWithReason": "[已拒绝并附带原因] {0} 操作已被用户拒绝。原因: {1}", + + "agentFile.openFailed": "打开 Agent 文件失败: {0}", + + "editFile.reopenFailed": "重新打开编辑器失败: {0}", + + "serializer.newAgent": "新 Agent", + "renderer.parseError": "错误: 解析渲染数据失败\n 原因: {0}\n", + + "notify.approvalNeededSubtitle": "需要审批", + "notify.approvalNeededMessage": "{0} — 请在侧边栏查看" +} diff --git a/package.json b/package.json index b7c549e..bec12f4 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,8 @@ { "name": "mutsumi", - "displayName": "Mutsumi", - "description": "Multi-Agent Notebook Environment", + "displayName": "%displayName%", + "description": "%description%", + "l10n": "./l10n", "author": { "name": "Malachite", "email": "malachiten@outlook.com", @@ -27,7 +28,7 @@ "activitybar": [ { "id": "mutsumi-sidebar", - "title": "Mutsumi Agents", + "title": "%viewContainer.title%", "icon": "assets/sidebar-icon.png" } ] @@ -36,25 +37,25 @@ "mutsumi-sidebar": [ { "id": "mutsumi.agentSidebar", - "name": "Agents", + "name": "%view.agents.name%", "when": "true", "icon": "assets/sidebar-icon.png" }, { "id": "mutsumi.approvalSidebar", - "name": "Pending Approvals", + "name": "%view.pendingApprovals.name%", "when": "true", "icon": "assets/sidebar-icon.png" }, { "id": "mutsumi.contextSidebar", - "name": "Context Items", + "name": "%view.contextItems.name%", "when": "true", "icon": "assets/sidebar-icon.png" }, { "id": "mutsumi.shellTaskSidebar", - "name": "Shell Tasks", + "name": "%view.shellTasks.name%", "when": "true", "icon": "assets/sidebar-icon.png" } @@ -63,7 +64,7 @@ "notebooks": [ { "type": "mutsumi-notebook", - "displayName": "Mutsumi Agent Session", + "displayName": "%notebook.displayName%", "selector": [ { "filenamePattern": "*.mtm" @@ -74,7 +75,7 @@ "notebookRenderer": [ { "id": "mutsumi-agent-renderer", - "displayName": "Mutsumi Agent Chat Renderer", + "displayName": "%notebookRenderer.displayName%", "entrypoint": "./dist/notebookRenderer.js", "mimeTypes": [ "application/vnd.mutsumi.agent-chat" @@ -84,148 +85,148 @@ "commands": [ { "command": "mutsumi.newAgent", - "title": "Mutsumi: New Agent" + "title": "%command.newAgent.title%" }, { "command": "mutsumi.openAgentFile", - "title": "Open Agent Notebook", + "title": "%command.openAgentFile.title%", "category": "Mutsumi" }, { "command": "mutsumi.copyReference", - "title": "Copy Mutsumi Reference", + "title": "%command.copyReference.title%", "category": "Mutsumi" }, { "command": "mutsumi.approveRequest", - "title": "Approve", + "title": "%command.approveRequest.title%", "icon": "$(check)" }, { "command": "mutsumi.rejectRequest", - "title": "Reject", + "title": "%command.rejectRequest.title%", "icon": "$(x)" }, { "command": "mutsumi.customRequestAction", - "title": "Inspect", + "title": "%command.customRequestAction.title%", "icon": "$(search)" }, { "command": "mutsumi.selectModel", - "title": "Select Model", + "title": "%command.selectModel.title%", "category": "Mutsumi", "icon": "$(server-environment)" }, { "command": "mutsumi.renameSession", - "title": "Rename Session", + "title": "%command.renameSession.title%", "category": "Mutsumi", "icon": "$(symbol-keyword)" }, { "command": "mutsumi.debugContext", - "title": "Debug Context", + "title": "%command.debugContext.title%", "category": "Mutsumi", "icon": "$(debug-console)" }, { "command": "mutsumi.pruneGhostBlocks", - "title": "Prune Old Ghost Versions", + "title": "%command.pruneGhostBlocks.title%", "category": "Mutsumi", "icon": "$(history)" }, { "command": "mutsumi.toggleAutoApprove", - "title": "Enter Auto-Approve", + "title": "%command.toggleAutoApprove.title%", "category": "Mutsumi", "icon": "$(run-all)" }, { "command": "mutsumi.toggleAutoApproveOn", - "title": "Exit Auto-Approve", + "title": "%command.toggleAutoApproveOn.title%", "category": "Mutsumi", "icon": "$(debug-disconnect)" }, { "command": "mutsumi.testRagSearch", - "title": "Test RAG Search", + "title": "%command.testRagSearch.title%", "category": "Mutsumi", "icon": "$(search)" }, { "command": "mutsumi.compressConversation", - "title": "Compress Conversation", + "title": "%command.compressConversation.title%", "category": "Mutsumi", "icon": "$(fold)" }, { "command": "mutsumi.viewContextItem", - "title": "View Context Item", + "title": "%command.viewContextItem.title%", "category": "Mutsumi", "icon": "$(open-preview)" }, { "command": "mutsumi.toggleRule", - "title": "Toggle Rule", + "title": "%command.toggleRule.title%", "category": "Mutsumi", "icon": "$(check)" }, { "command": "mutsumi.toggleSkill", - "title": "Toggle Skill", + "title": "%command.toggleSkill.title%", "category": "Mutsumi", "icon": "$(check)" }, { "command": "mutsumi.removeMacro", - "title": "Remove Macro", + "title": "%command.removeMacro.title%", "category": "Mutsumi", "icon": "$(close)" }, { "command": "mutsumi.removeFile", - "title": "Remove File", + "title": "%command.removeFile.title%", "category": "Mutsumi", "icon": "$(close)" }, { "command": "mutsumi.pruneFileVersions", - "title": "Prune Old Versions", + "title": "%command.pruneFileVersions.title%", "category": "Mutsumi", "icon": "$(history)" }, { "command": "mutsumi.refreshContextTree", - "title": "Refresh Context Tree", + "title": "%command.refreshContextTree.title%", "category": "Mutsumi", "icon": "$(refresh)" }, { "command": "mutsumi.clearToolCache", - "title": "Clear Tool Cache", + "title": "%command.clearToolCache.title%", "category": "Mutsumi", "icon": "$(clear-all)" }, { "command": "mutsumi.generateHttpServerPassword", - "title": "Generate HTTP Server Password", + "title": "%command.generateHttpServerPassword.title%", "category": "Mutsumi", "icon": "$(key)" }, { "command": "mutsumi.detachShellTask", - "title": "Move to Background", + "title": "%command.detachShellTask.title%", "icon": "$(debug-disconnect)" }, { "command": "mutsumi.killShellTask", - "title": "Stop", + "title": "%command.killShellTask.title%", "icon": "$(debug-stop)" }, { "command": "mutsumi.removeShellTask", - "title": "Remove", + "title": "%command.removeShellTask.title%", "icon": "$(close)" } ], @@ -373,26 +374,26 @@ ] }, "configuration": { - "title": "Mutsumi", + "title": "%configuration.title%", "properties": { "mutsumi.providers": { "type": "array", - "markdownDescription": "Configured LLM API providers\n\nExample:\n```json\n\"mutsumi.providers\": [\n {\n \"name\": \"kimi-for-coding\",\n \"baseurl\": \"https://api.kimi.com/coding/v1\",\n \"api_key\": \"sk-kimi-XXXXXXXXXXXXXXXXXXXXXX\"\n }\n]\n```", + "markdownDescription": "%configuration.providers.markdownDescription%", "items": { "type": "object", "properties": { "name": { "type": "string", - "markdownDescription": "LLM API Provider Name (e.g., OpenAI, Anthropic, llama.cpp)" + "markdownDescription": "%configuration.providers.items.name.markdownDescription%" }, "baseurl": { "type": "string", - "markdownDescription": "LLM API BaseURL (e.g., https://api.openai.com/v1, http://localhost:8080/v1)" + "markdownDescription": "%configuration.providers.items.baseurl.markdownDescription%" }, "api_key": { "type": "string", "default": "", - "markdownDescription": "LLM API Key (required, use dummy value for local deployments without authentication)" + "markdownDescription": "%configuration.providers.items.api_key.markdownDescription%" } }, "required": [ @@ -409,65 +410,65 @@ "type": "string" }, "default": {}, - "markdownDescription": "Model to provider name mapping. Key is model identifier, value is the provider name from mutsumi.providers\n\nExample:\n```json\n\"mutsumi.models\": {\n \"kimi-for-coding\": \"kimi-for-coding\"\n}\n```" + "markdownDescription": "%configuration.models.markdownDescription%" }, "mutsumi.defaultModel": { "type": "string", "default": "kimi-for-coding", - "markdownDescription": "Default model to use (must be a key in mutsumi.models)" + "markdownDescription": "%configuration.defaultModel.markdownDescription%" }, "mutsumi.titleGeneratorModel": { "type": "string", "default": "kimi-for-coding", - "markdownDescription": "Model used for generating agent titles (must be a key in mutsumi.models)" + "markdownDescription": "%configuration.titleGeneratorModel.markdownDescription%" }, "mutsumi.autoApproveEnabled": { "type": "boolean", "default": false, - "description": "When enabled, all tool calls will be automatically approved without user confirmation (use with caution)" + "description": "%configuration.autoApproveEnabled.description%" }, "mutsumi.compressModel": { "type": "string", "default": "kimi-for-coding", - "markdownDescription": "Model used for compressing conversations (must be a key in mutsumi.models). If empty, falls back to titleGeneratorModel then defaultModel" + "markdownDescription": "%configuration.compressModel.markdownDescription%" }, "mutsumi.embeddingEndpoint": { "type": "string", "default": "", - "description": "OpenAI-compatible embedding endpoint URL (e.g., http://localhost:1234/v1/embeddings). If empty, RAG features will be disabled." + "description": "%configuration.embeddingEndpoint.description%" }, "mutsumi.shellSyncTimeout": { "type": "number", "default": 60, "minimum": 0, - "description": "Seconds a synchronous (non-background) shell call may run before being auto-detached to background. Set 0 to disable auto-detach." + "description": "%configuration.shellSyncTimeout.description%" }, "mutsumi.httpServer.enabled": { "type": "boolean", "default": false, - "description": "Enable the Mutsumi HTTP Server (disabled by default). All endpoints require Bearer token authentication when enabled." + "description": "%configuration.httpServer.enabled.description%" }, "mutsumi.httpServer.password": { "type": "string", "default": "", - "description": "Bearer token password for the Mutsumi HTTP Server. Stored in PLAINTEXT in settings. If empty while the server is enabled, the server refuses to start. Use the 'Mutsumi: Generate HTTP Server Password' command to create one." + "description": "%configuration.httpServer.password.description%" }, "mutsumi.httpServer.host": { "type": "string", "default": "127.0.0.1", - "description": "Address the HTTP Server binds to. WARNING: changing this to 0.0.0.0 exposes the server to your LAN or the public internet — set a strong password (mutsumi.httpServer.password) first." + "description": "%configuration.httpServer.host.description%" }, "mutsumi.httpServer.port": { "type": "number", "default": 3000, "minimum": 1, "maximum": 65535, - "description": "Starting port for the HTTP Server. If occupied, subsequent ports are scanned (up to +100)." + "description": "%configuration.httpServer.port.description%" }, "mutsumi.agentConfig": { "type": "object", "default": {}, - "markdownDescription": "Agent type & tool set configuration. Overrides built-in defaults. Leave empty `{}` to use built-in defaults.\n\nShape:\n```json\n\"mutsumi.agentConfig\": {\n \"toolSets\": {\n \"read\": [\"read\", \"glob\"],\n \"deliver\": [\"shell\", \"write\"]\n },\n \"agentTypes\": {\n \"chat\": {\n \"toolSets\": [],\n \"defaultModel\": \"kimi-for-coding\",\n \"defaultRules\": [\"default/chat.md\"],\n \"defaultSkills\": [],\n \"allowedChildTypes\": [],\n \"isEntry\": true\n }\n }\n}\n```\n\n`toolSets`: each key maps to an array of tool names. Given names fully replace the built-in tool set. New names add new tool sets.\n\n`agentTypes`: for a type that exists in built-in defaults, fields you provide override the built-in fields (fields you omit are kept from defaults). For a new type, provide all required fields.\n\nNote: `version` is not configurable here; it is fixed to 1 internally.", + "markdownDescription": "%configuration.agentConfig.markdownDescription%", "properties": { "toolSets": { "type": "object", diff --git a/package.nls.json b/package.nls.json new file mode 100644 index 0000000..2b2dcf2 --- /dev/null +++ b/package.nls.json @@ -0,0 +1,57 @@ +{ + "displayName": "Mutsumi", + "description": "Multi-Agent Notebook Environment", + "viewContainer.title": "Mutsumi Agents", + "view.agents.name": "Agents", + "view.pendingApprovals.name": "Pending Approvals", + "view.contextItems.name": "Context Items", + "view.shellTasks.name": "Shell Tasks", + "notebook.displayName": "Mutsumi Agent Session", + "notebookRenderer.displayName": "Mutsumi Agent Chat Renderer", + "notebookController.displayName": "Mutsumi Agent", + + "command.newAgent.title": "Mutsumi: New Agent", + "command.openAgentFile.title": "Open Agent Notebook", + "command.copyReference.title": "Copy Mutsumi Reference", + "command.approveRequest.title": "Approve", + "command.rejectRequest.title": "Reject", + "command.customRequestAction.title": "Inspect", + "command.selectModel.title": "Select Model", + "command.renameSession.title": "Rename Session", + "command.debugContext.title": "Debug Context", + "command.pruneGhostBlocks.title": "Prune Old Ghost Versions", + "command.toggleAutoApprove.title": "Enter Auto-Approve", + "command.toggleAutoApproveOn.title": "Exit Auto-Approve", + "command.testRagSearch.title": "Test RAG Search", + "command.compressConversation.title": "Compress Conversation", + "command.viewContextItem.title": "View Context Item", + "command.toggleRule.title": "Toggle Rule", + "command.toggleSkill.title": "Toggle Skill", + "command.removeMacro.title": "Remove Macro", + "command.removeFile.title": "Remove File", + "command.pruneFileVersions.title": "Prune Old Versions", + "command.refreshContextTree.title": "Refresh Context Tree", + "command.clearToolCache.title": "Clear Tool Cache", + "command.generateHttpServerPassword.title": "Generate HTTP Server Password", + "command.detachShellTask.title": "Move to Background", + "command.killShellTask.title": "Stop", + "command.removeShellTask.title": "Remove", + + "configuration.title": "Mutsumi", + "configuration.providers.markdownDescription": "Configured LLM API providers\n\nExample:\n```json\n\"mutsumi.providers\": [\n {\n \"name\": \"kimi-for-coding\",\n \"baseurl\": \"https://api.kimi.com/coding/v1\",\n \"api_key\": \"sk-kimi-XXXXXXXXXXXXXXXXXXXXXX\"\n }\n]\n```", + "configuration.providers.items.name.markdownDescription": "LLM API Provider Name (e.g., OpenAI, Anthropic, llama.cpp)", + "configuration.providers.items.baseurl.markdownDescription": "LLM API BaseURL (e.g., https://api.openai.com/v1, http://localhost:8080/v1)", + "configuration.providers.items.api_key.markdownDescription": "LLM API Key (required, use dummy value for local deployments without authentication)", + "configuration.models.markdownDescription": "Model to provider name mapping. Key is model identifier, value is the provider name from mutsumi.providers\n\nExample:\n```json\n\"mutsumi.models\": {\n \"kimi-for-coding\": \"kimi-for-coding\"\n}\n```", + "configuration.defaultModel.markdownDescription": "Default model to use (must be a key in mutsumi.models)", + "configuration.titleGeneratorModel.markdownDescription": "Model used for generating agent titles (must be a key in mutsumi.models)", + "configuration.autoApproveEnabled.description": "When enabled, all tool calls will be automatically approved without user confirmation (use with caution)", + "configuration.compressModel.markdownDescription": "Model used for compressing conversations (must be a key in mutsumi.models). If empty, falls back to titleGeneratorModel then defaultModel", + "configuration.embeddingEndpoint.description": "OpenAI-compatible embedding endpoint URL (e.g., http://localhost:1234/v1/embeddings). If empty, RAG features will be disabled.", + "configuration.shellSyncTimeout.description": "Seconds a synchronous (non-background) shell call may run before being auto-detached to background. Set 0 to disable auto-detach.", + "configuration.httpServer.enabled.description": "Enable the Mutsumi HTTP Server (disabled by default). All endpoints require Bearer token authentication when enabled.", + "configuration.httpServer.password.description": "Bearer token password for the Mutsumi HTTP Server. Stored in PLAINTEXT in settings. If empty while the server is enabled, the server refuses to start. Use the 'Mutsumi: Generate HTTP Server Password' command to create one.", + "configuration.httpServer.host.description": "Address the HTTP Server binds to. WARNING: changing this to 0.0.0.0 exposes the server to your LAN or the public internet — set a strong password (mutsumi.httpServer.password) first.", + "configuration.httpServer.port.description": "Starting port for the HTTP Server. If occupied, subsequent ports are scanned (up to +100).", + "configuration.agentConfig.markdownDescription": "Agent type & tool set configuration. Overrides built-in defaults. Leave empty `{}` to use built-in defaults.\n\nShape:\n```json\n\"mutsumi.agentConfig\": {\n \"toolSets\": {\n \"read\": [\"read\", \"glob\"],\n \"deliver\": [\"shell\", \"write\"]\n },\n \"agentTypes\": {\n \"chat\": {\n \"toolSets\": [],\n \"defaultModel\": \"kimi-for-coding\",\n \"defaultRules\": [\"default/chat.md\"],\n \"defaultSkills\": [],\n \"allowedChildTypes\": [],\n \"isEntry\": true\n }\n }\n}\n```\n\n`toolSets`: each key maps to an array of tool names. Given names fully replace the built-in tool set. New names add new tool sets.\n\n`agentTypes`: for a type that exists in built-in defaults, fields you provide override the built-in fields (fields you omit are kept from defaults). For a new type, provide all required fields.\n\nNote: `version` is not configurable here; it is fixed to 1 internally." +} diff --git a/package.nls.zh-cn.json b/package.nls.zh-cn.json new file mode 100644 index 0000000..95176e5 --- /dev/null +++ b/package.nls.zh-cn.json @@ -0,0 +1,57 @@ +{ + "displayName": "Mutsumi", + "description": "多 Agent 笔记本环境", + "viewContainer.title": "Mutsumi Agents", + "view.agents.name": "Agent", + "view.pendingApprovals.name": "待审批", + "view.contextItems.name": "上下文项", + "view.shellTasks.name": "Shell 任务", + "notebook.displayName": "Mutsumi Agent 会话", + "notebookRenderer.displayName": "Mutsumi Agent 聊天渲染器", + "notebookController.displayName": "Mutsumi Agent", + + "command.newAgent.title": "Mutsumi: 新建 Agent", + "command.openAgentFile.title": "打开 Agent 笔记本", + "command.copyReference.title": "复制 Mutsumi 引用", + "command.approveRequest.title": "批准", + "command.rejectRequest.title": "拒绝", + "command.customRequestAction.title": "检查", + "command.selectModel.title": "选择模型", + "command.renameSession.title": "重命名会话", + "command.debugContext.title": "调试上下文", + "command.pruneGhostBlocks.title": "裁剪旧 Ghost 版本", + "command.toggleAutoApprove.title": "进入自动批准", + "command.toggleAutoApproveOn.title": "退出自动批准", + "command.testRagSearch.title": "测试 RAG 搜索", + "command.compressConversation.title": "压缩对话", + "command.viewContextItem.title": "查看上下文项", + "command.toggleRule.title": "切换规则", + "command.toggleSkill.title": "切换技能", + "command.removeMacro.title": "移除宏", + "command.removeFile.title": "移除文件", + "command.pruneFileVersions.title": "裁剪旧版本", + "command.refreshContextTree.title": "刷新上下文树", + "command.clearToolCache.title": "清除工具缓存", + "command.generateHttpServerPassword.title": "生成 HTTP Server 密码", + "command.detachShellTask.title": "移至后台", + "command.killShellTask.title": "停止", + "command.removeShellTask.title": "移除", + + "configuration.title": "Mutsumi", + "configuration.providers.markdownDescription": "已配置的 LLM API 提供商\n\n示例:\n```json\n\"mutsumi.providers\": [\n {\n \"name\": \"kimi-for-coding\",\n \"baseurl\": \"https://api.kimi.com/coding/v1\",\n \"api_key\": \"sk-kimi-XXXXXXXXXXXXXXXXXXXXXX\"\n }\n]\n```", + "configuration.providers.items.name.markdownDescription": "LLM API 提供商名称(例如 OpenAI、Anthropic、llama.cpp)", + "configuration.providers.items.baseurl.markdownDescription": "LLM API BaseURL(例如 https://api.openai.com/v1、http://localhost:8080/v1)", + "configuration.providers.items.api_key.markdownDescription": "LLM API 密钥(必填,本地无鉴权部署可使用占位值)", + "configuration.models.markdownDescription": "模型到提供商名称的映射。键为模型标识符,值为 mutsumi.providers 中的提供商名称\n\n示例:\n```json\n\"mutsumi.models\": {\n \"kimi-for-coding\": \"kimi-for-coding\"\n}\n```", + "configuration.defaultModel.markdownDescription": "默认使用的模型(必须是 mutsumi.models 中的键)", + "configuration.titleGeneratorModel.markdownDescription": "用于生成 Agent 标题的模型(必须是 mutsumi.models 中的键)", + "configuration.autoApproveEnabled.description": "启用后,所有工具调用将自动批准,无需用户确认(请谨慎使用)", + "configuration.compressModel.markdownDescription": "用于压缩对话的模型(必须是 mutsumi.models 中的键)。若为空,则依次回退到 titleGeneratorModel、defaultModel", + "configuration.embeddingEndpoint.description": "OpenAI 兼容的嵌入端点 URL(例如 http://localhost:1234/v1/embeddings)。若为空,RAG 功能将被禁用。", + "configuration.shellSyncTimeout.description": "同步(非后台)Shell 调用在自动转入后台前可运行的秒数。设为 0 可禁用自动转入。", + "configuration.httpServer.enabled.description": "启用 Mutsumi HTTP Server(默认禁用)。启用后所有端点均需 Bearer 令牌鉴权。", + "configuration.httpServer.password.description": "Mutsumi HTTP Server 的 Bearer 令牌密码。以明文形式存储在设置中。若启用服务器但密码为空,服务器将拒绝启动。可使用“Mutsumi: 生成 HTTP Server 密码”命令创建密码。", + "configuration.httpServer.host.description": "HTTP Server 绑定的地址。警告:将其改为 0.0.0.0 会将服务器暴露给局域网或公网——请先设置强密码(mutsumi.httpServer.password)。", + "configuration.httpServer.port.description": "HTTP Server 的起始端口。若被占用,将扫描后续端口(最多 +100)。", + "configuration.agentConfig.markdownDescription": "Agent 类型与工具集配置。覆盖内置默认值。留空 `{}` 则使用内置默认值。\n\n结构:\n```json\n\"mutsumi.agentConfig\": {\n \"toolSets\": {\n \"read\": [\"read\", \"glob\"],\n \"deliver\": [\"shell\", \"write\"]\n },\n \"agentTypes\": {\n \"chat\": {\n \"toolSets\": [],\n \"defaultModel\": \"kimi-for-coding\",\n \"defaultRules\": [\"default/chat.md\"],\n \"defaultSkills\": [],\n \"allowedChildTypes\": [],\n \"isEntry\": true\n }\n }\n}\n```\n\n`toolSets`:每个键映射到工具名数组。给定的名称将完全替换内置工具集。新名称将添加新的工具集。\n\n`agentTypes`:对于内置默认值中已存在的类型,提供的字段会覆盖内置字段(省略的字段保留默认值)。对于新类型,请提供所有必填字段。\n\n注意:`version` 在此处不可配置,内部固定为 1。" +} diff --git a/src/agent/agentRunner.ts b/src/agent/agentRunner.ts index 09c1a91..a1e8f8e 100644 --- a/src/agent/agentRunner.ts +++ b/src/agent/agentRunner.ts @@ -17,6 +17,7 @@ import { LiteAgentSession } from '../adapters/liteAdapter'; import { debugLogger } from '../debugLogger'; import { getModelCredentials } from '../utils'; import { AgentRunOptions } from './types'; +import { t } from '../i18n'; export { AgentRunOptions } from './types'; @@ -160,11 +161,12 @@ export class AgentRunner { console.error('LLM Stream Error:', error); // Show error as VSCode notification (non-modal) + const copyDetailsBtn = t('controller.copyDetails'); vscode.window.showErrorMessage( - `Mutsumi LLM Error: ${errorMessage}`, - 'Copy Details' + t('agentRunner.llmError', errorMessage), + copyDetailsBtn ).then(selection => { - if (selection === 'Copy Details') { + if (selection === copyDetailsBtn) { vscode.env.clipboard.writeText(error.stack || errorMessage); } }); diff --git a/src/controller.ts b/src/controller.ts index c73c1c3..9626bf6 100644 --- a/src/controller.ts +++ b/src/controller.ts @@ -12,6 +12,7 @@ import { buildInteractionHistory } from './contextManagement/history'; import { AgentMetadata } from './types'; import { getModelCredentials } from './utils'; import { normalizeReasoningEffort } from './agent/types'; +import { t } from './i18n'; /** * Controls the execution of agent notebooks. @@ -174,14 +175,15 @@ export class AgentController { // Only show notification here as a fallback for unhandled errors const errorMessage = err.message || String(err); console.error('Agent execution error:', err); - + + const copyDetailsBtn = t('controller.copyDetails'); vscode.window.showErrorMessage( - `Mutsumi Error: ${errorMessage}`, - 'Copy Details' + t('controller.mutsumiError', errorMessage), + copyDetailsBtn ).then(selection => { - if (selection === 'Copy Details') { - vscode.env.clipboard.writeText(err.stack || errorMessage); - } + if (selection === copyDetailsBtn) { + vscode.env.clipboard.writeText(err.stack || errorMessage); + } }); // Do NOT replace output - preserve any streamed content that was displayed diff --git a/src/debugLogger.ts b/src/debugLogger.ts index c8b37a6..c1a7e6c 100644 --- a/src/debugLogger.ts +++ b/src/debugLogger.ts @@ -1,4 +1,5 @@ import * as vscode from 'vscode'; +import { t } from './i18n'; /** * Shared debug logger for all Mutsumi modules. @@ -13,7 +14,7 @@ class DebugLogger { */ public initialize(context: vscode.ExtensionContext): void { if (!this.outputChannel) { - this.outputChannel = vscode.window.createOutputChannel('Mutsumi Debug'); + this.outputChannel = vscode.window.createOutputChannel(t('Mutsumi Debug')); context.subscriptions.push(this.outputChannel); } } diff --git a/src/extension.ts b/src/extension.ts index 91dbd69..8212dc3 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -22,6 +22,7 @@ import { import { ImagePasteProvider } from "./contextManagement/imagePasteProvider"; import { SkillManager } from "./contextManagement/skillManager"; import { sanitizeFileName } from "./utils"; +import { t } from "./i18n"; import { registerToolbarCommands } from "./notebook/toolbar"; import { HeadlessAdapter } from "./adapters/headlessAdapter"; import { ToolRegistry } from "./tools.d/toolManager"; @@ -170,11 +171,11 @@ export async function activate( }; const warnHttpServerEmptyPassword = () => { - const OPEN_SETTINGS = "Open Settings"; - const GENERATE_PASSWORD = "Generate Random Password"; + const OPEN_SETTINGS = t("httpServer.emptyPassword.openSettings"); + const GENERATE_PASSWORD = t("httpServer.emptyPassword.generate"); vscode.window .showWarningMessage( - "Mutsumi HTTP Server is enabled but no password is set (mutsumi.httpServer.password). The server refuses to start.", + t("httpServer.emptyPassword.warning"), OPEN_SETTINGS, GENERATE_PASSWORD, ) @@ -269,7 +270,7 @@ export async function activate( ); await vscode.env.clipboard.writeText(password); vscode.window.showInformationMessage( - "Generated a random HTTP Server password, saved it to your user settings, and copied it to the clipboard.", + t("httpServer.passwordGenerated"), ); const cfg = getHttpServerConfig(); if (cfg.enabled && !httpServer) { @@ -298,7 +299,7 @@ export async function activate( const controller = vscode.notebooks.createNotebookController( "mutsumi-agent", "mutsumi-notebook", - "Mutsumi Agent", + t("notebookController.displayName"), ); controller.supportedLanguages = ["markdown"]; controller.supportsExecutionOrder = true; @@ -461,32 +462,37 @@ function registerCommands(context: vscode.ExtensionContext): void { vscode.commands.registerCommand("mutsumi.newAgent", async () => { const wsFolders = vscode.workspace.workspaceFolders; if (!wsFolders) { - vscode.window.showErrorMessage("Please open a workspace folder first."); + vscode.window.showErrorMessage(t("newAgent.noWorkspace")); return; } - + // AgentType Step 1: Show QuickPick for Agent Type Selection const entryTypes = getEntryAgentTypes(); - + if (entryTypes.length === 0) { vscode.window.showErrorMessage( - "No entry agent types available. Please check your configuration.", + t("newAgent.noEntryTypes"), ); return; } - + // Build QuickPick items with descriptions const typeItems = entryTypes.map(({ name, config }) => ({ label: name, description: `${config.toolSets.join("+")}`, - detail: `Model: ${config.defaultModel} | Rules: ${config.defaultRules.length} | Skills: ${config.defaultSkills.length}`, + detail: t( + "newAgent.detail", + config.defaultModel, + config.defaultRules.length, + config.defaultSkills.length, + ), typeName: name, })); - + // Show QuickPick for agent type selection const selectedType = await vscode.window.showQuickPick(typeItems, { - placeHolder: "Select an agent type to create", - title: "Mutsumi: New Agent", + placeHolder: t("newAgent.quickPickPlaceHolder"), + title: t("newAgent.quickPickTitle"), }); if (!selectedType) { @@ -547,7 +553,12 @@ function registerCommands(context: vscode.ExtensionContext): void { // Show confirmation message with agent type info vscode.window.showInformationMessage( - `Created ${selectedAgentType} agent with ${defaults.rules.length} rules and ${defaults.skills.length} skills`, + t( + "newAgent.created", + selectedAgentType, + defaults.rules.length, + defaults.skills.length, + ), ); }), ); @@ -566,7 +577,7 @@ function registerCommands(context: vscode.ExtensionContext): void { targetUri = editor.document.uri; selection = editor.selection; } else { - vscode.window.showErrorMessage("No file selected or active."); + vscode.window.showErrorMessage(t("copyReference.noFile")); return; } } else { @@ -584,7 +595,7 @@ function registerCommands(context: vscode.ExtensionContext): void { const workspaceFolder = vscode.workspace.getWorkspaceFolder(targetUri); if (!workspaceFolder) { - vscode.window.showErrorMessage("File is not in the workspace."); + vscode.window.showErrorMessage(t("copyReference.notInWorkspace")); return; } @@ -630,7 +641,7 @@ function registerCommands(context: vscode.ExtensionContext): void { await vscode.env.clipboard.writeText(refString); vscode.window.setStatusBarMessage( - `Copied reference: ${refString}`, + t("copyReference.statusBar", refString), 3000, ); }, @@ -641,7 +652,7 @@ function registerCommands(context: vscode.ExtensionContext): void { context.subscriptions.push( vscode.commands.registerCommand("mutsumi.clearToolCache", () => { clearToolCache(); - vscode.window.showInformationMessage("Tool result cache cleared."); + vscode.window.showInformationMessage(t("clearToolCache.done")); }), ); diff --git a/src/i18n.ts b/src/i18n.ts new file mode 100644 index 0000000..b47b3ef --- /dev/null +++ b/src/i18n.ts @@ -0,0 +1,36 @@ +/** + * @fileoverview Localization helper wrapping `vscode.l10n`. + * + * This re-exports `vscode.l10n.t` so call sites can use a short `t(...)` + * import while keeping the bundle keys centralized in `l10n/`. + * + * Usage: + * ```ts + * import { t } from "./i18n"; + * vscode.window.showInformationMessage(t("clearToolCache.done")); + * vscode.window.showErrorMessage(t("controller.mutsumiError", errorMessage)); + * ``` + * + * @module i18n + */ + +import * as vscode from "vscode"; + +/** + * Translate a message string using the active VS Code locale. + * + * Pass the bundle key as the first argument. If the key is not found, the key + * itself is returned (which for this project is always a readable English + * sentence). Additional positional arguments are substituted into `{0}`, + * `{1}`, ... placeholders inside the translated string. + * + * @param message The bundle key (or literal message) to translate. + * @param args Values to substitute into `{n}` placeholders. + * @returns The localized string. + */ +export function t( + message: string, + ...args: Array +): string { + return vscode.l10n.t(message, ...args); +} diff --git a/src/notebook/commands/compressConversation.ts b/src/notebook/commands/compressConversation.ts index ab9ed32..679b7b8 100644 --- a/src/notebook/commands/compressConversation.ts +++ b/src/notebook/commands/compressConversation.ts @@ -12,6 +12,7 @@ import type { AgentRunOptions } from '../../agent/types'; import { MutsumiSerializer } from '../serializer'; import { formatMessagesToString, createDebugSessionFromNotebook } from './utils'; import { getModelCredentials } from '../../utils'; +import { t } from '../../i18n'; /** * Register the compress conversation command. @@ -22,12 +23,12 @@ export function registerCompressConversationCommand(context: vscode.ExtensionCon vscode.commands.registerCommand('mutsumi.compressConversation', async () => { const editor = vscode.window.activeNotebookEditor; if (!editor) { - vscode.window.showWarningMessage('No active notebook editor.'); + vscode.window.showWarningMessage(t('notebook.noEditor')); return; } if (editor.notebook.notebookType !== 'mutsumi-notebook') { - vscode.window.showWarningMessage('This command only works with Mutsumi notebooks.'); + vscode.window.showWarningMessage(t('notebook.onlyMutsumi')); return; } @@ -41,7 +42,7 @@ export function registerCompressConversationCommand(context: vscode.ExtensionCon } if (lastCodeCellIndex === -1) { - vscode.window.showWarningMessage('No code cell found in notebook.'); + vscode.window.showWarningMessage(t('notebook.noCodeCell')); return; } @@ -51,7 +52,7 @@ export function registerCompressConversationCommand(context: vscode.ExtensionCon const compressModel = config.get('compressModel') || config.get('titleGeneratorModel') || config.get('defaultModel'); if (!compressModel) { - vscode.window.showErrorMessage('Please configure mutsumi.compressModel or mutsumi.defaultModel in settings.'); + vscode.window.showErrorMessage(t('compress.noModel')); return; } @@ -59,7 +60,7 @@ export function registerCompressConversationCommand(context: vscode.ExtensionCon try { credentials = getModelCredentials(compressModel); } catch (err: any) { - vscode.window.showErrorMessage(`Compression failed: ${err.message}`); + vscode.window.showErrorMessage(t('compress.failed', err.message)); return; } const { apiKey, baseUrl } = credentials; @@ -69,14 +70,14 @@ export function registerCompressConversationCommand(context: vscode.ExtensionCon const { messages } = await buildInteractionHistory(session); if (messages.length <= 1) { - vscode.window.showWarningMessage('Not enough conversation content to compress.'); + vscode.window.showWarningMessage(t('compress.notEnough')); return; } // Show progress await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, - title: 'Compressing conversation...', + title: t('compress.progress'), cancellable: false }, async () => { // Dynamically import AgentRunner to avoid circular dependency @@ -162,7 +163,7 @@ export function registerCompressConversationCommand(context: vscode.ExtensionCon const compressedMetadata: AgentMetadata = { ...originalMetadata, uuid: uuidv4(), - name: `${originalMetadata?.name || 'Compressed'} (Compressed)`, + name: t('compress.nameSuffix', originalMetadata?.name || 'Compressed'), created_at: new Date().toISOString(), parent_agent_id: null }; @@ -193,11 +194,11 @@ export function registerCompressConversationCommand(context: vscode.ExtensionCon const doc = await vscode.workspace.openNotebookDocument(newUri); await vscode.window.showNotebookDocument(doc); - vscode.window.showInformationMessage(`Conversation compressed and saved to: ${newFileName}`); + vscode.window.showInformationMessage(t('compress.done', newFileName)); }); } catch (error: any) { console.error('Failed to compress conversation:', error); - vscode.window.showErrorMessage(`Failed to compress conversation: ${error.message}`); + vscode.window.showErrorMessage(t('compress.failedOverall', error.message)); } }) ); diff --git a/src/notebook/commands/debugContext.ts b/src/notebook/commands/debugContext.ts index 0232976..0464439 100644 --- a/src/notebook/commands/debugContext.ts +++ b/src/notebook/commands/debugContext.ts @@ -6,6 +6,7 @@ import * as vscode from 'vscode'; import { buildInteractionHistory } from '../../contextManagement/history'; import { formatMessagesToString, createDebugSessionFromNotebook } from './utils'; +import { t } from '../../i18n'; /** * Register the debug context command. @@ -16,12 +17,12 @@ export function registerDebugContextCommand(context: vscode.ExtensionContext): v vscode.commands.registerCommand('mutsumi.debugContext', async () => { const editor = vscode.window.activeNotebookEditor; if (!editor) { - vscode.window.showWarningMessage('No active notebook editor.'); + vscode.window.showWarningMessage(t('notebook.noEditor')); return; } if (editor.notebook.notebookType !== 'mutsumi-notebook') { - vscode.window.showWarningMessage('This command only works with Mutsumi notebooks.'); + vscode.window.showWarningMessage(t('notebook.onlyMutsumi')); return; } @@ -35,7 +36,7 @@ export function registerDebugContextCommand(context: vscode.ExtensionContext): v } if (lastCodeCellIndex === -1) { - vscode.window.showWarningMessage('No code cell found in notebook.'); + vscode.window.showWarningMessage(t('notebook.noCodeCell')); return; } @@ -57,10 +58,10 @@ export function registerDebugContextCommand(context: vscode.ExtensionContext): v }); await vscode.window.showTextDocument(doc, { preview: true }); - vscode.window.showInformationMessage(`Debug context displayed. Total messages: ${messages.length}`); + vscode.window.showInformationMessage(t('debugContext.displayed', messages.length)); } catch (error) { console.error('Failed to debug context:', error); - vscode.window.showErrorMessage(`Failed to debug context: ${error}`); + vscode.window.showErrorMessage(t('debugContext.failed', String(error))); } }) ); diff --git a/src/notebook/commands/pruneGhostBlocks.ts b/src/notebook/commands/pruneGhostBlocks.ts index d7395b1..5435098 100644 --- a/src/notebook/commands/pruneGhostBlocks.ts +++ b/src/notebook/commands/pruneGhostBlocks.ts @@ -7,6 +7,7 @@ import * as vscode from 'vscode'; import { AgentMetadata } from '../../types'; import { decodeGhostBlock } from '../../contextManagement/ghostBlocks'; import { buildGhostStripEdits } from './utils'; +import { t } from '../../i18n'; /** * Register the prune ghost blocks command. @@ -23,12 +24,12 @@ export function registerPruneGhostBlocksCommand(context: vscode.ExtensionContext vscode.commands.registerCommand('mutsumi.pruneGhostBlocks', async () => { const editor = vscode.window.activeNotebookEditor; if (!editor) { - vscode.window.showWarningMessage('No active notebook editor.'); + vscode.window.showWarningMessage(t('notebook.noEditor')); return; } if (editor.notebook.notebookType !== 'mutsumi-notebook') { - vscode.window.showWarningMessage('This command only works with Mutsumi notebooks.'); + vscode.window.showWarningMessage(t('notebook.onlyMutsumi')); return; } @@ -59,14 +60,14 @@ export function registerPruneGhostBlocksCommand(context: vscode.ExtensionContext ); if (edits.length === 0) { - vscode.window.showInformationMessage('All file references are already at their latest versions.'); + vscode.window.showInformationMessage(t('pruneGhostBlocks.alreadyLatest')); return; } const edit = new vscode.WorkspaceEdit(); edit.set(notebook.uri, edits); await vscode.workspace.applyEdit(edit); - vscode.window.showInformationMessage(`Pruned old ghost versions from ${edits.length} cell(s).`); + vscode.window.showInformationMessage(t('pruneGhostBlocks.done', edits.length)); }) ); } diff --git a/src/notebook/commands/renameSession.ts b/src/notebook/commands/renameSession.ts index cc4aefc..97eca3f 100644 --- a/src/notebook/commands/renameSession.ts +++ b/src/notebook/commands/renameSession.ts @@ -7,6 +7,7 @@ import * as vscode from 'vscode'; import { regenerateTitleForSession, extractMessagesFromNotebook, getTitleGeneratorConfig, sanitizeFileName, updateNotebookMetadataWithSync } from '../../agent/titleGenerator'; import { LiteAdapter, LiteAgentSessionConfig } from '../../adapters/liteAdapter'; import { AgentMetadata } from '../../types'; +import { t } from '../../i18n'; /** * Register the regenerate title command. @@ -17,19 +18,19 @@ export function registerRenameSessionCommand(context: vscode.ExtensionContext): vscode.commands.registerCommand('mutsumi.renameSession', async () => { const editor = vscode.window.activeNotebookEditor; if (!editor) { - vscode.window.showWarningMessage('No active notebook editor.'); + vscode.window.showWarningMessage(t('notebook.noEditor')); return; } if (editor.notebook.notebookType !== 'mutsumi-notebook') { - vscode.window.showWarningMessage('This command only works with Mutsumi notebooks.'); + vscode.window.showWarningMessage(t('notebook.onlyMutsumi')); return; } const currentTitle = editor.notebook.metadata?.name || ''; const titleInput = await vscode.window.showInputBox({ value: currentTitle, - prompt: 'Enter new session title (leave empty to auto-generate)' + prompt: t('renameSession.prompt') }); if (titleInput === undefined) { @@ -40,9 +41,9 @@ export function registerRenameSessionCommand(context: vscode.ExtensionContext): try { const sanitized = sanitizeFileName(titleInput); await updateNotebookMetadataWithSync(editor.notebook, sanitized); - vscode.window.showInformationMessage(`Session renamed: ${sanitized}`); + vscode.window.showInformationMessage(t('renameSession.renamed', sanitized)); } catch (error: any) { - vscode.window.showErrorMessage(`Failed to rename session: ${error.message}`); + vscode.window.showErrorMessage(t('renameSession.failed', error.message)); } return; } @@ -65,10 +66,10 @@ export function registerRenameSessionCommand(context: vscode.ExtensionContext): }); const title = await regenerateTitleForSession(session, messages, config, editor.notebook); - vscode.window.showInformationMessage(`Title regenerated: ${title}`); + vscode.window.showInformationMessage(t('renameSession.regenerated', title)); } catch (error: any) { console.error('Failed to regenerate title:', error); - vscode.window.showErrorMessage(`Failed to regenerate title: ${error.message}`); + vscode.window.showErrorMessage(t('renameSession.regenerateFailed', error.message)); } }) ); diff --git a/src/notebook/commands/selectModel.ts b/src/notebook/commands/selectModel.ts index 76fde2b..6c23d11 100644 --- a/src/notebook/commands/selectModel.ts +++ b/src/notebook/commands/selectModel.ts @@ -7,6 +7,7 @@ import * as vscode from 'vscode'; import { normalizeReasoningEffort, REASONING_EFFORT_SETTING_VALUES } from '../../agent/types'; import type { ReasoningEffortSetting } from '../../agent/types'; import { getModelsConfig } from '../../utils'; +import { t } from '../../i18n'; /** QuickPick item representing a configured model. */ interface ModelQuickPickItem extends vscode.QuickPickItem { @@ -30,13 +31,13 @@ type SelectModelQuickPickItem = ModelQuickPickItem | ReasoningEffortQuickPickIte /** Human-readable descriptions for concrete reasoning effort levels. */ const reasoningEffortDescriptions: Readonly, string>> = { - none: 'Disable reasoning', - minimal: 'Minimal reasoning', - low: 'Low reasoning', - medium: 'Medium reasoning', - high: 'High reasoning', - xhigh: 'Extra-high reasoning', - max: 'Maximum reasoning' + none: t('selectModel.effort.none'), + minimal: t('selectModel.effort.minimal'), + low: t('selectModel.effort.low'), + medium: t('selectModel.effort.medium'), + high: t('selectModel.effort.high'), + xhigh: t('selectModel.effort.xhigh'), + max: t('selectModel.effort.max') }; /** @@ -48,12 +49,12 @@ export function registerSelectModelCommand(context: vscode.ExtensionContext): vo vscode.commands.registerCommand('mutsumi.selectModel', async () => { const editor = vscode.window.activeNotebookEditor; if (!editor) { - vscode.window.showWarningMessage('No active notebook editor.'); + vscode.window.showWarningMessage(t('notebook.noEditor')); return; } if (editor.notebook.notebookType !== 'mutsumi-notebook') { - vscode.window.showWarningMessage('This command only works with Mutsumi notebooks.'); + vscode.window.showWarningMessage(t('notebook.onlyMutsumi')); return; } @@ -61,7 +62,7 @@ export function registerSelectModelCommand(context: vscode.ExtensionContext): vo const modelNames = Object.keys(modelsConfig); if (modelNames.length === 0) { - vscode.window.showErrorMessage('No models configured in settings.'); + vscode.window.showErrorMessage(t('selectModel.noModels')); return; } @@ -96,14 +97,14 @@ export function registerSelectModelCommand(context: vscode.ExtensionContext): vo })); const items: SelectModelQuickPickItem[] = [ - { itemType: 'separator', label: 'Models', kind: vscode.QuickPickItemKind.Separator }, + { itemType: 'separator', label: t('selectModel.separatorModels'), kind: vscode.QuickPickItemKind.Separator }, ...modelItems, - { itemType: 'separator', label: 'Reasoning efforts', kind: vscode.QuickPickItemKind.Separator }, + { itemType: 'separator', label: t('selectModel.separatorReasoning'), kind: vscode.QuickPickItemKind.Separator }, ...effortItems ]; const selected = await vscode.window.showQuickPick(items, { - placeHolder: `Select model or reasoning effort (switching model resets effort to default; current: ${currentModel || 'default'} / ${effectiveReasoningEffort})` + placeHolder: t('selectModel.placeHolder', currentModel || 'default', effectiveReasoningEffort) }); if (!selected || selected.itemType === 'separator') { @@ -128,10 +129,10 @@ export function registerSelectModelCommand(context: vscode.ExtensionContext): vo if (selected.itemType === 'model') { vscode.window.showInformationMessage( - `Model changed to: ${selected.value}; reasoning effort reset to default.` + t('selectModel.modelChanged', selected.value) ); } else { - vscode.window.showInformationMessage(`Reasoning effort changed to: ${selected.value}`); + vscode.window.showInformationMessage(t('selectModel.effortChanged', selected.value)); } }) ); diff --git a/src/notebook/commands/testRagSearch.ts b/src/notebook/commands/testRagSearch.ts index 38124ca..be49f92 100644 --- a/src/notebook/commands/testRagSearch.ts +++ b/src/notebook/commands/testRagSearch.ts @@ -5,6 +5,7 @@ import * as vscode from 'vscode'; import { RagService } from '../../codebase/rag/service'; +import { t } from '../../i18n'; /** * Register the test RAG search command. @@ -15,13 +16,13 @@ export function registerTestRagSearchCommand(context: vscode.ExtensionContext): vscode.commands.registerCommand('mutsumi.testRagSearch', async () => { // 获取查询输入 const query = await vscode.window.showInputBox({ - prompt: 'Enter natural language query for RAG search', - placeHolder: 'e.g., "how to handle file operations"', + prompt: t('testRag.prompt'), + placeHolder: t('testRag.placeHolder'), ignoreFocusOut: true }); if (!query || !query.trim()) { - vscode.window.showInformationMessage('Search cancelled or empty query.'); + vscode.window.showInformationMessage(t('testRag.cancelled')); return; } @@ -30,7 +31,7 @@ export function registerTestRagSearchCommand(context: vscode.ExtensionContext): const workspaces = vscode.workspace.workspaceFolders; if (!workspaces || workspaces.length === 0) { - vscode.window.showWarningMessage('No workspace folders open.'); + vscode.window.showWarningMessage(t('testRag.noWorkspaces')); return; } @@ -79,10 +80,10 @@ export function registerTestRagSearchCommand(context: vscode.ExtensionContext): }); await vscode.window.showTextDocument(doc, { preview: true }); - vscode.window.showInformationMessage(`RAG search completed across ${workspaces.length} workspace(s).`); + vscode.window.showInformationMessage(t('testRag.completed', workspaces.length)); } catch (error: any) { console.error('RAG search failed:', error); - vscode.window.showErrorMessage(`RAG search failed: ${error.message}`); + vscode.window.showErrorMessage(t('testRag.failed', error.message)); } }) ); diff --git a/src/notebook/commands/toggleAutoApprove.ts b/src/notebook/commands/toggleAutoApprove.ts index a53380a..1280d08 100644 --- a/src/notebook/commands/toggleAutoApprove.ts +++ b/src/notebook/commands/toggleAutoApprove.ts @@ -5,6 +5,7 @@ import * as vscode from 'vscode'; import { toggleAutoApprove, isAutoApproveEnabled } from '../../tools.d/permission'; +import { t } from '../../i18n'; /** * Register the toggle auto approve commands. @@ -20,13 +21,13 @@ export function registerToggleAutoApproveCommands(context: vscode.ExtensionConte await vscode.commands.executeCommand('setContext', 'mutsumi:autoApproveEnabled', newState); if (newState) { - vscode.window.showWarningMessage('Auto-approve mode is now ON. Tools will be executed without confirmation.'); + vscode.window.showWarningMessage(t('toggleAutoApprove.on')); } else { - vscode.window.showInformationMessage('Auto-approve mode is now OFF. Tools will require confirmation.'); + vscode.window.showInformationMessage(t('toggleAutoApprove.off')); } } catch (error) { console.error('Failed to toggle auto-approve:', error); - vscode.window.showErrorMessage(`Failed to toggle auto-approve: ${error}`); + vscode.window.showErrorMessage(t('toggleAutoApprove.failed', String(error))); } }) ); @@ -40,13 +41,13 @@ export function registerToggleAutoApproveCommands(context: vscode.ExtensionConte await vscode.commands.executeCommand('setContext', 'mutsumi:autoApproveEnabled', newState); if (newState) { - vscode.window.showWarningMessage('Auto-approve mode is now ON. Tools will be executed without confirmation.'); + vscode.window.showWarningMessage(t('toggleAutoApprove.on')); } else { - vscode.window.showInformationMessage('Auto-approve mode is now OFF. Tools will require confirmation.'); + vscode.window.showInformationMessage(t('toggleAutoApprove.off')); } } catch (error) { console.error('Failed to toggle auto-approve:', error); - vscode.window.showErrorMessage(`Failed to toggle auto-approve: ${error}`); + vscode.window.showErrorMessage(t('toggleAutoApprove.failed', String(error))); } }) ); diff --git a/src/notebook/completionProvider.ts b/src/notebook/completionProvider.ts index b472f96..4bf4a84 100644 --- a/src/notebook/completionProvider.ts +++ b/src/notebook/completionProvider.ts @@ -2,6 +2,7 @@ import * as vscode from 'vscode'; import * as path from 'path'; import { isCommonIgnored } from '../tools.d/utils'; import { ToolManager } from '../tools.d/toolManager'; +import { t } from '../i18n'; /** * Recursively append a JSON-shaped snippet placeholder for a tool parameter schema. @@ -116,8 +117,8 @@ export class ReferenceCompletionProvider implements vscode.CompletionItemProvide const item = new vscode.CompletionItem(relPath, vscode.CompletionItemKind.File); item.insertText = `[${relPath}]`; - item.detail = 'File Reference'; - item.documentation = new vscode.MarkdownString(`Reference content of \`${relPath}\``); + item.detail = t('completion.fileReference'); + item.documentation = new vscode.MarkdownString(t('completion.referenceDoc', relPath)); item.sortText = '000_' + relPath; items.push(item); } @@ -137,7 +138,7 @@ export class ReferenceCompletionProvider implements vscode.CompletionItemProvide const item = new vscode.CompletionItem(displayLabel, vscode.CompletionItemKind.Folder); item.insertText = `[${displayLabel}]`; - item.detail = 'Directory Reference'; + item.detail = t('completion.directoryReference'); item.sortText = '001_' + displayLabel; items.push(item); } @@ -155,11 +156,11 @@ export class ReferenceCompletionProvider implements vscode.CompletionItemProvide for (const tool of tools) { const fn = (tool as any).function; const name = fn.name; - const desc = fn.description || 'Tool'; + const desc = fn.description || t('completion.tool'); const parameters = fn.parameters || {}; const item = new vscode.CompletionItem(name, vscode.CompletionItemKind.Function); - item.detail = 'Tool Call'; + item.detail = t('completion.toolCall'); const properties = parameters.properties || {}; const required = parameters.required || []; diff --git a/src/notebook/serializer.ts b/src/notebook/serializer.ts index ab9c22a..7dbaf38 100644 --- a/src/notebook/serializer.ts +++ b/src/notebook/serializer.ts @@ -9,6 +9,7 @@ import { resolveAgentDefaults } from '../config/resolver'; import { RenderBlock, RenderData } from './renderTypes'; import { GhostBlock } from '../contextManagement/interfaces'; import { decodeGhostBlock } from '../contextManagement/ghostBlocks'; +import { t } from '../i18n'; // ============================================================================ // Core Data Structures (VSCode-agnostic) @@ -396,7 +397,7 @@ export class MutsumiSerializer implements vscode.NotebookSerializer { raw = { metadata: { uuid: uuidv4(), - name: 'New Agent', + name: t('serializer.newAgent'), created_at: new Date().toISOString(), parent_agent_id: null, allowed_uris: ['/'], @@ -488,7 +489,7 @@ export class MutsumiSerializer implements vscode.NotebookSerializer { const raw: AgentContext = { metadata: { uuid: uuid ?? uuidv4(), - name: 'New Agent', + name: t('serializer.newAgent'), created_at: new Date().toISOString(), parent_agent_id: null, allowed_uris: allowedUris, diff --git a/src/notifications.ts b/src/notifications.ts index b0dbfa0..d743cf2 100644 --- a/src/notifications.ts +++ b/src/notifications.ts @@ -1,4 +1,5 @@ import { NotificationCenter, WindowsToaster } from "node-notifier"; +import { t } from "./i18n"; type MacNotifier = InstanceType; type WinNotifier = InstanceType; @@ -59,7 +60,7 @@ export function notifyWaitingForApproval( export function notifyApprovalNeeded(actionDescription: string): void { notifyWaitingForApproval( "Mutsumi", - `${actionDescription} — review in the sidebar`, - "Approval needed", + t("notify.approvalNeededMessage", actionDescription), + t("notify.approvalNeededSubtitle"), ); } diff --git a/src/sidebar/agentTreeItem.ts b/src/sidebar/agentTreeItem.ts index 3358825..01abfa2 100644 --- a/src/sidebar/agentTreeItem.ts +++ b/src/sidebar/agentTreeItem.ts @@ -1,5 +1,6 @@ import * as vscode from 'vscode'; import { AgentRuntimeStatus } from '../types'; +import { t } from '../i18n'; /** * @description Agent node data interface, defining the basic information of Agent tree items @@ -66,10 +67,10 @@ export class AgentTreeItem extends vscode.TreeItem { */ private getStatusLabel(status: AgentRuntimeStatus): string { switch (status) { - case 'running': return 'Running'; - case 'pending': return 'Pending'; - case 'finished': return 'Finished'; - case 'standby': return 'Standby'; + case 'running': return t('status.running'); + case 'pending': return t('status.pending'); + case 'finished': return t('status.finished'); + case 'standby': return t('status.standby'); default: return ''; } } @@ -114,7 +115,7 @@ export function registerAgentCommands(context: vscode.ExtensionContext): void { preview: false }); } catch (e) { - vscode.window.showErrorMessage(`Failed to open agent file: ${e}`); + vscode.window.showErrorMessage(t('agentFile.openFailed', String(e))); } } }) diff --git a/src/sidebar/approvalTreeItem.ts b/src/sidebar/approvalTreeItem.ts index 94de84b..ca84e08 100644 --- a/src/sidebar/approvalTreeItem.ts +++ b/src/sidebar/approvalTreeItem.ts @@ -1,5 +1,6 @@ import * as vscode from 'vscode'; import { ApprovalRequest, approvalManager } from '../tools.d/permission'; +import { t } from '../i18n'; /** * @description Approval request tree node item for displaying tool call approval requests in the sidebar @@ -48,17 +49,17 @@ export class ApprovalTreeItem extends vscode.TreeItem { private buildTooltip(): vscode.MarkdownString { const md = new vscode.MarkdownString(); md.appendMarkdown(`**${this.request.actionDescription}**\n\n`); - md.appendMarkdown(`📁 Target: \`${this.request.targetUri}\`\n\n`); + md.appendMarkdown(t('approval.target', this.request.targetUri) + `\n\n`); if (this.request.customAction) { - md.appendMarkdown(`🔍 **Custom Action Available**: ${this.request.customAction.label}\n\n`); + md.appendMarkdown(t('approval.customActionAvailable', this.request.customAction.label) + `\n\n`); } if (this.request.details) { - md.appendMarkdown(`📝 Details:\n\`\`\`\n${this.request.details}\n\`\`\`\n\n`); + md.appendMarkdown(t('approval.details', this.request.details)); } - md.appendMarkdown(`🕐 Time: ${this.request.timestamp.toLocaleString()}\n\n`); - md.appendMarkdown(`Status: ${this.getStatusText()}`); + md.appendMarkdown(t('approval.time', this.request.timestamp.toLocaleString()) + `\n\n`); + md.appendMarkdown(t('approval.status', this.getStatusText())); return md; } @@ -68,9 +69,9 @@ export class ApprovalTreeItem extends vscode.TreeItem { */ private getStatusText(): string { switch (this.request.status) { - case 'pending': return '⏳ Pending'; - case 'approved': return '✅ Approved'; - case 'rejected': return '❌ Rejected'; + case 'pending': return t('approval.pending'); + case 'approved': return t('approval.approved'); + case 'rejected': return t('approval.rejected'); } } diff --git a/src/sidebar/contextTreeItem.ts b/src/sidebar/contextTreeItem.ts index 0bd8342..e9a41dc 100644 --- a/src/sidebar/contextTreeItem.ts +++ b/src/sidebar/contextTreeItem.ts @@ -3,6 +3,7 @@ import { TemplateEngine } from '../contextManagement/templateEngine'; import { AgentMetadata } from '../types'; import { ContextTreeDataProvider } from './contextTreeProvider'; import { buildGhostStripEdits } from '../notebook/commands/utils'; +import { t } from '../i18n'; /** * @description Context item type definition @@ -180,15 +181,15 @@ export class ContextTreeItem extends vscode.TreeItem { if (type === 'category') { switch (category) { case 'rules': - return 'Rules: Active context rules for Agents'; + return t('context.category.rules'); case 'skills': - return 'Skills: Active context skills for Agents'; + return t('context.category.skills'); case 'macros': - return 'Macros: Reusable text snippets'; + return t('context.category.macros'); case 'files': - return 'Files: Referenced context files'; + return t('context.category.files'); default: - return 'Category'; + return t('context.category.default'); } } @@ -197,7 +198,7 @@ export class ContextTreeItem extends vscode.TreeItem { // Type label let typeLabel = type.charAt(0).toUpperCase() + type.slice(1); if ((type === 'rule' || type === 'skill') && isActive !== undefined) { - typeLabel += isActive ? ' (Active)' : ' (Inactive)'; + typeLabel += isActive ? t('context.active') : t('context.inactive'); } md.appendMarkdown(`**${typeLabel}**: \`${this.data.key}\`\n\n`); @@ -231,7 +232,7 @@ export function registerContextCommands( context.subscriptions.push( vscode.commands.registerCommand('mutsumi.refreshContextTree', async () => { await contextTreeDataProvider.refreshAll(); - vscode.window.showInformationMessage('Context tree refreshed'); + vscode.window.showInformationMessage(t('context.refreshed')); }) ); // Register view context item command @@ -282,7 +283,7 @@ export function registerContextCommands( displayContent = renderedText; } } catch (error) { - displayContent = `Error reading rule: ${error}`; + displayContent = t('context.readError', String(error)); } } else if (args.type === 'skill') { // Skills: read skill file and display as markdown (no TemplateEngine expansion) @@ -304,7 +305,7 @@ export function registerContextCommands( const skillText = new TextDecoder().decode(skillContent); displayContent = skillText; } catch (innerError) { - displayContent = `Error reading skill: ${error}`; + displayContent = t('context.readError', String(error)); } } } else if (args.type === 'file') { @@ -333,7 +334,7 @@ export function registerContextCommands( displayContent = fileItem.content; } } else { - displayContent = `File not found: ${args.key}`; + displayContent = t('context.fileNotFound', args.key); } } @@ -374,11 +375,11 @@ export function registerContextCommands( if (index === -1) { // Add to active rules activeRules.push(ruleName); - vscode.window.showInformationMessage(`Rule "${ruleName}" activated`); + vscode.window.showInformationMessage(t('context.ruleActivated', ruleName)); } else { // Remove from active rules activeRules.splice(index, 1); - vscode.window.showInformationMessage(`Rule "${ruleName}" deactivated`); + vscode.window.showInformationMessage(t('context.ruleDeactivated', ruleName)); } // Update notebook metadata @@ -417,11 +418,11 @@ export function registerContextCommands( if (index === -1) { // Add to active skills activeSkills.push(skillName); - vscode.window.showInformationMessage(`Skill "${item.data.key}" activated`); + vscode.window.showInformationMessage(t('context.skillActivated', item.data.key)); } else { // Remove from active skills activeSkills.splice(index, 1); - vscode.window.showInformationMessage(`Skill "${item.data.key}" deactivated`); + vscode.window.showInformationMessage(t('context.skillDeactivated', item.data.key)); } // Update notebook metadata @@ -537,7 +538,7 @@ export function registerContextCommands( file => file.key === key && file.version !== latestVersion ); if (edits.length === 0) { - vscode.window.showInformationMessage(`No older versions of "${key}" to prune`); + vscode.window.showInformationMessage(t('context.noOlderVersions', key)); return; } diff --git a/src/sidebar/shellTaskTreeItem.ts b/src/sidebar/shellTaskTreeItem.ts index a545bfa..b43db81 100644 --- a/src/sidebar/shellTaskTreeItem.ts +++ b/src/sidebar/shellTaskTreeItem.ts @@ -1,6 +1,7 @@ import * as vscode from "vscode"; import { type ShellTask, formatShellOutput } from "../tools.d/shell/shellTask"; import { shellTaskRegistry } from "../tools.d/shell/registry"; +import { t } from "../i18n"; /** * Shell task tree node for the Shell Tasks sidebar view. @@ -20,35 +21,35 @@ export class ShellTaskTreeItem extends vscode.TreeItem { private formatDescription(): string { const elapsed = Math.floor((Date.now() - this.task.createdAt) / 1000); if (this.task.isRunning) { - return this.task.background ? `bg ${elapsed}s` : `fg ${elapsed}s`; + return this.task.background ? t("shellTask.bg", elapsed) : t("shellTask.fg", elapsed); } const snap = this.task.snapshot(); - if (snap.aborted) return "stopped"; - const sig = snap.signal ? ` (${snap.signal})` : ""; - return `exit ${snap.exitCode}${sig}`; + if (snap.aborted) return t("shellTask.stopped"); + const sig = snap.signal ? t("shellTask.signal", snap.signal) : ""; + return t("shellTask.exit", String(snap.exitCode), sig); } private buildTooltip(): vscode.MarkdownString { const md = new vscode.MarkdownString(); md.appendMarkdown(`**${this.task.cmd}**\n\n`); - md.appendMarkdown(`📁 CWD: \`${this.task.cwd}\`\n\n`); - md.appendMarkdown(`🆔 Task: \`${this.task.id}\`\n\n`); - md.appendMarkdown(`🖥️ Agent Session: \`${this.task.agentSessionId}\`\n\n`); + md.appendMarkdown(t("shellTask.cwd", this.task.cwd) + `\n\n`); + md.appendMarkdown(t("shellTask.task", this.task.id) + `\n\n`); + md.appendMarkdown(t("shellTask.agentSession", this.task.agentSessionId) + `\n\n`); md.appendMarkdown( - `Mode: ${this.task.background ? "background" : "foreground"}\n\n`, + t("shellTask.mode", this.task.background ? t("shellTask.modeBackground") : t("shellTask.modeForeground")), ); const snap = this.task.snapshot(); if (snap.aborted) { - md.appendMarkdown(`Status: 🛑 stopped\n\n`); + md.appendMarkdown(t("shellTask.statusStopped")); } else if (this.task.isRunning) { - md.appendMarkdown(`Status: ▶️ running\n\n`); + md.appendMarkdown(t("shellTask.statusRunning")); } else { - const sig = snap.signal ? ` (signal: ${snap.signal})` : ""; - md.appendMarkdown(`Status: ✅ exit ${snap.exitCode}${sig}\n\n`); + const sig = snap.signal ? t("shellTask.signal", snap.signal) : ""; + md.appendMarkdown(t("shellTask.statusExit", String(snap.exitCode), sig)); } const out = formatShellOutput(snap, { showExit: true }); if (out && out !== "(no output)") { - md.appendMarkdown(`Output:\n\`\`\`\n${out}\n\`\`\``); + md.appendMarkdown(t("shellTask.output", out)); } return md; } diff --git a/src/tools.d/edit_file.ts b/src/tools.d/edit_file.ts index 949f6a2..e337de0 100644 --- a/src/tools.d/edit_file.ts +++ b/src/tools.d/edit_file.ts @@ -11,6 +11,7 @@ import { handleRejectionFlow, } from "./permission"; import { notifyApprovalNeeded } from "../notifications"; +import { t } from "../i18n"; // ============================================================================ // Types and Interfaces @@ -468,7 +469,7 @@ class EditService { ); } catch (e: any) { vscode.window.showErrorMessage( - `Failed to reopen editor: ${e.message}`, + t("editFile.reopenFailed", e.message), ); } }, diff --git a/src/tools.d/permission.ts b/src/tools.d/permission.ts index 55adc1b..197c3bf 100644 --- a/src/tools.d/permission.ts +++ b/src/tools.d/permission.ts @@ -2,6 +2,7 @@ import * as vscode from "vscode"; import type { ToolContext } from "./interface"; import { v4 as uuidv4 } from "uuid"; import { notifyApprovalNeeded } from "../notifications"; +import { t } from "../i18n"; // ====== Auto Approval Configuration ====== @@ -333,15 +334,15 @@ export async function handleRejectionFlow( signalTermination: (isTaskComplete?: boolean) => void, ): Promise { const reason = await vscode.window.showInputBox({ - prompt: `Reason for rejecting ${toolName}:`, - placeHolder: "Enter reason (ESC to abort generation)", + prompt: t("permission.rejectPrompt", toolName), + placeHolder: t("permission.rejectPlaceHolder"), }); if (reason === undefined || reason.trim() === "") { signalTermination(false); - return `[Rejected] The ${toolName} operation was rejected by user.`; + return t("permission.rejected", toolName); } else { - return `[Rejected with Reason] The ${toolName} operation was rejected by user. Reason: ${reason}`; + return t("permission.rejectedWithReason", toolName, reason); } } diff --git a/src/tools.d/toolsLogger.ts b/src/tools.d/toolsLogger.ts index 413e86a..62baa52 100644 --- a/src/tools.d/toolsLogger.ts +++ b/src/tools.d/toolsLogger.ts @@ -1,4 +1,5 @@ import * as vscode from 'vscode'; +import { t } from '../i18n'; /** * Shared tools logger for real-time tool output streaming. @@ -13,7 +14,7 @@ class ToolsLogger { */ public initialize(context: vscode.ExtensionContext): void { if (!this.outputChannel) { - this.outputChannel = vscode.window.createOutputChannel('Mutsumi Tools'); + this.outputChannel = vscode.window.createOutputChannel(t('Mutsumi Tools')); context.subscriptions.push(this.outputChannel); } } From 3ef6cff3845e382ef0abff0f06d4735d50031200 Mon Sep 17 00:00:00 2001 From: MalachiteN Date: Mon, 3 Aug 2026 04:15:04 +0800 Subject: [PATCH 2/2] fix(l10n): fix misplaced keys, complete approval flow localization - move notebookController.displayName from package.nls.* to runtime bundle - add missing notebook.noCodeCell to zh-cn bundle - localize approval flow content strings (shell/mkdir/edit actions) - localize selectModel current marker and default effort row - keep LLM-facing rejection messages in English - remove dead keys, unify output channel key style, fix whitespace --- l10n/bundle.l10n.json | 20 +++++++++++++----- l10n/bundle.l10n.zh-cn.json | 21 ++++++++++++++----- package.nls.json | 1 - package.nls.zh-cn.json | 1 - src/controller.ts | 12 +++++------ src/debugLogger.ts | 2 +- src/extension.ts | 8 +++---- src/i18n.ts | 7 ++++--- src/notebook/commands/compressConversation.ts | 2 +- src/notebook/commands/selectModel.ts | 14 ++++++++----- src/sidebar/contextTreeItem.ts | 2 +- src/tools.d/edit_file.ts | 6 +++--- src/tools.d/permission.ts | 6 +++--- src/tools.d/tools/fs_write_ops.ts | 3 ++- src/tools.d/tools/shell_exec.ts | 10 +++++++-- src/tools.d/toolsLogger.ts | 2 +- 16 files changed, 74 insertions(+), 43 deletions(-) diff --git a/l10n/bundle.l10n.json b/l10n/bundle.l10n.json index 4482cc0..ad466d6 100644 --- a/l10n/bundle.l10n.json +++ b/l10n/bundle.l10n.json @@ -1,8 +1,7 @@ { - "Mutsumi Debug": "Mutsumi Debug", - "Mutsumi Tools": "Mutsumi Tools", + "outputChannel.debug": "Mutsumi Debug", + "outputChannel.tools": "Mutsumi Tools", "context.readError": "Error reading: {0}", - "renderer.parseError": "Error: Failed to parse render data\n reason: {0}\n", "httpServer.emptyPassword.warning": "Mutsumi HTTP Server is enabled but no password is set (mutsumi.httpServer.password). The server refuses to start.", "httpServer.emptyPassword.openSettings": "Open Settings", @@ -40,6 +39,15 @@ "approval.details": "📝 Details:\n```\n{0}\n```\n\n", "approval.time": "🕐 Time: {0}", "approval.status": "Status: {0}", + "approval.requestNotification": "Agent requests: {0}", + "approval.shell.action": "Execute \"{0}\" at {1}", + "approval.shell.details": "Command:\n{0}\n\nShell: {1}\nBackground: {2}", + "approval.shell.yes": "yes", + "approval.shell.no": "no", + "approval.mkdir.action": "Create Directory", + "approval.edit.customAction": "Review Diff", + "approval.edit.details": "Review changes in Diff Editor. You can edit the right side (User Final) manually.", + "approval.edit.action": "Agent wants to edit {0}", "shellTask.bg": "bg {0}s", "shellTask.fg": "fg {0}s", @@ -76,6 +84,8 @@ "notebook.onlyMutsumi": "This command only works with Mutsumi notebooks.", "notebook.noCodeCell": "No code cell found in notebook.", + "notebookController.displayName": "Mutsumi Agent", + "selectModel.noModels": "No models configured in settings.", "selectModel.placeHolder": "Select model or reasoning effort (switching model resets effort to default; current: {0} / {1})", "selectModel.modelChanged": "Model changed to: {0}; reasoning effort reset to default.", @@ -83,6 +93,8 @@ "selectModel.separatorModels": "Models", "selectModel.separatorReasoning": "Reasoning efforts", "selectModel.effortDefault": "Default", + "selectModel.effortDefaultDesc": "Don't send; server decides", + "selectModel.current": "Current", "selectModel.effort.none": "Disable reasoning", "selectModel.effort.minimal": "Minimal reasoning", "selectModel.effort.low": "Low reasoning", @@ -130,8 +142,6 @@ "permission.rejectPrompt": "Reason for rejecting {0}:", "permission.rejectPlaceHolder": "Enter reason (ESC to abort generation)", - "permission.rejected": "[Rejected] The {0} operation was rejected by user.", - "permission.rejectedWithReason": "[Rejected with Reason] The {0} operation was rejected by user. Reason: {1}", "agentFile.openFailed": "Failed to open agent file: {0}", diff --git a/l10n/bundle.l10n.zh-cn.json b/l10n/bundle.l10n.zh-cn.json index 3507408..19b4543 100644 --- a/l10n/bundle.l10n.zh-cn.json +++ b/l10n/bundle.l10n.zh-cn.json @@ -1,6 +1,6 @@ { - "Mutsumi Debug": "Mutsumi 调试", - "Mutsumi Tools": "Mutsumi 工具", + "outputChannel.debug": "Mutsumi 调试", + "outputChannel.tools": "Mutsumi 工具", "context.readError": "读取错误: {0}", "httpServer.emptyPassword.warning": "Mutsumi HTTP Server 已启用但未设置密码(mutsumi.httpServer.password)。服务器拒绝启动。", @@ -39,6 +39,15 @@ "approval.details": "📝 详情:\n```\n{0}\n```\n\n", "approval.time": "🕐 时间: {0}", "approval.status": "状态: {0}", + "approval.requestNotification": "Agent 请求: {0}", + "approval.shell.action": "在 {1} 执行 \"{0}\"", + "approval.shell.details": "命令:\n{0}\n\nShell: {1}\n后台: {2}", + "approval.shell.yes": "是", + "approval.shell.no": "否", + "approval.mkdir.action": "创建目录", + "approval.edit.customAction": "审查 Diff", + "approval.edit.details": "在 Diff 编辑器中审查更改。你可以手动编辑右侧(User Final)的内容。", + "approval.edit.action": "Agent 想要编辑 {0}", "shellTask.bg": "后台 {0}秒", "shellTask.fg": "前台 {0}秒", @@ -73,6 +82,9 @@ "notebook.noEditor": "没有活动的笔记本编辑器。", "notebook.onlyMutsumi": "此命令仅适用于 Mutsumi 笔记本。", + "notebook.noCodeCell": "笔记本中没有代码 Cell。", + + "notebookController.displayName": "Mutsumi Agent", "selectModel.noModels": "设置中未配置任何模型。", "selectModel.placeHolder": "选择模型或推理强度(切换模型会将强度重置为默认;当前: {0} / {1})", @@ -81,6 +93,8 @@ "selectModel.separatorModels": "模型", "selectModel.separatorReasoning": "推理强度", "selectModel.effortDefault": "默认", + "selectModel.effortDefaultDesc": "不发送;由服务器决定", + "selectModel.current": "当前", "selectModel.effort.none": "禁用推理", "selectModel.effort.minimal": "最小推理", "selectModel.effort.low": "低推理", @@ -128,15 +142,12 @@ "permission.rejectPrompt": "拒绝 {0} 的原因:", "permission.rejectPlaceHolder": "输入原因(ESC 中止生成)", - "permission.rejected": "[已拒绝] {0} 操作已被用户拒绝。", - "permission.rejectedWithReason": "[已拒绝并附带原因] {0} 操作已被用户拒绝。原因: {1}", "agentFile.openFailed": "打开 Agent 文件失败: {0}", "editFile.reopenFailed": "重新打开编辑器失败: {0}", "serializer.newAgent": "新 Agent", - "renderer.parseError": "错误: 解析渲染数据失败\n 原因: {0}\n", "notify.approvalNeededSubtitle": "需要审批", "notify.approvalNeededMessage": "{0} — 请在侧边栏查看" diff --git a/package.nls.json b/package.nls.json index 2b2dcf2..0cb2761 100644 --- a/package.nls.json +++ b/package.nls.json @@ -8,7 +8,6 @@ "view.shellTasks.name": "Shell Tasks", "notebook.displayName": "Mutsumi Agent Session", "notebookRenderer.displayName": "Mutsumi Agent Chat Renderer", - "notebookController.displayName": "Mutsumi Agent", "command.newAgent.title": "Mutsumi: New Agent", "command.openAgentFile.title": "Open Agent Notebook", diff --git a/package.nls.zh-cn.json b/package.nls.zh-cn.json index 95176e5..1259897 100644 --- a/package.nls.zh-cn.json +++ b/package.nls.zh-cn.json @@ -8,7 +8,6 @@ "view.shellTasks.name": "Shell 任务", "notebook.displayName": "Mutsumi Agent 会话", "notebookRenderer.displayName": "Mutsumi Agent 聊天渲染器", - "notebookController.displayName": "Mutsumi Agent", "command.newAgent.title": "Mutsumi: 新建 Agent", "command.openAgentFile.title": "打开 Agent 笔记本", diff --git a/src/controller.ts b/src/controller.ts index 9626bf6..0159ecd 100644 --- a/src/controller.ts +++ b/src/controller.ts @@ -175,15 +175,15 @@ export class AgentController { // Only show notification here as a fallback for unhandled errors const errorMessage = err.message || String(err); console.error('Agent execution error:', err); - + const copyDetailsBtn = t('controller.copyDetails'); vscode.window.showErrorMessage( - t('controller.mutsumiError', errorMessage), - copyDetailsBtn + t('controller.mutsumiError', errorMessage), + copyDetailsBtn ).then(selection => { - if (selection === copyDetailsBtn) { - vscode.env.clipboard.writeText(err.stack || errorMessage); - } + if (selection === copyDetailsBtn) { + vscode.env.clipboard.writeText(err.stack || errorMessage); + } }); // Do NOT replace output - preserve any streamed content that was displayed diff --git a/src/debugLogger.ts b/src/debugLogger.ts index c1a7e6c..295faf8 100644 --- a/src/debugLogger.ts +++ b/src/debugLogger.ts @@ -14,7 +14,7 @@ class DebugLogger { */ public initialize(context: vscode.ExtensionContext): void { if (!this.outputChannel) { - this.outputChannel = vscode.window.createOutputChannel(t('Mutsumi Debug')); + this.outputChannel = vscode.window.createOutputChannel(t('outputChannel.debug')); context.subscriptions.push(this.outputChannel); } } diff --git a/src/extension.ts b/src/extension.ts index 8212dc3..59bb9dc 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -465,17 +465,17 @@ function registerCommands(context: vscode.ExtensionContext): void { vscode.window.showErrorMessage(t("newAgent.noWorkspace")); return; } - + // AgentType Step 1: Show QuickPick for Agent Type Selection const entryTypes = getEntryAgentTypes(); - + if (entryTypes.length === 0) { vscode.window.showErrorMessage( t("newAgent.noEntryTypes"), ); return; } - + // Build QuickPick items with descriptions const typeItems = entryTypes.map(({ name, config }) => ({ label: name, @@ -488,7 +488,7 @@ function registerCommands(context: vscode.ExtensionContext): void { ), typeName: name, })); - + // Show QuickPick for agent type selection const selectedType = await vscode.window.showQuickPick(typeItems, { placeHolder: t("newAgent.quickPickPlaceHolder"), diff --git a/src/i18n.ts b/src/i18n.ts index b47b3ef..2705ec8 100644 --- a/src/i18n.ts +++ b/src/i18n.ts @@ -20,9 +20,10 @@ import * as vscode from "vscode"; * Translate a message string using the active VS Code locale. * * Pass the bundle key as the first argument. If the key is not found, the key - * itself is returned (which for this project is always a readable English - * sentence). Additional positional arguments are substituted into `{0}`, - * `{1}`, ... placeholders inside the translated string. + * itself is returned. English text is provided by the default bundle + * `l10n/bundle.l10n.json`, so every key must be defined there. Additional + * positional arguments are substituted into `{0}`, `{1}`, ... placeholders + * inside the translated string. * * @param message The bundle key (or literal message) to translate. * @param args Values to substitute into `{n}` placeholders. diff --git a/src/notebook/commands/compressConversation.ts b/src/notebook/commands/compressConversation.ts index 679b7b8..a31a0b2 100644 --- a/src/notebook/commands/compressConversation.ts +++ b/src/notebook/commands/compressConversation.ts @@ -163,7 +163,7 @@ export function registerCompressConversationCommand(context: vscode.ExtensionCon const compressedMetadata: AgentMetadata = { ...originalMetadata, uuid: uuidv4(), - name: t('compress.nameSuffix', originalMetadata?.name || 'Compressed'), + name: t('compress.nameSuffix', originalMetadata?.name || t('serializer.newAgent')), created_at: new Date().toISOString(), parent_agent_id: null }; diff --git a/src/notebook/commands/selectModel.ts b/src/notebook/commands/selectModel.ts index 6c23d11..319a084 100644 --- a/src/notebook/commands/selectModel.ts +++ b/src/notebook/commands/selectModel.ts @@ -74,7 +74,7 @@ export function registerSelectModelCommand(context: vscode.ExtensionContext): vo const modelItems: ModelQuickPickItem[] = modelNames.map(name => { const label = modelsConfig[name]; const description = label ? `🏷️ ${label}` : undefined; - const detail = name === currentModel ? '$(check) Current' : undefined; + const detail = name === currentModel ? '$(check) ' + t('selectModel.current') : undefined; return { itemType: 'model', value: name, @@ -88,11 +88,11 @@ export function registerSelectModelCommand(context: vscode.ExtensionContext): vo const effortItems: ReasoningEffortQuickPickItem[] = REASONING_EFFORT_SETTING_VALUES.map(value => ({ itemType: 'reasoningEffort', value, - label: value, + label: value === 'default' ? t('selectModel.effortDefault') : value, description: value === 'default' - ? "Don't send; server decides" + ? t('selectModel.effortDefaultDesc') : reasoningEffortDescriptions[value], - detail: value === effectiveReasoningEffort ? '$(check) Current' : undefined, + detail: value === effectiveReasoningEffort ? '$(check) ' + t('selectModel.current') : undefined, picked: value === effectiveReasoningEffort })); @@ -104,7 +104,11 @@ export function registerSelectModelCommand(context: vscode.ExtensionContext): vo ]; const selected = await vscode.window.showQuickPick(items, { - placeHolder: t('selectModel.placeHolder', currentModel || 'default', effectiveReasoningEffort) + placeHolder: t( + 'selectModel.placeHolder', + currentModel || 'default', + effectiveReasoningEffort === 'default' ? t('selectModel.effortDefault') : effectiveReasoningEffort + ) }); if (!selected || selected.itemType === 'separator') { diff --git a/src/sidebar/contextTreeItem.ts b/src/sidebar/contextTreeItem.ts index e9a41dc..55a593f 100644 --- a/src/sidebar/contextTreeItem.ts +++ b/src/sidebar/contextTreeItem.ts @@ -305,7 +305,7 @@ export function registerContextCommands( const skillText = new TextDecoder().decode(skillContent); displayContent = skillText; } catch (innerError) { - displayContent = t('context.readError', String(error)); + displayContent = t('context.readError', String(innerError)); } } } else if (args.type === 'file') { diff --git a/src/tools.d/edit_file.ts b/src/tools.d/edit_file.ts index e337de0..bfd2114 100644 --- a/src/tools.d/edit_file.ts +++ b/src/tools.d/edit_file.ts @@ -460,7 +460,7 @@ class EditService { await transaction.cleanup(this.diffController); }, customAction: { - label: "Review Diff", + label: t("approval.edit.customAction"), handler: async () => { try { await this.diffController.openDiff( @@ -475,7 +475,7 @@ class EditService { }, }, }, - "Review changes in Diff Editor. You can edit the right side (User Final) manually.", + t("approval.edit.details"), shouldAutoApprove, ); @@ -491,7 +491,7 @@ class EditService { // Native OS notification only when user action is required. if (!shouldAutoApprove) { notifyApprovalNeeded( - `Agent wants to edit ${path.basename(uri.path)}`, + t("approval.edit.action", path.basename(uri.path)), ); } } catch (e) { diff --git a/src/tools.d/permission.ts b/src/tools.d/permission.ts index 197c3bf..9b7ad91 100644 --- a/src/tools.d/permission.ts +++ b/src/tools.d/permission.ts @@ -340,9 +340,9 @@ export async function handleRejectionFlow( if (reason === undefined || reason.trim() === "") { signalTermination(false); - return t("permission.rejected", toolName); + return `[Rejected] The ${toolName} operation was rejected by user.`; } else { - return t("permission.rejectedWithReason", toolName, reason); + return `[Rejected with Reason] The ${toolName} operation was rejected by user. Reason: ${reason}`; } } @@ -399,6 +399,6 @@ export async function requestApproval( ); // Native OS notification only — approval actions live in the sidebar. - notifyApprovalNeeded(`Agent requests: ${actionDescription}`); + notifyApprovalNeeded(t("approval.requestNotification", actionDescription)); }); } diff --git a/src/tools.d/tools/fs_write_ops.ts b/src/tools.d/tools/fs_write_ops.ts index 627ae64..1b97863 100644 --- a/src/tools.d/tools/fs_write_ops.ts +++ b/src/tools.d/tools/fs_write_ops.ts @@ -1,6 +1,7 @@ import { ITool, ToolContext } from '../interface'; import { resolveUri, checkAccess } from '../utils'; import { requestApproval } from '../permission'; +import { t } from '../../i18n'; import * as vscode from 'vscode'; export const mkdirTool: ITool = { @@ -32,7 +33,7 @@ export const mkdirTool: ITool = { } // Approval - const rejectionMsg = await requestApproval('Create Directory', uriInput, context, 'mkdir'); + const rejectionMsg = await requestApproval(t('approval.mkdir.action'), uriInput, context, 'mkdir'); if (rejectionMsg !== null) { return rejectionMsg; } diff --git a/src/tools.d/tools/shell_exec.ts b/src/tools.d/tools/shell_exec.ts index b4f627b..3ab7b17 100644 --- a/src/tools.d/tools/shell_exec.ts +++ b/src/tools.d/tools/shell_exec.ts @@ -1,6 +1,7 @@ import type { ITool, ToolContext } from "../interface"; import { resolveUri } from "../utils"; import { requestApproval } from "../permission"; +import { t } from "../../i18n"; import { toolsLogger } from "../toolsLogger"; import * as path from "path"; import * as vscode from "vscode"; @@ -59,9 +60,14 @@ export const shellTool: ITool = { ? path.basename(shellPath) : "System Default Shell"; - const details = `Command:\n${cmd}\n\nShell: ${shellName}\nBackground: ${background ? "yes" : "no"}`; + const details = t( + "approval.shell.details", + cmd, + shellName, + background ? t("approval.shell.yes") : t("approval.shell.no"), + ); const rejectionMsg = await requestApproval( - `Execute "${cmd}" at ${uriInput}`, + t("approval.shell.action", cmd, uriInput), uriInput, context, "shell", diff --git a/src/tools.d/toolsLogger.ts b/src/tools.d/toolsLogger.ts index 62baa52..ec0a70b 100644 --- a/src/tools.d/toolsLogger.ts +++ b/src/tools.d/toolsLogger.ts @@ -14,7 +14,7 @@ class ToolsLogger { */ public initialize(context: vscode.ExtensionContext): void { if (!this.outputChannel) { - this.outputChannel = vscode.window.createOutputChannel(t('Mutsumi Tools')); + this.outputChannel = vscode.window.createOutputChannel(t('outputChannel.tools')); context.subscriptions.push(this.outputChannel); } }