/中,DOMPurify会保留其文本内容)
- let parsedContent = parseMarkdown(content);
+ let parsedContent = parseMarkdown(role === 'assistant' ? displayContent : content);
if (!parsedContent) {
parsedContent = content;
}
@@ -1087,14 +1114,16 @@ function addMessage(role, content, mcpExecutionIds = null, progressId = null, cr
formattedContent = DOMPurify.sanitize(parsedContent, defaultSanitizeConfig);
} else if (typeof marked !== 'undefined') {
- const parsedContent = parseMarkdown(content);
+ const rawForParse = role === 'assistant' ? displayContent : content;
+ const parsedContent = parseMarkdown(rawForParse);
if (parsedContent) {
formattedContent = parsedContent;
} else {
- formattedContent = escapeHtml(content).replace(/\n/g, '
');
+ formattedContent = escapeHtml(rawForParse).replace(/\n/g, '
');
}
} else {
- formattedContent = escapeHtml(content).replace(/\n/g, '
');
+ const rawForEscape = role === 'assistant' ? displayContent : content;
+ formattedContent = escapeHtml(rawForEscape).replace(/\n/g, '
');
}
bubble.innerHTML = formattedContent;
@@ -1129,8 +1158,8 @@ function addMessage(role, content, mcpExecutionIds = null, progressId = null, cr
if (role === 'assistant') {
const copyBtn = document.createElement('button');
copyBtn.className = 'message-copy-btn';
- copyBtn.innerHTML = '复制';
- copyBtn.title = '复制消息内容';
+ copyBtn.innerHTML = '' + (typeof window.t === 'function' ? window.t('common.copy') : '复制') + '';
+ copyBtn.title = typeof window.t === 'function' ? window.t('chat.copyMessageTitle') : '复制消息内容';
copyBtn.onclick = function(e) {
e.stopPropagation();
copyMessageToClipboard(messageDiv, this);
@@ -1169,7 +1198,7 @@ function addMessage(role, content, mcpExecutionIds = null, progressId = null, cr
const mcpLabel = document.createElement('div');
mcpLabel.className = 'mcp-call-label';
- mcpLabel.textContent = '📋 渗透测试详情';
+ mcpLabel.textContent = '📋 ' + (typeof window.t === 'function' ? window.t('chat.penetrationTestDetail') : '渗透测试详情');
mcpSection.appendChild(mcpLabel);
const buttonsContainer = document.createElement('div');
@@ -1192,7 +1221,7 @@ function addMessage(role, content, mcpExecutionIds = null, progressId = null, cr
if (progressId) {
const progressDetailBtn = document.createElement('button');
progressDetailBtn.className = 'mcp-detail-btn process-detail-btn';
- progressDetailBtn.innerHTML = '展开详情';
+ progressDetailBtn.innerHTML = '' + (typeof window.t === 'function' ? window.t('chat.expandDetail') : '展开详情') + '';
progressDetailBtn.onclick = () => toggleProcessDetails(progressId, messageDiv.id);
buttonsContainer.appendChild(progressDetailBtn);
// 存储进度ID到消息元素
@@ -1259,7 +1288,7 @@ function copyMessageToClipboard(messageDiv, button) {
function showCopySuccess(button) {
if (button) {
const originalText = button.innerHTML;
- button.innerHTML = '已复制';
+ button.innerHTML = '' + (typeof window.t === 'function' ? window.t('common.copied') : '已复制') + '';
button.style.color = '#10b981';
button.style.background = 'rgba(16, 185, 129, 0.1)';
button.style.borderColor = 'rgba(16, 185, 129, 0.3)';
@@ -1301,11 +1330,11 @@ function renderProcessDetails(messageId, processDetails) {
if (!mcpLabel && !buttonsContainer) {
mcpLabel = document.createElement('div');
mcpLabel.className = 'mcp-call-label';
- mcpLabel.textContent = '📋 渗透测试详情';
+ mcpLabel.textContent = '📋 ' + (typeof window.t === 'function' ? window.t('chat.penetrationTestDetail') : '渗透测试详情');
mcpSection.appendChild(mcpLabel);
- } else if (mcpLabel && mcpLabel.textContent !== '📋 渗透测试详情') {
+ } else if (mcpLabel && mcpLabel.textContent !== ('📋 ' + (typeof window.t === 'function' ? window.t('chat.penetrationTestDetail') : '渗透测试详情'))) {
// 如果标签存在但不是统一格式,更新它
- mcpLabel.textContent = '📋 渗透测试详情';
+ mcpLabel.textContent = '📋 ' + (typeof window.t === 'function' ? window.t('chat.penetrationTestDetail') : '渗透测试详情');
}
// 如果没有按钮容器,创建一个
@@ -1320,7 +1349,7 @@ function renderProcessDetails(messageId, processDetails) {
if (!processDetailBtn) {
processDetailBtn = document.createElement('button');
processDetailBtn.className = 'mcp-detail-btn process-detail-btn';
- processDetailBtn.innerHTML = '展开详情';
+ processDetailBtn.innerHTML = '' + (typeof window.t === 'function' ? window.t('chat.expandDetail') : '展开详情') + '';
processDetailBtn.onclick = () => toggleProcessDetails(null, messageId);
buttonsContainer.appendChild(processDetailBtn);
}
@@ -1360,7 +1389,7 @@ function renderProcessDetails(messageId, processDetails) {
// 如果没有processDetails或为空,显示空状态
if (!processDetails || processDetails.length === 0) {
// 显示空状态提示
- timeline.innerHTML = '暂无过程详情(可能执行过快或未触发详细事件)';
+ timeline.innerHTML = '' + (typeof window.t === 'function' ? window.t('chat.noProcessDetail') : '暂无过程详情(可能执行过快或未触发详细事件)') + '';
// 默认折叠
timeline.classList.remove('expanded');
return;
@@ -1425,7 +1454,7 @@ function renderProcessDetails(messageId, processDetails) {
// 更新按钮文本为"展开详情"
const processDetailBtn = messageElement.querySelector('.process-detail-btn');
if (processDetailBtn) {
- processDetailBtn.innerHTML = '展开详情';
+ processDetailBtn.innerHTML = '' + (typeof window.t === 'function' ? window.t('chat.expandDetail') : '展开详情') + '';
}
}
}
@@ -1679,7 +1708,8 @@ async function startNewConversation() {
currentConversationId = null;
currentConversationGroupId = null; // 新对话不属于任何分组
document.getElementById('chat-messages').innerHTML = '';
- addMessage('assistant', '系统已就绪。请输入您的测试需求,系统将自动执行相应的安全测试。');
+ const readyMsgNew = typeof window.t === 'function' ? window.t('chat.systemReadyMessage') : '系统已就绪。请输入您的测试需求,系统将自动执行相应的安全测试。';
+ addMessage('assistant', readyMsgNew);
addAttackChainButton(null);
updateActiveConversation();
// 刷新分组列表,清除分组高亮
@@ -2087,7 +2117,8 @@ async function loadConversation(conversationId) {
}
});
} else {
- addMessage('assistant', '系统已就绪。请输入您的测试需求,系统将自动执行相应的安全测试。');
+ const readyMsgEmpty = typeof window.t === 'function' ? window.t('chat.systemReadyMessage') : '系统已就绪。请输入您的测试需求,系统将自动执行相应的安全测试。';
+ addMessage('assistant', readyMsgEmpty);
}
// 滚动到底部
@@ -2127,7 +2158,8 @@ async function deleteConversation(conversationId, skipConfirm = false) {
if (conversationId === currentConversationId) {
currentConversationId = null;
document.getElementById('chat-messages').innerHTML = '';
- addMessage('assistant', '系统已就绪。请输入您的测试需求,系统将自动执行相应的安全测试。');
+ const readyMsgLoad = typeof window.t === 'function' ? window.t('chat.systemReadyMessage') : '系统已就绪。请输入您的测试需求,系统将自动执行相应的安全测试。';
+ addMessage('assistant', readyMsgLoad);
addAttackChainButton(null);
}
@@ -4171,13 +4203,13 @@ async function showConversationContextMenu(event) {
attackChainMenuItem.style.opacity = '1';
attackChainMenuItem.style.cursor = 'pointer';
attackChainMenuItem.onclick = showAttackChainFromContext;
- attackChainMenuItem.title = '查看当前对话的攻击链';
+ attackChainMenuItem.title = (typeof window.t === 'function' ? window.t('chat.viewAttackChainCurrentConv') : '查看当前对话的攻击链');
}
} else {
attackChainMenuItem.style.opacity = '0.5';
attackChainMenuItem.style.cursor = 'not-allowed';
attackChainMenuItem.onclick = null;
- attackChainMenuItem.title = '请选择一个对话以查看攻击链';
+ attackChainMenuItem.title = (typeof window.t === 'function' ? window.t('chat.viewAttackChainSelectConv') : '请选择一个对话以查看攻击链');
}
}
@@ -4210,21 +4242,25 @@ async function showConversationContextMenu(event) {
// 更新菜单文本
const pinMenuText = document.getElementById('pin-conversation-menu-text');
- if (pinMenuText) {
+ if (pinMenuText && typeof window.t === 'function') {
+ pinMenuText.textContent = isPinned ? window.t('contextMenu.unpinConversation') : window.t('contextMenu.pinConversation');
+ } else if (pinMenuText) {
pinMenuText.textContent = isPinned ? '取消置顶' : '置顶此对话';
}
} catch (error) {
console.error('获取对话置顶状态失败:', error);
- // 如果获取失败,使用默认文本
const pinMenuText = document.getElementById('pin-conversation-menu-text');
- if (pinMenuText) {
+ if (pinMenuText && typeof window.t === 'function') {
+ pinMenuText.textContent = window.t('contextMenu.pinConversation');
+ } else if (pinMenuText) {
pinMenuText.textContent = '置顶此对话';
}
}
} else {
- // 如果没有对话ID,使用默认文本
const pinMenuText = document.getElementById('pin-conversation-menu-text');
- if (pinMenuText) {
+ if (pinMenuText && typeof window.t === 'function') {
+ pinMenuText.textContent = window.t('contextMenu.pinConversation');
+ } else if (pinMenuText) {
pinMenuText.textContent = '置顶此对话';
}
}
@@ -4333,14 +4369,17 @@ async function showGroupContextMenu(event, groupId) {
// 更新菜单文本
const pinMenuText = document.getElementById('pin-group-menu-text');
- if (pinMenuText) {
+ if (pinMenuText && typeof window.t === 'function') {
+ pinMenuText.textContent = isPinned ? window.t('contextMenu.unpinGroup') : window.t('contextMenu.pinGroup');
+ } else if (pinMenuText) {
pinMenuText.textContent = isPinned ? '取消置顶' : '置顶此分组';
}
} catch (error) {
console.error('获取分组置顶状态失败:', error);
- // 如果获取失败,使用默认文本
const pinMenuText = document.getElementById('pin-group-menu-text');
- if (pinMenuText) {
+ if (pinMenuText && typeof window.t === 'function') {
+ pinMenuText.textContent = window.t('contextMenu.pinGroup');
+ } else if (pinMenuText) {
pinMenuText.textContent = '置顶此分组';
}
}
@@ -4443,7 +4482,9 @@ async function renameConversation() {
loadConversationsWithGroups();
} catch (error) {
console.error('重命名对话失败:', error);
- alert('重命名失败: ' + (error.message || '未知错误'));
+ const failedLabel = typeof window.t === 'function' ? window.t('chat.renameFailed') : '重命名失败';
+ const unknownErr = typeof window.t === 'function' ? window.t('createGroupModal.unknownError') : '未知错误';
+ alert(failedLabel + ': ' + (error.message || unknownErr));
}
closeContextMenu();
@@ -4636,13 +4677,14 @@ async function showMoveToGroupSubmenu() {
}
// 始终显示"创建分组"选项
+ const addGroupLabel = typeof window.t === 'function' ? window.t('chat.addNewGroup') : '+ 新增分组';
const addItem = document.createElement('div');
addItem.className = 'context-submenu-item add-group-item';
addItem.innerHTML = `
- + 新增分组
+ ${addGroupLabel}
`;
addItem.onclick = () => {
showCreateGroupModal(true);
@@ -4917,7 +4959,8 @@ function deleteConversationFromContext() {
const convId = contextMenuConversationId;
if (!convId) return;
- if (confirm('确定要删除此对话吗?')) {
+ const confirmMsg = typeof window.t === 'function' ? window.t('chat.deleteConversationConfirm') : '确定要删除此对话吗?';
+ if (confirm(confirmMsg)) {
deleteConversation(convId, true); // 跳过内部确认,因为这里已经确认过了
}
closeContextMenu();
@@ -4944,6 +4987,15 @@ function closeContextMenu() {
// 显示批量管理模态框
let allConversationsForBatch = [];
+// 更新批量管理模态框标题(含条数),支持 i18n;count 为当前条数
+function updateBatchManageTitle(count) {
+ const titleEl = document.getElementById('batch-manage-title');
+ if (!titleEl || typeof window.t !== 'function') return;
+ const template = window.t('batchManageModal.title', { count: '__C__' });
+ const parts = template.split('__C__');
+ titleEl.innerHTML = (parts[0] || '') + '' + (count || 0) + '' + (parts[1] || '');
+}
+
async function showBatchManageModal() {
try {
const response = await apiFetch('/api/conversations?limit=1000');
@@ -4957,10 +5009,7 @@ async function showBatchManageModal() {
}
const modal = document.getElementById('batch-manage-modal');
- const countEl = document.getElementById('batch-manage-count');
- if (countEl) {
- countEl.textContent = allConversationsForBatch.length;
- }
+ updateBatchManageTitle(allConversationsForBatch.length);
renderBatchConversations();
if (modal) {
@@ -4971,10 +5020,7 @@ async function showBatchManageModal() {
// 错误时使用空数组,不显示错误提示(更友好的用户体验)
allConversationsForBatch = [];
const modal = document.getElementById('batch-manage-modal');
- const countEl = document.getElementById('batch-manage-count');
- if (countEl) {
- countEl.textContent = 0;
- }
+ updateBatchManageTitle(0);
if (modal) {
renderBatchConversations();
modal.style.display = 'flex';
@@ -5041,7 +5087,7 @@ function renderBatchConversations(filtered = null) {
const name = document.createElement('div');
name.className = 'batch-table-col-name';
- const originalTitle = conv.title || '未命名对话';
+ const originalTitle = conv.title || (typeof window.t === 'function' ? window.t('batchManageModal.unnamedConversation') : '未命名对话');
// 使用安全截断函数,限制最大长度为45个字符(留出空间显示省略号)
const truncatedTitle = safeTruncateText(originalTitle, 45);
name.textContent = truncatedTitle;
@@ -5051,7 +5097,8 @@ function renderBatchConversations(filtered = null) {
const time = document.createElement('div');
time.className = 'batch-table-col-time';
const dateObj = conv.updatedAt ? new Date(conv.updatedAt) : new Date();
- time.textContent = dateObj.toLocaleString('zh-CN', {
+ const locale = (typeof i18next !== 'undefined' && i18next.language) ? i18next.language : 'zh-CN';
+ time.textContent = dateObj.toLocaleString(locale, {
year: 'numeric',
month: '2-digit',
day: '2-digit',
@@ -5105,11 +5152,12 @@ function toggleSelectAllBatch() {
async function deleteSelectedConversations() {
const checkboxes = document.querySelectorAll('.batch-conversation-checkbox:checked');
if (checkboxes.length === 0) {
- alert('请先选择要删除的对话');
+ alert(typeof window.t === 'function' ? window.t('batchManageModal.confirmDeleteNone') : '请先选择要删除的对话');
return;
}
- if (!confirm(`确定要删除选中的 ${checkboxes.length} 条对话吗?`)) {
+ const confirmMsg = typeof window.t === 'function' ? window.t('batchManageModal.confirmDeleteN', { count: checkboxes.length }) : '确定要删除选中的 ' + checkboxes.length + ' 条对话吗?';
+ if (!confirm(confirmMsg)) {
return;
}
@@ -5123,7 +5171,9 @@ async function deleteSelectedConversations() {
loadConversationsWithGroups();
} catch (error) {
console.error('删除失败:', error);
- alert('删除失败: ' + (error.message || '未知错误'));
+ const failedMsg = typeof window.t === 'function' ? window.t('batchManageModal.deleteFailed') : '删除失败';
+ const unknownErr = typeof window.t === 'function' ? window.t('createGroupModal.unknownError') : '未知错误';
+ alert(failedMsg + ': ' + (error.message || unknownErr));
}
}
@@ -5140,6 +5190,14 @@ function closeBatchManageModal() {
allConversationsForBatch = [];
}
+// 语言切换时刷新批量管理模态框标题(若当前正在显示)
+document.addEventListener('languagechange', function () {
+ const modal = document.getElementById('batch-manage-modal');
+ if (modal && modal.style.display === 'flex') {
+ updateBatchManageTitle(allConversationsForBatch.length);
+ }
+});
+
// 显示创建分组模态框
function showCreateGroupModal(andMoveConversation = false) {
const modal = document.getElementById('create-group-modal');
@@ -5208,6 +5266,15 @@ function selectSuggestion(name) {
}
}
+// 按 i18n key 选择建议标签(用于国际化下填充当前语言的文案)
+function selectSuggestionByKey(i18nKey) {
+ const input = document.getElementById('create-group-name-input');
+ if (input && typeof window.t === 'function') {
+ input.value = window.t(i18nKey);
+ input.focus();
+ }
+}
+
// 切换图标选择器显示状态
function toggleGroupIconPicker() {
const picker = document.getElementById('group-icon-picker');
@@ -5299,7 +5366,7 @@ async function createGroup(event) {
const name = input.value.trim();
if (!name) {
- alert('请输入分组名称');
+ alert(typeof window.t === 'function' ? window.t('createGroupModal.groupNamePlaceholder') : '请输入分组名称');
return;
}
@@ -5320,7 +5387,7 @@ async function createGroup(event) {
const nameExists = groups.some(g => g.name === name);
if (nameExists) {
- alert('分组名称已存在,请使用其他名称');
+ alert(typeof window.t === 'function' ? window.t('createGroupModal.nameExists') : '分组名称已存在,请使用其他名称');
return;
}
} catch (error) {
@@ -5345,11 +5412,13 @@ async function createGroup(event) {
if (!response.ok) {
const error = await response.json();
+ const nameExistsMsg = typeof window.t === 'function' ? window.t('createGroupModal.nameExists') : '分组名称已存在,请使用其他名称';
if (error.error && error.error.includes('已存在')) {
- alert('分组名称已存在,请使用其他名称');
+ alert(nameExistsMsg);
return;
}
- throw new Error(error.error || '创建失败');
+ const createFailedMsg = typeof window.t === 'function' ? window.t('createGroupModal.createFailed') : '创建失败';
+ throw new Error(error.error || createFailedMsg);
}
const newGroup = await response.json();
@@ -5375,7 +5444,9 @@ async function createGroup(event) {
}
} catch (error) {
console.error('创建分组失败:', error);
- alert('创建失败: ' + (error.message || '未知错误'));
+ const createFailedMsg = typeof window.t === 'function' ? window.t('createGroupModal.createFailed') : '创建失败';
+ const unknownErr = typeof window.t === 'function' ? window.t('createGroupModal.unknownError') : '未知错误';
+ alert(createFailedMsg + ': ' + (error.message || unknownErr));
}
}
@@ -5517,10 +5588,12 @@ async function loadGroupConversations(groupId, searchQuery = '') {
list.innerHTML = '';
if (groupConvs.length === 0) {
+ const emptyMsg = typeof window.t === 'function' ? window.t('chat.emptyGroupConversations') : '该分组暂无对话';
+ const noMatchMsg = typeof window.t === 'function' ? window.t('chat.noMatchingConversationsInGroup') : '未找到匹配的对话';
if (searchQuery && searchQuery.trim()) {
- list.innerHTML = '未找到匹配的对话';
+ list.innerHTML = '' + (noMatchMsg || '未找到匹配的对话') + '';
} else {
- list.innerHTML = '该分组暂无对话';
+ list.innerHTML = '' + (emptyMsg || '该分组暂无对话') + '';
}
return;
}
@@ -5651,7 +5724,8 @@ async function editGroup() {
const group = await response.json();
if (!group) return;
- const newName = prompt('请输入新名称:', group.name);
+ const renamePrompt = typeof window.t === 'function' ? window.t('chat.renameGroupPrompt') : '请输入新名称:';
+ const newName = prompt(renamePrompt, group.name);
if (newName === null || !newName.trim()) return;
const trimmedName = newName.trim();
@@ -5672,7 +5746,7 @@ async function editGroup() {
const nameExists = groups.some(g => g.name === trimmedName && g.id !== currentGroupId);
if (nameExists) {
- alert('分组名称已存在,请使用其他名称');
+ alert(typeof window.t === 'function' ? window.t('createGroupModal.nameExists') : '分组名称已存在,请使用其他名称');
return;
}
@@ -5712,7 +5786,8 @@ async function editGroup() {
async function deleteGroup() {
if (!currentGroupId) return;
- if (!confirm('确定要删除此分组吗?分组中的对话不会被删除,但会从分组中移除。')) {
+ const deleteConfirmMsg = typeof window.t === 'function' ? window.t('chat.deleteGroupConfirm') : '确定要删除此分组吗?分组中的对话不会被删除,但会从分组中移除。';
+ if (!confirm(deleteConfirmMsg)) {
return;
}
@@ -5758,7 +5833,8 @@ async function renameGroupFromContext() {
const group = await response.json();
if (!group) return;
- const newName = prompt('请输入新名称:', group.name);
+ const renamePrompt = typeof window.t === 'function' ? window.t('chat.renameGroupPrompt') : '请输入新名称:';
+ const newName = prompt(renamePrompt, group.name);
if (newName === null || !newName.trim()) {
closeGroupContextMenu();
return;
@@ -5782,7 +5858,7 @@ async function renameGroupFromContext() {
const nameExists = groups.some(g => g.name === trimmedName && g.id !== groupId);
if (nameExists) {
- alert('分组名称已存在,请使用其他名称');
+ alert(typeof window.t === 'function' ? window.t('createGroupModal.nameExists') : '分组名称已存在,请使用其他名称');
return;
}
@@ -5817,7 +5893,9 @@ async function renameGroupFromContext() {
}
} catch (error) {
console.error('重命名分组失败:', error);
- alert('重命名失败: ' + (error.message || '未知错误'));
+ const failedLabel = typeof window.t === 'function' ? window.t('chat.renameFailed') : '重命名失败';
+ const unknownErr = typeof window.t === 'function' ? window.t('createGroupModal.unknownError') : '未知错误';
+ alert(failedLabel + ': ' + (error.message || unknownErr));
}
closeGroupContextMenu();
@@ -5867,7 +5945,8 @@ async function deleteGroupFromContext() {
const groupId = contextMenuGroupId;
if (!groupId) return;
- if (!confirm('确定要删除此分组吗?分组中的对话不会被删除,但会从分组中移除。')) {
+ const deleteConfirmMsg = typeof window.t === 'function' ? window.t('chat.deleteGroupConfirm') : '确定要删除此分组吗?分组中的对话不会被删除,但会从分组中移除。';
+ if (!confirm(deleteConfirmMsg)) {
closeGroupContextMenu();
return;
}
diff --git a/web/static/js/dashboard.js b/web/static/js/dashboard.js
index edf17fb1..9dbd1a1b 100644
--- a/web/static/js/dashboard.js
+++ b/web/static/js/dashboard.js
@@ -17,7 +17,7 @@ async function refreshDashboard() {
setEl('dashboard-kpi-tools-calls', '…');
setEl('dashboard-kpi-success-rate', '…');
var chartPlaceholder = document.getElementById('dashboard-tools-pie-placeholder');
- if (chartPlaceholder) { chartPlaceholder.style.removeProperty('display'); chartPlaceholder.textContent = '加载中…'; }
+ if (chartPlaceholder) { chartPlaceholder.style.removeProperty('display'); chartPlaceholder.textContent = (typeof window.t === 'function' ? window.t('common.loading') : '加载中…'); }
var barChartEl = document.getElementById('dashboard-tools-bar-chart');
if (barChartEl) { barChartEl.style.display = 'none'; barChartEl.innerHTML = ''; }
@@ -77,7 +77,7 @@ async function refreshDashboard() {
setEl('dashboard-batch-pending', String(pending));
setEl('dashboard-batch-running', String(running));
setEl('dashboard-batch-done', String(done));
- setEl('dashboard-batch-total', total > 0 ? `共 ${total} 个` : '暂无任务');
+ setEl('dashboard-batch-total', total > 0 ? (typeof window.t === 'function' ? window.t('dashboard.totalCount', { count: total }) : `共 ${total} 个`) : (typeof window.t === 'function' ? window.t('dashboard.noTasks') : '暂无任务'));
// 更新进度条
if (total > 0) {
@@ -138,7 +138,7 @@ async function refreshDashboard() {
if (knowledgeRes && typeof knowledgeRes === 'object') {
if (knowledgeRes.enabled === false) {
// 功能未启用:用状态标签展示,数值保持为 "-"
- if (knowledgeStatusEl) knowledgeStatusEl.textContent = '未启用';
+ if (knowledgeStatusEl) knowledgeStatusEl.textContent = (typeof window.t === 'function' ? window.t('dashboard.notEnabled') : '未启用');
if (knowledgeItemsEl) knowledgeItemsEl.textContent = '-';
if (knowledgeCategoriesEl) knowledgeCategoriesEl.textContent = '-';
} else {
@@ -149,9 +149,9 @@ async function refreshDashboard() {
// 根据数据量给个轻量状态文案
if (knowledgeStatusEl) {
if (items > 0 || categories > 0) {
- knowledgeStatusEl.textContent = '已启用';
+ knowledgeStatusEl.textContent = (typeof window.t === 'function' ? window.t('dashboard.enabled') : '已启用');
} else {
- knowledgeStatusEl.textContent = '待配置';
+ knowledgeStatusEl.textContent = (typeof window.t === 'function' ? window.t('dashboard.toConfigure') : '待配置');
}
}
}
@@ -172,15 +172,15 @@ async function refreshDashboard() {
const statusEl = document.getElementById('dashboard-skills-status');
if (statusEl) {
if (totalCalls === 0) {
- statusEl.textContent = '待使用';
+ statusEl.textContent = (typeof window.t === 'function' ? window.t('dashboard.toUse') : '待使用');
statusEl.style.background = 'rgba(0, 0, 0, 0.05)';
statusEl.style.color = 'var(--text-secondary)';
} else if (totalCalls < 10) {
- statusEl.textContent = '活跃';
+ statusEl.textContent = (typeof window.t === 'function' ? window.t('dashboard.active') : '活跃');
statusEl.style.background = 'rgba(16, 185, 129, 0.1)';
statusEl.style.color = '#10b981';
} else {
- statusEl.textContent = '高频';
+ statusEl.textContent = (typeof window.t === 'function' ? window.t('dashboard.highFreq') : '高频');
statusEl.style.background = 'rgba(59, 130, 246, 0.1)';
statusEl.style.color = '#3b82f6';
}
@@ -200,7 +200,7 @@ async function refreshDashboard() {
setEl('dashboard-kpi-tools-calls', '-');
renderDashboardToolsBar(null);
var ph = document.getElementById('dashboard-tools-pie-placeholder');
- if (ph) { ph.style.removeProperty('display'); ph.textContent = '暂无调用数据'; }
+ if (ph) { ph.style.removeProperty('display'); ph.textContent = (typeof window.t === 'function' ? window.t('dashboard.noCallData') : '暂无调用数据'); }
}
}
@@ -257,7 +257,7 @@ function renderDashboardToolsBar(monitorRes) {
if (!monitorRes || typeof monitorRes !== 'object') {
placeholder.style.removeProperty('display');
- placeholder.textContent = '暂无调用数据';
+ placeholder.textContent = (typeof window.t === 'function' ? window.t('dashboard.noCallData') : '暂无调用数据');
barChartEl.style.display = 'none';
barChartEl.innerHTML = '';
return;
@@ -273,7 +273,7 @@ function renderDashboardToolsBar(monitorRes) {
if (entries.length === 0) {
placeholder.style.removeProperty('display');
- placeholder.textContent = '暂无调用数据';
+ placeholder.textContent = (typeof window.t === 'function' ? window.t('dashboard.noCallData') : '暂无调用数据');
barChartEl.style.display = 'none';
barChartEl.innerHTML = '';
return;
diff --git a/web/static/js/i18n.js b/web/static/js/i18n.js
new file mode 100644
index 00000000..9b75ccf7
--- /dev/null
+++ b/web/static/js/i18n.js
@@ -0,0 +1,202 @@
+// 前端国际化初始化(基于 i18next 浏览器版本)
+(function () {
+ const DEFAULT_LANG = 'zh-CN';
+ const STORAGE_KEY = 'csai_lang';
+ const RESOURCES_PREFIX = '/static/i18n';
+
+ const loadedLangs = {};
+
+ function detectInitialLang() {
+ try {
+ const stored = localStorage.getItem(STORAGE_KEY);
+ if (stored) {
+ return stored;
+ }
+ } catch (e) {
+ console.warn('无法读取语言设置:', e);
+ }
+
+ const navLang = (navigator.language || navigator.userLanguage || '').toLowerCase();
+ if (navLang.startsWith('zh')) {
+ return 'zh-CN';
+ }
+ if (navLang.startsWith('en')) {
+ return 'en-US';
+ }
+ return DEFAULT_LANG;
+ }
+
+ async function loadLanguageResources(lang) {
+ if (loadedLangs[lang]) {
+ return;
+ }
+ try {
+ const resp = await fetch(RESOURCES_PREFIX + '/' + lang + '.json', {
+ cache: 'no-cache'
+ });
+ if (!resp.ok) {
+ console.warn('加载语言包失败:', lang, resp.status);
+ return;
+ }
+ const data = await resp.json();
+ if (typeof i18next !== 'undefined') {
+ i18next.addResourceBundle(lang, 'translation', data, true, true);
+ }
+ loadedLangs[lang] = true;
+ } catch (e) {
+ console.error('加载语言包异常:', lang, e);
+ }
+ }
+
+ function applyTranslations(root) {
+ if (typeof i18next === 'undefined') return;
+ const container = root || document;
+ if (!container) return;
+
+ const elements = container.querySelectorAll('[data-i18n]');
+ elements.forEach(function (el) {
+ const key = el.getAttribute('data-i18n');
+ if (!key) return;
+ const skipText = el.getAttribute('data-i18n-skip-text') === 'true';
+ 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') {
+ el.textContent = text;
+ }
+
+ if (attrList) {
+ attrList.split(',').map(function (s) { return s.trim(); }).forEach(function (attr) {
+ if (!attr) return;
+ if (text && typeof text === 'string') {
+ el.setAttribute(attr, text);
+ }
+ });
+ }
+ });
+
+ // 对话输入框:若 value 与 placeholder 相同,清空 value 以便正确显示占位提示
+ try {
+ const chatInput = document.getElementById('chat-input');
+ if (chatInput && chatInput.tagName === 'TEXTAREA') {
+ const ph = (chatInput.getAttribute('placeholder') || '').trim();
+ if (ph && chatInput.value.trim() === ph) {
+ chatInput.value = '';
+ }
+ }
+ } catch (e) { /* ignore */ }
+
+ // 更新 html lang 属性
+ try {
+ if (document && document.documentElement) {
+ document.documentElement.lang = i18next.language || DEFAULT_LANG;
+ }
+ } catch (e) {
+ // ignore
+ }
+ }
+
+ function updateLangLabel() {
+ const label = document.getElementById('current-lang-label');
+ if (!label || typeof i18next === 'undefined') return;
+ const lang = (i18next.language || DEFAULT_LANG).toLowerCase();
+ if (lang.indexOf('zh') === 0) {
+ label.textContent = '中文';
+ } else {
+ label.textContent = 'English';
+ }
+ }
+
+ function closeLangDropdown() {
+ const dropdown = document.getElementById('lang-dropdown');
+ if (dropdown) {
+ dropdown.style.display = 'none';
+ }
+ }
+
+ function handleGlobalClickForLangDropdown(ev) {
+ const dropdown = document.getElementById('lang-dropdown');
+ const btn = document.querySelector('.lang-switcher-btn');
+ if (!dropdown || dropdown.style.display !== 'block') return;
+ const target = ev.target;
+ if (btn && btn.contains(target)) {
+ return;
+ }
+ if (!dropdown.contains(target)) {
+ closeLangDropdown();
+ }
+ }
+
+ async function changeLanguage(lang) {
+ if (typeof i18next === 'undefined') return;
+ const current = i18next.language || DEFAULT_LANG;
+ if (lang === current) return;
+ await loadLanguageResources(lang);
+ await i18next.changeLanguage(lang);
+ try {
+ localStorage.setItem(STORAGE_KEY, lang);
+ } catch (e) {
+ console.warn('无法保存语言设置:', e);
+ }
+ applyTranslations(document);
+ updateLangLabel();
+ try {
+ document.dispatchEvent(new CustomEvent('languagechange', { detail: { lang: lang } }));
+ } catch (e) { /* ignore */ }
+ }
+
+ async function initI18n() {
+ if (typeof i18next === 'undefined') {
+ console.warn('i18next 未加载,跳过前端国际化初始化');
+ return;
+ }
+
+ const initialLang = detectInitialLang();
+ await i18next.init({
+ lng: initialLang,
+ fallbackLng: DEFAULT_LANG,
+ debug: false,
+ resources: {}
+ });
+
+ await loadLanguageResources(initialLang);
+ applyTranslations(document);
+ updateLangLabel();
+
+ // 导出全局函数供其他脚本调用(支持插值参数,如 _t('key', { count: 2 }))
+ window.t = function (key, opts) {
+ if (typeof i18next === 'undefined') return key;
+ return i18next.t(key, opts);
+ };
+ window.changeLanguage = changeLanguage;
+ window.applyTranslations = applyTranslations;
+
+ // 语言切换下拉支持
+ window.toggleLangDropdown = function () {
+ const dropdown = document.getElementById('lang-dropdown');
+ if (!dropdown) return;
+ if (dropdown.style.display === 'block') {
+ dropdown.style.display = 'none';
+ } else {
+ dropdown.style.display = 'block';
+ }
+ };
+ window.onLanguageSelect = function (lang) {
+ changeLanguage(lang);
+ closeLangDropdown();
+ };
+
+ document.addEventListener('click', handleGlobalClickForLangDropdown);
+ }
+
+ document.addEventListener('DOMContentLoaded', function () {
+ // i18n 初始化在 DOM Ready 后执行
+ initI18n().catch(function (e) {
+ console.error('初始化国际化失败:', e);
+ });
+ });
+})();
+
diff --git a/web/static/js/info-collect.js b/web/static/js/info-collect.js
index 91fa26ff..a0d0cfa1 100644
--- a/web/static/js/info-collect.js
+++ b/web/static/js/info-collect.js
@@ -1,4 +1,7 @@
// 信息收集页面(FOFA)
+function _t(key, opts) {
+ return typeof window.t === 'function' ? window.t(key, opts) : key;
+}
const FOFA_FORM_STORAGE_KEY = 'info-collect-fofa-form';
const FOFA_HIDDEN_FIELDS_STORAGE_KEY = 'info-collect-fofa-hidden-fields';
@@ -197,12 +200,12 @@ async function submitFofaSearch() {
const full = !!els.full?.checked;
if (!query) {
- alert('请输入 FOFA 查询语法');
+ alert(_t('infoCollect.enterFofaQuery'));
return;
}
saveFofaFormToStorage({ query, size, page, fields, full });
- setFofaMeta('查询中...');
+ setFofaMeta(_t('infoCollect.querying'));
setFofaLoading(true);
try {
@@ -219,9 +222,9 @@ async function submitFofaSearch() {
renderFofaResults(result);
} catch (e) {
console.error('FOFA 查询失败:', e);
- setFofaMeta('查询失败');
+ setFofaMeta(_t('infoCollect.queryFailed'));
renderFofaResults({ query, fields: [], results: [], total: 0, page: 1, size: 0 });
- alert('FOFA 查询失败: ' + (e && e.message ? e.message : String(e)));
+ alert(_t('infoCollect.queryFailed') + ': ' + (e && e.message ? e.message : String(e)));
} finally {
setFofaLoading(false);
}
@@ -231,7 +234,7 @@ async function parseFofaNaturalLanguage() {
const els = getFofaFormElements();
const text = (els.nl?.value || '').trim();
if (!text) {
- alert('请输入自然语言描述');
+ alert(_t('infoCollect.enterNaturalLanguage'));
return;
}
@@ -243,16 +246,16 @@ async function parseFofaNaturalLanguage() {
// 先创建 controller,避免极快的重复点击触发并发请求
fofaParseAbortController = new AbortController();
- setFofaParseLoading(true, 'AI 解析中...');
+ setFofaParseLoading(true, _t('infoCollect.parsePending'));
// 持续提示:直到请求完成/取消/失败才消失
- fofaParseToastHandle = showInlineToast('AI 解析中...(点击按钮可取消)', { duration: 0, id: 'fofa-parse-pending' });
+ fofaParseToastHandle = showInlineToast(_t('infoCollect.parsePendingClickCancel'), { duration: 0, id: 'fofa-parse-pending' });
// 如果超过一小段时间还没返回,再强调“仍在进行中”,降低误判为失败的概率
fofaParseSlowTimer = setTimeout(() => {
const status = document.getElementById('fofa-nl-status');
if (status) {
- status.textContent = 'AI 解析耗时较长,仍在处理中…';
+ status.textContent = _t('infoCollect.parseSlow');
status.style.display = 'block';
}
}, 1800);
@@ -269,15 +272,15 @@ async function parseFofaNaturalLanguage() {
throw new Error(result.error || `请求失败: ${resp.status}`);
}
showFofaParseModal(text, result);
- showInlineToast('AI 解析完成');
+ showInlineToast(_t('infoCollect.parseDone'));
} catch (e) {
// AbortController 取消:不视为失败
if (e && (e.name === 'AbortError' || String(e).includes('AbortError'))) {
- showInlineToast('已取消 AI 解析');
+ showInlineToast(_t('infoCollect.parseCancelled'));
return;
}
console.error('FOFA 自然语言解析失败:', e);
- showInlineToast('AI 解析失败:' + (e && e.message ? e.message : String(e)), { duration: 2800 });
+ showInlineToast(_t('infoCollect.parseFailed') + (e && e.message ? e.message : String(e)), { duration: 2800 });
}
finally {
fofaParseAbortController = null;
@@ -298,17 +301,17 @@ function setFofaParseLoading(loading, statusText) {
const status = document.getElementById('fofa-nl-status');
if (btn) {
if (loading) {
- if (!btn.dataset.originalText) btn.dataset.originalText = btn.textContent || 'AI 解析';
+ if (!btn.dataset.originalText) btn.dataset.originalText = btn.textContent || _t('infoCollectPage.parseBtn');
btn.classList.add('btn-loading');
- btn.textContent = '取消解析';
- btn.title = '点击取消 AI 解析';
+ btn.textContent = _t('infoCollect.cancelParse');
+ btn.title = _t('infoCollect.clickToCancelParse');
btn.dataset.loading = '1';
btn.setAttribute('aria-busy', 'true');
btn.disabled = false;
} else {
btn.classList.remove('btn-loading');
- btn.textContent = btn.dataset.originalText || 'AI 解析';
- btn.title = '将自然语言解析为 FOFA 查询语法';
+ btn.textContent = btn.dataset.originalText || _t('infoCollectPage.parseBtn');
+ btn.title = _t('infoCollect.parseToFofa');
btn.disabled = false;
delete btn.dataset.loading;
btn.removeAttribute('aria-busy');
@@ -336,7 +339,7 @@ function showFofaParseModal(nlText, parsed) {
const warningsHtml = warnings.length
? `${warnings.map(w => `- ${escapeHtml(w)}
`).join('')}
`
- : `无`;
+ : '' + _t('infoCollect.none') + '';
const modal = document.createElement('div');
modal.id = 'fofa-parse-modal';
@@ -345,23 +348,23 @@ function showFofaParseModal(nlText, parsed) {
modal.innerHTML = `
${log.conversationId ? `
- 对话ID
- ${escapeHtml(log.conversationId)}
+ ${_t('retrievalLogs.conversationId')}
+ ${escapeHtml(log.conversationId)}
` : ''}
${log.messageId ? `
- 消息ID
- ${escapeHtml(log.messageId)}
+ ${_t('retrievalLogs.messageId')}
+ ${escapeHtml(log.messageId)}
` : ''}
- 检索结果
+ ${_t('retrievalLogs.retrievalResult')}
- ${hasResults ? (itemCount > 0 ? `找到 ${itemCount} 个相关知识项` : '找到相关知识项(数量未知)') : '未找到匹配的知识项'}
+ ${hasResults ? (itemCount > 0 ? _t('retrievalLogs.foundCount', { count: itemCount }) : _t('retrievalLogs.foundUnknown')) : _t('retrievalLogs.noMatch')}
${hasResults && log.retrievedItems && log.retrievedItems.length > 0 ? `
- 检索到的知识项:
+ ${_t('retrievalLogs.retrievedItemsLabel')}
${log.retrievedItems.slice(0, 3).map((itemId, idx) => `
${idx + 1}
@@ -1437,13 +1419,13 @@ function renderRetrievalLogs(logs) {
- 查看详情
+ ${_t('retrievalLogs.viewDetails')}
-
+
- 删除
+ ${_t('common.delete')}
@@ -1480,22 +1462,25 @@ function updateRetrievalStats(logs) {
statsContainer.innerHTML = `
- 总检索次数
+ 总检索次数
${totalLogs}
- 成功检索
+ 成功检索
${successfulLogs}
- 成功率
+ 成功率
${successRate}%
- 检索到知识项
+ 检索到知识项
${totalItems}
`;
+ if (typeof window.applyTranslations === 'function') {
+ window.applyTranslations(statsContainer);
+ }
}
// 获取相对时间
@@ -1591,7 +1576,7 @@ function refreshRetrievalLogs() {
// 删除检索日志
async function deleteRetrievalLog(id, index) {
- if (!confirm('确定要删除这条检索记录吗?')) {
+ if (!confirm(_t('retrievalLogs.deleteConfirm'))) {
return;
}
@@ -1677,7 +1662,7 @@ async function deleteRetrievalLog(id, index) {
}
}
- showNotification('❌ 删除检索日志失败: ' + error.message, 'error');
+ showNotification(_t('retrievalLogs.deleteError') + ': ' + error.message, 'error');
}
}
@@ -1699,12 +1684,11 @@ function updateRetrievalStatsAfterDelete() {
const badge = card.querySelector('.retrieval-log-result-badge');
if (badge && badge.classList.contains('success')) {
const text = badge.textContent.trim();
- const match = text.match(/(\d+)\s*项/);
+ const match = text.match(/(\d+)/);
if (match) {
- return sum + parseInt(match[1]);
- } else if (text === '有结果') {
- return sum + 1; // 简化处理,假设为1
+ return sum + parseInt(match[1], 10);
}
+ return sum + 1; // 有结果但数量未知(如 "Has results" / "有结果")
}
return sum;
}, 0);
@@ -1713,28 +1697,31 @@ function updateRetrievalStatsAfterDelete() {
statsContainer.innerHTML = `
- 总检索次数
+ 总检索次数
${totalLogs}
- 成功检索
+ 成功检索
${successfulLogs}
- 成功率
+ 成功率
${successRate}%
- 检索到知识项
+ 检索到知识项
${totalItems}
`;
+ if (typeof window.applyTranslations === 'function') {
+ window.applyTranslations(statsContainer);
+ }
}
// 显示检索日志详情
async function showRetrievalLogDetails(index) {
if (!retrievalLogsData || index < 0 || index >= retrievalLogsData.length) {
- showNotification('无法获取检索详情', 'error');
+ showNotification(_t('retrievalLogs.detailError'), 'error');
return;
}
@@ -1783,16 +1770,19 @@ function showRetrievalLogDetailsModal(log, retrievedItems) {
modal.innerHTML = `
`;
+ if (typeof window.applyTranslations === 'function') {
+ window.applyTranslations(modal);
+ }
document.body.appendChild(modal);
}
@@ -1816,57 +1806,57 @@ function showRetrievalLogDetailsModal(log, retrievedItems) {
return `
- ${idx + 1}. ${escapeHtml(item.title || '未命名')}
- ${escapeHtml(item.category || '未分类')}
+ ${idx + 1}. ${escapeHtml(item.title || _t('retrievalLogs.untitled'))}
+ ${escapeHtml(item.category || _t('retrievalLogs.uncategorized'))}
${item.filePath ? `📁 ${escapeHtml(item.filePath)}` : ''}
- ${escapeHtml(previewText || '无内容预览')}
+ ${escapeHtml(previewText || _t('retrievalLogs.noContentPreview'))}
`;
}).join('');
} else {
- itemsHtml = '未找到知识项详情';
+ itemsHtml = '' + _t('retrievalLogs.noItemDetails') + '';
}
content.innerHTML = `
- 查询信息
+ ${_t('retrievalLogs.queryInfo')}
- 查询内容:
- ${escapeHtml(log.query || '无查询内容')}
+ ${_t('retrievalLogs.queryContent')}
+ ${escapeHtml(log.query || _t('retrievalLogs.noQuery'))}
- 检索信息
+ ${_t('retrievalLogs.retrievalInfo')}
${log.riskType ? `
- 风险类型
+ ${_t('retrievalLogs.riskType')}
${escapeHtml(log.riskType)}
` : ''}
- 检索时间
+ ${_t('retrievalLogs.retrievalTime')}
${timeAgo}
- 检索结果
- ${retrievedItems.length} 个知识项
+ ${_t('retrievalLogs.retrievalResult')}
+ ${_t('retrievalLogs.itemsCount', { count: retrievedItems.length })}
${log.conversationId || log.messageId ? `
- 关联信息
+ ${_t('retrievalLogs.relatedInfo')}
${log.conversationId ? `
- 对话ID
+ ${_t('retrievalLogs.conversationId')}
${escapeHtml(log.conversationId)}
@@ -1874,7 +1864,7 @@ function showRetrievalLogDetailsModal(log, retrievedItems) {
` : ''}
${log.messageId ? `
- 消息ID
+ ${_t('retrievalLogs.messageId')}
${escapeHtml(log.messageId)}
@@ -1910,6 +1900,22 @@ window.addEventListener('click', function(event) {
}
});
+// 语言切换时重新渲染检索历史列表与统计,使动态内容随语言更新;知识管理页的「未启用」区块已使用 data-i18n,会由 applyTranslations(document) 自动更新
+document.addEventListener('languagechange', function () {
+ var cur = typeof window.currentPage === 'function' ? window.currentPage() : (window.currentPage || '');
+ if (cur === 'knowledge-retrieval-logs') {
+ if (retrievalLogsData && retrievalLogsData.length >= 0) {
+ renderRetrievalLogs(retrievalLogsData);
+ }
+ } else if (cur === 'knowledge-management') {
+ // 仅对「知识库未启用」状态:已有 data-i18n,applyTranslations 已处理;此处可选地重新应用一次以兼容旧 DOM
+ var listEl = document.getElementById('knowledge-items-list');
+ if (listEl && typeof window.applyTranslations === 'function') {
+ window.applyTranslations(listEl);
+ }
+ }
+});
+
// 页面切换时加载数据
if (typeof switchPage === 'function') {
const originalSwitchPage = switchPage;
diff --git a/web/static/js/monitor.js b/web/static/js/monitor.js
index c86b9aa9..b64535f9 100644
--- a/web/static/js/monitor.js
+++ b/web/static/js/monitor.js
@@ -1138,10 +1138,10 @@ async function refreshMonitorPanel(page = null) {
} catch (error) {
console.error('刷新监控面板失败:', error);
if (statsContainer) {
- statsContainer.innerHTML = `无法加载统计信息:${escapeHtml(error.message)}`;
+ statsContainer.innerHTML = `${escapeHtml(typeof window.t === 'function' ? window.t('mcpMonitor.loadStatsError') : '无法加载统计信息')}:${escapeHtml(error.message)}`;
}
if (execContainer) {
- execContainer.innerHTML = `无法加载执行记录:${escapeHtml(error.message)}`;
+ execContainer.innerHTML = `${escapeHtml(typeof window.t === 'function' ? window.t('mcpMonitor.loadExecutionsError') : '无法加载执行记录')}:${escapeHtml(error.message)}`;
}
}
}
@@ -1215,10 +1215,10 @@ async function refreshMonitorPanelWithFilter(statusFilter = 'all', toolFilter =
} catch (error) {
console.error('刷新监控面板失败:', error);
if (statsContainer) {
- statsContainer.innerHTML = `无法加载统计信息:${escapeHtml(error.message)}`;
+ statsContainer.innerHTML = `${escapeHtml(typeof window.t === 'function' ? window.t('mcpMonitor.loadStatsError') : '无法加载统计信息')}:${escapeHtml(error.message)}`;
}
if (execContainer) {
- execContainer.innerHTML = `无法加载执行记录:${escapeHtml(error.message)}`;
+ execContainer.innerHTML = `${escapeHtml(typeof window.t === 'function' ? window.t('mcpMonitor.loadExecutionsError') : '无法加载执行记录')}:${escapeHtml(error.message)}`;
}
}
}
@@ -1232,7 +1232,8 @@ function renderMonitorStats(statsMap = {}, lastFetchedAt = null) {
const entries = Object.values(statsMap);
if (entries.length === 0) {
- container.innerHTML = '暂无统计数据';
+ const noStats = typeof window.t === 'function' ? window.t('mcpMonitor.noStatsData') : '暂无统计数据';
+ container.innerHTML = '' + escapeHtml(noStats) + '';
return;
}
@@ -1252,24 +1253,32 @@ function renderMonitorStats(statsMap = {}, lastFetchedAt = null) {
);
const successRate = totals.total > 0 ? ((totals.success / totals.total) * 100).toFixed(1) : '0.0';
- const lastUpdatedText = lastFetchedAt ? lastFetchedAt.toLocaleString('zh-CN') : 'N/A';
- const lastCallText = totals.lastCallTime ? totals.lastCallTime.toLocaleString('zh-CN') : '暂无调用';
+ const locale = (typeof window.__locale === 'string' && window.__locale.startsWith('zh')) ? 'zh-CN' : undefined;
+ const lastUpdatedText = lastFetchedAt ? (lastFetchedAt.toLocaleString ? lastFetchedAt.toLocaleString(locale || 'en-US') : String(lastFetchedAt)) : 'N/A';
+ const noCallsYet = typeof window.t === 'function' ? window.t('mcpMonitor.noCallsYet') : '暂无调用';
+ const lastCallText = totals.lastCallTime ? (totals.lastCallTime.toLocaleString ? totals.lastCallTime.toLocaleString(locale || 'en-US') : String(totals.lastCallTime)) : noCallsYet;
+ const totalCallsLabel = typeof window.t === 'function' ? window.t('mcpMonitor.totalCalls') : '总调用次数';
+ const successFailedLabel = typeof window.t === 'function' ? window.t('mcpMonitor.successFailed', { success: totals.success, failed: totals.failed }) : `成功 ${totals.success} / 失败 ${totals.failed}`;
+ const successRateLabel = typeof window.t === 'function' ? window.t('mcpMonitor.successRate') : '成功率';
+ const statsFromAll = typeof window.t === 'function' ? window.t('mcpMonitor.statsFromAllTools') : '统计自全部工具调用';
+ const lastCallLabel = typeof window.t === 'function' ? window.t('mcpMonitor.lastCall') : '最近一次调用';
+ const lastRefreshLabel = typeof window.t === 'function' ? window.t('mcpMonitor.lastRefreshTime') : '最后刷新时间';
let html = `
- 总调用次数
+ ${escapeHtml(totalCallsLabel)}
${totals.total}
-
+
- 成功率
+ ${escapeHtml(successRateLabel)}
${successRate}%
-
+
- 最近一次调用
- ${lastCallText}
-
+ ${escapeHtml(lastCallLabel)}
+ ${escapeHtml(lastCallText)}
+
`;
@@ -1280,14 +1289,16 @@ function renderMonitorStats(statsMap = {}, lastFetchedAt = null) {
.sort((a, b) => (b.totalCalls || 0) - (a.totalCalls || 0))
.slice(0, 4);
+ const unknownToolLabel = typeof window.t === 'function' ? window.t('mcpMonitor.unknownTool') : '未知工具';
topTools.forEach(tool => {
const toolSuccessRate = tool.totalCalls > 0 ? ((tool.successCalls || 0) / tool.totalCalls * 100).toFixed(1) : '0.0';
+ const toolMeta = typeof window.t === 'function' ? window.t('mcpMonitor.successFailedRate', { success: tool.successCalls || 0, failed: tool.failedCalls || 0, rate: toolSuccessRate }) : `成功 ${tool.successCalls || 0} / 失败 ${tool.failedCalls || 0} · 成功率 ${toolSuccessRate}%`;
html += `
- ${escapeHtml(tool.toolName || '未知工具')}
+ ${escapeHtml(tool.toolName || unknownToolLabel)}
${tool.totalCalls || 0}
`;
@@ -1307,10 +1318,12 @@ function renderMonitorExecutions(executions = [], statusFilter = 'all') {
const toolFilter = document.getElementById('monitor-tool-filter');
const currentToolFilter = toolFilter ? toolFilter.value : 'all';
const hasFilter = (statusFilter && statusFilter !== 'all') || (currentToolFilter && currentToolFilter !== 'all');
+ const noRecordsFilter = typeof window.t === 'function' ? window.t('mcpMonitor.noRecordsWithFilter') : '当前筛选条件下暂无记录';
+ const noExecutions = typeof window.t === 'function' ? window.t('mcpMonitor.noExecutions') : '暂无执行记录';
if (hasFilter) {
- container.innerHTML = '当前筛选条件下暂无记录';
+ container.innerHTML = '' + escapeHtml(noRecordsFilter) + '';
} else {
- container.innerHTML = '暂无执行记录';
+ container.innerHTML = '' + escapeHtml(noExecutions) + '';
}
// 隐藏批量操作栏
const batchActions = document.getElementById('monitor-batch-actions');
@@ -1322,14 +1335,22 @@ function renderMonitorExecutions(executions = [], statusFilter = 'all') {
// 由于筛选已经在后端完成,这里直接使用所有传入的执行记录
// 不再需要前端再次筛选,因为后端已经返回了筛选后的数据
+ const unknownLabel = typeof window.t === 'function' ? window.t('mcpMonitor.unknown') : '未知';
+ const unknownToolLabel = typeof window.t === 'function' ? window.t('mcpMonitor.unknownTool') : '未知工具';
+ const viewDetailLabel = typeof window.t === 'function' ? window.t('mcpMonitor.viewDetail') : '查看详情';
+ const deleteLabel = typeof window.t === 'function' ? window.t('mcpMonitor.delete') : '删除';
+ const deleteExecTitle = typeof window.t === 'function' ? window.t('mcpMonitor.deleteExecTitle') : '删除此执行记录';
+ const statusKeyMap = { pending: 'statusPending', running: 'statusRunning', completed: 'statusCompleted', failed: 'statusFailed' };
+ const locale = (typeof window.__locale === 'string' && window.__locale.startsWith('zh')) ? 'zh-CN' : undefined;
const rows = executions
.map(exec => {
const status = (exec.status || 'unknown').toLowerCase();
const statusClass = `monitor-status-chip ${status}`;
- const statusLabel = getStatusText(status);
- const startTime = exec.startTime ? new Date(exec.startTime).toLocaleString('zh-CN') : '未知';
+ const statusKey = statusKeyMap[status];
+ const statusLabel = (typeof window.t === 'function' && statusKey) ? window.t('mcpMonitor.' + statusKey) : getStatusText(status);
+ const startTime = exec.startTime ? (new Date(exec.startTime).toLocaleString ? new Date(exec.startTime).toLocaleString(locale || 'en-US') : String(exec.startTime)) : unknownLabel;
const duration = formatExecutionDuration(exec.startTime, exec.endTime);
- const toolName = escapeHtml(exec.toolName || '未知工具');
+ const toolName = escapeHtml(exec.toolName || unknownToolLabel);
const executionId = escapeHtml(exec.id || '');
return `
@@ -1337,13 +1358,13 @@ function renderMonitorExecutions(executions = [], statusFilter = 'all') {
${toolName}
- ${statusLabel}
- ${startTime}
- ${duration}
+ ${escapeHtml(statusLabel)}
+ ${escapeHtml(startTime)}
+ ${escapeHtml(duration)}
- 查看详情
- 删除
+ ${escapeHtml(viewDetailLabel)}
+ ${escapeHtml(deleteLabel)}
@@ -1365,6 +1386,11 @@ function renderMonitorExecutions(executions = [], statusFilter = 'all') {
// 创建表格容器
const tableContainer = document.createElement('div');
tableContainer.className = 'monitor-table-container';
+ const colTool = typeof window.t === 'function' ? window.t('mcpMonitor.columnTool') : '工具';
+ const colStatus = typeof window.t === 'function' ? window.t('mcpMonitor.columnStatus') : '状态';
+ const colStartTime = typeof window.t === 'function' ? window.t('mcpMonitor.columnStartTime') : '开始时间';
+ const colDuration = typeof window.t === 'function' ? window.t('mcpMonitor.columnDuration') : '耗时';
+ const colActions = typeof window.t === 'function' ? window.t('mcpMonitor.columnActions') : '操作';
tableContainer.innerHTML = `
@@ -1372,11 +1398,11 @@ function renderMonitorExecutions(executions = [], statusFilter = 'all') {
- 工具
- 状态
- 开始时间
- 耗时
- 操作
+ ${escapeHtml(colTool)}
+ ${escapeHtml(colStatus)}
+ ${escapeHtml(colStartTime)}
+ ${escapeHtml(colDuration)}
+ ${escapeHtml(colActions)}
${rows}
@@ -1415,12 +1441,18 @@ function renderMonitorPagination() {
// 处理没有数据的情况
const startItem = total === 0 ? 0 : (page - 1) * pageSize + 1;
const endItem = total === 0 ? 0 : Math.min(page * pageSize, total);
-
+ const paginationInfoText = typeof window.t === 'function' ? window.t('mcpMonitor.paginationInfo', { start: startItem, end: endItem, total: total }) : `显示 ${startItem}-${endItem} / 共 ${total} 条记录`;
+ const perPageLabel = typeof window.t === 'function' ? window.t('mcpMonitor.perPageLabel') : '每页显示';
+ const firstPageLabel = typeof window.t === 'function' ? window.t('mcp.firstPage') : '首页';
+ const prevPageLabel = typeof window.t === 'function' ? window.t('mcp.prevPage') : '上一页';
+ const pageInfoText = typeof window.t === 'function' ? window.t('mcp.pageInfo', { page: page, total: totalPages || 1 }) : `第 ${page} / ${totalPages || 1} 页`;
+ const nextPageLabel = typeof window.t === 'function' ? window.t('mcp.nextPage') : '下一页';
+ const lastPageLabel = typeof window.t === 'function' ? window.t('mcp.lastPage') : '末页';
pagination.innerHTML = `
- 显示 ${startItem}-${endItem} / 共 ${total} 条记录
+ ${escapeHtml(paginationInfoText)}
- 首页
- 上一页
- 第 ${page} / ${totalPages || 1} 页
- = totalPages || total === 0 ? 'disabled' : ''}>下一页
- = totalPages || total === 0 ? 'disabled' : ''}>末页
+ ${escapeHtml(firstPageLabel)}
+ ${escapeHtml(prevPageLabel)}
+ ${escapeHtml(pageInfoText)}
+ = totalPages || total === 0 ? 'disabled' : ''}>${escapeHtml(nextPageLabel)}
+ = totalPages || total === 0 ? 'disabled' : ''}>${escapeHtml(lastPageLabel)}
`;
@@ -1450,8 +1482,8 @@ async function deleteExecution(executionId) {
return;
}
- // 确认删除
- if (!confirm('确定要删除此执行记录吗?此操作不可恢复。')) {
+ const deleteConfirmMsg = typeof window.t === 'function' ? window.t('mcpMonitor.deleteExecConfirmSingle') : '确定要删除此执行记录吗?此操作不可恢复。';
+ if (!confirm(deleteConfirmMsg)) {
return;
}
@@ -1462,17 +1494,20 @@ async function deleteExecution(executionId) {
if (!response.ok) {
const error = await response.json().catch(() => ({}));
- throw new Error(error.error || '删除执行记录失败');
+ const deleteFailedMsg = typeof window.t === 'function' ? window.t('mcpMonitor.deleteExecFailed') : '删除执行记录失败';
+ throw new Error(error.error || deleteFailedMsg);
}
// 删除成功后刷新当前页面
const currentPage = monitorState.pagination.page;
await refreshMonitorPanel(currentPage);
- alert('执行记录已删除');
+ const execDeletedMsg = typeof window.t === 'function' ? window.t('mcpMonitor.execDeleted') : '执行记录已删除';
+ alert(execDeletedMsg);
} catch (error) {
console.error('删除执行记录失败:', error);
- alert('删除执行记录失败: ' + error.message);
+ const deleteFailedMsg = typeof window.t === 'function' ? window.t('mcpMonitor.deleteExecFailed') : '删除执行记录失败';
+ alert(deleteFailedMsg + ': ' + error.message);
}
}
@@ -1488,7 +1523,7 @@ function updateBatchActionsState() {
batchActions.style.display = 'flex';
}
if (selectedCountSpan) {
- selectedCountSpan.textContent = `已选择 ${selectedCount} 项`;
+ selectedCountSpan.textContent = typeof window.t === 'function' ? window.t('mcp.selectedCount', { count: selectedCount }) : `已选择 ${selectedCount} 项`;
}
} else {
if (batchActions) {
@@ -1547,15 +1582,15 @@ function deselectAllExecutions() {
async function batchDeleteExecutions() {
const checkboxes = document.querySelectorAll('.monitor-execution-checkbox:checked');
if (checkboxes.length === 0) {
- alert('请先选择要删除的执行记录');
+ const selectFirstMsg = typeof window.t === 'function' ? window.t('mcpMonitor.selectExecFirst') : '请先选择要删除的执行记录';
+ alert(selectFirstMsg);
return;
}
const ids = Array.from(checkboxes).map(cb => cb.value);
const count = ids.length;
-
- // 确认删除
- if (!confirm(`确定要删除选中的 ${count} 条执行记录吗?此操作不可恢复。`)) {
+ const batchConfirmMsg = typeof window.t === 'function' ? window.t('mcpMonitor.batchDeleteConfirm', { count: count }) : `确定要删除选中的 ${count} 条执行记录吗?此操作不可恢复。`;
+ if (!confirm(batchConfirmMsg)) {
return;
}
@@ -1570,7 +1605,8 @@ async function batchDeleteExecutions() {
if (!response.ok) {
const error = await response.json().catch(() => ({}));
- throw new Error(error.error || '批量删除执行记录失败');
+ const batchFailedMsg = typeof window.t === 'function' ? window.t('mcp.batchDeleteFailed') : '批量删除执行记录失败';
+ throw new Error(error.error || batchFailedMsg);
}
const result = await response.json().catch(() => ({}));
@@ -1580,33 +1616,42 @@ async function batchDeleteExecutions() {
const currentPage = monitorState.pagination.page;
await refreshMonitorPanel(currentPage);
- alert(`成功删除 ${deletedCount} 条执行记录`);
+ const batchSuccessMsg = typeof window.t === 'function' ? window.t('mcpMonitor.batchDeleteSuccess', { count: deletedCount }) : `成功删除 ${deletedCount} 条执行记录`;
+ alert(batchSuccessMsg);
} catch (error) {
console.error('批量删除执行记录失败:', error);
- alert('批量删除执行记录失败: ' + error.message);
+ const batchFailedMsg = typeof window.t === 'function' ? window.t('mcp.batchDeleteFailed') : '批量删除执行记录失败';
+ alert(batchFailedMsg + ': ' + error.message);
}
}
function formatExecutionDuration(start, end) {
+ const unknownLabel = typeof window.t === 'function' ? window.t('mcpMonitor.unknown') : '未知';
if (!start) {
- return '未知';
+ return unknownLabel;
}
const startTime = new Date(start);
const endTime = end ? new Date(end) : new Date();
if (Number.isNaN(startTime.getTime()) || Number.isNaN(endTime.getTime())) {
- return '未知';
+ return unknownLabel;
}
const diffMs = Math.max(0, endTime - startTime);
const seconds = Math.floor(diffMs / 1000);
if (seconds < 60) {
- return `${seconds} 秒`;
+ return typeof window.t === 'function' ? window.t('mcpMonitor.durationSeconds', { n: seconds }) : seconds + ' 秒';
}
const minutes = Math.floor(seconds / 60);
if (minutes < 60) {
const remain = seconds % 60;
- return remain > 0 ? `${minutes} 分 ${remain} 秒` : `${minutes} 分`;
+ if (remain > 0) {
+ return typeof window.t === 'function' ? window.t('mcpMonitor.durationMinutes', { minutes: minutes, seconds: remain }) : minutes + ' 分 ' + remain + ' 秒';
+ }
+ return typeof window.t === 'function' ? window.t('mcpMonitor.durationMinutesOnly', { minutes: minutes }) : minutes + ' 分';
}
const hours = Math.floor(minutes / 60);
const remainMinutes = minutes % 60;
- return remainMinutes > 0 ? `${hours} 小时 ${remainMinutes} 分` : `${hours} 小时`;
+ if (remainMinutes > 0) {
+ return typeof window.t === 'function' ? window.t('mcpMonitor.durationHours', { hours: hours, minutes: remainMinutes }) : hours + ' 小时 ' + remainMinutes + ' 分';
+ }
+ return typeof window.t === 'function' ? window.t('mcpMonitor.durationHoursOnly', { hours: hours }) : hours + ' 小时';
}
diff --git a/web/static/js/roles.js b/web/static/js/roles.js
index 5053a948..cf990fa5 100644
--- a/web/static/js/roles.js
+++ b/web/static/js/roles.js
@@ -108,11 +108,13 @@ function updateRoleSelectorDisplay() {
}
}
roleSelectorIcon.textContent = icon;
- roleSelectorText.textContent = selectedRole.name || '默认';
+ const displayName = (selectedRole.name === '默认' || !selectedRole.name) && typeof window.t === 'function'
+ ? window.t('chat.defaultRole') : (selectedRole.name || (typeof window.t === 'function' ? window.t('chat.defaultRole') : '默认'));
+ roleSelectorText.textContent = displayName;
} else {
// 默认角色
roleSelectorIcon.textContent = '🔵';
- roleSelectorText.textContent = '默认';
+ roleSelectorText.textContent = typeof window.t === 'function' ? window.t('chat.defaultRole') : '默认';
}
}
diff --git a/web/static/js/settings.js b/web/static/js/settings.js
index 4a150ade..a1118eef 100644
--- a/web/static/js/settings.js
+++ b/web/static/js/settings.js
@@ -255,7 +255,10 @@ async function loadConfig(loadTools = true) {
}
} catch (error) {
console.error('加载配置失败:', error);
- alert('加载配置失败: ' + error.message);
+ const baseMsg = (typeof window !== 'undefined' && typeof window.t === 'function')
+ ? window.t('settings.apply.loadFailed')
+ : '加载配置失败';
+ alert(baseMsg + ': ' + error.message);
}
}
@@ -269,7 +272,7 @@ async function loadToolsList(page = 1, searchKeyword = '') {
// 显示加载状态
if (toolsList) {
// 清空整个容器,包括可能存在的分页控件
- toolsList.innerHTML = '⏳ 正在加载工具列表...';
+ toolsList.innerHTML = '⏳ ' + (typeof window.t === 'function' ? window.t('mcp.loadingTools') : '正在加载工具列表...') + '';
}
try {
@@ -324,8 +327,8 @@ async function loadToolsList(page = 1, searchKeyword = '') {
if (toolsList) {
const isTimeout = error.name === 'AbortError' || error.message.includes('timeout');
const errorMsg = isTimeout
- ? '加载工具列表超时,可能是外部MCP连接较慢。请点击"刷新"按钮重试,或检查外部MCP连接状态。'
- : `加载工具列表失败: ${escapeHtml(error.message)}`;
+ ? (typeof window.t === 'function' ? window.t('mcp.loadToolsTimeout') : '加载工具列表超时,可能是外部MCP连接较慢。请点击"刷新"按钮重试,或检查外部MCP连接状态。')
+ : (typeof window.t === 'function' ? window.t('mcp.loadToolsFailed') : '加载工具列表失败') + ': ' + escapeHtml(error.message);
toolsList.innerHTML = `${errorMsg}`;
}
}
@@ -399,7 +402,7 @@ function renderToolsList() {
listContainer.innerHTML = '';
if (allTools.length === 0) {
- listContainer.innerHTML = '暂无工具';
+ listContainer.innerHTML = '' + (typeof window.t === 'function' ? window.t('mcp.noTools') : '暂无工具') + '';
if (!toolsList.contains(listContainer)) {
toolsList.appendChild(listContainer);
}
@@ -428,8 +431,8 @@ function renderToolsList() {
let externalBadge = '';
if (toolState.is_external || tool.is_external) {
const externalMcpName = toolState.external_mcp || tool.external_mcp || '';
- const badgeText = externalMcpName ? `外部 (${escapeHtml(externalMcpName)})` : '外部';
- const badgeTitle = externalMcpName ? `外部MCP工具 - 来源:${escapeHtml(externalMcpName)}` : '外部MCP工具';
+ const badgeText = externalMcpName ? (typeof window.t === 'function' ? window.t('mcp.externalFrom', { name: escapeHtml(externalMcpName) }) : `外部 (${escapeHtml(externalMcpName)})`) : (typeof window.t === 'function' ? window.t('mcp.externalBadge') : '外部');
+ const badgeTitle = externalMcpName ? (typeof window.t === 'function' ? window.t('mcp.externalToolFrom', { name: escapeHtml(externalMcpName) }) : `外部MCP工具 - 来源:${escapeHtml(externalMcpName)}`) : (typeof window.t === 'function' ? window.t('mcp.externalBadge') : '外部MCP工具');
externalBadge = `${badgeText}`;
}
@@ -443,7 +446,7 @@ function renderToolsList() {
${escapeHtml(tool.name)}
${externalBadge}
- ${escapeHtml(tool.description || '无描述')}
+ ${escapeHtml(tool.description || (typeof window.t === 'function' ? window.t('mcp.noDescription') : '无描述'))}
`;
listContainer.appendChild(toolItem);
@@ -481,12 +484,19 @@ function renderToolsPagination() {
const endItem = Math.min(page * toolsPagination.pageSize, total);
const savedPageSize = getToolsPageSize();
+ const t = typeof window.t === 'function' ? window.t : (k) => k;
+ const paginationT = (key, opts) => {
+ if (typeof window.t === 'function') return window.t(key, opts);
+ if (key === 'mcp.paginationInfo' && opts) return `显示 ${opts.start}-${opts.end} / 共 ${opts.total} 个工具`;
+ if (key === 'mcp.pageInfo' && opts) return `第 ${opts.page} / ${opts.total} 页`;
+ return key;
+ };
pagination.innerHTML = `
- 显示 ${startItem}-${endItem} / 共 ${total} 个工具${toolsSearchKeyword ? ` (搜索: "${escapeHtml(toolsSearchKeyword)}")` : ''}
+ ${paginationT('mcp.paginationInfo', { start: startItem, end: endItem, total: total })}${toolsSearchKeyword ? ` (${t('common.search')}: "${escapeHtml(toolsSearchKeyword)}")` : ''}
-
+
- 首页
- 上一页
- 第 ${page} / ${totalPages} 页
- 下一页
- 末页
+ ${t('mcp.firstPage')}
+ ${t('mcp.prevPage')}
+ ${paginationT('mcp.pageInfo', { page: page, total: totalPages })}
+ ${t('mcp.nextPage')}
+ ${t('mcp.lastPage')}
`;
@@ -693,9 +703,10 @@ async function updateToolsStats() {
totalEnabled = currentPageEnabled;
}
+ const tStats = typeof window.t === 'function' ? window.t : (k) => k;
statsEl.innerHTML = `
- ✅ 当前页已启用: ${currentPageEnabled} / ${currentPageTotal}
- 📊 总计已启用: ${totalEnabled} / ${totalTools}
+ ✅ ${tStats('mcp.currentPageEnabled')}: ${currentPageEnabled} / ${currentPageTotal}
+ 📊 ${tStats('mcp.totalEnabled')}: ${totalEnabled} / ${totalTools}
`;
}
@@ -737,7 +748,10 @@ async function applySettings() {
}
if (hasError) {
- alert('请填写所有必填字段(标记为 * 的字段)');
+ const msg = (typeof window !== 'undefined' && typeof window.t === 'function')
+ ? window.t('settings.apply.fillRequired')
+ : '请填写所有必填字段(标记为 * 的字段)';
+ alert(msg);
return;
}
@@ -896,7 +910,10 @@ async function applySettings() {
if (!updateResponse.ok) {
const error = await updateResponse.json();
- throw new Error(error.error || '更新配置失败');
+ const fallback = (typeof window !== 'undefined' && typeof window.t === 'function')
+ ? window.t('settings.apply.applyFailed')
+ : '应用配置失败';
+ throw new Error(error.error || fallback);
}
// 应用配置
@@ -906,14 +923,23 @@ async function applySettings() {
if (!applyResponse.ok) {
const error = await applyResponse.json();
- throw new Error(error.error || '应用配置失败');
+ const fallback = (typeof window !== 'undefined' && typeof window.t === 'function')
+ ? window.t('settings.apply.applyFailed')
+ : '应用配置失败';
+ throw new Error(error.error || fallback);
}
- alert('配置已成功应用!');
+ const successMsg = (typeof window !== 'undefined' && typeof window.t === 'function')
+ ? window.t('settings.apply.applySuccess')
+ : '配置已成功应用!';
+ alert(successMsg);
closeSettings();
} catch (error) {
console.error('应用配置失败:', error);
- alert('应用配置失败: ' + error.message);
+ const baseMsg = (typeof window !== 'undefined' && typeof window.t === 'function')
+ ? window.t('settings.apply.applyFailed')
+ : '应用配置失败';
+ alert(baseMsg + ': ' + error.message);
}
}
@@ -1024,7 +1050,7 @@ async function saveToolsConfig() {
throw new Error(error.error || '应用配置失败');
}
- alert('工具配置已成功保存!');
+ alert(typeof window.t === 'function' ? window.t('mcp.toolsConfigSaved') : '工具配置已成功保存!');
// 重新加载工具列表以反映最新状态
if (typeof loadToolsList === 'function') {
@@ -1032,7 +1058,7 @@ async function saveToolsConfig() {
}
} catch (error) {
console.error('保存工具配置失败:', error);
- alert('保存工具配置失败: ' + error.message);
+ alert((typeof window.t === 'function' ? window.t('mcp.saveToolsConfigFailed') : '保存工具配置失败') + ': ' + error.message);
}
}
@@ -1079,7 +1105,7 @@ async function changePassword() {
}
if (hasError) {
- alert('请正确填写当前密码和新密码,新密码至少 8 位且需要两次输入一致。');
+ alert(typeof window.t === 'function' ? window.t('settings.security.fillPasswordHint') : '请正确填写当前密码和新密码,新密码至少 8 位且需要两次输入一致。');
return;
}
@@ -1104,13 +1130,14 @@ async function changePassword() {
throw new Error(result.error || '修改密码失败');
}
- alert('密码已更新,请使用新密码重新登录。');
+ const pwdMsg = typeof window.t === 'function' ? window.t('settings.security.passwordUpdated') : '密码已更新,请使用新密码重新登录。';
+ alert(pwdMsg);
resetPasswordForm();
- handleUnauthorized({ message: '密码已更新,请使用新密码重新登录。', silent: false });
+ handleUnauthorized({ message: pwdMsg, silent: false });
closeSettings();
} catch (error) {
console.error('修改密码失败:', error);
- alert('修改密码失败: ' + error.message);
+ alert((typeof window.t === 'function' ? window.t('settings.security.changePasswordFailed') : '修改密码失败') + ': ' + error.message);
} finally {
if (submitBtn) {
submitBtn.disabled = false;
@@ -1173,7 +1200,8 @@ function renderExternalMCPList(servers) {
if (!list) return;
if (Object.keys(servers).length === 0) {
- list.innerHTML = '📋 暂无外部MCP配置
点击"添加外部MCP"按钮开始配置';
+ const emptyT = typeof window.t === 'function' ? window.t : (k) => k;
+ list.innerHTML = '📋 ' + emptyT('mcp.noExternalMCP') + '
' + emptyT('mcp.clickToAddExternal') + '';
return;
}
@@ -1184,10 +1212,11 @@ function renderExternalMCPList(servers) {
status === 'connecting' ? 'status-connecting' :
status === 'error' ? 'status-error' :
status === 'disabled' ? 'status-disabled' : 'status-disconnected';
- const statusText = status === 'connected' ? '已连接' :
- status === 'connecting' ? '连接中...' :
- status === 'error' ? '连接失败' :
- status === 'disabled' ? '已禁用' : '未连接';
+ const statusT = typeof window.t === 'function' ? window.t : (k) => k;
+ const statusText = status === 'connected' ? statusT('mcp.connected') :
+ status === 'connecting' ? statusT('mcp.connecting') :
+ status === 'error' ? statusT('mcp.connectionFailed') :
+ status === 'disabled' ? statusT('mcp.disabled') : statusT('mcp.disconnected');
const transport = server.config.transport || (server.config.command ? 'stdio' : 'http');
const transportIcon = transport === 'stdio' ? '⚙️' : '🌐';
@@ -1200,15 +1229,15 @@ function renderExternalMCPList(servers) {
${status === 'connected' || status === 'disconnected' || status === 'error' ?
- `
- ${status === 'connected' ? '⏸ 停止' : '▶ 启动'}
+ `
+ ${status === 'connected' ? '⏸ ' + statusT('mcp.stop') : '▶ ' + statusT('mcp.start')}
` :
status === 'connecting' ?
`
⏳ 连接中...
` : ''}
- ✏️ 编辑
- 🗑 删除
+ ✏️ ${statusT('common.edit')}
+ 🗑 ${statusT('common.delete')}
${status === 'error' && server.error ? `
@@ -1217,31 +1246,31 @@ function renderExternalMCPList(servers) {
` : ''}
- 传输模式
+ ${statusT('mcp.transportMode')}
${transportIcon} ${escapeHtml(transport.toUpperCase())}
${server.tool_count !== undefined && server.tool_count > 0 ? `
- 工具数量
+ ${statusT('mcp.toolCount')}
🔧 ${server.tool_count} 个工具
` : server.tool_count === 0 && status === 'connected' ? `
- 工具数量
- 暂无工具
+ ${statusT('mcp.toolCount')}
+ ${statusT('mcp.noTools')}
` : ''}
${server.config.description ? `
- 描述
+ ${statusT('mcp.description')}
${escapeHtml(server.config.description)}
` : ''}
${server.config.timeout ? `
- 超时时间
+ ${statusT('mcp.timeout')}
${server.config.timeout} 秒
` : ''}
${transport === 'stdio' && server.config.command ? `
- 命令
+ ${statusT('mcp.command')}
${escapeHtml(server.config.command)}
` : ''}
${transport === 'http' && server.config.url ? `
@@ -1267,18 +1296,19 @@ function renderExternalMCPStats(stats) {
const disabled = stats.disabled || 0;
const connected = stats.connected || 0;
+ const statsT = typeof window.t === 'function' ? window.t : (k) => k;
statsEl.innerHTML = `
- 📊 总数: ${total}
- ✅ 已启用: ${enabled}
- ⏸ 已停用: ${disabled}
- 🔗 已连接: ${connected}
+ 📊 ${statsT('mcp.totalCount')}: ${total}
+ ✅ ${statsT('mcp.enabledCount')}: ${enabled}
+ ⏸ ${statsT('mcp.disabledCount')}: ${disabled}
+ 🔗 ${statsT('mcp.connectedCount')}: ${connected}
`;
}
// 显示添加外部MCP模态框
function showAddExternalMCPModal() {
currentEditingMCPName = null;
- document.getElementById('external-mcp-modal-title').textContent = '添加外部MCP';
+ document.getElementById('external-mcp-modal-title').textContent = (typeof window.t === 'function' ? window.t('mcp.addExternalMCP') : '添加外部MCP');
document.getElementById('external-mcp-json').value = '';
document.getElementById('external-mcp-json-error').style.display = 'none';
document.getElementById('external-mcp-json-error').textContent = '';
@@ -1303,7 +1333,7 @@ async function editExternalMCP(name) {
const server = await response.json();
currentEditingMCPName = name;
- document.getElementById('external-mcp-modal-title').textContent = '编辑外部MCP';
+ document.getElementById('external-mcp-modal-title').textContent = (typeof window.t === 'function' ? window.t('mcp.editExternalMCP') : '编辑外部MCP');
// 将配置转换为对象格式(key为名称)
const config = { ...server.config };
@@ -1325,7 +1355,7 @@ async function editExternalMCP(name) {
document.getElementById('external-mcp-modal').style.display = 'block';
} catch (error) {
console.error('编辑外部MCP失败:', error);
- alert('编辑失败: ' + error.message);
+ alert((typeof window.t === 'function' ? window.t('mcp.operationFailed') : '编辑失败') + ': ' + error.message);
}
}
@@ -1528,7 +1558,7 @@ async function saveExternalMCP() {
}
// 轮询几次以拉取后端异步更新的工具数量(无固定延迟,拿到即停)
pollExternalMCPToolCount(null, 5);
- alert('保存成功');
+ alert(typeof window.t === 'function' ? window.t('mcp.saveSuccess') : '保存成功');
} catch (error) {
console.error('保存外部MCP失败:', error);
errorDiv.textContent = '保存失败: ' + error.message;
@@ -1539,7 +1569,7 @@ async function saveExternalMCP() {
// 删除外部MCP
async function deleteExternalMCP(name) {
- if (!confirm(`确定要删除外部MCP "${name}" 吗?`)) {
+ if (!confirm((typeof window.t === 'function' ? window.t('mcp.deleteExternalConfirm', { name: name }) : `确定要删除外部MCP "${name}" 吗?`))) {
return;
}
@@ -1558,10 +1588,10 @@ async function deleteExternalMCP(name) {
if (typeof window !== 'undefined' && typeof window.refreshMentionTools === 'function') {
window.refreshMentionTools();
}
- alert('删除成功');
+ alert(typeof window.t === 'function' ? window.t('mcp.deleteSuccess') : '删除成功');
} catch (error) {
console.error('删除外部MCP失败:', error);
- alert('删除失败: ' + error.message);
+ alert((typeof window.t === 'function' ? window.t('mcp.operationFailed') : '删除失败') + ': ' + error.message);
}
}
@@ -1626,7 +1656,7 @@ async function toggleExternalMCP(name, currentStatus) {
}
} catch (error) {
console.error('切换外部MCP状态失败:', error);
- alert('操作失败: ' + error.message);
+ alert((typeof window.t === 'function' ? window.t('mcp.operationFailed') : '操作失败') + ': ' + error.message);
// 恢复按钮状态
if (button) {
@@ -1679,7 +1709,7 @@ async function pollExternalMCPStatus(name, maxAttempts = 30) {
window.refreshMentionTools();
}
if (status === 'error') {
- alert('连接失败,请检查配置和网络连接');
+ alert(typeof window.t === 'function' ? window.t('mcp.connectionFailedCheck') : '连接失败,请检查配置和网络连接');
}
return;
} else if (status === 'connecting') {
@@ -1701,7 +1731,7 @@ async function pollExternalMCPStatus(name, maxAttempts = 30) {
if (typeof window !== 'undefined' && typeof window.refreshMentionTools === 'function') {
window.refreshMentionTools();
}
- alert('连接超时,请检查配置和网络连接');
+ alert(typeof window.t === 'function' ? window.t('mcp.connectionTimeout') : '连接超时,请检查配置和网络连接');
}
// 在打开设置时加载外部MCP列表
diff --git a/web/static/js/tasks.js b/web/static/js/tasks.js
index 21746e07..22b8aa19 100644
--- a/web/static/js/tasks.js
+++ b/web/static/js/tasks.js
@@ -1,4 +1,7 @@
// 任务管理页面功能
+function _t(key, opts) {
+ return typeof window.t === 'function' ? window.t(key, opts) : key;
+}
// HTML转义函数(如果未定义)
if (typeof escapeHtml === 'undefined') {
@@ -106,7 +109,7 @@ async function loadTasks() {
const listContainer = document.getElementById('tasks-list');
if (!listContainer) return;
- listContainer.innerHTML = '加载中...';
+ listContainer.innerHTML = '' + _t('tasks.loadingTasks') + '';
try {
// 并行加载运行中的任务和已完成的任务历史
@@ -117,7 +120,7 @@ async function loadTasks() {
// 处理运行中的任务
if (activeResponse.status === 'rejected' || !activeResponse.value || !activeResponse.value.ok) {
- throw new Error('获取任务列表失败');
+ throw new Error(_t('tasks.loadTaskListFailed'));
}
const activeResult = await activeResponse.value.json();
@@ -177,8 +180,8 @@ async function loadTasks() {
console.error('加载任务失败:', error);
listContainer.innerHTML = `
- 加载失败: ${escapeHtml(error.message)}
- 重试
+ ${_t('tasks.loadFailedRetry')}: ${escapeHtml(error.message)}
+ ${_t('tasks.retry')}
`;
}
@@ -296,21 +299,21 @@ function toggleShowHistory(show) {
// 计算执行时长
function calculateDuration(startedAt) {
- if (!startedAt) return '未知';
+ if (!startedAt) return _t('tasks.unknown');
const start = new Date(startedAt);
const now = new Date();
- const diff = Math.floor((now - start) / 1000); // 秒
+ const diff = Math.floor((now - start) / 1000);
if (diff < 60) {
- return `${diff}秒`;
+ return diff + _t('tasks.durationSeconds');
} else if (diff < 3600) {
const minutes = Math.floor(diff / 60);
const seconds = diff % 60;
- return `${minutes}分${seconds}秒`;
+ return minutes + _t('tasks.durationMinutes') + ' ' + seconds + _t('tasks.durationSeconds');
} else {
const hours = Math.floor(diff / 3600);
const minutes = Math.floor((diff % 3600) / 60);
- return `${hours}小时${minutes}分`;
+ return hours + _t('tasks.durationHours') + ' ' + minutes + _t('tasks.durationMinutes');
}
}
@@ -349,9 +352,9 @@ function renderTasks(tasks) {
if (tasks.length === 0) {
listContainer.innerHTML = `
- 当前没有符合条件的任务
+ ${_t('tasks.noMatchingTasks')}
${tasksState.allTasks.length === 0 && tasksState.completedTasksHistory.length > 0 ?
- '提示:有已完成的任务历史,请勾选"显示历史记录"查看
' : ''}
+ '' + _t('tasks.historyHint') + '
' : ''}
`;
return;
@@ -359,12 +362,12 @@ function renderTasks(tasks) {
// 状态映射
const statusMap = {
- 'running': { text: '执行中', class: 'task-status-running' },
- 'cancelling': { text: '取消中', class: 'task-status-cancelling' },
- 'failed': { text: '执行失败', class: 'task-status-failed' },
- 'timeout': { text: '执行超时', class: 'task-status-timeout' },
- 'cancelled': { text: '已取消', class: 'task-status-cancelled' },
- 'completed': { text: '已完成', class: 'task-status-completed' }
+ 'running': { text: _t('tasks.statusRunning'), class: 'task-status-running' },
+ 'cancelling': { text: _t('tasks.statusCancelling'), class: 'task-status-cancelling' },
+ 'failed': { text: _t('tasks.statusFailed'), class: 'task-status-failed' },
+ 'timeout': { text: _t('tasks.statusTimeout'), class: 'task-status-timeout' },
+ 'cancelled': { text: _t('tasks.statusCancelled'), class: 'task-status-cancelled' },
+ 'completed': { text: _t('tasks.statusCompleted'), class: 'task-status-completed' }
};
// 分离当前任务和历史任务
@@ -382,8 +385,8 @@ function renderTasks(tasks) {
if (historyTasks.length > 0) {
html += `
- 📜 最近完成的任务(最近24小时)
- 清空历史
+ 📜 ` + _t('tasks.recentCompletedTasks') + `
+ ` + _t('tasks.clearHistory') + `
${historyTasks.map(task => renderTaskItem(task, statusMap, true)).join('')}
`;
@@ -406,7 +409,7 @@ function renderTaskItem(task, statusMap, isHistory = false) {
minute: '2-digit',
second: '2-digit'
})
- : '未知时间';
+ : _t('tasks.unknownTime');
const completedText = completedTime && !isNaN(completedTime.getTime())
? completedTime.toLocaleString('zh-CN', {
@@ -438,22 +441,22 @@ function renderTaskItem(task, statusMap, isHistory = false) {
` : ''}
${status.text}
- ${isHistory ? '📜' : ''}
-
+ ${isHistory ? '📜' : ''}
+
- ${duration ? `⏱ ${duration}` : ''}
-
+ ${duration ? `⏱ ${duration}` : ''}
+
${isHistory && completedText ? completedText : timeText}
- ${canCancel ? `取消任务 ` : ''}
- ${task.conversationId ? `查看对话 ` : ''}
+ ${canCancel ? `` + _t('tasks.cancelTask') + ` ` : ''}
+ ${task.conversationId ? `` + _t('tasks.viewConversation') + ` ` : ''}
${task.conversationId ? `
- 对话ID:
- ${escapeHtml(task.conversationId)}
+ ` + _t('tasks.conversationIdLabel') + `:
+ ${escapeHtml(task.conversationId)}
` : ''}
@@ -462,7 +465,7 @@ function renderTaskItem(task, statusMap, isHistory = false) {
// 清空任务历史
function clearTasksHistory() {
- if (!confirm('确定要清空所有任务历史记录吗?')) {
+ if (!confirm(_t('tasks.clearHistoryConfirm'))) {
return;
}
tasksState.completedTasksHistory = [];
@@ -490,7 +493,7 @@ function updateBatchActions() {
const count = tasksState.selectedTasks.size;
if (count > 0) {
batchActions.style.display = 'flex';
- selectedCount.textContent = `已选择 ${count} 项`;
+ selectedCount.textContent = typeof window.t === 'function' ? window.t('mcp.selectedCount', { count: count }) : `已选择 ${count} 项`;
} else {
batchActions.style.display = 'none';
}
@@ -509,7 +512,7 @@ async function batchCancelTasks() {
const selected = Array.from(tasksState.selectedTasks);
if (selected.length === 0) return;
- if (!confirm(`确定要取消 ${selected.length} 个任务吗?`)) {
+ if (!confirm(_t('tasks.confirmCancelTasks', { n: selected.length }))) {
return;
}
@@ -545,9 +548,9 @@ async function batchCancelTasks() {
// 显示结果
if (failCount > 0) {
- alert(`批量取消完成:成功 ${successCount} 个,失败 ${failCount} 个`);
+ alert(_t('tasks.batchCancelResultPartial', { success: successCount, fail: failCount }));
} else {
- alert(`成功取消 ${successCount} 个任务`);
+ alert(_t('tasks.batchCancelResultSuccess', { n: successCount }));
}
}
@@ -556,7 +559,7 @@ function copyTaskId(conversationId) {
navigator.clipboard.writeText(conversationId).then(() => {
// 显示复制成功提示
const tooltip = document.createElement('div');
- tooltip.textContent = '已复制!';
+ tooltip.textContent = _t('tasks.copiedToast');
tooltip.style.cssText = 'position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(0,0,0,0.8); color: white; padding: 8px 16px; border-radius: 4px; z-index: 10000;';
document.body.appendChild(tooltip);
setTimeout(() => tooltip.remove(), 1000);
@@ -571,7 +574,7 @@ async function cancelTask(conversationId, button) {
const originalText = button.textContent;
button.disabled = true;
- button.textContent = '取消中...';
+ button.textContent = _t('tasks.cancelling');
try {
const response = await apiFetch('/api/agent-loop/cancel', {
@@ -584,7 +587,7 @@ async function cancelTask(conversationId, button) {
if (!response.ok) {
const result = await response.json().catch(() => ({}));
- throw new Error(result.error || '取消任务失败');
+ throw new Error(result.error || _t('tasks.cancelTaskFailed'));
}
// 从选择中移除
@@ -595,7 +598,7 @@ async function cancelTask(conversationId, button) {
await loadTasks();
} catch (error) {
console.error('取消任务失败:', error);
- alert('取消任务失败: ' + error.message);
+ alert(_t('tasks.cancelTaskFailed') + ': ' + error.message);
button.disabled = false;
button.textContent = originalText;
}
@@ -738,7 +741,7 @@ async function showBatchImportModal() {
try {
const loadedRoles = await loadRoles();
// 清空现有选项(除了默认选项)
- roleSelect.innerHTML = '';
+ roleSelect.innerHTML = '';
// 添加已启用的角色
const sortedRoles = loadedRoles.sort((a, b) => {
@@ -782,7 +785,7 @@ function updateBatchImportStats(text) {
const count = lines.length;
if (count > 0) {
- statsEl.innerHTML = `共 ${count} 个任务`;
+ statsEl.innerHTML = '' + _t('tasks.taskCount', { count: count }) + '';
statsEl.style.display = 'block';
} else {
statsEl.style.display = 'none';
@@ -808,14 +811,14 @@ async function createBatchQueue() {
const text = input.value.trim();
if (!text) {
- alert('请输入至少一个任务');
+ alert(_t('tasks.enterTaskPrompt'));
return;
}
// 按行分割任务
const tasks = text.split('\n').map(line => line.trim()).filter(line => line !== '');
if (tasks.length === 0) {
- alert('没有有效的任务');
+ alert(_t('tasks.noValidTask'));
return;
}
@@ -836,7 +839,7 @@ async function createBatchQueue() {
if (!response.ok) {
const result = await response.json().catch(() => ({}));
- throw new Error(result.error || '创建批量任务队列失败');
+ throw new Error(result.error || _t('tasks.createBatchQueueFailed'));
}
const result = await response.json();
@@ -849,7 +852,7 @@ async function createBatchQueue() {
refreshBatchQueues();
} catch (error) {
console.error('创建批量任务队列失败:', error);
- alert('创建批量任务队列失败: ' + error.message);
+ alert(_t('tasks.createBatchQueueFailed') + ': ' + error.message);
}
}
@@ -916,7 +919,7 @@ async function loadBatchQueues(page) {
try {
const response = await apiFetch(`/api/batch-tasks?${params.toString()}`);
if (!response.ok) {
- throw new Error('获取批量任务队列失败');
+ throw new Error(_t('tasks.loadFailedRetry'));
}
const result = await response.json();
@@ -929,7 +932,7 @@ async function loadBatchQueues(page) {
section.style.display = 'block';
const list = document.getElementById('batch-queues-list');
if (list) {
- list.innerHTML = '加载失败: ' + escapeHtml(error.message) + '
重试 ';
+ list.innerHTML = '' + _t('tasks.loadFailedRetry') + ': ' + escapeHtml(error.message) + '
' + _t('tasks.retry') + ' ';
}
}
}
@@ -964,7 +967,7 @@ function renderBatchQueues() {
const queues = batchQueuesState.queues;
if (queues.length === 0) {
- list.innerHTML = '当前没有批量任务队列
';
+ list.innerHTML = '' + _t('tasks.noBatchQueues') + '
';
if (pagination) pagination.style.display = 'none';
return;
}
@@ -976,11 +979,11 @@ function renderBatchQueues() {
list.innerHTML = queues.map(queue => {
const statusMap = {
- 'pending': { text: '待执行', class: 'batch-queue-status-pending' },
- 'running': { text: '执行中', class: 'batch-queue-status-running' },
- 'paused': { text: '已暂停', class: 'batch-queue-status-paused' },
- 'completed': { text: '已完成', class: 'batch-queue-status-completed' },
- 'cancelled': { text: '已取消', class: 'batch-queue-status-cancelled' }
+ 'pending': { text: _t('tasks.statusPending'), class: 'batch-queue-status-pending' },
+ 'running': { text: _t('tasks.statusRunning'), class: 'batch-queue-status-running' },
+ 'paused': { text: _t('tasks.statusPaused'), class: 'batch-queue-status-paused' },
+ 'completed': { text: _t('tasks.statusCompleted'), class: 'batch-queue-status-completed' },
+ 'cancelled': { text: _t('tasks.statusCancelled'), class: 'batch-queue-status-cancelled' }
};
const status = statusMap[queue.status] || { text: queue.status, class: 'batch-queue-status-unknown' };
@@ -1012,8 +1015,8 @@ function renderBatchQueues() {
// 显示角色信息(使用正确的角色图标)
const loadedRoles = batchQueuesState.loadedRoles || [];
const roleIcon = getRoleIconForDisplay(queue.role, loadedRoles);
- const roleName = queue.role && queue.role !== '' ? queue.role : '默认';
- const roleDisplay = `${roleIcon} ${escapeHtml(roleName)}`;
+ const roleName = queue.role && queue.role !== '' ? queue.role : _t('batchQueueDetailModal.defaultRole');
+ const roleDisplay = `${roleIcon} ${escapeHtml(roleName)}`;
return `
@@ -1022,8 +1025,8 @@ function renderBatchQueues() {
${titleDisplay}
${roleDisplay}
${status.text}
- 队列ID: ${escapeHtml(queue.id)}
- 创建时间: ${new Date(queue.createdAt).toLocaleString('zh-CN')}
+ ${_t('tasks.queueIdLabel')}: ${escapeHtml(queue.id)}
+ ${_t('tasks.createdTimeLabel')}: ${new Date(queue.createdAt).toLocaleString()}
- ${canDelete ? `删除 ` : ''}
+ ${canDelete ? `${_t('common.delete')} ` : ''}
- 总计: ${stats.total}
- 待执行: ${stats.pending}
- 执行中: ${stats.running}
- 已完成: ${stats.completed}
- 失败: ${stats.failed}
- ${stats.cancelled > 0 ? `已取消: ${stats.cancelled}` : ''}
+ ${_t('tasks.totalLabel')}: ${stats.total}
+ ${_t('tasks.pendingLabel')}: ${stats.pending}
+ ${_t('tasks.runningLabel')}: ${stats.running}
+ ${_t('tasks.completedLabel')}: ${stats.completed}
+ ${_t('tasks.failedLabel')}: ${stats.failed}
+ ${stats.cancelled > 0 ? `${_t('tasks.cancelledLabel')}: ${stats.cancelled}` : ''}
`;
@@ -1073,9 +1076,9 @@ function renderBatchQueuesPagination() {
// 左侧:显示范围信息和每页数量选择器(参考Skills样式)
paginationHTML += `
- 显示 ${start}-${end} / 共 ${total} 条
+ ` + _t('tasks.paginationShow', { start: start, end: end, total: total }) + `
`;
}).join('')}
@@ -1343,7 +1346,7 @@ async function showBatchQueueDetail(queueId) {
}
} catch (error) {
console.error('获取队列详情失败:', error);
- alert('获取队列详情失败: ' + error.message);
+ alert(_t('tasks.getQueueDetailFailed') + ': ' + error.message);
}
}
@@ -1359,7 +1362,7 @@ async function startBatchQueue() {
if (!response.ok) {
const result = await response.json().catch(() => ({}));
- throw new Error(result.error || '启动批量任务失败');
+ throw new Error(result.error || _t('tasks.startBatchQueueFailed'));
}
// 刷新详情
@@ -1367,7 +1370,7 @@ async function startBatchQueue() {
refreshBatchQueues();
} catch (error) {
console.error('启动批量任务失败:', error);
- alert('启动批量任务失败: ' + error.message);
+ alert(_t('tasks.startBatchQueueFailed') + ': ' + error.message);
}
}
@@ -1376,7 +1379,7 @@ async function pauseBatchQueue() {
const queueId = batchQueuesState.currentQueueId;
if (!queueId) return;
- if (!confirm('确定要暂停这个批量任务队列吗?当前正在执行的任务将被停止,后续任务将保留待执行状态。')) {
+ if (!confirm(_t('tasks.pauseQueueConfirm'))) {
return;
}
@@ -1387,7 +1390,7 @@ async function pauseBatchQueue() {
if (!response.ok) {
const result = await response.json().catch(() => ({}));
- throw new Error(result.error || '暂停批量任务失败');
+ throw new Error(result.error || _t('tasks.pauseQueueFailed'));
}
// 刷新详情
@@ -1395,7 +1398,7 @@ async function pauseBatchQueue() {
refreshBatchQueues();
} catch (error) {
console.error('暂停批量任务失败:', error);
- alert('暂停批量任务失败: ' + error.message);
+ alert(_t('tasks.pauseQueueFailed') + ': ' + error.message);
}
}
@@ -1404,7 +1407,7 @@ async function deleteBatchQueue() {
const queueId = batchQueuesState.currentQueueId;
if (!queueId) return;
- if (!confirm('确定要删除这个批量任务队列吗?此操作不可恢复。')) {
+ if (!confirm(_t('tasks.deleteQueueConfirm'))) {
return;
}
@@ -1415,14 +1418,14 @@ async function deleteBatchQueue() {
if (!response.ok) {
const result = await response.json().catch(() => ({}));
- throw new Error(result.error || '删除批量任务队列失败');
+ throw new Error(result.error || _t('tasks.deleteQueueFailed'));
}
closeBatchQueueDetailModal();
refreshBatchQueues();
} catch (error) {
console.error('删除批量任务队列失败:', error);
- alert('删除批量任务队列失败: ' + error.message);
+ alert(_t('tasks.deleteQueueFailed') + ': ' + error.message);
}
}
@@ -1430,7 +1433,7 @@ async function deleteBatchQueue() {
async function deleteBatchQueueFromList(queueId) {
if (!queueId) return;
- if (!confirm('确定要删除这个批量任务队列吗?此操作不可恢复。')) {
+ if (!confirm(_t('tasks.deleteQueueConfirm'))) {
return;
}
@@ -1441,7 +1444,7 @@ async function deleteBatchQueueFromList(queueId) {
if (!response.ok) {
const result = await response.json().catch(() => ({}));
- throw new Error(result.error || '删除批量任务队列失败');
+ throw new Error(result.error || _t('tasks.deleteQueueFailed'));
}
// 如果当前正在查看这个队列的详情,关闭详情模态框
@@ -1453,7 +1456,7 @@ async function deleteBatchQueueFromList(queueId) {
refreshBatchQueues();
} catch (error) {
console.error('删除批量任务队列失败:', error);
- alert('删除批量任务队列失败: ' + error.message);
+ alert(_t('tasks.deleteQueueFailed') + ': ' + error.message);
}
}
@@ -1599,18 +1602,18 @@ async function saveBatchTask() {
const messageInput = document.getElementById('edit-task-message');
if (!queueId || !taskId) {
- alert('任务信息不完整');
+ alert(_t('tasks.taskIncomplete'));
return;
}
if (!messageInput) {
- alert('无法获取任务消息输入框');
+ alert(_t('tasks.cannotGetTaskMessageInput'));
return;
}
const message = messageInput.value.trim();
if (!message) {
- alert('任务消息不能为空');
+ alert(_t('tasks.taskMessageRequired'));
return;
}
@@ -1625,7 +1628,7 @@ async function saveBatchTask() {
if (!response.ok) {
const result = await response.json().catch(() => ({}));
- throw new Error(result.error || '更新任务失败');
+ throw new Error(result.error || _t('tasks.updateTaskFailed'));
}
// 关闭编辑模态框
@@ -1640,7 +1643,7 @@ async function saveBatchTask() {
refreshBatchQueues();
} catch (error) {
console.error('保存任务失败:', error);
- alert('保存任务失败: ' + error.message);
+ alert(_t('tasks.saveTaskFailed') + ': ' + error.message);
}
}
@@ -1648,7 +1651,7 @@ async function saveBatchTask() {
function showAddBatchTaskModal() {
const queueId = batchQueuesState.currentQueueId;
if (!queueId) {
- alert('队列信息不存在');
+ alert(_t('tasks.queueInfoMissing'));
return;
}
@@ -1706,18 +1709,18 @@ async function saveAddBatchTask() {
const messageInput = document.getElementById('add-task-message');
if (!queueId) {
- alert('队列信息不存在');
+ alert(_t('tasks.queueInfoMissing'));
return;
}
if (!messageInput) {
- alert('无法获取任务消息输入框');
+ alert(_t('tasks.cannotGetTaskMessageInput'));
return;
}
const message = messageInput.value.trim();
if (!message) {
- alert('任务消息不能为空');
+ alert(_t('tasks.taskMessageRequired'));
return;
}
@@ -1732,7 +1735,7 @@ async function saveAddBatchTask() {
if (!response.ok) {
const result = await response.json().catch(() => ({}));
- throw new Error(result.error || '添加任务失败');
+ throw new Error(result.error || _t('tasks.addTaskFailed'));
}
// 关闭添加任务模态框
@@ -1747,7 +1750,7 @@ async function saveAddBatchTask() {
refreshBatchQueues();
} catch (error) {
console.error('添加任务失败:', error);
- alert('添加任务失败: ' + error.message);
+ alert(_t('tasks.addTaskFailed') + ': ' + error.message);
}
}
@@ -1779,7 +1782,7 @@ function deleteBatchTaskFromElement(button) {
? decodedMessage.substring(0, 50) + '...'
: decodedMessage;
- if (!confirm(`确定要删除这个任务吗?\n\n任务内容: ${displayMessage}\n\n此操作不可恢复。`)) {
+ if (!confirm(_t('tasks.confirmDeleteTask', { message: displayMessage }))) {
return;
}
@@ -1789,7 +1792,7 @@ function deleteBatchTaskFromElement(button) {
// 删除批量任务
async function deleteBatchTask(queueId, taskId) {
if (!queueId || !taskId) {
- alert('任务信息不完整');
+ alert(_t('tasks.taskIncomplete'));
return;
}
@@ -1800,7 +1803,7 @@ async function deleteBatchTask(queueId, taskId) {
if (!response.ok) {
const result = await response.json().catch(() => ({}));
- throw new Error(result.error || '删除任务失败');
+ throw new Error(result.error || _t('tasks.deleteTaskFailed'));
}
// 刷新队列详情
@@ -1812,7 +1815,7 @@ async function deleteBatchTask(queueId, taskId) {
refreshBatchQueues();
} catch (error) {
console.error('删除任务失败:', error);
- alert('删除任务失败: ' + error.message);
+ alert(_t('tasks.deleteTaskFailed') + ': ' + error.message);
}
}
diff --git a/web/static/js/vulnerability.js b/web/static/js/vulnerability.js
index 8b4c96d5..59590192 100644
--- a/web/static/js/vulnerability.js
+++ b/web/static/js/vulnerability.js
@@ -156,14 +156,20 @@ async function loadVulnerabilities(page = null) {
function renderVulnerabilities(vulnerabilities) {
const listContainer = document.getElementById('vulnerabilities-list');
- // 处理空值情况
+ // 处理空值情况(使用 data-i18n 以便语言切换时自动更新)
if (!vulnerabilities || !Array.isArray(vulnerabilities)) {
- listContainer.innerHTML = '暂无漏洞记录';
+ listContainer.innerHTML = '暂无漏洞记录';
+ if (typeof window.applyTranslations === 'function') {
+ window.applyTranslations(listContainer);
+ }
return;
}
if (vulnerabilities.length === 0) {
- listContainer.innerHTML = '暂无漏洞记录';
+ listContainer.innerHTML = '暂无漏洞记录';
+ if (typeof window.applyTranslations === 'function') {
+ window.applyTranslations(listContainer);
+ }
// 清空分页信息
const paginationContainer = document.getElementById('vulnerability-pagination');
if (paginationContainer) {
@@ -328,7 +334,7 @@ async function changeVulnerabilityPageSize() {
// 显示添加漏洞模态框
function showAddVulnerabilityModal() {
currentVulnerabilityId = null;
- document.getElementById('vulnerability-modal-title').textContent = '添加漏洞';
+ document.getElementById('vulnerability-modal-title').textContent = (typeof window.t === 'function' ? window.t('vulnerability.addVuln') : '添加漏洞');
// 清空表单
document.getElementById('vulnerability-conversation-id').value = '';
@@ -353,7 +359,7 @@ async function editVulnerability(id) {
const vuln = await response.json();
currentVulnerabilityId = id;
- document.getElementById('vulnerability-modal-title').textContent = '编辑漏洞';
+ document.getElementById('vulnerability-modal-title').textContent = (typeof window.t === 'function' ? window.t('vulnerability.editVuln') : '编辑漏洞');
// 填充表单
document.getElementById('vulnerability-conversation-id').value = vuln.conversation_id || '';
diff --git a/web/templates/index.html b/web/templates/index.html
index a9ad2d29..cb15cc00 100644
--- a/web/templates/index.html
+++ b/web/templates/index.html
@@ -13,17 +13,17 @@