diff --git a/web/static/i18n/en-US.json b/web/static/i18n/en-US.json index 2f807062..b5c652f1 100644 --- a/web/static/i18n/en-US.json +++ b/web/static/i18n/en-US.json @@ -14,7 +14,8 @@ "confirm": "Confirm", "copy": "Copy", "copied": "Copied", - "copyFailed": "Copy failed" + "copyFailed": "Copy failed", + "view": "View" }, "header": { "title": "CyberStrikeAI", @@ -340,6 +341,7 @@ "configStdioNeedCommand": "Config error: \"{{name}}\" stdio mode needs command", "configHttpNeedUrl": "Config error: \"{{name}}\" http mode needs url", "configSseNeedUrl": "Config error: \"{{name}}\" sse mode needs url", + "configEditMustContainName": "Config error: In edit mode, JSON must contain config name \"{{name}}\"", "saveSuccess": "Saved", "deleteSuccess": "Deleted", "deleteExternalConfirm": "Delete external MCP \"{{name}}\"?", @@ -364,15 +366,36 @@ "description": "Configure WeCom, DingTalk and Lark bots so you can chat with CyberStrikeAI on your phone without opening the web UI.", "wecom": { "title": "WeCom", - "enabled": "Enable WeCom bot" + "enabled": "Enable WeCom bot", + "token": "Token", + "tokenPlaceholder": "Token", + "encodingAesKey": "EncodingAESKey", + "encodingAesKeyPlaceholder": "EncodingAESKey (leave empty for plain mode)", + "corpId": "CorpID", + "corpIdPlaceholder": "Corp ID", + "secret": "Secret", + "secretPlaceholder": "App Secret", + "agentId": "AgentID", + "agentIdPlaceholder": "App AgentId" }, "dingtalk": { "title": "DingTalk", - "enabled": "Enable DingTalk bot" + "enabled": "Enable DingTalk bot", + "clientIdLabel": "Client ID (AppKey)", + "clientIdPlaceholder": "DingTalk App Key", + "clientSecretLabel": "Client Secret", + "clientSecretPlaceholder": "DingTalk App Secret", + "streamHint": "Enable bot capability and configure streaming access in the open platform." }, "lark": { "title": "Lark", - "enabled": "Enable Lark bot" + "enabled": "Enable Lark bot", + "appIdLabel": "App ID", + "appIdPlaceholder": "Lark/Feishu App ID", + "appSecretLabel": "App Secret", + "appSecretPlaceholder": "Lark/Feishu App Secret", + "verifyTokenLabel": "Verify Token (Optional)", + "verifyTokenPlaceholder": "Event subscription Verification Token" } }, "apply": { @@ -414,7 +437,15 @@ "title": "Role management", "createRole": "Create role", "searchPlaceholder": "Search roles...", - "deleteConfirm": "Delete this role?" + "deleteConfirm": "Delete this role?", + "loadFailed": "Failed to load roles", + "noDescription": "No description", + "defaultRoleDescription": "Default role, no extra user prompt, uses default MCP", + "noMatchingRoles": "No matching roles", + "noRoles": "No roles", + "enabled": "Enabled", + "disabled": "Disabled", + "noDescriptionShort": "No description" }, "skills": { "title": "Skills management", @@ -436,7 +467,36 @@ "loadStatsFailed": "Failed to load skills monitor data", "clearStatsConfirm": "Clear all Skills statistics? This cannot be undone.", "statsCleared": "Skills statistics cleared", - "clearStatsFailed": "Failed to clear statistics" + "clearStatsFailed": "Failed to clear statistics", + "noDescription": "No description", + "viewSkillTitle": "View Skill: {{name}}", + "descriptionLabel": "Description:", + "pathLabel": "Path:", + "modTimeLabel": "Modified:", + "contentLabel": "Content:", + "nameRequired": "Skill name is required", + "contentRequired": "Skill content is required", + "nameInvalid": "Skill name can only contain letters, numbers, hyphens and underscores", + "saveSuccess": "Skill updated", + "createdSuccess": "Skill created", + "deleteConfirm": "Are you sure you want to delete skill \"{{name}}\"? This cannot be undone.", + "deleteConfirmWithRoles": "Are you sure you want to delete skill \"{{name}}\"?\n\n⚠️ This skill is currently bound to {{count}} role(s):\n{{roles}}\n\nAfter deletion, the system will automatically remove this skill from those roles.\n\nThis cannot be undone. Continue?", + "deleteSuccess": "Skill deleted", + "deleteSuccessWithRoles": "Skill deleted and automatically removed from {{count}} role(s): {{roles}}", + "loadFailedShort": "Load failed", + "totalSkillsCount": "Total Skills", + "totalCallsCount": "Total Call Count", + "successfulCalls": "Successful Calls", + "failedCalls": "Failed Calls", + "successRate": "Success Rate", + "skillName": "Skill Name", + "totalCalls": "Total Calls", + "success": "Success", + "failure": "Failure", + "lastCallTime": "Last Call Time", + "noCallRecords": "No Skills call records yet", + "loadStatsErrorShort": "Failed to load statistics", + "loadCallStatsError": "Failed to load call statistics" }, "apiDocs": { "curlCopied": "curl command copied to clipboard!" @@ -580,6 +640,15 @@ "presetLogin": "Login page + China", "presetDomain": "By domain", "presetIp": "By IP", + "queryPresetsAria": "FOFA query presets", + "fieldsPresetsAria": "FOFA field presets", + "resultsToolbarAria": "Results toolbar", + "fillExample": "Fill example", + "parseBtnTitle": "Parse natural language to FOFA query", + "minFieldsTitle": "For quick export", + "webCommonTitle": "For browsing and filtering", + "intelEnhancedTitle": "More fingerprint/intel", + "fullLabel": "full", "nlPlaceholder": "e.g. Apache sites in Missouri, US, title contains Home", "showHideColumns": "Show/hide columns", "exportCsvTitle": "Export results as CSV (UTF-8)", @@ -617,7 +686,14 @@ "clearStatsTitle": "Clear all statistics", "skillsCallStats": "Skills call stats", "searchPlaceholder": "Search Skills...", - "loading": "Loading..." + "loading": "Loading...", + "paginationShow": "Show {{start}}-{{end}} of {{total}}", + "perPageLabel": "Per page", + "firstPage": "First", + "prevPage": "Previous", + "pageOf": "Page {{current}} / {{total}}", + "nextPage": "Next", + "lastPage": "Last" }, "settingsBasic": { "basicTitle": "Basic settings", @@ -702,8 +778,20 @@ "changePasswordBtn": "Change password" }, "settingsRobotsExtra": { - "botCommandsTitle": "Bot commands", - "botCommandsDesc": "You can send these commands in chat (Chinese and English supported):" + "botCommandsTitle": "Bot command instructions", + "botCommandsDesc": "You can send the following commands in chat (Chinese and English supported):", + "botCmdHelp": "Show this help", + "botCmdList": "List conversations", + "botCmdSwitch": "Switch to conversation", + "botCmdNew": "Start new conversation", + "botCmdClear": "Clear context", + "botCmdCurrent": "Show current conversation", + "botCmdStop": "Stop running task", + "botCmdRoles": "List roles", + "botCmdRole": "Switch role", + "botCmdDelete": "Delete conversation", + "botCmdVersion": "Show version", + "botCommandsFooter": "Otherwise, send any text for AI penetration testing / security analysis." }, "mcpDetailModal": { "title": "Tool call details", @@ -752,6 +840,15 @@ "configJson": "Config JSON", "formatLabel": "Format:", "formatDesc": "JSON object; key = config name, value = config. Use Start/Stop buttons to control state.", + "configExample": "Configuration example:", + "stdioMode": "stdio mode:", + "httpMode": "HTTP mode:", + "sseMode": "SSE mode:", + "placeholder": "{\n \"hexstrike-ai\": {\n \"command\": \"python3\",\n \"args\": [\"/path/to/script.py\"],\n \"description\": \"Description\",\n \"timeout\": 300\n }\n}", + "exampleStdio": "{\n \"hexstrike-ai\": {\n \"command\": \"python3\",\n \"args\": [\"/path/to/script.py\", \"--server\", \"http://example.com\"],\n \"description\": \"Description\",\n \"timeout\": 300\n }\n}", + "exampleHttp": "{\n \"cyberstrike-ai-http\": {\n \"transport\": \"http\",\n \"url\": \"http://127.0.0.1:8081/mcp\"\n }\n}", + "exampleSse": "{\n \"cyberstrike-ai-sse\": {\n \"transport\": \"sse\",\n \"url\": \"http://127.0.0.1:8081/mcp/sse\"\n }\n}", + "exampleDescription": "Example description", "formatJson": "Format JSON", "loadExample": "Load example" }, @@ -765,7 +862,7 @@ "descriptionPlaceholder": "Short description", "contentLabel": "Content (Markdown)", "contentPlaceholder": "Enter skill content in Markdown...", - "contentHint": "YAML front matter supported (optional)" + "contentHint": "YAML front matter supported (optional), e.g.:" }, "knowledgeItemModal": { "addKnowledge": "Add knowledge", @@ -920,6 +1017,33 @@ "searchSkillsPlaceholder": "Search skill...", "loadingSkills": "Loading skills...", "relatedSkillsHint": "Selected skills are injected into system prompt before task execution.", - "enableRole": "Enable this role" + "enableRole": "Enable this role", + "selectAll": "Select All", + "deselectAll": "Deselect All", + "roleNameRequired": "Role name is required", + "roleNotFound": "Role not found", + "firstRoleNoToolsHint": "First role with no tools selected will use all tools by default.", + "currentPageSelected": "Current page: {{current}} / {{total}}", + "totalSelected": "Total selected: {{current}} / {{total}}", + "usingAllEnabledTools": "(Using all enabled tools)", + "currentPageSelectedTitle": "Selected on current page (enabled tools only)", + "totalSelectedTitle": "Total tools linked to this role", + "skillsSelectedCount": "Selected {{count}} / {{total}}", + "loadToolsFailed": "Failed to load tools", + "loadSkillsFailed": "Failed to load skills", + "cannotDeleteDefaultRole": "Cannot delete default role", + "noMatchingSkills": "No matching skills", + "noSkillsAvailable": "No skills available", + "usingAllTools": "Use all tools", + "andNMore": " and {{count}} more", + "toolsLabel": "Tools:", + "noTools": "No tools", + "paginationShow": "{{start}}-{{end}} of {{total}} tools", + "paginationSearch": " (search: \"{{keyword}}\")", + "firstPage": "First", + "prevPage": "Previous", + "pageOf": "Page {{page}} / {{total}}", + "nextPage": "Next", + "lastPage": "Last" } } diff --git a/web/static/i18n/zh-CN.json b/web/static/i18n/zh-CN.json index ef253cd6..b7c72e45 100644 --- a/web/static/i18n/zh-CN.json +++ b/web/static/i18n/zh-CN.json @@ -14,7 +14,8 @@ "confirm": "确认", "copy": "复制", "copied": "已复制", - "copyFailed": "复制失败" + "copyFailed": "复制失败", + "view": "查看" }, "header": { "title": "CyberStrikeAI", @@ -340,6 +341,7 @@ "configStdioNeedCommand": "配置错误: \"{{name}}\" stdio模式需要command字段", "configHttpNeedUrl": "配置错误: \"{{name}}\" http模式需要url字段", "configSseNeedUrl": "配置错误: \"{{name}}\" sse模式需要url字段", + "configEditMustContainName": "配置错误: 编辑模式下,JSON必须包含配置名称 \"{{name}}\"", "saveSuccess": "保存成功", "deleteSuccess": "删除成功", "deleteExternalConfirm": "确定要删除外部MCP \"{{name}}\" 吗?", @@ -364,15 +366,36 @@ "description": "配置企业微信、钉钉、飞书等机器人,在手机端直接与 CyberStrikeAI 对话,无需在服务器上打开网页。", "wecom": { "title": "企业微信", - "enabled": "启用企业微信机器人" + "enabled": "启用企业微信机器人", + "token": "Token", + "tokenPlaceholder": "Token", + "encodingAesKey": "EncodingAESKey", + "encodingAesKeyPlaceholder": "EncodingAESKey(明文模式可留空)", + "corpId": "CorpID", + "corpIdPlaceholder": "企业 ID", + "secret": "Secret", + "secretPlaceholder": "应用 Secret", + "agentId": "AgentID", + "agentIdPlaceholder": "应用 AgentId" }, "dingtalk": { "title": "钉钉", - "enabled": "启用钉钉机器人" + "enabled": "启用钉钉机器人", + "clientIdLabel": "Client ID (AppKey)", + "clientIdPlaceholder": "钉钉应用 AppKey", + "clientSecretLabel": "Client Secret", + "clientSecretPlaceholder": "钉钉应用 Secret", + "streamHint": "需开启机器人能力并配置流式接入" }, "lark": { "title": "飞书 (Lark)", - "enabled": "启用飞书机器人" + "enabled": "启用飞书机器人", + "appIdLabel": "App ID", + "appIdPlaceholder": "飞书应用 App ID", + "appSecretLabel": "App Secret", + "appSecretPlaceholder": "飞书应用 App Secret", + "verifyTokenLabel": "Verify Token(可选)", + "verifyTokenPlaceholder": "事件订阅 Verification Token" } }, "apply": { @@ -414,7 +437,15 @@ "title": "角色管理", "createRole": "创建角色", "searchPlaceholder": "搜索角色...", - "deleteConfirm": "确定要删除角色..." + "deleteConfirm": "确定要删除角色...", + "loadFailed": "加载角色失败", + "noDescription": "暂无描述", + "defaultRoleDescription": "默认角色,不额外携带用户提示词,使用默认MCP", + "noMatchingRoles": "没有找到匹配的角色", + "noRoles": "暂无角色", + "enabled": "已启用", + "disabled": "已禁用", + "noDescriptionShort": "无描述" }, "skills": { "title": "Skills管理", @@ -436,7 +467,36 @@ "loadStatsFailed": "加载skills监控数据失败", "clearStatsConfirm": "确定要清空所有Skills统计数据吗?此操作不可恢复。", "statsCleared": "已清空所有Skills统计数据", - "clearStatsFailed": "清空统计数据失败" + "clearStatsFailed": "清空统计数据失败", + "noDescription": "无描述", + "viewSkillTitle": "查看Skill: {{name}}", + "descriptionLabel": "描述:", + "pathLabel": "路径:", + "modTimeLabel": "修改时间:", + "contentLabel": "内容:", + "nameRequired": "skill名称不能为空", + "contentRequired": "skill内容不能为空", + "nameInvalid": "skill名称只能包含字母、数字、连字符和下划线", + "saveSuccess": "skill已更新", + "createdSuccess": "skill已创建", + "deleteConfirm": "确定要删除skill \"{{name}}\" 吗?此操作不可恢复。", + "deleteConfirmWithRoles": "确定要删除skill \"{{name}}\" 吗?\n\n⚠️ 该skill当前已被以下 {{count}} 个角色绑定:\n{{roles}}\n\n删除后,系统将自动从这些角色中移除该skill的绑定。\n\n此操作不可恢复,是否继续?", + "deleteSuccess": "skill已删除", + "deleteSuccessWithRoles": "skill已删除,已自动从 {{count}} 个角色中移除绑定:{{roles}}", + "loadFailedShort": "加载失败", + "totalSkillsCount": "总Skills数", + "totalCallsCount": "总调用次数", + "successfulCalls": "成功调用", + "failedCalls": "失败调用", + "successRate": "成功率", + "skillName": "Skill名称", + "totalCalls": "总调用", + "success": "成功", + "failure": "失败", + "lastCallTime": "最后调用时间", + "noCallRecords": "暂无Skills调用记录", + "loadStatsErrorShort": "无法加载统计信息", + "loadCallStatsError": "无法加载调用统计" }, "apiDocs": { "curlCopied": "curl命令已复制到剪贴板!" @@ -580,6 +640,15 @@ "presetLogin": "登录页 + 中国", "presetDomain": "指定域名", "presetIp": "指定 IP", + "queryPresetsAria": "FOFA 查询示例", + "fieldsPresetsAria": "FOFA 字段模板", + "resultsToolbarAria": "结果工具条", + "fillExample": "填入示例", + "parseBtnTitle": "将自然语言解析为 FOFA 查询语法", + "minFieldsTitle": "适合快速导出目标", + "webCommonTitle": "适合浏览和筛选", + "intelEnhancedTitle": "更偏指纹/情报", + "fullLabel": "full", "nlPlaceholder": "例如:找美国 Missouri 的 Apache 站点,标题包含 Home", "showHideColumns": "显示/隐藏字段", "exportCsvTitle": "导出当前结果为 CSV(UTF-8,兼容中文)", @@ -617,7 +686,14 @@ "clearStatsTitle": "清空所有统计数据", "skillsCallStats": "Skills调用统计", "searchPlaceholder": "搜索Skills...", - "loading": "加载中..." + "loading": "加载中...", + "paginationShow": "显示 {{start}}-{{end}} / 共 {{total}} 条", + "perPageLabel": "每页显示", + "firstPage": "首页", + "prevPage": "上一页", + "pageOf": "第 {{current}} / {{total}} 页", + "nextPage": "下一页", + "lastPage": "尾页" }, "settingsBasic": { "basicTitle": "基本设置", @@ -703,7 +779,19 @@ }, "settingsRobotsExtra": { "botCommandsTitle": "机器人命令说明", - "botCommandsDesc": "在对话中可发送以下命令(支持中英文):" + "botCommandsDesc": "在对话中可发送以下命令(支持中英文):", + "botCmdHelp": "显示本帮助 | Show this help", + "botCmdList": "列出所有对话标题与 ID | List conversations", + "botCmdSwitch": "指定对话继续 | Switch to conversation", + "botCmdNew": "开启新对话 | Start new conversation", + "botCmdClear": "清空当前上下文 | Clear context", + "botCmdCurrent": "显示当前对话 ID 与标题 | Show current conversation", + "botCmdStop": "中断当前任务 | Stop running task", + "botCmdRoles": "列出所有可用角色 | List roles", + "botCmdRole": "切换当前角色 | Switch role", + "botCmdDelete": "删除指定对话 | Delete conversation", + "botCmdVersion": "显示当前版本号 | Show version", + "botCommandsFooter": "除以上命令外,直接输入内容将发送给 AI 进行渗透测试/安全分析。Otherwise, send any text for AI penetration testing / security analysis." }, "mcpDetailModal": { "title": "工具调用详情", @@ -752,6 +840,15 @@ "configJson": "配置JSON", "formatLabel": "配置格式:", "formatDesc": "JSON对象,key为配置名称,value为配置内容。状态通过\"启动/停止\"按钮控制,无需在JSON中配置。", + "configExample": "配置示例:", + "stdioMode": "stdio模式:", + "httpMode": "HTTP模式:", + "sseMode": "SSE模式:", + "placeholder": "{\n \"hexstrike-ai\": {\n \"command\": \"python3\",\n \"args\": [\"/path/to/script.py\"],\n \"description\": \"描述\",\n \"timeout\": 300\n }\n}", + "exampleStdio": "{\n \"hexstrike-ai\": {\n \"command\": \"python3\",\n \"args\": [\"/path/to/script.py\", \"--server\", \"http://example.com\"],\n \"description\": \"描述\",\n \"timeout\": 300\n }\n}", + "exampleHttp": "{\n \"cyberstrike-ai-http\": {\n \"transport\": \"http\",\n \"url\": \"http://127.0.0.1:8081/mcp\"\n }\n}", + "exampleSse": "{\n \"cyberstrike-ai-sse\": {\n \"transport\": \"sse\",\n \"url\": \"http://127.0.0.1:8081/mcp/sse\"\n }\n}", + "exampleDescription": "示例描述", "formatJson": "格式化JSON", "loadExample": "加载示例" }, @@ -765,7 +862,7 @@ "descriptionPlaceholder": "Skill的简短描述", "contentLabel": "内容(Markdown格式)", "contentPlaceholder": "输入skill内容,支持Markdown格式...", - "contentHint": "支持YAML front matter格式(可选)" + "contentHint": "支持YAML front matter格式(可选),例如:" }, "knowledgeItemModal": { "addKnowledge": "添加知识", @@ -920,6 +1017,33 @@ "searchSkillsPlaceholder": "搜索skill...", "loadingSkills": "正在加载skills列表...", "relatedSkillsHint": "勾选要关联的skills,这些skills的内容会在执行任务前注入到系统提示词中,帮助AI更好地理解相关专业知识。", - "enableRole": "启用此角色" + "enableRole": "启用此角色", + "selectAll": "全选", + "deselectAll": "全不选", + "roleNameRequired": "角色名称不能为空", + "roleNotFound": "角色不存在", + "firstRoleNoToolsHint": "检测到这是首次添加角色且未选择工具,将默认使用全部工具", + "currentPageSelected": "当前页已选中: {{current}} / {{total}}", + "totalSelected": "总计已选中: {{current}} / {{total}}", + "usingAllEnabledTools": "(使用所有已启用工具)", + "currentPageSelectedTitle": "当前页选中的工具数(只统计已启用的工具)", + "totalSelectedTitle": "角色已关联的工具总数(基于角色实际配置)", + "skillsSelectedCount": "已选择 {{count}} / {{total}}", + "loadToolsFailed": "加载工具列表失败", + "loadSkillsFailed": "加载skills列表失败", + "cannotDeleteDefaultRole": "不能删除默认角色", + "noMatchingSkills": "没有找到匹配的skills", + "noSkillsAvailable": "暂无可用skills", + "usingAllTools": "使用所有工具", + "andNMore": " 等 {{count}} 个", + "toolsLabel": "工具:", + "noTools": "暂无工具", + "paginationShow": "显示 {{start}}-{{end}} / 共 {{total}} 个工具", + "paginationSearch": " (搜索: \"{{keyword}}\")", + "firstPage": "首页", + "prevPage": "上一页", + "pageOf": "第 {{page}} / {{total}} 页", + "nextPage": "下一页", + "lastPage": "末页" } } diff --git a/web/static/js/i18n.js b/web/static/js/i18n.js index 9b75ccf7..40cd0486 100644 --- a/web/static/js/i18n.js +++ b/web/static/js/i18n.js @@ -61,18 +61,23 @@ const isFormControl = (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA'); const attrList = el.getAttribute('data-i18n-attr'); const text = i18next.t(key); - - // 仅当未使用 data-i18n-attr 时才替换元素文本内容(否则会覆盖卡片内的数字、子节点等) - // input/textarea:永不设置 textContent(会变成 value),只更新属性 - if (!attrList && !skipText && !isFormControl && text && typeof text === 'string') { + // 仅当元素无子元素(仅文本或空)时才替换文本,避免覆盖卡片内的数字、子节点等;input/textarea 永不设置 textContent + const hasNoElementChildren = !el.querySelector('*'); + if (!skipText && !isFormControl && hasNoElementChildren && text && typeof text === 'string') { el.textContent = text; } if (attrList) { + const titleKey = el.getAttribute('data-i18n-title'); attrList.split(',').map(function (s) { return s.trim(); }).forEach(function (attr) { if (!attr) return; - if (text && typeof text === 'string') { - el.setAttribute(attr, text); + var val = text; + if (attr === 'title' && titleKey) { + var titleText = i18next.t(titleKey); + if (titleText && typeof titleText === 'string') val = titleText; + } + if (val && typeof val === 'string') { + el.setAttribute(attr, val); } }); } diff --git a/web/static/js/roles.js b/web/static/js/roles.js index cf990fa5..09ad85ac 100644 --- a/web/static/js/roles.js +++ b/web/static/js/roles.js @@ -1,4 +1,7 @@ // 角色管理相关功能 +function _t(key, opts) { + return typeof window.t === 'function' ? window.t(key, opts) : key; +} let currentRole = localStorage.getItem('currentRole') || ''; let roles = []; let rolesSearchKeyword = ''; // 角色搜索关键词 @@ -54,7 +57,7 @@ async function loadRoles() { return roles; } catch (error) { console.error('加载角色失败:', error); - showNotification('加载角色失败: ' + error.message, 'error'); + showNotification(_t('roles.loadFailed') + ': ' + error.message, 'error'); return []; } } @@ -167,9 +170,9 @@ function renderRoleSelectionSidebar() { const icon = getRoleIcon(role); // 处理默认角色的描述 - let description = role.description || '暂无描述'; + let description = role.description || _t('roles.noDescription'); if (isDefaultRole && !role.description) { - description = '默认角色,不额外携带用户提示词,使用默认MCP'; + description = _t('roles.defaultRoleDescription'); } roleItem.innerHTML = ` @@ -282,7 +285,7 @@ function renderRolesList() { if (filteredRoles.length === 0) { rolesList.innerHTML = '
' + - (rolesSearchKeyword ? '没有找到匹配的角色' : '暂无角色') + + (rolesSearchKeyword ? _t('roles.noMatchingRoles') : _t('roles.noRoles')) + '
'; return; } @@ -312,7 +315,7 @@ function renderRolesList() { let toolsDisplay = ''; let toolsCount = 0; if (role.name === '默认') { - toolsDisplay = '使用所有工具'; + toolsDisplay = _t('roleModal.usingAllTools'); } else if (role.tools && role.tools.length > 0) { toolsCount = role.tools.length; // 显示前5个工具名称 @@ -324,13 +327,13 @@ function renderRolesList() { if (toolsCount <= 5) { toolsDisplay = toolNames.join(', '); } else { - toolsDisplay = toolNames.join(', ') + ` 等 ${toolsCount} 个`; + toolsDisplay = toolNames.join(', ') + _t('roleModal.andNMore', { count: toolsCount }); } } else if (role.mcps && role.mcps.length > 0) { toolsCount = role.mcps.length; - toolsDisplay = `等 ${toolsCount} 个`; + toolsDisplay = _t('roleModal.andNMore', { count: toolsCount }); } else { - toolsDisplay = '使用所有工具'; + toolsDisplay = _t('roleModal.usingAllTools'); } return ` @@ -341,17 +344,17 @@ function renderRolesList() { ${escapeHtml(role.name)} - ${role.enabled !== false ? '已启用' : '已禁用'} + ${role.enabled !== false ? _t('roles.enabled') : _t('roles.disabled')} -
${escapeHtml(role.description || '无描述')}
+
${escapeHtml(role.description || _t('roles.noDescriptionShort'))}
- 工具: + ${_t('roleModal.toolsLabel')} ${toolsDisplay}
- - ${role.name !== '默认' ? `` : ''} + + ${role.name !== '默认' ? `` : ''}
`; @@ -503,7 +506,7 @@ async function loadRoleTools(page = 1, searchKeyword = '') { console.error('加载工具列表失败:', error); const toolsList = document.getElementById('role-tools-list'); if (toolsList) { - toolsList.innerHTML = `
加载工具列表失败: ${escapeHtml(error.message)}
`; + toolsList.innerHTML = `
${_t('roleModal.loadToolsFailed')}: ${escapeHtml(error.message)}
`; } } } @@ -521,7 +524,7 @@ function renderRoleToolsList() { listContainer.innerHTML = ''; if (allRoleTools.length === 0) { - listContainer.innerHTML = '
暂无工具
'; + listContainer.innerHTML = '
' + _t('roleModal.noTools') + '
'; toolsList.appendChild(listContainer); return; } @@ -594,16 +597,16 @@ function renderRoleToolsPagination() { const startItem = (page - 1) * roleToolsPagination.pageSize + 1; const endItem = Math.min(page * roleToolsPagination.pageSize, total); + const paginationShowText = _t('roleModal.paginationShow', { start: startItem, end: endItem, total: total }) + + (roleToolsSearchKeyword ? _t('roleModal.paginationSearch', { keyword: roleToolsSearchKeyword }) : ''); pagination.innerHTML = ` -
- 显示 ${startItem}-${endItem} / 共 ${total} 个工具${roleToolsSearchKeyword ? ` (搜索: "${escapeHtml(roleToolsSearchKeyword)}")` : ''} -
+
${paginationShowText}
- - - 第 ${page} / ${totalPages} 页 - - + + + ${_t('roleModal.pageOf', { page: page, total: totalPages })} + +
`; @@ -727,8 +730,8 @@ function updateRoleToolsStats() { // 总工具数(所有工具,包括已启用和未启用的) const totalTools = roleToolsPagination.total || 0; statsEl.innerHTML = ` - ✅ 当前页已选中: ${currentPageEnabled} / ${currentPageTotal} - 📊 总计已选中: ${totalEnabled} / ${totalTools} (使用所有已启用工具) + ✅ ${_t('roleModal.currentPageSelected', { current: currentPageEnabled, total: currentPageTotal })} + 📊 ${_t('roleModal.totalSelected', { current: totalEnabled, total: totalTools })} ${_t('roleModal.usingAllEnabledTools')} `; return; } @@ -779,8 +782,8 @@ function updateRoleToolsStats() { const totalTools = roleToolsPagination.total || 0; statsEl.innerHTML = ` - ✅ 当前页已选中: ${currentPageEnabled} / ${currentPageTotal} - 📊 总计已选中: ${totalSelected} / ${totalTools} + ✅ ${_t('roleModal.currentPageSelected', { current: currentPageEnabled, total: currentPageTotal })} + 📊 ${_t('roleModal.totalSelected', { current: totalSelected, total: totalTools })} `; } @@ -838,7 +841,7 @@ async function showAddRoleModal() { const modal = document.getElementById('role-modal'); if (!modal) return; - document.getElementById('role-modal-title').textContent = '添加角色'; + document.getElementById('role-modal-title').textContent = _t('roleModal.addRole'); document.getElementById('role-name').value = ''; document.getElementById('role-name').disabled = false; document.getElementById('role-description').value = ''; @@ -918,14 +921,14 @@ async function showAddRoleModal() { async function editRole(roleName) { const role = roles.find(r => r.name === roleName); if (!role) { - showNotification('角色不存在', 'error'); + showNotification(_t('roleModal.roleNotFound'), 'error'); return; } const modal = document.getElementById('role-modal'); if (!modal) return; - document.getElementById('role-modal-title').textContent = '编辑角色'; + document.getElementById('role-modal-title').textContent = _t('roleModal.editRole'); document.getElementById('role-name').value = role.name; document.getElementById('role-name').disabled = true; // 编辑时不允许修改名称 document.getElementById('role-description').value = role.description || ''; @@ -1186,7 +1189,7 @@ async function loadAllToolsToStateMap() { async function saveRole() { const name = document.getElementById('role-name').value.trim(); if (!name) { - showNotification('角色名称不能为空', 'error'); + showNotification(_t('roleModal.roleNameRequired'), 'error'); return; } @@ -1227,7 +1230,7 @@ async function saveRole() { // 如果是首次添加角色且没有选择工具,默认使用全部工具 if (isFirstUserRole && allSelectedTools.length === 0) { roleUsesAllTools = true; - showNotification('检测到这是首次添加角色且未选择工具,将默认使用全部工具', 'info'); + showNotification(_t('roleModal.firstRoleNoToolsHint'), 'info'); } else if (roleUsesAllTools) { // 如果当前使用所有工具,需要检查用户是否取消了一些工具 // 检查状态映射中是否有未选中的已启用工具 @@ -1358,7 +1361,7 @@ async function saveRole() { // 删除角色 async function deleteRole(roleName) { if (roleName === '默认') { - showNotification('不能删除默认角色', 'error'); + showNotification(_t('roleModal.cannotDeleteDefaultRole'), 'error'); return; } @@ -1469,7 +1472,7 @@ async function loadRoleSkills() { allRoleSkills = []; const skillsList = document.getElementById('role-skills-list'); if (skillsList) { - skillsList.innerHTML = '
加载skills列表失败: ' + error.message + '
'; + skillsList.innerHTML = '
' + _t('roleModal.loadSkillsFailed') + ': ' + error.message + '
'; } } } @@ -1490,7 +1493,7 @@ function renderRoleSkills() { if (filteredSkills.length === 0) { skillsList.innerHTML = '
' + - (roleSkillsSearchKeyword ? '没有找到匹配的skills' : '暂无可用skills') + + (roleSkillsSearchKeyword ? _t('roleModal.noMatchingSkills') : _t('roleModal.noSkillsAvailable')) + '
'; updateRoleSkillsStats(); return; @@ -1596,7 +1599,7 @@ function updateRoleSkillsStats() { filteredSkills.includes(skill) ).length; - statsEl.textContent = `已选择 ${selectedCount} / ${filteredSkills.length}`; + statsEl.textContent = _t('roleModal.skillsSelectedCount', { count: selectedCount, total: filteredSkills.length }); } // HTML转义函数 diff --git a/web/static/js/settings.js b/web/static/js/settings.js index a1118eef..1f114f0f 100644 --- a/web/static/js/settings.js +++ b/web/static/js/settings.js @@ -1367,7 +1367,7 @@ function formatExternalMCPJSON() { try { const jsonStr = jsonTextarea.value.trim(); if (!jsonStr) { - errorDiv.textContent = 'JSON不能为空'; + errorDiv.textContent = (typeof window.t === 'function' ? window.t('mcp.jsonEmpty') : 'JSON不能为空'); errorDiv.style.display = 'block'; jsonTextarea.classList.add('error'); return; @@ -1379,7 +1379,7 @@ function formatExternalMCPJSON() { errorDiv.style.display = 'none'; jsonTextarea.classList.remove('error'); } catch (error) { - errorDiv.textContent = 'JSON格式错误: ' + error.message; + errorDiv.textContent = (typeof window.t === 'function' ? window.t('mcp.jsonError') : 'JSON格式错误') + ': ' + error.message; errorDiv.style.display = 'block'; jsonTextarea.classList.add('error'); } @@ -1387,6 +1387,7 @@ function formatExternalMCPJSON() { // 加载示例 function loadExternalMCPExample() { + const desc = (typeof window.t === 'function' ? window.t('externalMcpModal.exampleDescription') : '示例描述'); const example = { "hexstrike-ai": { command: "python3", @@ -1395,7 +1396,7 @@ function loadExternalMCPExample() { "--server", "http://example.com" ], - description: "示例描述", + description: desc, timeout: 300 }, "cyberstrike-ai-http": { @@ -1420,7 +1421,7 @@ async function saveExternalMCP() { const errorDiv = document.getElementById('external-mcp-json-error'); if (!jsonStr) { - errorDiv.textContent = 'JSON配置不能为空'; + errorDiv.textContent = (typeof window.t === 'function' ? window.t('mcp.jsonEmpty') : 'JSON不能为空'); errorDiv.style.display = 'block'; jsonTextarea.classList.add('error'); jsonTextarea.focus(); @@ -1431,16 +1432,17 @@ async function saveExternalMCP() { try { configObj = JSON.parse(jsonStr); } catch (error) { - errorDiv.textContent = 'JSON格式错误: ' + error.message; + errorDiv.textContent = (typeof window.t === 'function' ? window.t('mcp.jsonError') : 'JSON格式错误') + ': ' + error.message; errorDiv.style.display = 'block'; jsonTextarea.classList.add('error'); jsonTextarea.focus(); return; } + const t = (typeof window.t === 'function' ? window.t : function (k, opts) { return k; }); // 验证必须是对象格式 if (typeof configObj !== 'object' || Array.isArray(configObj) || configObj === null) { - errorDiv.textContent = '配置错误: 必须是JSON对象格式,key为配置名称,value为配置内容'; + errorDiv.textContent = t('mcp.configMustBeObject'); errorDiv.style.display = 'block'; jsonTextarea.classList.add('error'); return; @@ -1449,7 +1451,7 @@ async function saveExternalMCP() { // 获取所有配置名称 const names = Object.keys(configObj); if (names.length === 0) { - errorDiv.textContent = '配置错误: 至少需要一个配置项'; + errorDiv.textContent = t('mcp.configNeedOne'); errorDiv.style.display = 'block'; jsonTextarea.classList.add('error'); return; @@ -1458,7 +1460,7 @@ async function saveExternalMCP() { // 验证每个配置 for (const name of names) { if (!name || name.trim() === '') { - errorDiv.textContent = '配置错误: 配置名称不能为空'; + errorDiv.textContent = t('mcp.configNameEmpty'); errorDiv.style.display = 'block'; jsonTextarea.classList.add('error'); return; @@ -1466,7 +1468,7 @@ async function saveExternalMCP() { const config = configObj[name]; if (typeof config !== 'object' || Array.isArray(config) || config === null) { - errorDiv.textContent = `配置错误: "${name}" 的配置必须是对象`; + errorDiv.textContent = t('mcp.configMustBeObj', { name: name }); errorDiv.style.display = 'block'; jsonTextarea.classList.add('error'); return; @@ -1478,28 +1480,28 @@ async function saveExternalMCP() { // 验证配置内容 const transport = config.transport || (config.command ? 'stdio' : config.url ? 'http' : ''); if (!transport) { - errorDiv.textContent = `配置错误: "${name}" 需要指定command(stdio模式)或url(http/sse模式)`; + errorDiv.textContent = t('mcp.configNeedCommand', { name: name }); errorDiv.style.display = 'block'; jsonTextarea.classList.add('error'); return; } if (transport === 'stdio' && !config.command) { - errorDiv.textContent = `配置错误: "${name}" stdio模式需要command字段`; + errorDiv.textContent = t('mcp.configStdioNeedCommand', { name: name }); errorDiv.style.display = 'block'; jsonTextarea.classList.add('error'); return; } if (transport === 'http' && !config.url) { - errorDiv.textContent = `配置错误: "${name}" http模式需要url字段`; + errorDiv.textContent = t('mcp.configHttpNeedUrl', { name: name }); errorDiv.style.display = 'block'; jsonTextarea.classList.add('error'); return; } if (transport === 'sse' && !config.url) { - errorDiv.textContent = `配置错误: "${name}" sse模式需要url字段`; + errorDiv.textContent = t('mcp.configSseNeedUrl', { name: name }); errorDiv.style.display = 'block'; jsonTextarea.classList.add('error'); return; @@ -1514,7 +1516,7 @@ async function saveExternalMCP() { // 如果是编辑模式,只更新当前编辑的配置 if (currentEditingMCPName) { if (!configObj[currentEditingMCPName]) { - errorDiv.textContent = `配置错误: 编辑模式下,JSON必须包含配置名称 "${currentEditingMCPName}"`; + errorDiv.textContent = (typeof window.t === 'function' ? window.t('mcp.configEditMustContainName', { name: currentEditingMCPName }) : '配置错误: 编辑模式下,JSON必须包含配置名称 "' + currentEditingMCPName + '"'); errorDiv.style.display = 'block'; jsonTextarea.classList.add('error'); return; @@ -1561,7 +1563,7 @@ async function saveExternalMCP() { alert(typeof window.t === 'function' ? window.t('mcp.saveSuccess') : '保存成功'); } catch (error) { console.error('保存外部MCP失败:', error); - errorDiv.textContent = '保存失败: ' + error.message; + errorDiv.textContent = (typeof window.t === 'function' ? window.t('mcp.operationFailed') : '保存失败') + ': ' + error.message; errorDiv.style.display = 'block'; jsonTextarea.classList.add('error'); } diff --git a/web/static/js/skills.js b/web/static/js/skills.js index 20581702..38b1db28 100644 --- a/web/static/js/skills.js +++ b/web/static/js/skills.js @@ -1,4 +1,7 @@ // Skills管理相关功能 +function _t(key, opts) { + return typeof window.t === 'function' ? window.t(key, opts) : key; +} let skillsList = []; let currentEditingSkillName = null; let isSavingSkill = false; // 防止重复提交 @@ -65,7 +68,7 @@ async function loadSkills(page = 1, pageSize = null) { const response = await apiFetch(url); if (!response.ok) { - throw new Error('获取skills列表失败'); + throw new Error(_t('skills.loadListFailed')); } const data = await response.json(); skillsList = data.skills || []; @@ -76,10 +79,10 @@ async function loadSkills(page = 1, pageSize = null) { updateSkillsManagementStats(); } catch (error) { console.error('加载skills列表失败:', error); - showNotification('加载skills列表失败: ' + error.message, 'error'); + showNotification(_t('skills.loadListFailed') + ': ' + error.message, 'error'); const skillsListEl = document.getElementById('skills-list'); if (skillsListEl) { - skillsListEl.innerHTML = '
加载失败: ' + error.message + '
'; + skillsListEl.innerHTML = '
' + _t('skills.loadFailedShort') + ': ' + escapeHtml(error.message) + '
'; } } } @@ -94,7 +97,7 @@ function renderSkillsList() { if (filteredSkills.length === 0) { skillsListEl.innerHTML = '
' + - (skillsSearchKeyword ? '没有找到匹配的skills' : '暂无skills,点击"创建Skill"创建第一个skill') + + (skillsSearchKeyword ? _t('skills.noMatch') : _t('skills.noSkills')) + '
'; // 搜索时隐藏分页 const paginationContainer = document.getElementById('skills-pagination'); @@ -109,12 +112,12 @@ function renderSkillsList() {

${escapeHtml(skill.name || '')}

-
${escapeHtml(skill.description || '无描述')}
+
${escapeHtml(skill.description || _t('skills.noDescription'))}
- - - + + +
`; @@ -154,12 +157,19 @@ function renderSkillsPagination() { let paginationHTML = ' -
+
已选择 0 条
@@ -1281,24 +1281,24 @@
- - + +
- - + +
- - + +
- - + +
- - + +
@@ -1315,13 +1315,13 @@
- - + +
- - - 需开启机器人能力并配置流式接入 + + + 需开启机器人能力并配置流式接入
@@ -1338,41 +1338,41 @@
- - + +
- - + +
- - + +
-

机器人命令说明

-

在对话中可发送以下命令(支持中英文):

+

机器人命令说明

+

在对话中可发送以下命令(支持中英文):

-

除以上命令外,直接输入内容将发送给 AI 进行渗透测试/安全分析。Otherwise, send any text for AI penetration testing / security analysis.

+

除以上命令外,直接输入内容将发送给 AI 进行渗透测试/安全分析。Otherwise, send any text for AI penetration testing / security analysis.

- +
@@ -1507,18 +1507,18 @@