From 7509a1eddcaa0bb7a10dd8748ee16f2f97a317cb Mon Sep 17 00:00:00 2001 From: Supra4E8C Date: Sun, 26 Oct 2025 14:54:07 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E8=A1=A5=E5=85=A8i18n?= =?UTF-8?q?=E5=9B=BD=E9=99=85=E5=8C=96=E6=96=87=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.js | 142 +++++++++++++++++++++++++++-------------------------- i18n.js | 46 +++++++++++++++++ index.html | 41 +++++++--------- 3 files changed, 136 insertions(+), 93 deletions(-) diff --git a/app.js b/app.js index 4943951..a4b6998 100644 --- a/app.js +++ b/app.js @@ -182,12 +182,12 @@ class CLIProxyManager { // 如果有完整的连接信息且之前已登录,尝试自动登录 if (savedBase && savedKey && wasLoggedIn) { try { - console.log('检测到本地连接数据,尝试自动登录...'); + console.log(i18n.t('auto_login.title')); this.showAutoLoginLoading(); await this.attemptAutoLogin(savedBase, savedKey); return; // 自动登录成功,不显示登录页面 } catch (error) { - console.log('自动登录失败:', error.message); + console.log(`${i18n.t('notification.login_failed')}: ${error.message}`); // 清除无效的登录状态 localStorage.removeItem('isLoggedIn'); this.hideAutoLoginLoading(); @@ -232,7 +232,7 @@ class CLIProxyManager { this.hideAutoLoginLoading(); this.showMainPage(); - console.log('自动登录成功'); + console.log(i18n.t('auto_login.title')); return true; } catch (error) { console.error('自动登录失败:', error); @@ -807,7 +807,8 @@ class CLIProxyManager { // 更新按钮提示文本 const toggleBtn = document.getElementById('sidebar-toggle-btn-desktop'); if (toggleBtn) { - toggleBtn.setAttribute('title', isCollapsed ? '展开侧边栏' : '收起侧边栏'); + toggleBtn.setAttribute('data-i18n-title', isCollapsed ? 'sidebar.toggle_expand' : 'sidebar.toggle_collapse'); + toggleBtn.title = i18n.t(isCollapsed ? 'sidebar.toggle_expand' : 'sidebar.toggle_collapse'); } } } @@ -828,7 +829,8 @@ class CLIProxyManager { // 更新按钮提示文本 const toggleBtn = document.getElementById('sidebar-toggle-btn-desktop'); if (toggleBtn) { - toggleBtn.setAttribute('title', '展开侧边栏'); + toggleBtn.setAttribute('data-i18n-title', 'sidebar.toggle_expand'); + toggleBtn.title = i18n.t('sidebar.toggle_expand'); } } } @@ -1420,7 +1422,7 @@ class CLIProxyManager { // 加载所有数据 - 使用新的 /config 端点一次性获取所有配置 async loadAllData(forceRefresh = false) { try { - console.log('使用新的 /config 端点加载所有配置...'); + console.log(i18n.t('system_info.real_time_data')); // 使用新的 /config 端点一次性获取所有配置 const config = await this.getConfig(forceRefresh); @@ -2213,10 +2215,10 @@ class CLIProxyManager {
${this.maskApiKey(key)}
- 成功: ${keyStats.success} + ${i18n.t('stats.success')}: ${keyStats.success} - 失败: ${keyStats.failure} + ${i18n.t('stats.failure')}: ${keyStats.failure}
@@ -2238,14 +2240,14 @@ class CLIProxyManager { const modalBody = document.getElementById('modal-body'); modalBody.innerHTML = ` -

添加Gemini API密钥

+

${i18n.t('ai_providers.gemini_add_modal_title')}

- - + +
`; @@ -2257,7 +2259,7 @@ class CLIProxyManager { const newKey = document.getElementById('new-gemini-key').value.trim(); if (!newKey) { - this.showNotification('请输入Gemini API密钥', 'error'); + this.showNotification(i18n.t('notification.please_enter') + ' ' + i18n.t('notification.gemini_api_key'), 'error'); return; } @@ -2274,9 +2276,9 @@ class CLIProxyManager { this.clearCache(); // 清除缓存 this.closeModal(); this.loadGeminiKeys(); - this.showNotification('Gemini密钥添加成功', 'success'); + this.showNotification(i18n.t('notification.gemini_key_added'), 'success'); } catch (error) { - this.showNotification(`添加Gemini密钥失败: ${error.message}`, 'error'); + this.showNotification(`${i18n.t('notification.add_failed')}: ${error.message}`, 'error'); } } @@ -2286,14 +2288,14 @@ class CLIProxyManager { const modalBody = document.getElementById('modal-body'); modalBody.innerHTML = ` -

编辑Gemini API密钥

+

${i18n.t('ai_providers.gemini_edit_modal_title')}

- +
`; @@ -2305,7 +2307,7 @@ class CLIProxyManager { const newKey = document.getElementById('edit-gemini-key').value.trim(); if (!newKey) { - this.showNotification('请输入Gemini API密钥', 'error'); + this.showNotification(i18n.t('notification.please_enter') + ' ' + i18n.t('notification.gemini_api_key'), 'error'); return; } @@ -2318,9 +2320,9 @@ class CLIProxyManager { this.clearCache(); // 清除缓存 this.closeModal(); this.loadGeminiKeys(); - this.showNotification('Gemini密钥更新成功', 'success'); + this.showNotification(i18n.t('notification.gemini_key_updated'), 'success'); } catch (error) { - this.showNotification(`更新Gemini密钥失败: ${error.message}`, 'error'); + this.showNotification(`${i18n.t('notification.update_failed')}: ${error.message}`, 'error'); } } @@ -2332,9 +2334,9 @@ class CLIProxyManager { await this.makeRequest(`/generative-language-api-key?value=${encodeURIComponent(key)}`, { method: 'DELETE' }); this.clearCache(); // 清除缓存 this.loadGeminiKeys(); - this.showNotification('Gemini密钥删除成功', 'success'); + this.showNotification(i18n.t('notification.gemini_key_deleted'), 'success'); } catch (error) { - this.showNotification(`删除Gemini密钥失败: ${error.message}`, 'error'); + this.showNotification(`${i18n.t('notification.delete_failed')}: ${error.message}`, 'error'); } } @@ -2381,10 +2383,10 @@ class CLIProxyManager { ${config['proxy-url'] ? `
${i18n.t('common.proxy_url')}: ${this.escapeHtml(config['proxy-url'])}
` : ''}
- 成功: ${keyStats.success} + ${i18n.t('stats.success')}: ${keyStats.success} - 失败: ${keyStats.failure} + ${i18n.t('stats.failure')}: ${keyStats.failure}
@@ -2461,9 +2463,9 @@ class CLIProxyManager { this.clearCache(); // 清除缓存 this.closeModal(); this.loadCodexKeys(); - this.showNotification('Codex配置添加成功', 'success'); + this.showNotification(i18n.t('notification.codex_config_added'), 'success'); } catch (error) { - this.showNotification(`添加Codex配置失败: ${error.message}`, 'error'); + this.showNotification(`${i18n.t('notification.add_failed')}: ${error.message}`, 'error'); } } @@ -2523,9 +2525,9 @@ class CLIProxyManager { this.clearCache(); // 清除缓存 this.closeModal(); this.loadCodexKeys(); - this.showNotification('Codex配置更新成功', 'success'); + this.showNotification(i18n.t('notification.codex_config_updated'), 'success'); } catch (error) { - this.showNotification(`更新Codex配置失败: ${error.message}`, 'error'); + this.showNotification(`${i18n.t('notification.update_failed')}: ${error.message}`, 'error'); } } @@ -2537,9 +2539,9 @@ class CLIProxyManager { await this.makeRequest(`/codex-api-key?api-key=${encodeURIComponent(apiKey)}`, { method: 'DELETE' }); this.clearCache(); // 清除缓存 this.loadCodexKeys(); - this.showNotification('Codex配置删除成功', 'success'); + this.showNotification(i18n.t('notification.codex_config_deleted'), 'success'); } catch (error) { - this.showNotification(`删除Codex配置失败: ${error.message}`, 'error'); + this.showNotification(`${i18n.t('notification.delete_failed')}: ${error.message}`, 'error'); } } @@ -2586,10 +2588,10 @@ class CLIProxyManager { ${config['proxy-url'] ? `
${i18n.t('common.proxy_url')}: ${this.escapeHtml(config['proxy-url'])}
` : ''}
- 成功: ${keyStats.success} + ${i18n.t('stats.success')}: ${keyStats.success} - 失败: ${keyStats.failure} + ${i18n.t('stats.failure')}: ${keyStats.failure}
@@ -2666,9 +2668,9 @@ class CLIProxyManager { this.clearCache(); // 清除缓存 this.closeModal(); this.loadClaudeKeys(); - this.showNotification('Claude配置添加成功', 'success'); + this.showNotification(i18n.t('notification.claude_config_added'), 'success'); } catch (error) { - this.showNotification(`添加Claude配置失败: ${error.message}`, 'error'); + this.showNotification(`${i18n.t('notification.add_failed')}: ${error.message}`, 'error'); } } @@ -2728,9 +2730,9 @@ class CLIProxyManager { this.clearCache(); // 清除缓存 this.closeModal(); this.loadClaudeKeys(); - this.showNotification('Claude配置更新成功', 'success'); + this.showNotification(i18n.t('notification.claude_config_updated'), 'success'); } catch (error) { - this.showNotification(`更新Claude配置失败: ${error.message}`, 'error'); + this.showNotification(`${i18n.t('notification.update_failed')}: ${error.message}`, 'error'); } } @@ -2742,9 +2744,9 @@ class CLIProxyManager { await this.makeRequest(`/claude-api-key?api-key=${encodeURIComponent(apiKey)}`, { method: 'DELETE' }); this.clearCache(); // 清除缓存 this.loadClaudeKeys(); - this.showNotification('Claude配置删除成功', 'success'); + this.showNotification(i18n.t('notification.claude_config_deleted'), 'success'); } catch (error) { - this.showNotification(`删除Claude配置失败: ${error.message}`, 'error'); + this.showNotification(`${i18n.t('notification.delete_failed')}: ${error.message}`, 'error'); } } @@ -2829,10 +2831,10 @@ class CLIProxyManager { ${this.renderOpenAIModelBadges(models)}
- 成功: ${totalSuccess} + ${i18n.t('stats.success')}: ${totalSuccess} - 失败: ${totalFailure} + ${i18n.t('stats.failure')}: ${totalFailure}
@@ -2927,9 +2929,9 @@ class CLIProxyManager { this.clearCache(); // 清除缓存 this.closeModal(); this.loadOpenAIProviders(); - this.showNotification('OpenAI提供商添加成功', 'success'); + this.showNotification(i18n.t('notification.openai_provider_added'), 'success'); } catch (error) { - this.showNotification(`添加OpenAI提供商失败: ${error.message}`, 'error'); + this.showNotification(`${i18n.t('notification.add_failed')}: ${error.message}`, 'error'); } } @@ -3021,9 +3023,9 @@ class CLIProxyManager { this.clearCache(); // 清除缓存 this.closeModal(); this.loadOpenAIProviders(); - this.showNotification('OpenAI提供商更新成功', 'success'); + this.showNotification(i18n.t('notification.openai_provider_updated'), 'success'); } catch (error) { - this.showNotification(`更新OpenAI提供商失败: ${error.message}`, 'error'); + this.showNotification(`${i18n.t('notification.update_failed')}: ${error.message}`, 'error'); } } @@ -3035,9 +3037,9 @@ class CLIProxyManager { await this.makeRequest(`/openai-compatibility?name=${encodeURIComponent(name)}`, { method: 'DELETE' }); this.clearCache(); // 清除缓存 this.loadOpenAIProviders(); - this.showNotification('OpenAI提供商删除成功', 'success'); + this.showNotification(i18n.t('notification.openai_provider_deleted'), 'success'); } catch (error) { - this.showNotification(`删除OpenAI提供商失败: ${error.message}`, 'error'); + this.showNotification(`${i18n.t('notification.delete_failed')}: ${error.message}`, 'error'); } } @@ -3124,10 +3126,10 @@ class CLIProxyManager {
${i18n.t('auth_files.file_modified')}: ${new Date(file.modtime).toLocaleString(i18n.currentLanguage === 'zh-CN' ? 'zh-CN' : 'en-US')}
- 成功: ${fileStats.success} + ${i18n.t('stats.success')}: ${fileStats.success} - 失败: ${fileStats.failure} + ${i18n.t('stats.failure')}: ${fileStats.failure}
@@ -3189,7 +3191,7 @@ class CLIProxyManager { this.loadAuthFiles(); this.showNotification(i18n.t('auth_files.upload_success'), 'success'); } catch (error) { - this.showNotification(`文件上传失败: ${error.message}`, 'error'); + this.showNotification(`${i18n.t('notification.upload_failed')}: ${error.message}`, 'error'); } // 清空文件输入 @@ -3219,7 +3221,7 @@ class CLIProxyManager { this.showNotification(i18n.t('auth_files.download_success'), 'success'); } catch (error) { - this.showNotification(`文件下载失败: ${error.message}`, 'error'); + this.showNotification(`${i18n.t('notification.download_failed')}: ${error.message}`, 'error'); } } @@ -3233,7 +3235,7 @@ class CLIProxyManager { this.loadAuthFiles(); this.showNotification(i18n.t('auth_files.delete_success'), 'success'); } catch (error) { - this.showNotification(`文件删除失败: ${error.message}`, 'error'); + this.showNotification(`${i18n.t('notification.delete_failed')}: ${error.message}`, 'error'); } } @@ -3247,7 +3249,7 @@ class CLIProxyManager { this.loadAuthFiles(); this.showNotification(`${i18n.t('auth_files.delete_all_success')} ${response.deleted} ${i18n.t('auth_files.files_count')}`, 'success'); } catch (error) { - this.showNotification(`删除文件失败: ${error.message}`, 'error'); + this.showNotification(`${i18n.t('notification.delete_failed')}: ${error.message}`, 'error'); } } @@ -3313,12 +3315,12 @@ class CLIProxyManager { if (urlInput && urlInput.value) { try { await navigator.clipboard.writeText(urlInput.value); - this.showNotification('链接已复制到剪贴板', 'success'); + this.showNotification(i18n.t('notification.link_copied'), 'success'); } catch (error) { // 降级方案:使用传统的复制方法 urlInput.select(); document.execCommand('copy'); - this.showNotification('链接已复制到剪贴板', 'success'); + this.showNotification(i18n.t('notification.link_copied'), 'success'); } } } @@ -3326,7 +3328,7 @@ class CLIProxyManager { // 开始轮询 OAuth 状态 startCodexOAuthPolling(state) { if (!state) { - this.showNotification('无法获取认证状态参数', 'error'); + this.showNotification(i18n.t('auth_login.missing_state'), 'error'); return; } @@ -3458,12 +3460,12 @@ class CLIProxyManager { if (urlInput && urlInput.value) { try { await navigator.clipboard.writeText(urlInput.value); - this.showNotification('链接已复制到剪贴板', 'success'); + this.showNotification(i18n.t('notification.link_copied'), 'success'); } catch (error) { // 降级方案:使用传统的复制方法 urlInput.select(); document.execCommand('copy'); - this.showNotification('链接已复制到剪贴板', 'success'); + this.showNotification(i18n.t('notification.link_copied'), 'success'); } } } @@ -3471,7 +3473,7 @@ class CLIProxyManager { // 开始轮询 Anthropic OAuth 状态 startAnthropicOAuthPolling(state) { if (!state) { - this.showNotification('无法获取认证状态参数', 'error'); + this.showNotification(i18n.t('auth_login.missing_state'), 'error'); return; } @@ -3612,12 +3614,12 @@ class CLIProxyManager { if (urlInput && urlInput.value) { try { await navigator.clipboard.writeText(urlInput.value); - this.showNotification('链接已复制到剪贴板', 'success'); + this.showNotification(i18n.t('notification.link_copied'), 'success'); } catch (error) { // 降级方案:使用传统的复制方法 urlInput.select(); document.execCommand('copy'); - this.showNotification('链接已复制到剪贴板', 'success'); + this.showNotification(i18n.t('notification.link_copied'), 'success'); } } } @@ -3625,7 +3627,7 @@ class CLIProxyManager { // 开始轮询 Gemini CLI OAuth 状态 startGeminiCliOAuthPolling(state) { if (!state) { - this.showNotification('无法获取认证状态参数', 'error'); + this.showNotification(i18n.t('auth_login.missing_state'), 'error'); return; } @@ -3757,12 +3759,12 @@ class CLIProxyManager { if (urlInput && urlInput.value) { try { await navigator.clipboard.writeText(urlInput.value); - this.showNotification('链接已复制到剪贴板', 'success'); + this.showNotification(i18n.t('notification.link_copied'), 'success'); } catch (error) { // 降级方案:使用传统的复制方法 urlInput.select(); document.execCommand('copy'); - this.showNotification('链接已复制到剪贴板', 'success'); + this.showNotification(i18n.t('notification.link_copied'), 'success'); } } } @@ -3770,7 +3772,7 @@ class CLIProxyManager { // 开始轮询 Qwen OAuth 状态 startQwenOAuthPolling(state) { if (!state) { - this.showNotification('无法获取认证状态参数', 'error'); + this.showNotification(i18n.t('auth_login.missing_state'), 'error'); return; } @@ -3902,12 +3904,12 @@ class CLIProxyManager { if (urlInput && urlInput.value) { try { await navigator.clipboard.writeText(urlInput.value); - this.showNotification('链接已复制到剪贴板', 'success'); + this.showNotification(i18n.t('notification.link_copied'), 'success'); } catch (error) { // 降级方案:使用传统的复制方法 urlInput.select(); document.execCommand('copy'); - this.showNotification('链接已复制到剪贴板', 'success'); + this.showNotification(i18n.t('notification.link_copied'), 'success'); } } } @@ -3915,7 +3917,7 @@ class CLIProxyManager { // 开始轮询 iFlow OAuth 状态 startIflowOAuthPolling(state) { if (!state) { - this.showNotification('无法获取认证状态参数', 'error'); + this.showNotification(i18n.t('auth_login.missing_state'), 'error'); return; } diff --git a/i18n.js b/i18n.js index 275a726..c9580de 100644 --- a/i18n.js +++ b/i18n.js @@ -38,6 +38,8 @@ const i18n = { 'common.base_url': '地址', 'common.proxy_url': '代理', 'common.alias': '别名', + 'common.failure': '失败', + 'common.unknown_error': '未知错误', // 页面标题 'title.main': 'CLI Proxy API Management Center', @@ -275,6 +277,7 @@ const i18n = { 'auth_login.qwen_oauth_status_error': '认证失败:', 'auth_login.qwen_oauth_start_error': '启动 Qwen OAuth 失败:', 'auth_login.qwen_oauth_polling_error': '检查认证状态失败:', + 'auth_login.missing_state': '无法获取认证状态参数', // iFlow OAuth 'auth_login.iflow_oauth_title': 'iFlow OAuth', @@ -308,6 +311,8 @@ const i18n = { 'usage_stats.tokens_count': 'Token数量', 'usage_stats.models': '模型统计', 'usage_stats.success_rate': '成功率', + 'stats.success': '成功', + 'stats.failure': '失败', // 日志查看 'logs.title': '日志查看', @@ -347,6 +352,7 @@ const i18n = { 'config_management.status_save_failed': '保存失败', 'config_management.save_success': '配置已保存', 'config_management.error_yaml_not_supported': '服务器未返回 YAML 格式,请确认 /config.yaml 接口可用', + 'config_management.editor_placeholder': 'key: value', // 系统信息 'system_info.title': '系统信息', @@ -402,6 +408,7 @@ const i18n = { 'notification.gemini_api_key': 'Gemini API密钥', 'notification.codex_api_key': 'Codex API密钥', 'notification.claude_api_key': 'Claude API密钥', + 'notification.link_copied': '链接已复制到剪贴板', // 语言切换 'language.switch': '语言', @@ -416,6 +423,10 @@ const i18n = { 'theme.switch_to_dark': '切换到暗色模式', 'theme.auto': '跟随系统', + // 侧边栏 + 'sidebar.toggle_expand': '展开侧边栏', + 'sidebar.toggle_collapse': '收起侧边栏', + // 页脚 'footer.version': '版本', 'footer.author': '作者' @@ -453,6 +464,8 @@ const i18n = { 'common.base_url': 'Address', 'common.proxy_url': 'Proxy', 'common.alias': 'Alias', + 'common.failure': 'Failure', + 'common.unknown_error': 'Unknown error', // Page titles 'title.main': 'CLI Proxy API Management Center', @@ -689,6 +702,7 @@ const i18n = { 'auth_login.qwen_oauth_status_error': 'Authentication failed:', 'auth_login.qwen_oauth_start_error': 'Failed to start Qwen OAuth:', 'auth_login.qwen_oauth_polling_error': 'Failed to check authentication status:', + 'auth_login.missing_state': 'Unable to retrieve authentication state parameter', // iFlow OAuth 'auth_login.iflow_oauth_title': 'iFlow OAuth', @@ -722,6 +736,8 @@ const i18n = { 'usage_stats.tokens_count': 'Token Count', 'usage_stats.models': 'Model Statistics', 'usage_stats.success_rate': 'Success Rate', + 'stats.success': 'Success', + 'stats.failure': 'Failure', // Logs viewer 'logs.title': 'Logs Viewer', @@ -761,6 +777,7 @@ const i18n = { 'config_management.status_save_failed': 'Save failed', 'config_management.save_success': 'Configuration saved successfully', 'config_management.error_yaml_not_supported': 'Server did not return YAML. Verify the /config.yaml endpoint is available.', + 'config_management.editor_placeholder': 'key: value', // System info 'system_info.title': 'System Information', @@ -816,6 +833,7 @@ const i18n = { 'notification.gemini_api_key': 'Gemini API key', 'notification.codex_api_key': 'Codex API key', 'notification.claude_api_key': 'Claude API key', + 'notification.link_copied': 'Link copied to clipboard', // Language switch 'language.switch': 'Language', @@ -830,6 +848,10 @@ const i18n = { 'theme.switch_to_dark': 'Switch to dark mode', 'theme.auto': 'Follow system', + // Sidebar + 'sidebar.toggle_expand': 'Expand sidebar', + 'sidebar.toggle_collapse': 'Collapse sidebar', + // Footer 'footer.version': 'Version', 'footer.author': 'Author' @@ -879,6 +901,30 @@ const i18n = { } }); + // 更新所有包含 data-i18n-placeholder 的输入框占位符 + document.querySelectorAll('[data-i18n-placeholder]').forEach(element => { + const key = element.getAttribute('data-i18n-placeholder'); + element.placeholder = this.t(key); + }); + + // 更新 data-i18n-title + document.querySelectorAll('[data-i18n-title]').forEach(element => { + const key = element.getAttribute('data-i18n-title'); + element.title = this.t(key); + }); + + // 更新 data-i18n-tooltip + document.querySelectorAll('[data-i18n-tooltip]').forEach(element => { + const key = element.getAttribute('data-i18n-tooltip'); + element.setAttribute('data-tooltip', this.t(key)); + }); + + // 更新 data-i18n-text(常用于按钮或标签) + document.querySelectorAll('[data-i18n-text]').forEach(element => { + const key = element.getAttribute('data-i18n-text'); + element.textContent = this.t(key); + }); + // 更新所有带有 data-i18n-html 属性的元素(支持HTML) document.querySelectorAll('[data-i18n-html]').forEach(element => { const key = element.getAttribute('data-i18n-html'); diff --git a/index.html b/index.html index 37e30fd..885105b 100644 --- a/index.html +++ b/index.html @@ -77,8 +77,7 @@
- + @@ -123,7 +121,7 @@ -
@@ -161,29 +159,29 @@