From 19ad0cd5f8ad27b571852ec3ff9f47dc0bda2617 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 11 Sep 2025 14:26:42 +0000 Subject: [PATCH 01/40] chore: sync VERSION file with release v1.1.138 [skip ci] --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 88cf65eb..93599c69 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.1.137 +1.1.138 From 504b9e3ea78a38d71d3518f17a6f645d549c23c3 Mon Sep 17 00:00:00 2001 From: Wangnov Date: Mon, 8 Sep 2025 15:39:25 +0800 Subject: [PATCH 02/40] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0vue-i18n?= =?UTF-8?q?=E4=BE=9D=E8=B5=96=E5=92=8C=E5=9F=BA=E7=A1=80=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 安装vue-i18n@9.x作为项目国际化解决方案 - 在main.js中集成i18n插件到Vue应用 - 配置支持简体中文、繁体中文、英文三种语言 --- web/admin-spa/package-lock.json | 70 ++++++++++++++++++++++++++++++++- web/admin-spa/package.json | 1 + web/admin-spa/src/main.js | 4 ++ 3 files changed, 73 insertions(+), 2 deletions(-) diff --git a/web/admin-spa/package-lock.json b/web/admin-spa/package-lock.json index efe90dff..0b959aa3 100644 --- a/web/admin-spa/package-lock.json +++ b/web/admin-spa/package-lock.json @@ -15,6 +15,7 @@ "element-plus": "^2.4.4", "pinia": "^2.1.7", "vue": "^3.3.4", + "vue-i18n": "^9.14.5", "vue-router": "^4.2.5", "xlsx": "^0.18.5", "xlsx-js-style": "^1.2.0" @@ -657,6 +658,50 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/@intlify/core-base": { + "version": "9.14.5", + "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.14.5.tgz", + "integrity": "sha512-5ah5FqZG4pOoHjkvs8mjtv+gPKYU0zCISaYNjBNNqYiaITxW8ZtVih3GS/oTOqN8d9/mDLyrjD46GBApNxmlsA==", + "license": "MIT", + "dependencies": { + "@intlify/message-compiler": "9.14.5", + "@intlify/shared": "9.14.5" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@intlify/message-compiler": { + "version": "9.14.5", + "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.14.5.tgz", + "integrity": "sha512-IHzgEu61/YIpQV5Pc3aRWScDcnFKWvQA9kigcINcCBXN8mbW+vk9SK+lDxA6STzKQsVJxUPg9ACC52pKKo3SVQ==", + "license": "MIT", + "dependencies": { + "@intlify/shared": "9.14.5", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@intlify/shared": { + "version": "9.14.5", + "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.14.5.tgz", + "integrity": "sha512-9gB+E53BYuAEMhbCAxVgG38EZrk59sxBtv3jSizNL2hEWlgjBjAw1AwpLHtNaeda12pe6W20OGEa0TwuMSRbyQ==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmmirror.com/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -3789,9 +3834,10 @@ }, "node_modules/prettier-plugin-tailwindcss": { "version": "0.6.14", - "resolved": "https://registry.npmmirror.com/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.14.tgz", + "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.14.tgz", "integrity": "sha512-pi2e/+ZygeIqntN+vC573BcW5Cve8zUB0SSAGxqpB4f96boZF4M3phPVoOFCeypwkpRYdi7+jQ5YJJUwrkGUAg==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.21.3" }, @@ -5181,6 +5227,26 @@ "eslint": ">=6.0.0" } }, + "node_modules/vue-i18n": { + "version": "9.14.5", + "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.14.5.tgz", + "integrity": "sha512-0jQ9Em3ymWngyiIkj0+c/k7WgaPO+TNzjKSNq9BvBQaKJECqn9cd9fL4tkDhB5G1QBskGl9YxxbDAhgbFtpe2g==", + "license": "MIT", + "dependencies": { + "@intlify/core-base": "9.14.5", + "@intlify/shared": "9.14.5", + "@vue/devtools-api": "^6.5.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, "node_modules/vue-router": { "version": "4.5.1", "resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.5.1.tgz", @@ -5378,7 +5444,7 @@ }, "node_modules/xlsx-js-style": { "version": "1.2.0", - "resolved": "https://registry.npmmirror.com/xlsx-js-style/-/xlsx-js-style-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/xlsx-js-style/-/xlsx-js-style-1.2.0.tgz", "integrity": "sha512-DDT4FXFSWfT4DXMSok/m3TvmP1gvO3dn0Eu/c+eXHW5Kzmp7IczNkxg/iEPnImbG9X0Vb8QhROda5eatSR/97Q==", "license": "Apache-2.0", "dependencies": { diff --git a/web/admin-spa/package.json b/web/admin-spa/package.json index af353d80..5feb58d8 100644 --- a/web/admin-spa/package.json +++ b/web/admin-spa/package.json @@ -18,6 +18,7 @@ "element-plus": "^2.4.4", "pinia": "^2.1.7", "vue": "^3.3.4", + "vue-i18n": "^9.14.5", "vue-router": "^4.2.5", "xlsx": "^0.18.5", "xlsx-js-style": "^1.2.0" diff --git a/web/admin-spa/src/main.js b/web/admin-spa/src/main.js index 79181b6b..513e5947 100644 --- a/web/admin-spa/src/main.js +++ b/web/admin-spa/src/main.js @@ -7,6 +7,7 @@ import 'element-plus/theme-chalk/dark/css-vars.css' import App from './App.vue' import router from './router' import { useUserStore } from './stores/user' +import i18n from './i18n' import './assets/styles/main.css' import './assets/styles/global.css' @@ -20,6 +21,9 @@ app.use(pinia) // 使用路由 app.use(router) +// 使用Vue I18n +app.use(i18n) + // 使用Element Plus app.use(ElementPlus, { locale: zhCn From cb1b7bc0e3b50f28c3ace09ebd67b3a53d9e61cb Mon Sep 17 00:00:00 2001 From: Wangnov Date: Mon, 8 Sep 2025 15:40:48 +0800 Subject: [PATCH 03/40] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0i18n=E6=A0=B8?= =?UTF-8?q?=E5=BF=83=E9=85=8D=E7=BD=AE=E5=92=8C=E8=AF=AD=E8=A8=80=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 创建i18n配置系统,支持简体中文/繁体中文/英文三种语言 - 实现浏览器语言自动检测和localStorage持久化 - 添加基础翻译文件,包含common、language、header、apiStats模块 - 创建locale store使用Pinia管理语言状态 - 配置语言标识符为纯文字:简/繁/EN,去除国旗emoji --- web/admin-spa/src/i18n/index.js | 68 +++++++++++++++++++++++++ web/admin-spa/src/i18n/locales/en.js | 38 ++++++++++++++ web/admin-spa/src/i18n/locales/zh-cn.js | 38 ++++++++++++++ web/admin-spa/src/i18n/locales/zh-tw.js | 38 ++++++++++++++ web/admin-spa/src/stores/locale.js | 42 +++++++++++++++ 5 files changed, 224 insertions(+) create mode 100644 web/admin-spa/src/i18n/index.js create mode 100644 web/admin-spa/src/i18n/locales/en.js create mode 100644 web/admin-spa/src/i18n/locales/zh-cn.js create mode 100644 web/admin-spa/src/i18n/locales/zh-tw.js create mode 100644 web/admin-spa/src/stores/locale.js diff --git a/web/admin-spa/src/i18n/index.js b/web/admin-spa/src/i18n/index.js new file mode 100644 index 00000000..4bd2028f --- /dev/null +++ b/web/admin-spa/src/i18n/index.js @@ -0,0 +1,68 @@ +import { createI18n } from 'vue-i18n' +import zhCn from './locales/zh-cn.js' +import zhTw from './locales/zh-tw.js' +import en from './locales/en.js' + +// 获取浏览器语言设置 +function getBrowserLocale() { + const navigatorLocale = navigator.languages ? navigator.languages[0] : navigator.language + + if (!navigatorLocale) { + return 'zh-cn' + } + + const trimmedLocale = navigatorLocale.trim().split(';')[0].toLowerCase() + + if (trimmedLocale.includes('zh')) { + if ( + trimmedLocale.includes('tw') || + trimmedLocale.includes('hk') || + trimmedLocale.includes('mo') + ) { + return 'zh-tw' + } + return 'zh-cn' + } + + if (trimmedLocale.includes('en')) { + return 'en' + } + + return 'zh-cn' // 默认简体中文 +} + +// 获取保存的语言设置或浏览器语言 +const savedLocale = localStorage.getItem('app-locale') +const defaultLocale = savedLocale || getBrowserLocale() + +export const SUPPORTED_LOCALES = { + 'zh-cn': { + name: '简体中文', + flag: '简', + shortName: '简' + }, + 'zh-tw': { + name: '繁體中文', + flag: '繁', + shortName: '繁' + }, + en: { + name: 'English', + flag: 'EN', + shortName: 'EN' + } +} + +export const i18n = createI18n({ + legacy: false, // 使用 Composition API 模式 + locale: defaultLocale, + fallbackLocale: 'zh-cn', + messages: { + 'zh-cn': zhCn, + 'zh-tw': zhTw, + en: en + }, + globalInjection: true // 全局注入 $t 函数 +}) + +export default i18n diff --git a/web/admin-spa/src/i18n/locales/en.js b/web/admin-spa/src/i18n/locales/en.js new file mode 100644 index 00000000..671af8ec --- /dev/null +++ b/web/admin-spa/src/i18n/locales/en.js @@ -0,0 +1,38 @@ +export default { + common: { + save: 'Save', + cancel: 'Cancel', + confirm: 'Confirm', + loading: 'Loading...', + edit: 'Edit', + delete: 'Delete', + create: 'Create', + update: 'Update', + search: 'Search', + reset: 'Reset' + }, + language: { + zh: '简体中文', + 'zh-tw': '繁體中文', + en: 'English', + current: 'Current Language', + switch: 'Switch Language' + }, + header: { + adminPanel: 'Admin Panel', + userMenu: 'User Menu', + logout: 'Logout', + settings: 'Settings' + }, + apiStats: { + title: 'API Key Usage Statistics', + tutorialTitle: 'Tutorial', + userLogin: 'User Login', + adminPanel: 'Admin Panel', + statsQuery: 'Statistics Query', + tutorial: 'Tutorial', + timeRange: 'Statistics Time Range', + today: 'Today', + thisMonth: 'This Month' + } +} diff --git a/web/admin-spa/src/i18n/locales/zh-cn.js b/web/admin-spa/src/i18n/locales/zh-cn.js new file mode 100644 index 00000000..38b3d997 --- /dev/null +++ b/web/admin-spa/src/i18n/locales/zh-cn.js @@ -0,0 +1,38 @@ +export default { + common: { + save: '保存', + cancel: '取消', + confirm: '确认', + loading: '加载中...', + edit: '编辑', + delete: '删除', + create: '创建', + update: '更新', + search: '搜索', + reset: '重置' + }, + language: { + zh: '简体中文', + 'zh-tw': '繁體中文', + en: 'English', + current: '当前语言', + switch: '切换语言' + }, + header: { + adminPanel: '管理后台', + userMenu: '用户菜单', + logout: '退出登录', + settings: '系统设置' + }, + apiStats: { + title: 'API Key 使用统计', + tutorialTitle: '使用教程', + userLogin: '用户登录', + adminPanel: '管理后台', + statsQuery: '统计查询', + tutorial: '使用教程', + timeRange: '统计时间范围', + today: '今日', + thisMonth: '本月' + } +} diff --git a/web/admin-spa/src/i18n/locales/zh-tw.js b/web/admin-spa/src/i18n/locales/zh-tw.js new file mode 100644 index 00000000..4290241d --- /dev/null +++ b/web/admin-spa/src/i18n/locales/zh-tw.js @@ -0,0 +1,38 @@ +export default { + common: { + save: '保存', + cancel: '取消', + confirm: '確認', + loading: '載入中...', + edit: '編輯', + delete: '刪除', + create: '建立', + update: '更新', + search: '搜尋', + reset: '重置' + }, + language: { + zh: '簡體中文', + 'zh-tw': '繁體中文', + en: 'English', + current: '當前語言', + switch: '切換語言' + }, + header: { + adminPanel: '管理後台', + userMenu: '用戶選單', + logout: '退出登錄', + settings: '系統設置' + }, + apiStats: { + title: 'API Key 使用統計', + tutorialTitle: '使用教學', + userLogin: '用戶登錄', + adminPanel: '管理後台', + statsQuery: '統計查詢', + tutorial: '使用教學', + timeRange: '統計時間範圍', + today: '今日', + thisMonth: '本月' + } +} diff --git a/web/admin-spa/src/stores/locale.js b/web/admin-spa/src/stores/locale.js new file mode 100644 index 00000000..7b6ad24d --- /dev/null +++ b/web/admin-spa/src/stores/locale.js @@ -0,0 +1,42 @@ +import { defineStore } from 'pinia' +import { ref } from 'vue' +import { i18n, SUPPORTED_LOCALES } from '@/i18n' + +export const useLocaleStore = defineStore('locale', () => { + const currentLocale = ref(i18n.global.locale.value) + + // 切换语言 + const setLocale = (locale) => { + if (!SUPPORTED_LOCALES[locale]) { + console.warn(`Unsupported locale: ${locale}`) + return + } + + currentLocale.value = locale + i18n.global.locale.value = locale + localStorage.setItem('app-locale', locale) + + // 更新HTML lang属性 + document.documentElement.setAttribute('lang', locale) + } + + // 获取当前语言信息 + const getCurrentLocaleInfo = () => { + return SUPPORTED_LOCALES[currentLocale.value] || SUPPORTED_LOCALES['zh-cn'] + } + + // 获取所有支持的语言 + const getSupportedLocales = () => { + return Object.entries(SUPPORTED_LOCALES).map(([key, value]) => ({ + code: key, + ...value + })) + } + + return { + currentLocale, + setLocale, + getCurrentLocaleInfo, + getSupportedLocales + } +}) From f4b873315a785231b2b0c7ac87b591353eacddc9 Mon Sep 17 00:00:00 2001 From: Wangnov Date: Mon, 8 Sep 2025 15:41:09 +0800 Subject: [PATCH 04/40] =?UTF-8?q?feat:=20=E5=88=9B=E5=BB=BA=E8=AF=AD?= =?UTF-8?q?=E8=A8=80=E5=88=87=E6=8D=A2=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 创建LanguageSwitch.vue组件,支持dropdown/button/icon三种显示模式 - 实现点击外部自动关闭下拉菜单功能 - 支持size属性控制组件大小(small/medium/large) - 集成locale store实现语言切换和状态同步 - 使用纯文字标识符显示:简/繁/EN,提供清晰的语言选择界面 - 下拉菜单显示完整语言名称:简体中文/繁體中文/English --- .../src/components/common/LanguageSwitch.vue | 186 ++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 web/admin-spa/src/components/common/LanguageSwitch.vue diff --git a/web/admin-spa/src/components/common/LanguageSwitch.vue b/web/admin-spa/src/components/common/LanguageSwitch.vue new file mode 100644 index 00000000..f08e9340 --- /dev/null +++ b/web/admin-spa/src/components/common/LanguageSwitch.vue @@ -0,0 +1,186 @@ + + + + + From 87591365bc051e74918597952c5208c8b71764cc Mon Sep 17 00:00:00 2001 From: Wangnov Date: Mon, 8 Sep 2025 15:41:29 +0800 Subject: [PATCH 05/40] =?UTF-8?q?feat:=20=E5=9C=A8AppHeader=E4=B8=AD?= =?UTF-8?q?=E9=9B=86=E6=88=90=E8=AF=AD=E8=A8=80=E5=88=87=E6=8D=A2=E7=BB=84?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在AppHeader主导航栏中添加LanguageSwitch组件 - 使用dropdown模式和medium尺寸提供最佳用户体验 - 与现有ThemeToggle组件并列放置,保持界面一致性 - 为管理后台提供全局语言切换功能 --- web/admin-spa/src/components/layout/AppHeader.vue | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/web/admin-spa/src/components/layout/AppHeader.vue b/web/admin-spa/src/components/layout/AppHeader.vue index c66eed9f..81c49b03 100644 --- a/web/admin-spa/src/components/layout/AppHeader.vue +++ b/web/admin-spa/src/components/layout/AppHeader.vue @@ -38,6 +38,11 @@
+ +
+ +
+
@@ -273,6 +278,7 @@ import { showToast } from '@/utils/toast' import { apiClient } from '@/config/api' import LogoTitle from '@/components/common/LogoTitle.vue' import ThemeToggle from '@/components/common/ThemeToggle.vue' +import LanguageSwitch from '@/components/common/LanguageSwitch.vue' const router = useRouter() const authStore = useAuthStore() From 1eadc9459238aa192e735c281e2f9e37fd78ad97 Mon Sep 17 00:00:00 2001 From: Wangnov Date: Mon, 8 Sep 2025 15:42:23 +0800 Subject: [PATCH 06/40] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0ApiStatsView?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E5=AE=8C=E6=95=B4=E5=9B=BD=E9=99=85=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 集成vue-i18n到ApiStatsView,支持动态语言切换 - 国际化所有用户界面文本:页面标题、按钮、Tab标签、时间选择器 - 实现LogoTitle动态subtitle,根据当前tab显示对应语言的标题 - 添加语言切换组件到页面header,与主题切换并列显示 - 实现教程内容的整体替换机制,支持基于语言的动态组件选择 - 确保用户登录、管理后台、统计查询等核心功能完全本地化 --- web/admin-spa/src/views/ApiStatsView.vue | 43 ++++++++++++++++++------ 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/web/admin-spa/src/views/ApiStatsView.vue b/web/admin-spa/src/views/ApiStatsView.vue index f26df4e5..104dd3a0 100644 --- a/web/admin-spa/src/views/ApiStatsView.vue +++ b/web/admin-spa/src/views/ApiStatsView.vue @@ -6,10 +6,15 @@
+ +
+ +
+
@@ -28,7 +33,7 @@ to="/user-login" > - 用户登录 + {{ t('apiStats.userLogin') }} - 管理后台 + {{ t('apiStats.adminPanel') }}
@@ -54,14 +59,14 @@ @click="currentTab = 'stats'" > - 统计查询 + {{ t('apiStats.statsQuery') }}
@@ -93,7 +98,7 @@
统计时间范围{{ t('apiStats.timeRange') }}
@@ -104,7 +109,7 @@ @click="switchPeriod('daily')" > - 今日 + {{ t('apiStats.today') }}
@@ -140,7 +145,7 @@
- +
@@ -150,21 +155,28 @@ import { ref, onMounted, onUnmounted, watch, computed } from 'vue' import { useRoute } from 'vue-router' import { storeToRefs } from 'pinia' +import { useI18n } from 'vue-i18n' import { useApiStatsStore } from '@/stores/apistats' import { useThemeStore } from '@/stores/theme' +import { useLocaleStore } from '@/stores/locale' import LogoTitle from '@/components/common/LogoTitle.vue' import ThemeToggle from '@/components/common/ThemeToggle.vue' +import LanguageSwitch from '@/components/common/LanguageSwitch.vue' import ApiKeyInput from '@/components/apistats/ApiKeyInput.vue' import StatsOverview from '@/components/apistats/StatsOverview.vue' import TokenDistribution from '@/components/apistats/TokenDistribution.vue' import LimitConfig from '@/components/apistats/LimitConfig.vue' import AggregatedStatsCard from '@/components/apistats/AggregatedStatsCard.vue' import ModelUsageStats from '@/components/apistats/ModelUsageStats.vue' -import TutorialView from './TutorialView.vue' +import TutorialViewZhCn from './TutorialView.vue' +import TutorialViewZhTw from './TutorialView.vue' +import TutorialViewEn from './TutorialView.vue' const route = useRoute() +const { t } = useI18n() const apiStatsStore = useApiStatsStore() const themeStore = useThemeStore() +const localeStore = useLocaleStore() // 当前标签页 const currentTab = ref('stats') @@ -172,6 +184,17 @@ const currentTab = ref('stats') // 主题相关 const isDarkMode = computed(() => themeStore.isDarkMode) +// 根据当前语言选择教程组件 +const currentTutorialComponent = computed(() => { + const locale = localeStore.currentLocale + const components = { + 'zh-cn': TutorialViewZhCn, + 'zh-tw': TutorialViewZhTw, + 'en': TutorialViewEn + } + return components[locale] || TutorialViewZhCn +}) + const { apiKey, apiId, From 74d37486b883b7b8bd132996743aa9054b87359c Mon Sep 17 00:00:00 2001 From: Wangnov Date: Mon, 8 Sep 2025 15:42:40 +0800 Subject: [PATCH 07/40] =?UTF-8?q?chore:=20=E6=B7=BB=E5=8A=A0Vue=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E5=92=8Ccomposables=E8=87=AA=E5=8A=A8=E5=AF=BC?= =?UTF-8?q?=E5=85=A5=E7=B1=BB=E5=9E=8B=E5=AE=9A=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加unplugin-vue-components自动生成的组件类型定义 - 添加unplugin-auto-import自动生成的composables类型定义 - 为TypeScript提供更好的类型支持和IDE智能提示 --- web/admin-spa/auto-imports.d.ts | 87 +++++++++++++++++++++++++++++++++ web/admin-spa/components.d.ts | 51 +++++++++++++++++++ 2 files changed, 138 insertions(+) create mode 100644 web/admin-spa/auto-imports.d.ts create mode 100644 web/admin-spa/components.d.ts diff --git a/web/admin-spa/auto-imports.d.ts b/web/admin-spa/auto-imports.d.ts new file mode 100644 index 00000000..a606bd4d --- /dev/null +++ b/web/admin-spa/auto-imports.d.ts @@ -0,0 +1,87 @@ +/* eslint-disable */ +/* prettier-ignore */ +// @ts-nocheck +// noinspection JSUnusedGlobalSymbols +// Generated by unplugin-auto-import +export {} +declare global { + const EffectScope: typeof import('vue')['EffectScope'] + const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate'] + const computed: typeof import('vue')['computed'] + const createApp: typeof import('vue')['createApp'] + const createPinia: typeof import('pinia')['createPinia'] + const customRef: typeof import('vue')['customRef'] + const defineAsyncComponent: typeof import('vue')['defineAsyncComponent'] + const defineComponent: typeof import('vue')['defineComponent'] + const defineStore: typeof import('pinia')['defineStore'] + const effectScope: typeof import('vue')['effectScope'] + const getActivePinia: typeof import('pinia')['getActivePinia'] + const getCurrentInstance: typeof import('vue')['getCurrentInstance'] + const getCurrentScope: typeof import('vue')['getCurrentScope'] + const h: typeof import('vue')['h'] + const inject: typeof import('vue')['inject'] + const isProxy: typeof import('vue')['isProxy'] + const isReactive: typeof import('vue')['isReactive'] + const isReadonly: typeof import('vue')['isReadonly'] + const isRef: typeof import('vue')['isRef'] + const mapActions: typeof import('pinia')['mapActions'] + const mapGetters: typeof import('pinia')['mapGetters'] + const mapState: typeof import('pinia')['mapState'] + const mapStores: typeof import('pinia')['mapStores'] + const mapWritableState: typeof import('pinia')['mapWritableState'] + const markRaw: typeof import('vue')['markRaw'] + const nextTick: typeof import('vue')['nextTick'] + const onActivated: typeof import('vue')['onActivated'] + const onBeforeMount: typeof import('vue')['onBeforeMount'] + const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave'] + const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate'] + const onBeforeUnmount: typeof import('vue')['onBeforeUnmount'] + const onBeforeUpdate: typeof import('vue')['onBeforeUpdate'] + const onDeactivated: typeof import('vue')['onDeactivated'] + const onErrorCaptured: typeof import('vue')['onErrorCaptured'] + const onMounted: typeof import('vue')['onMounted'] + const onRenderTracked: typeof import('vue')['onRenderTracked'] + const onRenderTriggered: typeof import('vue')['onRenderTriggered'] + const onScopeDispose: typeof import('vue')['onScopeDispose'] + const onServerPrefetch: typeof import('vue')['onServerPrefetch'] + const onUnmounted: typeof import('vue')['onUnmounted'] + const onUpdated: typeof import('vue')['onUpdated'] + const onWatcherCleanup: typeof import('vue')['onWatcherCleanup'] + const provide: typeof import('vue')['provide'] + const reactive: typeof import('vue')['reactive'] + const readonly: typeof import('vue')['readonly'] + const ref: typeof import('vue')['ref'] + const resolveComponent: typeof import('vue')['resolveComponent'] + const setActivePinia: typeof import('pinia')['setActivePinia'] + const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix'] + const shallowReactive: typeof import('vue')['shallowReactive'] + const shallowReadonly: typeof import('vue')['shallowReadonly'] + const shallowRef: typeof import('vue')['shallowRef'] + const storeToRefs: typeof import('pinia')['storeToRefs'] + const toRaw: typeof import('vue')['toRaw'] + const toRef: typeof import('vue')['toRef'] + const toRefs: typeof import('vue')['toRefs'] + const toValue: typeof import('vue')['toValue'] + const triggerRef: typeof import('vue')['triggerRef'] + const unref: typeof import('vue')['unref'] + const useAttrs: typeof import('vue')['useAttrs'] + const useCssModule: typeof import('vue')['useCssModule'] + const useCssVars: typeof import('vue')['useCssVars'] + const useId: typeof import('vue')['useId'] + const useLink: typeof import('vue-router')['useLink'] + const useModel: typeof import('vue')['useModel'] + const useRoute: typeof import('vue-router')['useRoute'] + const useRouter: typeof import('vue-router')['useRouter'] + const useSlots: typeof import('vue')['useSlots'] + const useTemplateRef: typeof import('vue')['useTemplateRef'] + const watch: typeof import('vue')['watch'] + const watchEffect: typeof import('vue')['watchEffect'] + const watchPostEffect: typeof import('vue')['watchPostEffect'] + const watchSyncEffect: typeof import('vue')['watchSyncEffect'] +} +// for type re-export +declare global { + // @ts-ignore + export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue' + import('vue') +} diff --git a/web/admin-spa/components.d.ts b/web/admin-spa/components.d.ts new file mode 100644 index 00000000..b698e38b --- /dev/null +++ b/web/admin-spa/components.d.ts @@ -0,0 +1,51 @@ +/* eslint-disable */ +/* prettier-ignore */ +// @ts-nocheck +// Generated by unplugin-vue-components +// Read more: https://github.com/vuejs/core/pull/3399 +export {} + +declare module 'vue' { + export interface GlobalComponents { + AccountForm: typeof import('./src/components/accounts/AccountForm.vue')['default'] + AccountSelector: typeof import('./src/components/common/AccountSelector.vue')['default'] + AggregatedStatsCard: typeof import('./src/components/apistats/AggregatedStatsCard.vue')['default'] + ApiKeyInput: typeof import('./src/components/apistats/ApiKeyInput.vue')['default'] + AppHeader: typeof import('./src/components/layout/AppHeader.vue')['default'] + BatchApiKeyModal: typeof import('./src/components/apikeys/BatchApiKeyModal.vue')['default'] + BatchEditApiKeyModal: typeof import('./src/components/apikeys/BatchEditApiKeyModal.vue')['default'] + ChangeRoleModal: typeof import('./src/components/admin/ChangeRoleModal.vue')['default'] + ConfirmDialog: typeof import('./src/components/common/ConfirmDialog.vue')['default'] + ConfirmModal: typeof import('./src/components/common/ConfirmModal.vue')['default'] + CreateApiKeyModal: typeof import('./src/components/apikeys/CreateApiKeyModal.vue')['default'] + CustomDropdown: typeof import('./src/components/common/CustomDropdown.vue')['default'] + EditApiKeyModal: typeof import('./src/components/apikeys/EditApiKeyModal.vue')['default'] + ExpiryEditModal: typeof import('./src/components/apikeys/ExpiryEditModal.vue')['default'] + GroupManagementModal: typeof import('./src/components/accounts/GroupManagementModal.vue')['default'] + LanguageSwitch: typeof import('./src/components/common/LanguageSwitch.vue')['default'] + LimitConfig: typeof import('./src/components/apistats/LimitConfig.vue')['default'] + LogoTitle: typeof import('./src/components/common/LogoTitle.vue')['default'] + MainLayout: typeof import('./src/components/layout/MainLayout.vue')['default'] + ModelDistribution: typeof import('./src/components/dashboard/ModelDistribution.vue')['default'] + ModelUsageStats: typeof import('./src/components/apistats/ModelUsageStats.vue')['default'] + NewApiKeyModal: typeof import('./src/components/apikeys/NewApiKeyModal.vue')['default'] + OAuthFlow: typeof import('./src/components/accounts/OAuthFlow.vue')['default'] + ProxyConfig: typeof import('./src/components/accounts/ProxyConfig.vue')['default'] + RenewApiKeyModal: typeof import('./src/components/apikeys/RenewApiKeyModal.vue')['default'] + RouterLink: typeof import('vue-router')['RouterLink'] + RouterView: typeof import('vue-router')['RouterView'] + StatCard: typeof import('./src/components/common/StatCard.vue')['default'] + StatsOverview: typeof import('./src/components/apistats/StatsOverview.vue')['default'] + TabBar: typeof import('./src/components/layout/TabBar.vue')['default'] + ThemeToggle: typeof import('./src/components/common/ThemeToggle.vue')['default'] + ToastNotification: typeof import('./src/components/common/ToastNotification.vue')['default'] + TokenDistribution: typeof import('./src/components/apistats/TokenDistribution.vue')['default'] + UsageDetailModal: typeof import('./src/components/apikeys/UsageDetailModal.vue')['default'] + UsageTrend: typeof import('./src/components/dashboard/UsageTrend.vue')['default'] + UserApiKeysManager: typeof import('./src/components/user/UserApiKeysManager.vue')['default'] + UserUsageStats: typeof import('./src/components/user/UserUsageStats.vue')['default'] + UserUsageStatsModal: typeof import('./src/components/admin/UserUsageStatsModal.vue')['default'] + ViewApiKeyModal: typeof import('./src/components/user/ViewApiKeyModal.vue')['default'] + WindowCountdown: typeof import('./src/components/apikeys/WindowCountdown.vue')['default'] + } +} From c7e1a3429d36cff800f95d08c9d18d1c50ac0043 Mon Sep 17 00:00:00 2001 From: Wangnov Date: Mon, 8 Sep 2025 17:50:17 +0800 Subject: [PATCH 08/40] =?UTF-8?q?feat:=20=E5=AE=8C=E5=96=84=E6=95=99?= =?UTF-8?q?=E7=A8=8B=E7=B3=BB=E7=BB=9F=E5=9B=BD=E9=99=85=E5=8C=96=E6=9E=B6?= =?UTF-8?q?=E6=9E=84=E5=B9=B6=E5=AE=8C=E6=88=90=E8=8B=B1=E6=96=87=E6=95=99?= =?UTF-8?q?=E7=A8=8B=E7=BF=BB=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 创建分离式多语言教程组件架构 - TutorialView-zh-cn.vue (简体中文教程) - TutorialView-zh-tw.vue (繁体中文教程,统一台湾语言习惯) - TutorialView-en.vue (英文教程,全面翻译用户界面和技术文档) - 重构教程路由系统 - 新的TutorialView.vue作为国际化代理组件 - 根据用户语言设置动态选择合适的教程组件 - 保持/tutorial路径可访问性,提升用户体验 - 完成英文教程完整翻译 - Windows/macOS/Linux安装教程全英文化 - 环境变量配置说明英文化 - 故障排除章节英文化 - JavaScript注释和用户界面文本英文化 - 优化国际化架构 - ApiStatsView使用新的分离式教程组件 - 统一的语言选择逻辑和组件复用 - 更清晰的代码组织和维护性 --- web/admin-spa/src/views/ApiStatsView.vue | 6 +- web/admin-spa/src/views/TutorialView.vue | 1698 +---------------- .../src/views/tutorials/TutorialView-en.vue | 1690 ++++++++++++++++ .../views/tutorials/TutorialView-zh-cn.vue | 1690 ++++++++++++++++ .../views/tutorials/TutorialView-zh-tw.vue | 1690 ++++++++++++++++ 5 files changed, 5089 insertions(+), 1685 deletions(-) create mode 100644 web/admin-spa/src/views/tutorials/TutorialView-en.vue create mode 100644 web/admin-spa/src/views/tutorials/TutorialView-zh-cn.vue create mode 100644 web/admin-spa/src/views/tutorials/TutorialView-zh-tw.vue diff --git a/web/admin-spa/src/views/ApiStatsView.vue b/web/admin-spa/src/views/ApiStatsView.vue index 104dd3a0..d96b3c50 100644 --- a/web/admin-spa/src/views/ApiStatsView.vue +++ b/web/admin-spa/src/views/ApiStatsView.vue @@ -168,9 +168,9 @@ import TokenDistribution from '@/components/apistats/TokenDistribution.vue' import LimitConfig from '@/components/apistats/LimitConfig.vue' import AggregatedStatsCard from '@/components/apistats/AggregatedStatsCard.vue' import ModelUsageStats from '@/components/apistats/ModelUsageStats.vue' -import TutorialViewZhCn from './TutorialView.vue' -import TutorialViewZhTw from './TutorialView.vue' -import TutorialViewEn from './TutorialView.vue' +import TutorialViewZhCn from './tutorials/TutorialView-zh-cn.vue' +import TutorialViewZhTw from './tutorials/TutorialView-zh-tw.vue' +import TutorialViewEn from './tutorials/TutorialView-en.vue' const route = useRoute() const { t } = useI18n() diff --git a/web/admin-spa/src/views/TutorialView.vue b/web/admin-spa/src/views/TutorialView.vue index ac938825..1401b520 100644 --- a/web/admin-spa/src/views/TutorialView.vue +++ b/web/admin-spa/src/views/TutorialView.vue @@ -1,1690 +1,24 @@ - - + \ No newline at end of file diff --git a/web/admin-spa/src/views/tutorials/TutorialView-en.vue b/web/admin-spa/src/views/tutorials/TutorialView-en.vue new file mode 100644 index 00000000..ca0a8c65 --- /dev/null +++ b/web/admin-spa/src/views/tutorials/TutorialView-en.vue @@ -0,0 +1,1690 @@ + + + + + diff --git a/web/admin-spa/src/views/tutorials/TutorialView-zh-cn.vue b/web/admin-spa/src/views/tutorials/TutorialView-zh-cn.vue new file mode 100644 index 00000000..ac938825 --- /dev/null +++ b/web/admin-spa/src/views/tutorials/TutorialView-zh-cn.vue @@ -0,0 +1,1690 @@ + + + + + diff --git a/web/admin-spa/src/views/tutorials/TutorialView-zh-tw.vue b/web/admin-spa/src/views/tutorials/TutorialView-zh-tw.vue new file mode 100644 index 00000000..f24feab3 --- /dev/null +++ b/web/admin-spa/src/views/tutorials/TutorialView-zh-tw.vue @@ -0,0 +1,1690 @@ + + + + + From 4aae4aaec0ef2756a0d85f2bc32953a0219f915c Mon Sep 17 00:00:00 2001 From: Wangnov Date: Mon, 8 Sep 2025 19:21:41 +0800 Subject: [PATCH 09/40] =?UTF-8?q?feat:=20=E5=AE=8C=E6=88=90API=E7=BB=9F?= =?UTF-8?q?=E8=AE=A1=E7=BB=84=E4=BB=B6=E5=AE=8C=E6=95=B4=E5=9B=BD=E9=99=85?= =?UTF-8?q?=E5=8C=96=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 完成6个apistats组件的全面国际化改造 * ModelUsageStats.vue - 模型使用统计 * AggregatedStatsCard.vue - 聚合统计卡片 * StatsOverview.vue - 统计概览 * LimitConfig.vue - 限制配置 * TokenDistribution.vue - Token使用分布 * ApiKeyInput.vue - API Key输入组件 - 扩展三语言翻译支持(zh-cn/zh-tw/en) * 新增100+专业翻译键涵盖所有UI文字 * 台湾本地化的繁体中文翻译 * 技术专业的英文术语翻译 * 支持参数化翻译处理动态内容 - 技术优化 * 统一使用Vue 3 Composition API的useI18n()模式 * 智能日期格式国际化处理 * 完全消除硬编码中文文字 * 支持条件性翻译和动态时间段显示 现在整个API统计功能模块支持完整的多语言切换体验 --- .../apistats/AggregatedStatsCard.vue | 15 +- .../src/components/apistats/ApiKeyInput.vue | 33 +++-- .../src/components/apistats/LimitConfig.vue | 57 ++++---- .../components/apistats/ModelUsageStats.vue | 23 ++-- .../src/components/apistats/StatsOverview.vue | 75 +++++----- .../components/apistats/TokenDistribution.vue | 17 ++- web/admin-spa/src/i18n/locales/en.js | 130 +++++++++++++++++- web/admin-spa/src/i18n/locales/zh-cn.js | 130 +++++++++++++++++- web/admin-spa/src/i18n/locales/zh-tw.js | 130 +++++++++++++++++- 9 files changed, 505 insertions(+), 105 deletions(-) diff --git a/web/admin-spa/src/components/apistats/AggregatedStatsCard.vue b/web/admin-spa/src/components/apistats/AggregatedStatsCard.vue index 5b857f8e..20c693ad 100644 --- a/web/admin-spa/src/components/apistats/AggregatedStatsCard.vue +++ b/web/admin-spa/src/components/apistats/AggregatedStatsCard.vue @@ -5,10 +5,10 @@ > - 使用占比 + {{ t('apiStats.usageRatio') }} ({{ statsPeriod === 'daily' ? '今日' : '本月' }})({{ statsPeriod === 'daily' ? t('apiStats.today') : t('apiStats.thisMonth') }}) @@ -33,7 +33,7 @@
- {{ formatNumber(getStatUsage(stat)?.requests || 0) }}次 + {{ formatNumber(getStatUsage(stat)?.requests || 0) }}{{ t('apiStats.requests') }} {{ getStatUsage(stat)?.formattedCost || '$0.00' }}
@@ -41,7 +41,7 @@
- 其他 {{ otherKeysCount }} 个Keys + {{ t('apiStats.otherKeys') }} {{ otherKeysCount }} {{ t('apiStats.individual') }}{{ t('apiStats.keys') }} {{ otherPercentage }}%
@@ -54,7 +54,7 @@ >
-

使用占比仅在多Key查询时显示

+

{{ t('apiStats.usageRatioOnlyInMultiMode') }}

@@ -63,7 +63,7 @@ class="flex h-32 items-center justify-center text-sm text-gray-500 dark:text-gray-400" > - 暂无数据 + {{ t('apiStats.noData') }} @@ -71,8 +71,11 @@ diff --git a/web/admin-spa/src/components/apistats/TokenDistribution.vue b/web/admin-spa/src/components/apistats/TokenDistribution.vue index 56baa2bc..a08850d3 100644 --- a/web/admin-spa/src/components/apistats/TokenDistribution.vue +++ b/web/admin-spa/src/components/apistats/TokenDistribution.vue @@ -5,17 +5,17 @@ > - Token 使用分布 + {{ t('apiStats.tokenDistribution') }} ({{ statsPeriod === 'daily' ? '今日' : '本月' }})({{ statsPeriod === 'daily' ? t('apiStats.today') : t('apiStats.thisMonth') }})
- 输入 Token + {{ t('apiStats.inputToken') }} {{ formatNumber(currentPeriodData.inputTokens) @@ -24,7 +24,7 @@
- 输出 Token + {{ t('apiStats.outputToken') }} {{ formatNumber(currentPeriodData.outputTokens) @@ -33,7 +33,7 @@
- 缓存创建 Token + {{ t('apiStats.cacheCreateToken') }} {{ formatNumber(currentPeriodData.cacheCreateTokens) @@ -42,7 +42,7 @@
- 缓存读取 Token + {{ t('apiStats.cacheReadToken') }} {{ formatNumber(currentPeriodData.cacheReadTokens) @@ -52,7 +52,7 @@
{{ statsPeriod === 'daily' ? '今日' : '本月' }}总计{{ statsPeriod === 'daily' ? t('apiStats.todayTotal') : t('apiStats.monthlyTotal') }} {{ formatNumber(currentPeriodData.allTokens) }}
@@ -62,8 +62,11 @@ - - diff --git a/web/admin-spa/src/i18n/locales/en.js b/web/admin-spa/src/i18n/locales/en.js index 7932a1a3..b9057c1a 100644 --- a/web/admin-spa/src/i18n/locales/en.js +++ b/web/admin-spa/src/i18n/locales/en.js @@ -993,5 +993,298 @@ export default { bark: 'Bark', custom: 'Custom' } + }, + + // AccountForm Component + accountForm: { + // Titles and modal + editAccount: 'Edit Account', + addAccount: 'Add Account', + + // Step indicators + stepBasicInfo: 'Basic Information', + stepAuthorization: 'Authorization', + + // Platform selection + platform: 'Platform', + platformClaude: 'Claude', + platformClaudeConsole: 'Claude Console', + platformGemini: 'Gemini', + platformOpenAI: 'OpenAI', + platformAzureOpenAI: 'Azure OpenAI', + platformBedrock: 'Bedrock', + + // Add methods + addMethod: 'Add Method', + addTypeSetupToken: 'Setup Token (Recommended)', + addTypeOAuth: 'OAuth Authorization', + addTypeManual: 'Manual Access Token Input', + + // Basic information fields + accountName: 'Account Name', + accountNamePlaceholder: 'Set an easily recognizable name for the account', + description: 'Description', + descriptionOptional: 'Description (Optional)', + descriptionPlaceholder: 'Account usage description...', + + // Account type + accountType: 'Account Type', + accountTypeShared: 'Shared Account', + accountTypeDedicated: 'Dedicated Account', + accountTypeGroup: 'Group Scheduling', + accountTypeDescription: 'Shared: Available to all API Keys; Dedicated: Only for specific API Keys; Group: Join group for group scheduling', + + // Group selection + selectGroup: 'Select Group', + selectGroupRequired: 'Select Group *', + noGroupsAvailable: 'No groups available', + memberCount: 'members', + newGroup: 'New Group', + refreshGroups: 'Refresh Groups', + + // Gemini Project ID + projectId: 'Project ID', + projectIdOptional: 'Project ID (Optional)', + projectIdPlaceholder: 'e.g.: verdant-wares-464411-k9', + projectIdDescription: 'Google Cloud/Workspace accounts need to provide Project ID', + projectIdInstructions: 'How to get Project ID:', + projectIdStep1: 'Visit Google Cloud Console', + projectIdStep2: 'Copy Project ID (Project ID), usually in string format', + projectIdStep3: '⚠️ Note: Copy Project ID, not Project Number!', + projectIdTip: 'Tip: If your account is a regular personal account (not bound to Google Cloud), leave this field blank.', + projectIdGoogleCloudRequired: 'Google Cloud/Workspace accounts require Project ID', + projectIdGoogleCloudDescription: 'Some Google accounts (especially those bound to Google Cloud) will be identified as Workspace accounts and require an additional Project ID.', + + // Bedrock fields + awsAccessKeyId: 'AWS Access Key ID', + awsAccessKeyIdRequired: 'AWS Access Key ID *', + awsAccessKeyIdPlaceholder: 'Please enter AWS Access Key ID', + awsSecretAccessKey: 'AWS Secret Access Key', + awsSecretAccessKeyRequired: 'AWS Secret Access Key *', + awsSecretAccessKeyPlaceholder: 'Please enter AWS Secret Access Key', + awsRegion: 'AWS Region', + awsRegionRequired: 'AWS Region *', + awsRegionPlaceholder: 'e.g.: us-east-1', + awsRegionReference: 'Common AWS regions reference:', + awsRegionUsEast1: '• us-east-1 (US East)', + awsRegionUsWest2: '• us-west-2 (US West)', + awsRegionEuWest1: '• eu-west-1 (Europe Ireland)', + awsRegionApSoutheast1: '• ap-southeast-1 (Singapore)', + awsRegionApNortheast1: '• ap-northeast-1 (Tokyo)', + awsRegionEuCentral1: '• eu-central-1 (Frankfurt)', + awsRegionTip: '💡 Please enter complete region code, like us-east-1', + sessionToken: 'Session Token', + sessionTokenOptional: 'Session Token (Optional)', + sessionTokenPlaceholder: 'If using temporary credentials, please enter session token', + sessionTokenDescription: 'Only required when using temporary AWS credentials', + defaultModel: 'Default Primary Model', + defaultModelOptional: 'Default Primary Model (Optional)', + defaultModelPlaceholder: 'e.g.: us.anthropic.claude-sonnet-4-20250514-v1:0', + defaultModelDescription: 'Leave blank to use system default model. Supports inference profile ID or ARN', + bedrockModelConfigTitle: 'Bedrock Model Configuration:', + bedrockModelConfigInferenceProfile: '• Supports Inference Profile ID (recommended)', + bedrockModelConfigArn: '• Supports Application Inference Profile ARN', + bedrockModelConfigCommon: '• Common model: us.anthropic.claude-sonnet-4-20250514-v1:0', + bedrockModelConfigDefault: '• Leave blank to use system configured default model', + smallFastModel: 'Small Fast Model', + smallFastModelOptional: 'Small Fast Model (Optional)', + smallFastModelPlaceholder: 'e.g.: us.anthropic.claude-3-5-haiku-20241022-v1:0', + smallFastModelDescription: 'Lightweight model for fast responses, leave blank to use system default', + + // Azure OpenAI fields + azureEndpoint: 'Azure Endpoint', + azureEndpointRequired: 'Azure Endpoint *', + azureEndpointPlaceholder: 'https://your-resource.openai.azure.com', + azureEndpointDescription: 'Azure OpenAI resource endpoint URL, format: https://your-resource.openai.azure.com', + apiVersion: 'API Version', + apiVersionPlaceholder: '2024-02-01', + apiVersionDescription: 'Azure OpenAI API version, defaults to latest stable version 2024-02-01', + deploymentName: 'Deployment Name', + deploymentNameRequired: 'Deployment Name *', + deploymentNamePlaceholder: 'gpt-4', + deploymentNameDescription: 'Deployment name created in Azure OpenAI Studio', + apiKey: 'API Key', + apiKeyRequired: 'API Key *', + apiKeyPlaceholder: 'Please enter API Key', + apiKeyDescription: 'API key obtained from Azure portal', + supportedModels: 'Supported Models', + supportedModelsDescription: 'Select model types supported by this deployment', + + // Claude Console fields + apiUrl: 'API URL', + apiUrlRequired: 'API URL *', + apiUrlPlaceholder: 'e.g.: https://api.example.com', + apiKeyClaudeConsoleRequired: 'API Key *', + apiKeyClaudeConsolePlaceholder: 'Please enter API Key', + dailyQuota: 'Daily Quota Limit', + dailyQuotaLabel: 'Daily Quota Limit ($)', + dailyQuotaPlaceholder: '0 means no limit', + dailyQuotaDescription: 'Set daily usage quota, 0 means no limit', + quotaResetTime: 'Quota Reset Time', + quotaResetTimePlaceholder: '00:00', + quotaResetTimeDescription: 'Daily automatic quota reset time', + todayUsage: "Today's Usage", + remaining: 'Remaining', + used: 'Used', + modelMapping: 'Model Mapping Table', + modelMappingOptional: 'Model Mapping Table (Optional)', + modelMappingDescription: 'Leave blank to support all models without modification. With mapping configured, left models will be recognized as supported, right models are actually sent.', + originalModel: 'Original Model Name', + mappedModel: 'Mapped Model Name', + addModelMapping: 'Add Model Mapping', + userAgent: 'Custom User-Agent', + userAgentOptional: 'Custom User-Agent (Optional)', + userAgentPlaceholder: 'Leave blank to pass through client User-Agent', + userAgentDescription: 'When blank, will automatically use client User-Agent, only fill when need to fix specific UA', + rateLimitMechanism: 'Rate Limit Mechanism', + enableRateLimit: 'Enable Rate Limiting', + rateLimitDescription: 'When enabled, will pause scheduling for a period when account returns 429 errors', + rateLimitDuration: 'Rate Limit Duration (minutes)', + rateLimitDurationDescription: 'Time to pause scheduling after account is rate limited (minutes)', + + // Claude subscription types + subscriptionType: 'Subscription Type', + subscriptionClaudeMax: 'Claude Max', + subscriptionClaudePro: 'Claude Pro', + claudeProLimitation: 'Pro accounts do not support Claude Opus 4 model', + + // Claude special features + autoStopOnWarning: 'Auto stop scheduling when 5-hour usage approaches limit', + autoStopOnWarningDescription: 'When system detects account approaching 5-hour usage limit, automatically pause scheduling this account. Will automatically resume when entering new time window.', + useUnifiedUserAgent: 'Use Unified Claude Code Version', + useUnifiedUserAgentDescription: 'When enabled, will use unified User-Agent captured from real Claude Code client to improve compatibility', + currentUnifiedVersion: '💡 Current unified version: ', + clearCache: 'Clear Cache', + clearing: 'Clearing...', + waitingForCapture: '⏳ Waiting to capture User-Agent from Claude Code client', + captureHint: '💡 Tip: If unable to capture for a long time, please confirm Claude Code client is using this account, or contact developer to check if User-Agent format has changed', + useUnifiedClientId: 'Use Unified Client Identifier', + useUnifiedClientIdDescription: 'When enabled, will use fixed client identifier to make all requests appear from same client, reducing fingerprint', + clientId: 'Client Identifier ID', + regenerate: 'Regenerate', + clientIdDescription: 'This ID will replace user_id client part in requests, keeping session part for sticky sessions', + + // Schedule priority + schedulePriority: 'Schedule Priority', + schedulePriorityRange: 'Schedule Priority (1-100)', + schedulePriorityPlaceholder: 'Lower number = higher priority, default 50', + schedulePriorityDescription: 'Lower number = higher priority, recommended range: 1-100', + + // Manual token input + manualTokenTitle: 'Manual Token Input', + manualTokenDescription: 'Please enter valid Access Token. If you have Refresh Token, also recommend filling it to support auto refresh.', + manualTokenClaudeDescription: 'Please enter valid Claude Access Token. If you have Refresh Token, also recommend filling it to support auto refresh.', + manualTokenGeminiDescription: 'Please enter valid Gemini Access Token. If you have Refresh Token, also recommend filling it to support auto refresh.', + manualTokenOpenAIDescription: 'Please enter valid OpenAI Access Token. If you have Refresh Token, also recommend filling it to support auto refresh.', + obtainTokenMethods: 'Methods to obtain Access Token:', + claudeTokenPath: 'Please get credentials from ~/.claude/.credentials.json file on machine with logged-in Claude Code, do not use keys from Claude official website API Keys page.', + geminiTokenPath: 'Please get credentials from ~/.config/gemini/credentials.json file on machine with logged-in Gemini CLI.', + openaiTokenPath: 'Please get authentication credentials from machine with logged-in OpenAI account, or get Access Token through OAuth authorization flow.', + accessToken: 'Access Token', + accessTokenOptional: 'Access Token (Optional)', + accessTokenRequired: 'Access Token *', + accessTokenPlaceholder: 'Please enter Access Token...', + accessTokenOptionalPlaceholder: 'Optional: If not filled, system will automatically get via Refresh Token...', + accessTokenOptionalDescription: 'Access Token is optional. If not provided, system will automatically get via Refresh Token.', + refreshToken: 'Refresh Token', + refreshTokenOptional: 'Refresh Token (Optional)', + refreshTokenRequired: 'Refresh Token *', + refreshTokenPlaceholder: 'Please enter Refresh Token...', + refreshTokenRequiredPlaceholder: 'Please enter Refresh Token (required)...', + refreshTokenDescription: 'System will use Refresh Token to automatically get Access Token and user info', + refreshTokenTip: '💡 If Refresh Token not filled, token needs manual update after expiry.', + + // Setup Token flow + setupTokenTitle: 'Claude Setup Token Authorization', + setupTokenDescription: 'Please follow these steps to complete Claude account authorization via Setup Token:', + setupTokenStep1Title: 'Click button below to generate authorization link', + setupTokenStep2Title: 'Open link in browser and complete authorization', + setupTokenStep2Description: 'Please open authorization link in new tab, login to your Claude account and authorize Claude Code.', + setupTokenStep2Warning: 'Note: If you have proxy configured, please ensure browser also uses same proxy to access authorization page.', + setupTokenStep3Title: 'Enter Authorization Code', + setupTokenStep3Description: 'After authorization completes, copy Authorization Code from return page and paste into input below:', + generateSetupTokenUrl: 'Generate Setup Token Authorization Link', + generating: 'Generating...', + copyLink: 'Copy Link', + regenerateLink: 'Regenerate', + authorizationCode: 'Authorization Code', + authorizationCodePlaceholder: 'Paste Authorization Code obtained from Claude Code authorization page...', + authorizationCodeDescription: 'Please paste Authorization Code copied from Claude Code authorization page', + verifying: 'Verifying...', + completeAuthorization: 'Complete Authorization', + + // Token update (edit mode) + updateTokenTitle: 'Update Token', + updateTokenDescription: 'Can update Access Token and Refresh Token. For security, current Token values are not displayed.', + updateTokenTip: '💡 Leave blank to not update that field.', + newAccessToken: 'New Access Token', + newRefreshToken: 'New Refresh Token', + leaveBlankNoUpdate: 'Leave blank to not update...', + + // Usage information + currentUsage: 'Current Usage', + + // Buttons + cancel: 'Cancel', + nextStep: 'Next Step', + previousStep: 'Previous Step', + create: 'Create', + creating: 'Creating...', + update: 'Update', + updating: 'Updating...', + + // Error messages + pleaseEnterAccountName: 'Please enter account name', + pleaseSelectGroup: 'Please select a group', + pleaseEnterApiUrl: 'Please enter API URL', + pleaseEnterApiKey: 'Please enter API Key', + pleaseEnterAccessKeyId: 'Please enter AWS Access Key ID', + pleaseEnterSecretAccessKey: 'Please enter AWS Secret Access Key', + pleaseEnterRegion: 'Please select AWS region', + pleaseEnterAzureEndpoint: 'Please enter Azure Endpoint', + pleaseEnterDeploymentName: 'Please enter deployment name', + pleaseEnterAccessToken: 'Please enter Access Token', + pleaseEnterRefreshToken: 'Please enter Refresh Token', + + // Success messages + linkCopied: 'Link copied', + extractedAuthCode: 'Successfully extracted authorization code!', + cacheClearedSuccess: 'Unified User-Agent cache cleared', + newClientIdGenerated: 'New client identifier generated', + groupsRefreshed: 'Groups list refreshed', + modelMappingAdded: 'Mapping added', + modelMappingExists: 'Model mapping already exists', + + // Warnings and hints + copyFailed: 'Copy failed, please copy manually', + clearCacheFailed: 'Clear cache failed', + urlNotFound: 'Authorization code parameter not found in URL, please check if link is correct', + urlFormatError: 'Link format error, please check if it is a complete URL', + wrongUrlFormat: 'Please paste link starting with http://localhost:45462', + loadGroupsFailed: 'Failed to load groups list', + + // Confirmation dialogs + projectIdNotFilledTitle: 'Project ID Not Filled', + projectIdNotFilledMessage: 'You have not filled Project ID.\n\nIf your Google account is bound to Google Cloud or identified as Workspace account, Project ID is required.\nIf you are using regular personal account, you can continue without filling.', + continueButton: 'Continue', + goBackToFill: 'Go Back to Fill', + continueSave: 'Continue Save', + + // Quick model mapping buttons + presetSonnet4: '+ Sonnet 4', + presetOpus41: '+ Opus 4.1', + presetHaiku35: '+ Haiku 3.5', + presetOpus41ToSonnet4: '+ Opus 4.1 → Sonnet 4', + + // Edit mode special hints + leaveBlankNoUpdateApiKey: 'Leave blank to not update API Key', + leaveBlankNoUpdateAwsKey: 'Leave blank to not update AWS Access Key ID', + leaveBlankNoUpdateAwsSecret: 'Leave blank to not update AWS Secret Access Key', + leaveBlankNoUpdateSession: 'Leave blank to not update', + + // General description text + allModelsIfEmpty: 'Leave blank to support all models. If models specified, requests with models not in list will not be scheduled to this account', + systemDefaultIfEmpty: 'Leave blank to use system default model. Supports inference profile ID or ARN', + noUpdateIfEmpty: 'Leave blank to not update this field' } } diff --git a/web/admin-spa/src/i18n/locales/zh-cn.js b/web/admin-spa/src/i18n/locales/zh-cn.js index 774815cb..27a526e6 100644 --- a/web/admin-spa/src/i18n/locales/zh-cn.js +++ b/web/admin-spa/src/i18n/locales/zh-cn.js @@ -993,5 +993,298 @@ export default { bark: 'Bark', custom: '自定义' } + }, + + // AccountForm 组件 + accountForm: { + // 标题和模态框 + editAccount: '编辑账户', + addAccount: '添加账户', + + // 步骤指示器 + stepBasicInfo: '基本信息', + stepAuthorization: '授权认证', + + // 平台选择 + platform: '平台', + platformClaude: 'Claude', + platformClaudeConsole: 'Claude Console', + platformGemini: 'Gemini', + platformOpenAI: 'OpenAI', + platformAzureOpenAI: 'Azure OpenAI', + platformBedrock: 'Bedrock', + + // 添加方式 + addMethod: '添加方式', + addTypeSetupToken: 'Setup Token (推荐)', + addTypeOAuth: 'OAuth 授权', + addTypeManual: '手动输入 Access Token', + + // 基本信息字段 + accountName: '账户名称', + accountNamePlaceholder: '为账户设置一个易识别的名称', + description: '描述', + descriptionOptional: '描述 (可选)', + descriptionPlaceholder: '账户用途说明...', + + // 账户类型 + accountType: '账户类型', + accountTypeShared: '共享账户', + accountTypeDedicated: '专属账户', + accountTypeGroup: '分组调度', + accountTypeDescription: '共享账户:供所有API Key使用;专属账户:仅供特定API Key使用;分组调度:加入分组供分组内调度', + + // 分组选择 + selectGroup: '选择分组', + selectGroupRequired: '选择分组 *', + noGroupsAvailable: '暂无可用分组', + memberCount: '个成员', + newGroup: '新建分组', + refreshGroups: '刷新分组', + + // Gemini 项目 ID + projectId: '项目 ID', + projectIdOptional: '项目 ID (可选)', + projectIdPlaceholder: '例如:verdant-wares-464411-k9', + projectIdDescription: 'Google Cloud/Workspace 账号需要提供项目 ID', + projectIdInstructions: '如何获取项目 ID:', + projectIdStep1: '访问 Google Cloud Console', + projectIdStep2: '复制项目 ID(Project ID),通常是字符串格式', + projectIdStep3: '⚠️ 注意:要复制项目 ID(Project ID),不要复制项目编号(Project Number)!', + projectIdTip: '提示:如果您的账号是普通个人账号(未绑定 Google Cloud),请留空此字段。', + projectIdGoogleCloudRequired: 'Google Cloud/Workspace 账号需要提供项目 ID', + projectIdGoogleCloudDescription: '某些 Google 账号(特别是绑定了 Google Cloud 的账号)会被识别为 Workspace 账号,需要提供额外的项目 ID。', + + // Bedrock 字段 + awsAccessKeyId: 'AWS 访问密钥 ID', + awsAccessKeyIdRequired: 'AWS 访问密钥 ID *', + awsAccessKeyIdPlaceholder: '请输入 AWS Access Key ID', + awsSecretAccessKey: 'AWS 秘密访问密钥', + awsSecretAccessKeyRequired: 'AWS 秘密访问密钥 *', + awsSecretAccessKeyPlaceholder: '请输入 AWS Secret Access Key', + awsRegion: 'AWS 区域', + awsRegionRequired: 'AWS 区域 *', + awsRegionPlaceholder: '例如:us-east-1', + awsRegionReference: '常用 AWS 区域参考:', + awsRegionUsEast1: '• us-east-1 (美国东部)', + awsRegionUsWest2: '• us-west-2 (美国西部)', + awsRegionEuWest1: '• eu-west-1 (欧洲爱尔兰)', + awsRegionApSoutheast1: '• ap-southeast-1 (新加坡)', + awsRegionApNortheast1: '• ap-northeast-1 (东京)', + awsRegionEuCentral1: '• eu-central-1 (法兰克福)', + awsRegionTip: '💡 请输入完整的区域代码,如 us-east-1', + sessionToken: '会话令牌', + sessionTokenOptional: '会话令牌 (可选)', + sessionTokenPlaceholder: '如果使用临时凭证,请输入会话令牌', + sessionTokenDescription: '仅在使用临时 AWS 凭证时需要填写', + defaultModel: '默认主模型', + defaultModelOptional: '默认主模型 (可选)', + defaultModelPlaceholder: '例如:us.anthropic.claude-sonnet-4-20250514-v1:0', + defaultModelDescription: '留空将使用系统默认模型。支持 inference profile ID 或 ARN', + bedrockModelConfigTitle: 'Bedrock 模型配置说明:', + bedrockModelConfigInferenceProfile: '• 支持 Inference Profile ID(推荐)', + bedrockModelConfigArn: '• 支持 Application Inference Profile ARN', + bedrockModelConfigCommon: '• 常用模型:us.anthropic.claude-sonnet-4-20250514-v1:0', + bedrockModelConfigDefault: '• 留空将使用系统配置的默认模型', + smallFastModel: '小快速模型', + smallFastModelOptional: '小快速模型 (可选)', + smallFastModelPlaceholder: '例如:us.anthropic.claude-3-5-haiku-20241022-v1:0', + smallFastModelDescription: '用于快速响应的轻量级模型,留空将使用系统默认', + + // Azure OpenAI 字段 + azureEndpoint: 'Azure Endpoint', + azureEndpointRequired: 'Azure Endpoint *', + azureEndpointPlaceholder: 'https://your-resource.openai.azure.com', + azureEndpointDescription: 'Azure OpenAI 资源的终结点 URL,格式:https://your-resource.openai.azure.com', + apiVersion: 'API 版本', + apiVersionPlaceholder: '2024-02-01', + apiVersionDescription: 'Azure OpenAI API 版本,默认使用最新稳定版本 2024-02-01', + deploymentName: '部署名称', + deploymentNameRequired: '部署名称 *', + deploymentNamePlaceholder: 'gpt-4', + deploymentNameDescription: '在 Azure OpenAI Studio 中创建的部署名称', + apiKey: 'API Key', + apiKeyRequired: 'API Key *', + apiKeyPlaceholder: '请输入 API Key', + apiKeyDescription: '从 Azure 门户获取的 API 密钥', + supportedModels: '支持的模型', + supportedModelsDescription: '选择此部署支持的模型类型', + + // Claude Console 字段 + apiUrl: 'API URL', + apiUrlRequired: 'API URL *', + apiUrlPlaceholder: '例如:https://api.example.com', + apiKeyClaudeConsoleRequired: 'API Key *', + apiKeyClaudeConsolePlaceholder: '请输入API Key', + dailyQuota: '每日额度限制', + dailyQuotaLabel: '每日额度限制 ($)', + dailyQuotaPlaceholder: '0 表示不限制', + dailyQuotaDescription: '设置每日使用额度,0 表示不限制', + quotaResetTime: '额度重置时间', + quotaResetTimePlaceholder: '00:00', + quotaResetTimeDescription: '每日自动重置额度的时间', + todayUsage: '今日使用情况', + remaining: '剩余', + used: '已使用', + modelMapping: '模型映射表', + modelMappingOptional: '模型映射表 (可选)', + modelMappingDescription: '留空表示支持所有模型且不修改请求。配置映射后,左侧模型会被识别为支持的模型,右侧是实际发送的模型。', + originalModel: '原始模型名称', + mappedModel: '映射后的模型名称', + addModelMapping: '添加模型映射', + userAgent: '自定义 User-Agent', + userAgentOptional: '自定义 User-Agent (可选)', + userAgentPlaceholder: '留空则透传客户端 User-Agent', + userAgentDescription: '留空时将自动使用客户端的 User-Agent,仅在需要固定特定 UA 时填写', + rateLimitMechanism: '限流机制', + enableRateLimit: '启用限流机制', + rateLimitDescription: '启用后,当账号返回429错误时将暂停调度一段时间', + rateLimitDuration: '限流时间 (分钟)', + rateLimitDurationDescription: '账号被限流后暂停调度的时间(分钟)', + + // Claude 订阅类型 + subscriptionType: '订阅类型', + subscriptionClaudeMax: 'Claude Max', + subscriptionClaudePro: 'Claude Pro', + claudeProLimitation: 'Pro 账号不支持 Claude Opus 4 模型', + + // Claude 特殊功能 + autoStopOnWarning: '5小时使用量接近限制时自动停止调度', + autoStopOnWarningDescription: '当系统检测到账户接近5小时使用限制时,自动暂停调度该账户。进入新的时间窗口后会自动恢复调度。', + useUnifiedUserAgent: '使用统一 Claude Code 版本', + useUnifiedUserAgentDescription: '开启后将使用从真实 Claude Code 客户端捕获的统一 User-Agent,提高兼容性', + currentUnifiedVersion: '💡 当前统一版本:', + clearCache: '清除缓存', + clearing: '清除中...', + waitingForCapture: '⏳ 等待从 Claude Code 客户端捕获 User-Agent', + captureHint: '💡 提示:如果长时间未能捕获,请确认有 Claude Code 客户端正在使用此账户,或联系开发者检查 User-Agent 格式是否发生变化', + useUnifiedClientId: '使用统一的客户端标识', + useUnifiedClientIdDescription: '开启后将使用固定的客户端标识,使所有请求看起来来自同一个客户端,减少特征', + clientId: '客户端标识 ID', + regenerate: '重新生成', + clientIdDescription: '此ID将替换请求中的user_id客户端部分,保留session部分用于粘性会话', + + // 调度优先级 + schedulePriority: '调度优先级', + schedulePriorityRange: '调度优先级 (1-100)', + schedulePriorityPlaceholder: '数字越小优先级越高,默认50', + schedulePriorityDescription: '数字越小优先级越高,建议范围:1-100', + + // 手动输入 Token + manualTokenTitle: '手动输入 Token', + manualTokenDescription: '请输入有效的 Access Token。如果您有 Refresh Token,建议也一并填写以支持自动刷新。', + manualTokenClaudeDescription: '请输入有效的 Claude Access Token。如果您有 Refresh Token,建议也一并填写以支持自动刷新。', + manualTokenGeminiDescription: '请输入有效的 Gemini Access Token。如果您有 Refresh Token,建议也一并填写以支持自动刷新。', + manualTokenOpenAIDescription: '请输入有效的 OpenAI Access Token。如果您有 Refresh Token,建议也一并填写以支持自动刷新。', + obtainTokenMethods: '获取 Access Token 的方法:', + claudeTokenPath: '请从已登录 Claude Code 的机器上获取 ~/.claude/.credentials.json 文件中的凭证, 请勿使用 Claude 官网 API Keys 页面的密钥。', + geminiTokenPath: '请从已登录 Gemini CLI 的机器上获取 ~/.config/gemini/credentials.json 文件中的凭证。', + openaiTokenPath: '请从已登录 OpenAI 账户的机器上获取认证凭证, 或通过 OAuth 授权流程获取 Access Token。', + accessToken: 'Access Token', + accessTokenOptional: 'Access Token (可选)', + accessTokenRequired: 'Access Token *', + accessTokenPlaceholder: '请输入 Access Token...', + accessTokenOptionalPlaceholder: '可选:如果不填写,系统会自动通过 Refresh Token 获取...', + accessTokenOptionalDescription: 'Access Token 可选填。如果不提供,系统会通过 Refresh Token 自动获取。', + refreshToken: 'Refresh Token', + refreshTokenOptional: 'Refresh Token (可选)', + refreshTokenRequired: 'Refresh Token *', + refreshTokenPlaceholder: '请输入 Refresh Token...', + refreshTokenRequiredPlaceholder: '请输入 Refresh Token(必填)...', + refreshTokenDescription: '系统将使用 Refresh Token 自动获取 Access Token 和用户信息', + refreshTokenTip: '💡 如果未填写 Refresh Token,Token 过期后需要手动更新。', + + // Setup Token 流程 + setupTokenTitle: 'Claude Setup Token 授权', + setupTokenDescription: '请按照以下步骤通过 Setup Token 完成 Claude 账户的授权:', + setupTokenStep1Title: '点击下方按钮生成授权链接', + setupTokenStep2Title: '在浏览器中打开链接并完成授权', + setupTokenStep2Description: '请在新标签页中打开授权链接,登录您的 Claude 账户并授权 Claude Code。', + setupTokenStep2Warning: '注意:如果您设置了代理,请确保浏览器也使用相同的代理访问授权页面。', + setupTokenStep3Title: '输入 Authorization Code', + setupTokenStep3Description: '授权完成后,从返回页面复制 Authorization Code,并粘贴到下方输入框:', + generateSetupTokenUrl: '生成 Setup Token 授权链接', + generating: '生成中...', + copyLink: '复制链接', + regenerateLink: '重新生成', + authorizationCode: 'Authorization Code', + authorizationCodePlaceholder: '粘贴从Claude Code授权页面获取的Authorization Code...', + authorizationCodeDescription: '请粘贴从Claude Code授权页面复制的Authorization Code', + verifying: '验证中...', + completeAuthorization: '完成授权', + + // Token 更新(编辑模式) + updateTokenTitle: '更新 Token', + updateTokenDescription: '可以更新 Access Token 和 Refresh Token。为了安全起见,不会显示当前的 Token 值。', + updateTokenTip: '💡 留空表示不更新该字段。', + newAccessToken: '新的 Access Token', + newRefreshToken: '新的 Refresh Token', + leaveBlankNoUpdate: '留空表示不更新...', + + // 使用情况 + currentUsage: '当前使用情况', + + // 按钮 + cancel: '取消', + nextStep: '下一步', + previousStep: '上一步', + create: '创建', + creating: '创建中...', + update: '更新', + updating: '更新中...', + + // 错误消息 + pleaseEnterAccountName: '请填写账户名称', + pleaseSelectGroup: '请选择一个分组', + pleaseEnterApiUrl: '请填写 API URL', + pleaseEnterApiKey: '请填写 API Key', + pleaseEnterAccessKeyId: '请填写 AWS 访问密钥 ID', + pleaseEnterSecretAccessKey: '请填写 AWS 秘密访问密钥', + pleaseEnterRegion: '请选择 AWS 区域', + pleaseEnterAzureEndpoint: '请填写 Azure Endpoint', + pleaseEnterDeploymentName: '请填写部署名称', + pleaseEnterAccessToken: '请填写 Access Token', + pleaseEnterRefreshToken: '请填写 Refresh Token', + + // 成功消息 + linkCopied: '链接已复制', + extractedAuthCode: '成功提取授权码!', + cacheClearedSuccess: '统一User-Agent缓存已清除', + newClientIdGenerated: '已生成新的客户端标识', + groupsRefreshed: '分组列表已刷新', + modelMappingAdded: '已添加映射', + modelMappingExists: '模型映射已存在', + + // 警告和提示 + copyFailed: '复制失败,请手动复制', + clearCacheFailed: '清除缓存失败', + urlNotFound: 'URL 中未找到授权码参数,请检查链接是否正确', + urlFormatError: '链接格式错误,请检查是否为完整的 URL', + wrongUrlFormat: '请粘贴以 http://localhost:45462 开头的链接', + loadGroupsFailed: '加载分组列表失败', + + // 确认对话框 + projectIdNotFilledTitle: '项目 ID 未填写', + projectIdNotFilledMessage: '您尚未填写项目 ID。\n\n如果您的Google账号绑定了Google Cloud或被识别为Workspace账号,需要提供项目 ID。\n如果您使用的是普通个人账号,可以继续不填写。', + continueButton: '继续', + goBackToFill: '返回填写', + continueSave: '继续保存', + + // 快捷模型映射按钮 + presetSonnet4: '+ Sonnet 4', + presetOpus41: '+ Opus 4.1', + presetHaiku35: '+ Haiku 3.5', + presetOpus41ToSonnet4: '+ Opus 4.1 → Sonnet 4', + + // 编辑模式特殊提示 + leaveBlankNoUpdateApiKey: '留空表示不更新 API Key', + leaveBlankNoUpdateAwsKey: '留空表示不更新 AWS Access Key ID', + leaveBlankNoUpdateAwsSecret: '留空表示不更新 AWS Secret Access Key', + leaveBlankNoUpdateSession: '留空表示不更新', + + // 通用描述文本 + allModelsIfEmpty: '留空表示支持所有模型。如果指定模型,请求中的模型不在列表内将不会调度到此账号', + systemDefaultIfEmpty: '留空将使用系统默认模型。支持 inference profile ID 或 ARN', + noUpdateIfEmpty: '留空表示不更新该字段' } } diff --git a/web/admin-spa/src/i18n/locales/zh-tw.js b/web/admin-spa/src/i18n/locales/zh-tw.js index 1d6fc820..88f1dbef 100644 --- a/web/admin-spa/src/i18n/locales/zh-tw.js +++ b/web/admin-spa/src/i18n/locales/zh-tw.js @@ -993,5 +993,298 @@ export default { bark: 'Bark', custom: '自定義' } + }, + + // AccountForm 組件 + accountForm: { + // 標題和模態框 + editAccount: '編輯帳戶', + addAccount: '新增帳戶', + + // 步驟指示器 + stepBasicInfo: '基本資訊', + stepAuthorization: '授權認證', + + // 平台選擇 + platform: '平台', + platformClaude: 'Claude', + platformClaudeConsole: 'Claude Console', + platformGemini: 'Gemini', + platformOpenAI: 'OpenAI', + platformAzureOpenAI: 'Azure OpenAI', + platformBedrock: 'Bedrock', + + // 新增方式 + addMethod: '新增方式', + addTypeSetupToken: 'Setup Token(推薦)', + addTypeOAuth: 'OAuth 授權', + addTypeManual: '手動輸入 Access Token', + + // 基本資訊欄位 + accountName: '帳戶名稱', + accountNamePlaceholder: '為帳戶設置一個易識別的名稱', + description: '描述', + descriptionOptional: '描述(可選)', + descriptionPlaceholder: '帳戶用途說明...', + + // 帳戶類型 + accountType: '帳戶類型', + accountTypeShared: '共用帳戶', + accountTypeDedicated: '專屬帳戶', + accountTypeGroup: '群組排程', + accountTypeDescription: '共用帳戶:供所有API Key使用;專屬帳戶:僅供特定API Key使用;群組排程:加入群組供群組內排程', + + // 群組選擇 + selectGroup: '選擇群組', + selectGroupRequired: '選擇群組 *', + noGroupsAvailable: '暫無可用群組', + memberCount: '個成員', + newGroup: '新建群組', + refreshGroups: '刷新群組', + + // Gemini 專案 ID + projectId: '專案 ID', + projectIdOptional: '專案 ID(可選)', + projectIdPlaceholder: '例如:verdant-wares-464411-k9', + projectIdDescription: 'Google Cloud/Workspace 帳號需要提供專案 ID', + projectIdInstructions: '如何獲取專案 ID:', + projectIdStep1: '訪問 Google Cloud Console', + projectIdStep2: '複製專案 ID(Project ID),通常是字串格式', + projectIdStep3: '⚠️ 注意:要複製專案 ID(Project ID),不要複製專案編號(Project Number)!', + projectIdTip: '提示:如果您的帳號是普通個人帳號(未綁定 Google Cloud),請留空此欄位。', + projectIdGoogleCloudRequired: 'Google Cloud/Workspace 帳號需要提供專案 ID', + projectIdGoogleCloudDescription: '某些 Google 帳號(特別是綁定了 Google Cloud 的帳號)會被識別為 Workspace 帳號,需要提供額外的專案 ID。', + + // Bedrock 欄位 + awsAccessKeyId: 'AWS 存取金鑰 ID', + awsAccessKeyIdRequired: 'AWS 存取金鑰 ID *', + awsAccessKeyIdPlaceholder: '請輸入 AWS Access Key ID', + awsSecretAccessKey: 'AWS 秘密存取金鑰', + awsSecretAccessKeyRequired: 'AWS 秘密存取金鑰 *', + awsSecretAccessKeyPlaceholder: '請輸入 AWS Secret Access Key', + awsRegion: 'AWS 區域', + awsRegionRequired: 'AWS 區域 *', + awsRegionPlaceholder: '例如:us-east-1', + awsRegionReference: '常用 AWS 區域參考:', + awsRegionUsEast1: '• us-east-1(美國東部)', + awsRegionUsWest2: '• us-west-2(美國西部)', + awsRegionEuWest1: '• eu-west-1(歐洲愛爾蘭)', + awsRegionApSoutheast1: '• ap-southeast-1(新加坡)', + awsRegionApNortheast1: '• ap-northeast-1(東京)', + awsRegionEuCentral1: '• eu-central-1(法蘭克福)', + awsRegionTip: '💡 請輸入完整的區域代碼,如 us-east-1', + sessionToken: '會話權杖', + sessionTokenOptional: '會話權杖(可選)', + sessionTokenPlaceholder: '如果使用臨時憑證,請輸入會話權杖', + sessionTokenDescription: '僅在使用臨時 AWS 憑證時需要填寫', + defaultModel: '預設主模型', + defaultModelOptional: '預設主模型(可選)', + defaultModelPlaceholder: '例如:us.anthropic.claude-sonnet-4-20250514-v1:0', + defaultModelDescription: '留空將使用系統預設模型。支援 inference profile ID 或 ARN', + bedrockModelConfigTitle: 'Bedrock 模型配置說明:', + bedrockModelConfigInferenceProfile: '• 支援 Inference Profile ID(推薦)', + bedrockModelConfigArn: '• 支援 Application Inference Profile ARN', + bedrockModelConfigCommon: '• 常用模型:us.anthropic.claude-sonnet-4-20250514-v1:0', + bedrockModelConfigDefault: '• 留空將使用系統配置的預設模型', + smallFastModel: '小快速模型', + smallFastModelOptional: '小快速模型(可選)', + smallFastModelPlaceholder: '例如:us.anthropic.claude-3-5-haiku-20241022-v1:0', + smallFastModelDescription: '用於快速回應的輕量級模型,留空將使用系統預設', + + // Azure OpenAI 欄位 + azureEndpoint: 'Azure Endpoint', + azureEndpointRequired: 'Azure Endpoint *', + azureEndpointPlaceholder: 'https://your-resource.openai.azure.com', + azureEndpointDescription: 'Azure OpenAI 資源的終結點 URL,格式:https://your-resource.openai.azure.com', + apiVersion: 'API 版本', + apiVersionPlaceholder: '2024-02-01', + apiVersionDescription: 'Azure OpenAI API 版本,預設使用最新穩定版本 2024-02-01', + deploymentName: '部署名稱', + deploymentNameRequired: '部署名稱 *', + deploymentNamePlaceholder: 'gpt-4', + deploymentNameDescription: '在 Azure OpenAI Studio 中建立的部署名稱', + apiKey: 'API Key', + apiKeyRequired: 'API Key *', + apiKeyPlaceholder: '請輸入 API Key', + apiKeyDescription: '從 Azure 入口網站取得的 API 金鑰', + supportedModels: '支援的模型', + supportedModelsDescription: '選擇此部署支援的模型類型', + + // Claude Console 欄位 + apiUrl: 'API URL', + apiUrlRequired: 'API URL *', + apiUrlPlaceholder: '例如:https://api.example.com', + apiKeyClaudeConsoleRequired: 'API Key *', + apiKeyClaudeConsolePlaceholder: '請輸入API Key', + dailyQuota: '每日額度限制', + dailyQuotaLabel: '每日額度限制($)', + dailyQuotaPlaceholder: '0 表示不限制', + dailyQuotaDescription: '設置每日使用額度,0 表示不限制', + quotaResetTime: '額度重置時間', + quotaResetTimePlaceholder: '00:00', + quotaResetTimeDescription: '每日自動重置額度的時間', + todayUsage: '今日使用情況', + remaining: '剩餘', + used: '已使用', + modelMapping: '模型映射表', + modelMappingOptional: '模型映射表(可選)', + modelMappingDescription: '留空表示支援所有模型且不修改請求。配置映射後,左側模型會被識別為支援的模型,右側是實際發送的模型。', + originalModel: '原始模型名稱', + mappedModel: '映射後的模型名稱', + addModelMapping: '新增模型映射', + userAgent: '自定義 User-Agent', + userAgentOptional: '自定義 User-Agent(可選)', + userAgentPlaceholder: '留空則透傳用戶端 User-Agent', + userAgentDescription: '留空時將自動使用用戶端的 User-Agent,僅在需要固定特定 UA 時填寫', + rateLimitMechanism: '限流機制', + enableRateLimit: '啟用限流機制', + rateLimitDescription: '啟用後,當帳號返回429錯誤時將暫停排程一段時間', + rateLimitDuration: '限流時間(分鐘)', + rateLimitDurationDescription: '帳號被限流後暫停排程的時間(分鐘)', + + // Claude 訂閱類型 + subscriptionType: '訂閱類型', + subscriptionClaudeMax: 'Claude Max', + subscriptionClaudePro: 'Claude Pro', + claudeProLimitation: 'Pro 帳號不支援 Claude Opus 4 模型', + + // Claude 特殊功能 + autoStopOnWarning: '5小時使用量接近限制時自動停止排程', + autoStopOnWarningDescription: '當系統偵測到帳戶接近5小時使用限制時,自動暫停排程該帳戶。進入新的時間視窗後會自動恢復排程。', + useUnifiedUserAgent: '使用統一 Claude Code 版本', + useUnifiedUserAgentDescription: '開啟後將使用從真實 Claude Code 用戶端擷取的統一 User-Agent,提高相容性', + currentUnifiedVersion: '💡 目前統一版本:', + clearCache: '清除快取', + clearing: '清除中...', + waitingForCapture: '⏳ 等待從 Claude Code 用戶端擷取 User-Agent', + captureHint: '💡 提示:如果長時間未能擷取,請確認有 Claude Code 用戶端正在使用此帳戶,或聯繫開發者檢查 User-Agent 格式是否發生變化', + useUnifiedClientId: '使用統一的用戶端識別', + useUnifiedClientIdDescription: '開啟後將使用固定的用戶端識別,使所有請求看起來來自同一個用戶端,減少特徵', + clientId: '用戶端識別 ID', + regenerate: '重新產生', + clientIdDescription: '此ID將替換請求中的user_id用戶端部分,保留session部分用於黏性會話', + + // 排程優先順序 + schedulePriority: '排程優先順序', + schedulePriorityRange: '排程優先順序(1-100)', + schedulePriorityPlaceholder: '數字越小優先順序越高,預設50', + schedulePriorityDescription: '數字越小優先順序越高,建議範圍:1-100', + + // 手動輸入 Token + manualTokenTitle: '手動輸入 Token', + manualTokenDescription: '請輸入有效的 Access Token。如果您有 Refresh Token,建議也一併填寫以支援自動重新整理。', + manualTokenClaudeDescription: '請輸入有效的 Claude Access Token。如果您有 Refresh Token,建議也一併填寫以支援自動重新整理。', + manualTokenGeminiDescription: '請輸入有效的 Gemini Access Token。如果您有 Refresh Token,建議也一併填寫以支援自動重新整理。', + manualTokenOpenAIDescription: '請輸入有效的 OpenAI Access Token。如果您有 Refresh Token,建議也一併填寫以支援自動重新整理。', + obtainTokenMethods: '取得 Access Token 的方法:', + claudeTokenPath: '請從已登入 Claude Code 的機器上取得 ~/.claude/.credentials.json 檔案中的憑證,請勿使用 Claude 官網 API Keys 頁面的金鑰。', + geminiTokenPath: '請從已登入 Gemini CLI 的機器上取得 ~/.config/gemini/credentials.json 檔案中的憑證。', + openaiTokenPath: '請從已登入 OpenAI 帳戶的機器上取得認證憑證,或透過 OAuth 授權流程取得 Access Token。', + accessToken: 'Access Token', + accessTokenOptional: 'Access Token(可選)', + accessTokenRequired: 'Access Token *', + accessTokenPlaceholder: '請輸入 Access Token...', + accessTokenOptionalPlaceholder: '可選:如果不填寫,系統會自動透過 Refresh Token 取得...', + accessTokenOptionalDescription: 'Access Token 可選填。如果不提供,系統會透過 Refresh Token 自動取得。', + refreshToken: 'Refresh Token', + refreshTokenOptional: 'Refresh Token(可選)', + refreshTokenRequired: 'Refresh Token *', + refreshTokenPlaceholder: '請輸入 Refresh Token...', + refreshTokenRequiredPlaceholder: '請輸入 Refresh Token(必填)...', + refreshTokenDescription: '系統將使用 Refresh Token 自動取得 Access Token 和使用者資訊', + refreshTokenTip: '💡 如果未填寫 Refresh Token,Token 過期後需要手動更新。', + + // Setup Token 流程 + setupTokenTitle: 'Claude Setup Token 授權', + setupTokenDescription: '請按照以下步驟透過 Setup Token 完成 Claude 帳戶的授權:', + setupTokenStep1Title: '點擊下方按鈕產生授權連結', + setupTokenStep2Title: '在瀏覽器中開啟連結並完成授權', + setupTokenStep2Description: '請在新分頁中開啟授權連結,登入您的 Claude 帳戶並授權 Claude Code。', + setupTokenStep2Warning: '注意:如果您設置了代理,請確保瀏覽器也使用相同的代理訪問授權頁面。', + setupTokenStep3Title: '輸入 Authorization Code', + setupTokenStep3Description: '授權完成後,從返回頁面複製 Authorization Code,並貼上到下方輸入框:', + generateSetupTokenUrl: '產生 Setup Token 授權連結', + generating: '產生中...', + copyLink: '複製連結', + regenerateLink: '重新產生', + authorizationCode: 'Authorization Code', + authorizationCodePlaceholder: '貼上從Claude Code授權頁面取得的Authorization Code...', + authorizationCodeDescription: '請貼上從Claude Code授權頁面複製的Authorization Code', + verifying: '驗證中...', + completeAuthorization: '完成授權', + + // Token 更新(編輯模式) + updateTokenTitle: '更新 Token', + updateTokenDescription: '可以更新 Access Token 和 Refresh Token。為了安全起見,不會顯示目前的 Token 值。', + updateTokenTip: '💡 留空表示不更新該欄位。', + newAccessToken: '新的 Access Token', + newRefreshToken: '新的 Refresh Token', + leaveBlankNoUpdate: '留空表示不更新...', + + // 使用情況 + currentUsage: '目前使用情況', + + // 按鈕 + cancel: '取消', + nextStep: '下一步', + previousStep: '上一步', + create: '建立', + creating: '建立中...', + update: '更新', + updating: '更新中...', + + // 錯誤訊息 + pleaseEnterAccountName: '請填寫帳戶名稱', + pleaseSelectGroup: '請選擇一個群組', + pleaseEnterApiUrl: '請填寫 API URL', + pleaseEnterApiKey: '請填寫 API Key', + pleaseEnterAccessKeyId: '請填寫 AWS 存取金鑰 ID', + pleaseEnterSecretAccessKey: '請填寫 AWS 秘密存取金鑰', + pleaseEnterRegion: '請選擇 AWS 區域', + pleaseEnterAzureEndpoint: '請填寫 Azure Endpoint', + pleaseEnterDeploymentName: '請填寫部署名稱', + pleaseEnterAccessToken: '請填寫 Access Token', + pleaseEnterRefreshToken: '請填寫 Refresh Token', + + // 成功訊息 + linkCopied: '連結已複製', + extractedAuthCode: '成功提取授權碼!', + cacheClearedSuccess: '統一User-Agent快取已清除', + newClientIdGenerated: '已產生新的用戶端識別', + groupsRefreshed: '群組列表已重新整理', + modelMappingAdded: '已新增映射', + modelMappingExists: '模型映射已存在', + + // 警告和提示 + copyFailed: '複製失敗,請手動複製', + clearCacheFailed: '清除快取失敗', + urlNotFound: 'URL 中未找到授權碼參數,請檢查連結是否正確', + urlFormatError: '連結格式錯誤,請檢查是否為完整的 URL', + wrongUrlFormat: '請貼上以 http://localhost:45462 開頭的連結', + loadGroupsFailed: '載入群組列表失敗', + + // 確認對話框 + projectIdNotFilledTitle: '專案 ID 未填寫', + projectIdNotFilledMessage: '您尚未填寫專案 ID。\n\n如果您的Google帳號綁定了Google Cloud或被識別為Workspace帳號,需要提供專案 ID。\n如果您使用的是普通個人帳號,可以繼續不填寫。', + continueButton: '繼續', + goBackToFill: '返回填寫', + continueSave: '繼續保存', + + // 快捷模型映射按鈕 + presetSonnet4: '+ Sonnet 4', + presetOpus41: '+ Opus 4.1', + presetHaiku35: '+ Haiku 3.5', + presetOpus41ToSonnet4: '+ Opus 4.1 → Sonnet 4', + + // 編輯模式特殊提示 + leaveBlankNoUpdateApiKey: '留空表示不更新 API Key', + leaveBlankNoUpdateAwsKey: '留空表示不更新 AWS Access Key ID', + leaveBlankNoUpdateAwsSecret: '留空表示不更新 AWS Secret Access Key', + leaveBlankNoUpdateSession: '留空表示不更新', + + // 通用描述文字 + allModelsIfEmpty: '留空表示支援所有模型。如果指定模型,請求中的模型不在列表內將不會排程到此帳號', + systemDefaultIfEmpty: '留空將使用系統預設模型。支援 inference profile ID 或 ARN', + noUpdateIfEmpty: '留空表示不更新該欄位' } } From 2e09896d0b06097fa9b5f3af7d0ef2e8360f68dc Mon Sep 17 00:00:00 2001 From: Wangnov Date: Tue, 9 Sep 2025 14:49:53 +0800 Subject: [PATCH 17/40] =?UTF-8?q?feat:=20=E7=BB=A7=E7=BB=AD=E5=AE=8C?= =?UTF-8?q?=E6=88=90AccountForm=E7=BB=84=E4=BB=B6=E5=9B=BD=E9=99=85?= =?UTF-8?q?=E5=8C=96=E7=9A=84=E6=A0=B8=E5=BF=83=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 完成手动Token输入部分国际化,支持Claude/Gemini/OpenAI三个平台 - 完成编辑模式所有特定功能的国际化:账户信息、类型、分组管理 - 完成Claude高级功能国际化:订阅类型、自动停止调度、统一User-Agent、客户端标识 - 完成Gemini Project ID配置的国际化支持 - 新增150+翻译键,涵盖三种语言(简中/繁中/英文) - 保持响应式特性和暗黑模式兼容性 技术改进: - 采用结构化翻译键命名策略 (accountForm.module.item) - 解决重复字符串精确匹配问题 - 使用上下文信息区分相似文本的不同用法 - 优化用户交互文本:占位符、提示、按钮等 进度:AccountForm组件(3730行)已完成约70%的国际化工作 --- .../src/components/accounts/AccountForm.vue | 124 +++++++++--------- web/admin-spa/src/i18n/locales/en.js | 66 +++++++++- web/admin-spa/src/i18n/locales/zh-cn.js | 66 +++++++++- web/admin-spa/src/i18n/locales/zh-tw.js | 66 +++++++++- 4 files changed, 254 insertions(+), 68 deletions(-) diff --git a/web/admin-spa/src/components/accounts/AccountForm.vue b/web/admin-spa/src/components/accounts/AccountForm.vue index b5a1c769..1f113bcd 100644 --- a/web/admin-spa/src/components/accounts/AccountForm.vue +++ b/web/admin-spa/src/components/accounts/AccountForm.vue @@ -916,7 +916,7 @@ >
- {{ clearingCache ? '清除中...' : '清除缓存' }} + {{ clearingCache ? t('accountForm.clearing') : t('accountForm.clearCache') }}
@@ -1001,11 +1001,11 @@ class="form-input w-full border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:placeholder-gray-400" max="100" min="1" - placeholder="数字越小优先级越高,默认50" + :placeholder="t('accountForm.priorityPlaceholder')" type="number" />

- 数字越小优先级越高,建议范围:1-100 + {{ t('accountForm.priorityDescription') }}

@@ -1026,51 +1026,48 @@
- 手动输入 Token + {{ t('accountForm.manualTokenInput') }}

- 请输入有效的 Claude Access Token。如果您有 Refresh - Token,建议也一并填写以支持自动刷新。 + {{ t('accountForm.manualTokenClaudeDescription') }}

- 请输入有效的 Gemini Access Token。如果您有 Refresh - Token,建议也一并填写以支持自动刷新。 + {{ t('accountForm.manualTokenGeminiDescription') }}

- 请输入有效的 OpenAI Access Token。如果您有 Refresh - Token,建议也一并填写以支持自动刷新。 + {{ t('accountForm.manualTokenOpenAIDescription') }}

- 获取 Access Token 的方法: + {{ t('accountForm.getAccessTokenMethod') }}

- 请从已登录 Claude Code 的机器上获取 + {{ t('accountForm.claudeCredentialsPath') }} ~/.claude/.credentials.json - 文件中的凭证, 请勿使用 Claude 官网 API Keys 页面的密钥。 + {{ t('accountForm.claudeCredentialsWarning') }}

- 请从已登录 Gemini CLI 的机器上获取 + {{ t('accountForm.geminiCredentialsPath') }} ~/.config/gemini/credentials.json @@ -1080,41 +1077,40 @@ v-else-if="form.platform === 'openai'" class="text-xs text-blue-800 dark:text-blue-300" > - 请从已登录 OpenAI 账户的机器上获取认证凭证, 或通过 OAuth 授权流程获取 Access - Token。 + {{ t('accountForm.openaiCredentialsPath') }}

- 💡 如果未填写 Refresh Token,Token 过期后需要手动更新。 + {{ t('accountForm.refreshTokenWarning') }}

{{ t('accountForm.accessTokenOptional') }}
@@ -73,7 +76,7 @@ type="button" @click="$emit('close')" > - Cancel + {{ t('user.createApiKeyModal.buttons.cancel') }}
@@ -121,11 +124,13 @@
-

API Key Created Successfully!

+

+ {{ t('user.createApiKeyModal.success.title') }} +

- Important: Copy your API key now. You won't be able to see it - again! + {{ t('user.createApiKeyModal.success.warning.important') }} + {{ t('user.createApiKeyModal.success.warning.message') }}

@@ -149,7 +154,7 @@ stroke-width="2" /> - Copy + {{ t('user.createApiKeyModal.buttons.copy') }}
@@ -159,7 +164,7 @@ class="rounded-md border border-transparent bg-green-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2" @click="handleClose" > - Done + {{ t('user.createApiKeyModal.buttons.done') }}
@@ -172,6 +177,7 @@ diff --git a/web/admin-spa/src/i18n/index.js b/web/admin-spa/src/i18n/index.js index 4bd2028f..31dd2b49 100644 --- a/web/admin-spa/src/i18n/index.js +++ b/web/admin-spa/src/i18n/index.js @@ -35,6 +35,28 @@ function getBrowserLocale() { const savedLocale = localStorage.getItem('app-locale') const defaultLocale = savedLocale || getBrowserLocale() +// 创建一个函数来获取本地化的语言信息 +export function getSupportedLocalesWithI18n(t) { + return { + 'zh-cn': { + name: t('common.languageSwitch.zhCnName'), + flag: t('common.languageSwitch.zhCnFlag'), + shortName: t('common.languageSwitch.zhCnFlag') + }, + 'zh-tw': { + name: t('common.languageSwitch.zhTwName'), + flag: t('common.languageSwitch.zhTwFlag'), + shortName: t('common.languageSwitch.zhTwFlag') + }, + en: { + name: t('common.languageSwitch.enName'), + flag: t('common.languageSwitch.enFlag'), + shortName: t('common.languageSwitch.enFlag') + } + } +} + +// 保持原有的SUPPORTED_LOCALES作为默认值,用于不依赖i18n的场景 export const SUPPORTED_LOCALES = { 'zh-cn': { name: '简体中文', diff --git a/web/admin-spa/src/stores/locale.js b/web/admin-spa/src/stores/locale.js index 7b6ad24d..eb795048 100644 --- a/web/admin-spa/src/stores/locale.js +++ b/web/admin-spa/src/stores/locale.js @@ -1,6 +1,6 @@ import { defineStore } from 'pinia' import { ref } from 'vue' -import { i18n, SUPPORTED_LOCALES } from '@/i18n' +import { i18n, SUPPORTED_LOCALES, getSupportedLocalesWithI18n } from '@/i18n' export const useLocaleStore = defineStore('locale', () => { const currentLocale = ref(i18n.global.locale.value) @@ -20,14 +20,19 @@ export const useLocaleStore = defineStore('locale', () => { document.documentElement.setAttribute('lang', locale) } - // 获取当前语言信息 - const getCurrentLocaleInfo = () => { + // 获取当前语言信息(兼容i18n) + const getCurrentLocaleInfo = (t = null) => { + if (t) { + const supportedLocales = getSupportedLocalesWithI18n(t) + return supportedLocales[currentLocale.value] || supportedLocales['zh-cn'] + } return SUPPORTED_LOCALES[currentLocale.value] || SUPPORTED_LOCALES['zh-cn'] } - // 获取所有支持的语言 - const getSupportedLocales = () => { - return Object.entries(SUPPORTED_LOCALES).map(([key, value]) => ({ + // 获取所有支持的语言(兼容i18n) + const getSupportedLocales = (t = null) => { + const supportedLocales = t ? getSupportedLocalesWithI18n(t) : SUPPORTED_LOCALES + return Object.entries(supportedLocales).map(([key, value]) => ({ code: key, ...value })) From 8522d20cad6cd92c551a83a7002c018820a512eb Mon Sep 17 00:00:00 2001 From: Wangnov Date: Thu, 11 Sep 2025 18:02:59 +0800 Subject: [PATCH 32/40] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E9=87=8D?= =?UTF-8?q?=E5=A4=8D=E9=94=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/admin-spa/src/i18n/locales/en.js | 139 ++++---------------- web/admin-spa/src/i18n/locales/zh-cn.js | 161 ++++-------------------- web/admin-spa/src/i18n/locales/zh-tw.js | 131 ++++--------------- 3 files changed, 74 insertions(+), 357 deletions(-) diff --git a/web/admin-spa/src/i18n/locales/en.js b/web/admin-spa/src/i18n/locales/en.js index 7a987fd8..b654125a 100644 --- a/web/admin-spa/src/i18n/locales/en.js +++ b/web/admin-spa/src/i18n/locales/en.js @@ -62,6 +62,30 @@ export default { info: 'Information' } }, + errors: { + loadDashboardFailed: 'Failed to load dashboard data', + loadUsageTrendFailed: 'Failed to load usage trend', + loadModelStatsFailed: 'Failed to load model statistics', + loadApiKeysTrendFailed: 'Failed to load API Keys trend', + createClaudeConsoleAccountFailed: 'Failed to create Claude Console account', + createAzureOpenAIAccountFailed: 'Failed to create Azure OpenAI account', + updateClaudeConsoleAccountFailed: 'Failed to update Claude Console account', + updateAzureOpenAIAccountFailed: 'Failed to update Azure OpenAI account', + generateSetupTokenUrlFailed: 'Failed to generate Setup Token URL', + exchangeSetupTokenFailed: 'Failed to exchange Setup Token authorization code', + allApiKeysInvalid: 'All API Keys are invalid', + loadOemSettingsFailed: 'Failed to load OEM settings', + getApiKeyStatsFailed: 'Failed to get API Key statistics', + getTagsFailed: 'Failed to get tags', + requestFailed: 'Request failed: {status}', + loadSupportedClientsFailed: 'Failed to load supported clients' + }, + system: { + status: { + normal: 'Normal', + abnormal: 'Abnormal' + } + }, confirmDialog: { confirm: 'Confirm', cancel: 'Cancel' @@ -131,10 +155,6 @@ export default { minutesAgo: '{minutes} minutes ago', hoursAgo: '{hours} hours ago', daysAgo: '{days} days ago' - }, - errors: { - requestFailed: 'Request failed: {status}', - loadSupportedClientsFailed: 'Failed to load supported clients' } }, language: { @@ -221,7 +241,6 @@ export default { apiKeyInfo: 'API Key Information', queryKeysCount: 'Query Keys Count', activeKeysCount: 'Active Keys Count', - invalidKeysCount: 'Invalid Keys Count', totalRequests: 'Total Requests', totalTokens: 'Total Tokens', totalCost: 'Total Cost', @@ -237,7 +256,6 @@ export default { modelUsageStats: 'Model Usage Statistics', loadingModelStats: 'Loading model statistics...', requestCount: ' requests', - totalCost: 'Total Cost', inputTokens: 'Input Tokens', outputTokens: 'Output Tokens', cacheCreateTokens: 'Cache Create', @@ -2436,12 +2454,6 @@ export default { // Manual Token Input Section manualTokenInput: 'Manual Token Input', - manualTokenClaudeDescription: - 'Please enter valid Claude Access Token. If you have Refresh Token, it is recommended to fill both for automatic refresh support.', - manualTokenGeminiDescription: - 'Please enter valid Gemini Access Token. If you have Refresh Token, it is recommended to fill both for automatic refresh support.', - manualTokenOpenAIDescription: - 'Please enter valid OpenAI Access Token. If you have Refresh Token, it is recommended to fill both for automatic refresh support.', getAccessTokenMethod: 'Methods to get Access Token:', claudeCredentialsPath: 'Please get from logged-in Claude Code machine', geminiCredentialsPath: 'Please get from logged-in Gemini CLI machine', @@ -2451,18 +2463,11 @@ export default { 'credentials from file, do not use keys from Claude official API Keys page.', refreshTokenWarning: '💡 If Refresh Token is not filled, Token needs manual update after expiration.', - accessTokenOptional: 'Access Token (Optional)', - accessTokenOptionalPlaceholder: - 'Optional: If not filled, system will automatically obtain through Refresh Token...', accessTokenOptionalInfo: 'Access Token is optional. If not provided, system will automatically obtain through Refresh Token.', - accessTokenRequired: 'Access Token *', accessTokenRequiredPlaceholder: 'Please enter Access Token...', - refreshTokenRequired: 'Refresh Token *', - refreshTokenRequiredPlaceholder: 'Please enter Refresh Token (required)...', refreshTokenRequiredInfo: 'System will use Refresh Token to automatically obtain Access Token and user information', - refreshTokenOptional: 'Refresh Token (Optional)', refreshTokenOptionalPlaceholder: 'Please enter Refresh Token...', // Priority Settings @@ -2471,35 +2476,11 @@ export default { prioritySchedulingTitle: 'Scheduling Priority (1-100)', priorityEditPlaceholder: 'Lower number = higher priority', - // Gemini Project ID - projectIdOptional: 'Project ID (Optional)', - projectIdPlaceholder: 'e.g., verdant-wares-464411-k9', - projectIdDescription: 'Google Cloud/Workspace accounts may require Project ID', - // Claude Subscription Type and Advanced Options - subscriptionType: 'Subscription Type', claudeMaxSubscription: 'Claude Max', claudeProSubscription: 'Claude Pro', - claudeProLimitation: 'Pro accounts do not support Claude Opus 4 model', - autoStopOnWarning: 'Auto-stop scheduling when approaching 5-hour limit', - autoStopOnWarningDescription: - 'When system detects account approaching 5-hour usage limit, automatically pause scheduling for this account. Will resume automatically when entering new time window.', - useUnifiedUserAgent: 'Use unified Claude Code version', - useUnifiedUserAgentDescription: - 'When enabled, will use unified User-Agent captured from real Claude Code client, improving compatibility', - currentUnifiedVersion: 'Current unified version:', - clearCache: 'Clear Cache', - clearing: 'Clearing...', - waitingForCapture: 'Waiting to capture User-Agent from Claude Code client', - captureHint: - '💡 Hint: If unable to capture for long time, please confirm Claude Code client is using this account, or contact developer to check if User-Agent format has changed', - useUnifiedClientId: 'Use unified client identifier', - useUnifiedClientIdDescription: - 'When enabled, will use fixed client identifier, making all requests appear to come from same client, reducing characteristics', clientIdLabel: 'Client Identifier ID', regenerateClientId: 'Regenerate', - clientIdDescription: - 'This ID will replace user_id client part in requests, preserving session part for sticky sessions', // Edit Mode Fields accountNameEdit: 'Account Name', @@ -2507,7 +2488,6 @@ export default { descriptionOptionalEdit: 'Description (Optional)', descriptionOptionalEditPlaceholder: 'Account usage description...', accountTypeEdit: 'Account Type', - selectGroupRequired: 'Select Group *', noAvailableGroups: 'No available groups', membersCount: ' members', createNewGroup: 'Create New Group', @@ -2516,32 +2496,14 @@ export default { bedrockCredentials: 'Credentials Configuration', bedrockCredentialsDescription: 'Please fill in AWS access credentials for calling Amazon Bedrock service.', - awsAccessKeyId: 'AWS Access Key ID *', - awsAccessKeyIdPlaceholder: 'Please enter AWS Access Key ID...', - awsSecretAccessKey: 'AWS Secret Access Key *', - awsSecretAccessKeyPlaceholder: 'Please enter AWS Secret Access Key...', - sessionTokenOptional: 'Session Token (Optional)', - sessionTokenOptionalPlaceholder: 'Session token for temporary credentials...', - sessionTokenDescription: - 'Only required when using temporary credentials (like STS generated credentials)', - awsRegion: 'AWS Region *', - awsRegionPlaceholder: 'Select AWS region...', bedrockModelConfig: 'Model Configuration', defaultModelLabel: 'Default Model', - defaultModelPlaceholder: 'e.g., anthropic.claude-3-5-sonnet-20240620-v1:0', - defaultModelDescription: - 'Leave blank to use system default model. Supports inference profile ID or ARN', smallFastModelLabel: 'Small Fast Model', - smallFastModelPlaceholder: 'e.g., anthropic.claude-3-haiku-20240307-v1:0', - smallFastModelDescription: 'Fast model for simple tasks, supports inference profile ID or ARN', // Azure OpenAI Configuration azureOpenAIConfig: 'Azure OpenAI Configuration', azureOpenAIDescription: 'Please configure connection information and deployment details for Azure OpenAI service.', - azureEndpoint: 'Azure Endpoint *', - azureEndpointPlaceholder: 'e.g., https://your-resource.openai.azure.com/', - azureEndpointDescription: 'Endpoint URL for Azure OpenAI service', azureApiKey: 'API Key *', azureApiKeyPlaceholder: 'Please enter Azure OpenAI API Key...', azureApiVersion: 'API Version', @@ -2565,7 +2527,6 @@ export default { modelMappingFromPlaceholder: 'e.g., claude-3-5-sonnet-20241022', modelMappingTo: 'Actual Model', modelMappingToPlaceholder: 'e.g., claude-3-5-sonnet-latest', - addModelMapping: 'Add Mapping', removeMapping: 'Remove', presetMappings: 'Preset Mappings', modelMappingExample: 'Example: claude-3-5-sonnet-20241022 → claude-3-5-sonnet-latest', @@ -2579,11 +2540,7 @@ export default { setupTokenStep1Description: 'System will generate a dedicated authorization link for obtaining temporary authorization code.', setupTokenStep2: 'Step 2: Complete Authorization', - setupTokenStep2Description: - 'Open authorization link in new window, log in with your Claude account and complete authorization.', setupTokenStep3: 'Step 3: Enter Authorization Code', - setupTokenStep3Description: - 'After successful authorization, system will display authorization code, please copy and paste into input box below.', setupTokenUrlGenerated: 'Authorization link generated', setupTokenOpenInBrowser: 'Open in browser', setupTokenCopyLink: 'Copy link', @@ -2611,16 +2568,9 @@ export default { // Rate Limiting and Quota Management rateLimitSettings: 'Rate Limit Settings', - enableRateLimit: 'Enable rate limiting', - rateLimitDuration: 'Rate limit duration (seconds)', rateLimitDurationPlaceholder: 'e.g., 60', - rateLimitDescription: - 'When enabled, will limit request frequency to prevent account being blocked', quotaManagement: 'Quota Management', - dailyQuotaLabel: 'Daily quota limit', - dailyQuotaPlaceholder: '0 means unlimited', quotaResetTimeLabel: 'Quota reset time', - quotaResetTimePlaceholder: 'e.g., 00:00', quotaResetDescription: 'Time point when daily quota resets', currentDailyUsage: 'Today used', @@ -2628,7 +2578,6 @@ export default { advancedSettings: 'Advanced Settings', customUserAgent: 'Custom User-Agent', customUserAgentPlaceholder: 'Leave blank to use default User-Agent...', - userAgentDescription: 'User-Agent identifier for requests', // General Hints and Status notSet: 'Not set', @@ -2669,7 +2618,6 @@ export default { 'Tip: If your account is a regular personal account (not bound to Google Cloud), please leave this field empty.', // AWS Region Reference - awsRegionReference: 'Common AWS regions reference:', awsRegionEastUS: 'us-east-1 (US East)', awsRegionWestUS: 'us-west-2 (US West)', awsRegionEuropeIreland: 'eu-west-1 (Europe Ireland)', @@ -2689,7 +2637,6 @@ export default { azureModelSelectionDesc: 'Select model types supported by this deployment', // Rate Limiting - rateLimitMechanism: 'Rate limiting mechanism', enableRateLimitMechanism: 'Enable rate limiting mechanism', rateLimitDescription2: 'When enabled, will pause scheduling for some time when account returns 429 error', @@ -2699,34 +2646,22 @@ export default { // Claude Console Specific Fields claudeConsoleFields: 'Claude Console specific fields', - quotaManagement: 'Quota management', modelMappingTable: 'Model mapping table', modelMappingTableOptional: 'Model mapping table (optional)', - addModelMapping: 'Add model mapping', - - // Claude Subscription Type - subscriptionType: 'Subscription type', // Setup Token Auth - setupTokenAuth: 'Setup Token authorization', claudeSetupTokenAuth: 'Claude Setup Token authorization', setupTokenAuthSteps: 'Please follow these steps to complete Claude account authorization through Setup Token:', generateSetupTokenLink: 'Generate Setup Token auth link', - generating: 'Generating...', // Buttons and actions - verifying: 'Verifying...', completeAuth: 'Complete authorization', - updating: 'Updating...', - update: 'Update', // Error messages generateSetupTokenFailed: 'Failed to generate Setup Token auth link', - copyFailed: 'Copy failed, please copy manually', setupTokenAuthFailed: 'Setup Token authorization failed, please check if the authorization code is correct', - accountCreationFailed: 'Account creation failed', accountCreationError: 'Account creation failed:', // Page structure comments @@ -2741,29 +2676,18 @@ export default { // Validation messages nameRequired: 'Please enter account name', - apiUrlRequired: 'Please enter API URL', rateLimitDefault60: 'Default 60 minutes', rateLimitPauseDescription: 'Time to pause scheduling after account is rate limited (minutes)', - apiUrlPlaceholder: 'e.g., https://api.example.com', - apiKeyPlaceholder: 'Please enter API Key', dailyQuotaLimit: 'Daily quota limit ($)', quotaZeroUnlimited: '0 means unlimited', - dailyQuotaDescription: 'Set daily usage quota, 0 means unlimited', - quotaResetTime: 'Quota reset time', - quotaResetTimeDescription: 'Time to automatically reset quota daily', - modelMappingDescription: - 'Leave empty to support all models without modifying requests. After configuring mapping, left model will be recognized as supported model, right is the actual model sent.', // Quota Management quotaManagementFields: 'Quota management fields', dailyQuotaLimitDollar: 'Daily quota limit ($)', - quotaZeroUnlimited: '0 means unlimited', dailyQuotaDesc: 'Set daily usage quota, 0 means unlimited', - quotaResetTime: 'Quota reset time', quotaResetTimeDesc: 'Time to automatically reset quota daily', // Model Mapping - modelMappingOptional: 'Model mapping table (optional)', modelMappingDesc: 'Leave empty to support all models without modifying requests. After configuring mapping, left model will be recognized as supported model, right is the actual model sent.', originalModelName: 'Original model name', @@ -2776,7 +2700,6 @@ export default { // Claude Subscription Type claudeSubscriptionType: 'Subscription type', - claudeProLimitation: 'Pro accounts do not support Claude Opus 4 model', // Claude Advanced Options claudeAutoStopScheduling: 'Auto-stop scheduling when approaching 5-hour limit', @@ -2807,13 +2730,10 @@ export default { setupTokenCopyTitle: 'Copy link', // Step Indicators - stepIndicator: 'Step indicator', - step1BasicInfo: 'Step 1: Basic information and proxy settings', step2OAuth: 'Step 2: OAuth authorization', step2SetupToken: 'Step 2: Setup Token authorization', // Group Selector - groupSelector: 'Group selector', multiGroupInterface: 'Multi-group interface', createNewGroupOption: 'Create new group option', @@ -2823,15 +2743,12 @@ export default { // Placeholder texts originalModelNamePlaceholder: 'Original model name', mappedModelNamePlaceholder: 'Mapped model name', - userAgentPlaceholder: 'Leave empty to pass through client User-Agent', authCodePlaceholder: 'Paste Authorization Code obtained from Claude Code auth page...', leaveEmptyNoUpdate: 'Leave empty for no update', leaveEmptyNoUpdateKey: 'Leave empty for no API Key update', leaveEmptyNoUpdateToken: 'Leave empty for no update...', // Labels and descriptions - customUserAgentOptional: 'Custom User-Agent (optional)', - clientIdLabel: 'Client ID', schedulePriorityLabel: 'Schedule Priority (1-100)', attentionLabel: 'Attention:', supportedModelsLabel: 'Supported Models', @@ -2844,19 +2761,16 @@ export default { previousStepBtn: 'Previous Step', // Descriptive texts - claudeProLimitation: 'Pro accounts do not support Claude Opus 4 model', claude5HourLimitDesc: 'Auto-stop scheduling when approaching 5-hour usage limit', claude5HourLimitExplanation: 'When system detects account approaching 5-hour usage limit, automatically pause scheduling for this account. Will resume automatically when entering new time window.', useUnifiedClaudeVersion: 'Use unified Claude Code version', unifiedVersionDesc: 'When enabled, will use unified User-Agent captured from real Claude Code client, improving compatibility', - currentUnifiedVersion: '💡 Current unified version:', waitingUserAgent: '⏳ Waiting to capture User-Agent from Claude Code client', userAgentTip: '💡 Tip: If unable to capture for a long time, please confirm that Claude Code client is using this account,', contactDeveloper: 'or contact developer to check if User-Agent format has changed', - useUnifiedClientId: 'Use unified client identifier', unifiedClientIdDesc: 'When enabled, will use fixed client identifier, making all requests appear from same client, reducing fingerprinting', clientIdReplaceDesc: @@ -2883,9 +2797,6 @@ export default { awsRegionRef: 'Common AWS regions reference:', // Error messages - apiKeyRequired: 'Please enter API Key', - refreshTokenRequired: 'Please enter Refresh Token', - accessTokenRequired: 'Please enter Access Token', copyFailedManual: 'Copy failed, please copy manually', // Form descriptions @@ -2897,9 +2808,7 @@ export default { // Basic labels apiUrlLabel: 'API URL', - apiUrlRequired: 'API URL *', apiKeyLabel: 'API Key', - apiKeyRequired: 'API Key *', // More missing keys copyLinkTooltip: 'Copy Link', diff --git a/web/admin-spa/src/i18n/locales/zh-cn.js b/web/admin-spa/src/i18n/locales/zh-cn.js index dbb9ef06..400afd70 100644 --- a/web/admin-spa/src/i18n/locales/zh-cn.js +++ b/web/admin-spa/src/i18n/locales/zh-cn.js @@ -62,6 +62,30 @@ export default { info: '信息' } }, + errors: { + loadDashboardFailed: '加载仪表板数据失败', + loadUsageTrendFailed: '加载使用趋势失败', + loadModelStatsFailed: '加载模型统计失败', + loadApiKeysTrendFailed: '加载API Keys趋势失败', + createClaudeConsoleAccountFailed: '创建Claude Console账户失败', + createAzureOpenAIAccountFailed: '创建Azure OpenAI账户失败', + updateClaudeConsoleAccountFailed: '更新Claude Console账户失败', + updateAzureOpenAIAccountFailed: '更新Azure OpenAI账户失败', + generateSetupTokenUrlFailed: '生成Setup Token URL失败', + exchangeSetupTokenFailed: '交换Setup Token授权码失败', + allApiKeysInvalid: '所有 API Key 都无效', + loadOemSettingsFailed: '加载OEM设置失败', + getApiKeyStatsFailed: '获取API Key统计失败', + getTagsFailed: '获取标签失败', + requestFailed: '请求失败: {status}', + loadSupportedClientsFailed: '加载支持的客户端失败' + }, + system: { + status: { + normal: '正常', + abnormal: '异常' + } + }, confirmDialog: { confirm: '确认', cancel: '取消' @@ -131,10 +155,6 @@ export default { minutesAgo: '{minutes}分钟前', hoursAgo: '{hours}小时前', daysAgo: '{days}天前' - }, - errors: { - requestFailed: '请求失败: {status}', - loadSupportedClientsFailed: '加载支持的客户端失败' } }, language: { @@ -145,37 +165,6 @@ export default { switch: '切换语言' }, - layout: { - tabBar: { - tabs: { - dashboard: { - name: '仪表板', - shortName: '仪表板' - }, - apiKeys: { - name: 'API Keys', - shortName: 'API' - }, - accounts: { - name: '账户管理', - shortName: '账户' - }, - userManagement: { - name: '用户管理', - shortName: '用户' - }, - tutorial: { - name: '使用教程', - shortName: '教程' - }, - settings: { - name: '系统设置', - shortName: '设置' - } - } - } - }, - header: { adminPanel: '管理后台', userMenu: '用户菜单', @@ -253,7 +242,6 @@ export default { apiKeyInfo: 'API Key 信息', queryKeysCount: '查询 Keys 数', activeKeysCount: '有效 Keys 数', - invalidKeysCount: '无效 Keys 数', totalRequests: '总请求数', totalTokens: '总 Token 数', totalCost: '总费用', @@ -269,7 +257,6 @@ export default { modelUsageStats: '模型使用统计', loadingModelStats: '加载模型统计数据中...', requestCount: '次请求', - totalCost: '总费用', inputTokens: '输入 Token', outputTokens: '输出 Token', cacheCreateTokens: '缓存创建', @@ -2420,12 +2407,6 @@ export default { // 手动 Token 输入部分 manualTokenInput: '手动输入 Token', - manualTokenClaudeDescription: - '请输入有效的 Claude Access Token。如果您有 Refresh Token,建议也一并填写以支持自动刷新。', - manualTokenGeminiDescription: - '请输入有效的 Gemini Access Token。如果您有 Refresh Token,建议也一并填写以支持自动刷新。', - manualTokenOpenAIDescription: - '请输入有效的 OpenAI Access Token。如果您有 Refresh Token,建议也一并填写以支持自动刷新。', getAccessTokenMethod: '获取 Access Token 的方法:', claudeCredentialsPath: '请从已登录 Claude Code 的机器上获取', geminiCredentialsPath: '请从已登录 Gemini CLI 的机器上获取', @@ -2433,15 +2414,9 @@ export default { '请从已登录 OpenAI 账户的机器上获取认证凭证,或通过 OAuth 授权流程获取 Access Token。', claudeCredentialsWarning: '文件中的凭证,请勿使用 Claude 官网 API Keys 页面的密钥。', refreshTokenWarning: '💡 如果未填写 Refresh Token,Token 过期后需要手动更新。', - accessTokenOptional: 'Access Token (可选)', - accessTokenOptionalPlaceholder: '可选:如果不填写,系统会自动通过 Refresh Token 获取...', accessTokenOptionalInfo: 'Access Token 可选填。如果不提供,系统会通过 Refresh Token 自动获取。', - accessTokenRequired: 'Access Token *', accessTokenRequiredPlaceholder: '请输入 Access Token...', - refreshTokenRequired: 'Refresh Token *', - refreshTokenRequiredPlaceholder: '请输入 Refresh Token(必填)...', refreshTokenRequiredInfo: '系统将使用 Refresh Token 自动获取 Access Token 和用户信息', - refreshTokenOptional: 'Refresh Token (可选)', refreshTokenOptionalPlaceholder: '请输入 Refresh Token...', // 优先级设置 @@ -2450,34 +2425,11 @@ export default { prioritySchedulingTitle: '调度优先级 (1-100)', priorityEditPlaceholder: '数字越小优先级越高', - // Gemini 项目ID - projectIdOptional: '项目 ID (可选)', - projectIdPlaceholder: '例如:verdant-wares-464411-k9', - projectIdDescription: 'Google Cloud/Workspace 账号可能需要提供项目 ID', - // Claude 订阅类型和高级选项 - subscriptionType: '订阅类型', claudeMaxSubscription: 'Claude Max', claudeProSubscription: 'Claude Pro', - claudeProLimitation: 'Pro 账号不支持 Claude Opus 4 模型', - autoStopOnWarning: '5小时使用量接近限制时自动停止调度', - autoStopOnWarningDescription: - '当系统检测到账户接近5小时使用限制时,自动暂停调度该账户。进入新的时间窗口后会自动恢复调度。', - useUnifiedUserAgent: '使用统一 Claude Code 版本', - useUnifiedUserAgentDescription: - '开启后将使用从真实 Claude Code 客户端捕获的统一 User-Agent,提高兼容性', - currentUnifiedVersion: '当前统一版本:', - clearCache: '清除缓存', - clearing: '清除中...', - waitingForCapture: '等待从 Claude Code 客户端捕获 User-Agent', - captureHint: - '💡 提示:如果长时间未能捕获,请确认有 Claude Code 客户端正在使用此账户,或联系开发者检查 User-Agent 格式是否发生变化', - useUnifiedClientId: '使用统一的客户端标识', - useUnifiedClientIdDescription: - '开启后将使用固定的客户端标识,使所有请求看起来来自同一个客户端,减少特征', clientIdLabel: '客户端标识 ID', regenerateClientId: '重新生成', - clientIdDescription: '此ID将替换请求中的user_id客户端部分,保留session部分用于粘性会话', // 编辑模式字段 accountNameEdit: '账户名称', @@ -2485,7 +2437,6 @@ export default { descriptionOptionalEdit: '描述 (可选)', descriptionOptionalEditPlaceholder: '账户用途说明...', accountTypeEdit: '账户类型', - selectGroupRequired: '选择分组 *', noAvailableGroups: '暂无可用分组', membersCount: ' 个成员', createNewGroup: '新建分组', @@ -2493,29 +2444,14 @@ export default { // AWS Bedrock 配置 bedrockCredentials: '凭证配置', bedrockCredentialsDescription: '请填写 AWS 访问凭证,用于调用 Amazon Bedrock 服务。', - awsAccessKeyId: 'AWS Access Key ID *', - awsAccessKeyIdPlaceholder: '请输入 AWS 访问密钥 ID...', - awsSecretAccessKey: 'AWS Secret Access Key *', - awsSecretAccessKeyPlaceholder: '请输入 AWS 秘密访问密钥...', - sessionTokenOptional: 'Session Token (可选)', sessionTokenOptionalPlaceholder: '临时凭证的会话令牌...', - sessionTokenDescription: '仅在使用临时凭证(如 STS 生成的凭证)时需要填写', - awsRegion: 'AWS 区域 *', - awsRegionPlaceholder: '选择 AWS 区域...', bedrockModelConfig: '模型配置', defaultModelLabel: '默认模型', - defaultModelPlaceholder: '例如:anthropic.claude-3-5-sonnet-20240620-v1:0', - defaultModelDescription: '留空将使用系统默认模型。支持 inference profile ID 或 ARN', smallFastModelLabel: '小型快速模型', - smallFastModelPlaceholder: '例如:anthropic.claude-3-haiku-20240307-v1:0', - smallFastModelDescription: '用于简单任务的快速模型,支持 inference profile ID 或 ARN', // Azure OpenAI 配置 azureOpenAIConfig: 'Azure OpenAI 配置', azureOpenAIDescription: '请配置 Azure OpenAI 服务的连接信息和部署详情。', - azureEndpoint: 'Azure Endpoint *', - azureEndpointPlaceholder: '例如:https://your-resource.openai.azure.com/', - azureEndpointDescription: 'Azure OpenAI 服务的端点 URL', azureApiKey: 'API Key *', azureApiKeyPlaceholder: '请输入 Azure OpenAI API Key...', azureApiVersion: 'API 版本', @@ -2538,7 +2474,6 @@ export default { modelMappingFromPlaceholder: '例如:claude-3-5-sonnet-20241022', modelMappingTo: '实际模型', modelMappingToPlaceholder: '例如:claude-3-5-sonnet-latest', - addModelMapping: '添加映射', removeMapping: '移除', presetMappings: '预设映射', modelMappingExample: '示例:claude-3-5-sonnet-20241022 → claude-3-5-sonnet-latest', @@ -2550,9 +2485,7 @@ export default { setupTokenStep1: '步骤 1:生成授权链接', setupTokenStep1Description: '系统将生成一个专用的授权链接,用于获取临时授权码。', setupTokenStep2: '步骤 2:完成授权', - setupTokenStep2Description: '在新窗口中打开授权链接,使用您的 Claude 账户登录并完成授权。', setupTokenStep3: '步骤 3:输入授权码', - setupTokenStep3Description: '授权成功后,系统会显示授权码,请复制并粘贴到下方输入框。', setupTokenUrlGenerated: '授权链接已生成', setupTokenOpenInBrowser: '在浏览器中打开', setupTokenCopyLink: '复制链接', @@ -2577,15 +2510,9 @@ export default { // 限流和配额管理 rateLimitSettings: '限流设置', - enableRateLimit: '启用速率限制', - rateLimitDuration: '限流时长 (秒)', rateLimitDurationPlaceholder: '例如:60', - rateLimitDescription: '启用后将限制请求频率,防止账户被封锁', quotaManagement: '配额管理', - dailyQuotaLabel: '每日配额限制', - dailyQuotaPlaceholder: '0 表示不限制', quotaResetTimeLabel: '配额重置时间', - quotaResetTimePlaceholder: '例如:00:00', quotaResetDescription: '每天配额重置的时间点', currentDailyUsage: '今日已用', @@ -2593,7 +2520,6 @@ export default { advancedSettings: '高级设置', customUserAgent: '自定义 User-Agent', customUserAgentPlaceholder: '留空使用默认 User-Agent...', - userAgentDescription: '用于请求时的 User-Agent 标识', // 通用提示和状态 notSet: '未设置', @@ -2634,7 +2560,6 @@ export default { '\u63d0示\uff1a如果您的账号是普通个人账号(未绑定 Google Cloud),请留空此字段。', // AWS 区域参考 - awsRegionReference: '常用 AWS 区域参考:', awsRegionEastUS: 'us-east-1 (美国东部)', awsRegionWestUS: 'us-west-2 (美国西部)', awsRegionEuropeIreland: 'eu-west-1 (欧洲爱尔兰)', @@ -2654,38 +2579,25 @@ export default { azureModelSelectionDesc: '选择此部署支持的模型类型', // 限流机制 - rateLimitMechanism: '限流机制', enableRateLimitMechanism: '启用限流机制', rateLimitDescription2: '启用后,当账号返回429错误时将暂停调度一段时间', // Claude Console 特定字段 claudeConsoleFields: 'Claude Console 特定字段', - quotaManagement: '额度管理', modelMappingTable: '模型映射表', modelMappingTableOptional: '模型映射表 (可选)', - addModelMapping: '添加模型映射', - - // Claude 订阅类型 - subscriptionType: '订阅类型', // Setup Token 授权 - setupTokenAuth: 'Setup Token 授权', claudeSetupTokenAuth: 'Claude Setup Token 授权', setupTokenAuthSteps: '请按照以下步骤通过 Setup Token 完成 Claude 账户的授权:', generateSetupTokenLink: '生成 Setup Token 授权链接', - generating: '生成中...', // 按钮和操作 - verifying: '验证中...', completeAuth: '完成授权', - updating: '更新中...', - update: '更新', // 错误消息 generateSetupTokenFailed: '生成Setup Token授权链接失败', - copyFailed: '复制失败,请手动复制', setupTokenAuthFailed: 'Setup Token授权失败,请检查授权码是否正确', - accountCreationFailed: '账户创建失败', accountCreationError: '账户创建失败:', // 页面结构注释 @@ -2700,18 +2612,10 @@ export default { // 验证消息 nameRequired: '请填写账户名称', - apiUrlRequired: '请填写 API URL', rateLimitDefault60: '默认60分钟', rateLimitPauseDescription: '账号被限流后暂停调度的时间(分钟)', - apiUrlPlaceholder: '例如:https://api.example.com', - apiKeyPlaceholder: '请输入API Key', dailyQuotaLimit: '每日额度限制 ($)', quotaZeroUnlimited: '0 表示不限制', - dailyQuotaDescription: '设置每日使用额度,0 表示不限制', - quotaResetTime: '额度重置时间', - quotaResetTimeDescription: '每日自动重置额度的时间', - modelMappingDescription: - '留空表示支持所有模型且不修改请求。配置映射后,左侧模型会被识别为支持的模型,右侧是实际发送的模型。', rateLimitDurationMinutes: '限流时间 (分钟)', rateLimitDefaultMinutes: '默认60分钟', rateLimitPauseDesc: '账号被限流后暂停调度的时间(分钟)', @@ -2719,13 +2623,10 @@ export default { // 额度管理 quotaManagementFields: '额度管理字段', dailyQuotaLimitDollar: '每日额度限制 ($)', - quotaZeroUnlimited: '0 表示不限制', dailyQuotaDesc: '设置每日使用额度,0 表示不限制', - quotaResetTime: '额度重置时间', quotaResetTimeDesc: '每日自动重置额度的时间', // 模型映射 - modelMappingOptional: '模型映射表 (可选)', modelMappingDesc: '留空表示支持所有模型且不修改请求。配置映射后,左侧模型会被识别为支持的模型,右侧是实际发送的模型。', originalModelName: '原始模型名称', @@ -2737,7 +2638,6 @@ export default { // Claude 订阅类型 claudeSubscriptionType: '订阅类型', - claudeProLimitation: 'Pro 账号不支持 Claude Opus 4 模型', // Claude 高级选项 claudeAutoStopScheduling: '5小时使用量接近限制时自动停止调度', @@ -2765,13 +2665,10 @@ export default { setupTokenCopyTitle: '复制链接', // 步骤指示器 - stepIndicator: '步骤指示器', - step1BasicInfo: '步骤1: 基本信息和代理设置', step2OAuth: '步骤2: OAuth授权', step2SetupToken: '步骤2: Setup Token授权', // 分组选择器 - groupSelector: '分组选择器', multiGroupInterface: '多选分组界面', createNewGroupOption: '新建分组选项', @@ -2781,15 +2678,12 @@ export default { // Placeholder 文本 originalModelNamePlaceholder: '原始模型名称', mappedModelNamePlaceholder: '映射后的模型名称', - userAgentPlaceholder: '留空则透传客户端 User-Agent', authCodePlaceholder: '粘贴从Claude Code授权页面获取的Authorization Code...', leaveEmptyNoUpdate: '留空表示不更新', leaveEmptyNoUpdateKey: '留空表示不更新 API Key', leaveEmptyNoUpdateToken: '留空表示不更新...', // 标签和描述 - customUserAgentOptional: '自定义 User-Agent (可选)', - clientIdLabel: '客户端标识 ID', schedulePriorityLabel: '调度优先级 (1-100)', attentionLabel: '注意:', supportedModelsLabel: '支持的模型', @@ -2802,17 +2696,14 @@ export default { previousStepBtn: '上一步', // 描述性文本 - claudeProLimitation: 'Pro 账号不支持 Claude Opus 4 模型', claude5HourLimitDesc: '5小时使用量接近限制时自动停止调度', claude5HourLimitExplanation: '当系统检测到账户接近5小时使用限制时,自动暂停调度该账户。进入新的时间窗口后会自动恢复调度。', useUnifiedClaudeVersion: '使用统一 Claude Code 版本', unifiedVersionDesc: '开启后将使用从真实 Claude Code 客户端捕获的统一 User-Agent,提高兼容性', - currentUnifiedVersion: '💡 当前统一版本:', waitingUserAgent: '⏳ 等待从 Claude Code 客户端捕获 User-Agent', userAgentTip: '💡 提示:如果长时间未能捕获,请确认有 Claude Code 客户端正在使用此账户,', contactDeveloper: '或联系开发者检查 User-Agent 格式是否发生变化', - useUnifiedClientId: '使用统一的客户端标识', unifiedClientIdDesc: '开启后将使用固定的客户端标识,使所有请求看起来来自同一个客户端,减少特征', clientIdReplaceDesc: '此ID将替换请求中的user_id客户端部分,保留session部分用于粘性会话', @@ -2834,8 +2725,6 @@ export default { // Error messages apiKeyRequiredError: '请填写 API Key', - refreshTokenRequired: '请填写 Refresh Token', - accessTokenRequired: '请填写 Access Token', copyFailedManual: '复制失败,请手动复制', // 表单描述 @@ -2846,9 +2735,7 @@ export default { // 基础标签 apiUrlLabel: 'API URL', - apiUrlRequired: 'API URL *', apiKeyLabel: 'API Key', - apiKeyRequired: 'API Key *', // 更多缺失的翻译键 copyLinkTooltip: '复制链接', diff --git a/web/admin-spa/src/i18n/locales/zh-tw.js b/web/admin-spa/src/i18n/locales/zh-tw.js index 5768df67..c32064a7 100644 --- a/web/admin-spa/src/i18n/locales/zh-tw.js +++ b/web/admin-spa/src/i18n/locales/zh-tw.js @@ -62,6 +62,30 @@ export default { info: '資訊' } }, + errors: { + loadDashboardFailed: '載入儀表板數據失敗', + loadUsageTrendFailed: '載入使用趨勢失敗', + loadModelStatsFailed: '載入模型統計失敗', + loadApiKeysTrendFailed: '載入API Keys趨勢失敗', + createClaudeConsoleAccountFailed: '建立Claude Console帳戶失敗', + createAzureOpenAIAccountFailed: '建立Azure OpenAI帳戶失敗', + updateClaudeConsoleAccountFailed: '更新Claude Console帳戶失敗', + updateAzureOpenAIAccountFailed: '更新Azure OpenAI帳戶失敗', + generateSetupTokenUrlFailed: '產生Setup Token URL失敗', + exchangeSetupTokenFailed: '交換Setup Token授權碼失敗', + allApiKeysInvalid: '所有 API Key 都無效', + loadOemSettingsFailed: '載入OEM設定失敗', + getApiKeyStatsFailed: '獲取API Key統計失敗', + getTagsFailed: '獲取標籤失敗', + requestFailed: '請求失敗: {status}', + loadSupportedClientsFailed: '載入支援的客戶端失敗' + }, + system: { + status: { + normal: '正常', + abnormal: '異常' + } + }, confirmDialog: { confirm: '確認', cancel: '取消' @@ -131,10 +155,6 @@ export default { minutesAgo: '{minutes}分鐘前', hoursAgo: '{hours}小時前', daysAgo: '{days}天前' - }, - errors: { - requestFailed: '請求失敗: {status}', - loadSupportedClientsFailed: '載入支援的客戶端失敗' } }, language: { @@ -221,7 +241,6 @@ export default { apiKeyInfo: 'API Key 資訊', queryKeysCount: '查詢 Keys 數', activeKeysCount: '有效 Keys 數', - invalidKeysCount: '無效 Keys 數', totalRequests: '總請求數', totalTokens: '總 Token 數', totalCost: '總費用', @@ -237,7 +256,6 @@ export default { modelUsageStats: '模型使用統計', loadingModelStats: '載入模型統計資料中...', requestCount: '次請求', - totalCost: '總費用', inputTokens: '輸入 Token', outputTokens: '輸出 Token', cacheCreateTokens: '快取建立', @@ -2228,7 +2246,7 @@ export default { rateLimitDurationDescription: '帳號被限流後暫停排程的時間(分鐘)', // Claude 訂閱類型 - subscriptionType: '訂閱類型', + subscriptionClaudeMax: 'Claude Max', subscriptionClaudePro: 'Claude Pro', claudeProLimitation: 'Pro 帳號不支援 Claude Opus 4 模型', @@ -2391,12 +2409,6 @@ export default { // 手動 Token 輸入部分 manualTokenInput: '手動輸入 Token', - manualTokenClaudeDescription: - '請輸入有效的 Claude Access Token。如果您有 Refresh Token,建議也一併填寫以支援自動刷新。', - manualTokenGeminiDescription: - '請輸入有效的 Gemini Access Token。如果您有 Refresh Token,建議也一併填寫以支援自動刷新。', - manualTokenOpenAIDescription: - '請輸入有效的 OpenAI Access Token。如果您有 Refresh Token,建議也一併填寫以支援自動刷新。', getAccessTokenMethod: '取得 Access Token 的方法:', claudeCredentialsPath: '請從已登入 Claude Code 的機器上取得', geminiCredentialsPath: '請從已登入 Gemini CLI 的機器上取得', @@ -2404,15 +2416,9 @@ export default { '請從已登入 OpenAI 帳戶的機器上取得認證憑證,或透過 OAuth 授權流程取得 Access Token。', claudeCredentialsWarning: '檔案中的憑證,請勿使用 Claude 官網 API Keys 頁面的金鑰。', refreshTokenWarning: '💡 如果未填寫 Refresh Token,Token 過期後需要手動更新。', - accessTokenOptional: 'Access Token (可選)', - accessTokenOptionalPlaceholder: '可選:如果不填寫,系統會自動透過 Refresh Token 取得...', accessTokenOptionalInfo: 'Access Token 可選填。如果不提供,系統會透過 Refresh Token 自動取得。', - accessTokenRequired: 'Access Token *', accessTokenRequiredPlaceholder: '請輸入 Access Token...', - refreshTokenRequired: 'Refresh Token *', - refreshTokenRequiredPlaceholder: '請輸入 Refresh Token(必填)...', refreshTokenRequiredInfo: '系統將使用 Refresh Token 自動取得 Access Token 和使用者資訊', - refreshTokenOptional: 'Refresh Token (可選)', refreshTokenOptionalPlaceholder: '請輸入 Refresh Token...', // 優先級設定 @@ -2421,34 +2427,12 @@ export default { prioritySchedulingTitle: '排程優先級 (1-100)', priorityEditPlaceholder: '數字越小優先級越高', - // Gemini 專案ID - projectIdOptional: '專案 ID (可選)', - projectIdPlaceholder: '例如:verdant-wares-464411-k9', - projectIdDescription: 'Google Cloud/Workspace 帳號可能需要提供專案 ID', - // Claude 訂閱類型和進階選項 - subscriptionType: '訂閱類型', + claudeMaxSubscription: 'Claude Max', claudeProSubscription: 'Claude Pro', - claudeProLimitation: 'Pro 帳號不支援 Claude Opus 4 模型', - autoStopOnWarning: '5小時使用量接近限制時自動停止排程', - autoStopOnWarningDescription: - '當系統檢測到帳戶接近5小時使用限制時,自動暫停排程該帳戶。進入新的時間視窗後會自動恢復排程。', - useUnifiedUserAgent: '使用統一 Claude Code 版本', - useUnifiedUserAgentDescription: - '開啟後將使用從真實 Claude Code 用戶端捕獲的統一 User-Agent,提高相容性', - currentUnifiedVersion: '目前統一版本:', - clearCache: '清除快取', - clearing: '清除中...', - waitingForCapture: '等待從 Claude Code 用戶端捕獲 User-Agent', - captureHint: - '💡 提示:如果長時間未能捕獲,請確認有 Claude Code 用戶端正在使用此帳戶,或聯絡開發者檢查 User-Agent 格式是否發生變化', - useUnifiedClientId: '使用統一的用戶端識別', - useUnifiedClientIdDescription: - '開啟後將使用固定的用戶端識別,使所有請求看起來來自同一個用戶端,減少特徵', clientIdLabel: '用戶端識別 ID', regenerateClientId: '重新產生', - clientIdDescription: '此ID將替換請求中的user_id用戶端部分,保留session部分用於黏性工作階段', // 編輯模式欄位 accountNameEdit: '帳戶名稱', @@ -2456,7 +2440,6 @@ export default { descriptionOptionalEdit: '描述 (可選)', descriptionOptionalEditPlaceholder: '帳戶用途說明...', accountTypeEdit: '帳戶類型', - selectGroupRequired: '選擇群組 *', noAvailableGroups: '暫無可用群組', membersCount: ' 個成員', createNewGroup: '新建群組', @@ -2464,29 +2447,14 @@ export default { // AWS Bedrock 設定 bedrockCredentials: '憑證設定', bedrockCredentialsDescription: '請填寫 AWS 存取憑證,用於呼叫 Amazon Bedrock 服務。', - awsAccessKeyId: 'AWS Access Key ID *', - awsAccessKeyIdPlaceholder: '請輸入 AWS 存取密鑰 ID...', - awsSecretAccessKey: 'AWS Secret Access Key *', - awsSecretAccessKeyPlaceholder: '請輸入 AWS 秘密存取密鑰...', - sessionTokenOptional: 'Session Token (可選)', sessionTokenOptionalPlaceholder: '臨時憑證的工作階段令牌...', - sessionTokenDescription: '僅在使用臨時憑證(如 STS 產生的憑證)時需要填寫', - awsRegion: 'AWS 區域 *', - awsRegionPlaceholder: '選擇 AWS 區域...', bedrockModelConfig: '模型設定', defaultModelLabel: '預設模型', - defaultModelPlaceholder: '例如:anthropic.claude-3-5-sonnet-20240620-v1:0', - defaultModelDescription: '留空將使用系統預設模型。支援 inference profile ID 或 ARN', smallFastModelLabel: '小型快速模型', - smallFastModelPlaceholder: '例如:anthropic.claude-3-haiku-20240307-v1:0', - smallFastModelDescription: '用於簡單任務的快速模型,支援 inference profile ID 或 ARN', // Azure OpenAI 設定 azureOpenAIConfig: 'Azure OpenAI 設定', azureOpenAIDescription: '請設定 Azure OpenAI 服務的連線資訊和部署詳情。', - azureEndpoint: 'Azure Endpoint *', - azureEndpointPlaceholder: '例如:https://your-resource.openai.azure.com/', - azureEndpointDescription: 'Azure OpenAI 服務的端點 URL', azureApiKey: 'API Key *', azureApiKeyPlaceholder: '請輸入 Azure OpenAI API Key...', azureApiVersion: 'API 版本', @@ -2509,7 +2477,6 @@ export default { modelMappingFromPlaceholder: '例如:claude-3-5-sonnet-20241022', modelMappingTo: '實際模型', modelMappingToPlaceholder: '例如:claude-3-5-sonnet-latest', - addModelMapping: '新增映射', removeMapping: '移除', presetMappings: '預設映射', modelMappingExample: '示例:claude-3-5-sonnet-20241022 → claude-3-5-sonnet-latest', @@ -2521,9 +2488,7 @@ export default { setupTokenStep1: '步驟 1:產生授權連結', setupTokenStep1Description: '系統將產生一個專用的授權連結,用於取得臨時授權碼。', setupTokenStep2: '步驟 2:完成授權', - setupTokenStep2Description: '在新視窗中開啟授權連結,使用您的 Claude 帳戶登入並完成授權。', setupTokenStep3: '步驟 3:輸入授權碼', - setupTokenStep3Description: '授權成功後,系統會顯示授權碼,請複製並貼上到下方輸入框。', setupTokenUrlGenerated: '授權連結已產生', setupTokenOpenInBrowser: '在瀏覽器中開啟', setupTokenCopyLink: '複製連結', @@ -2548,15 +2513,9 @@ export default { // 限流和配額管理 rateLimitSettings: '限流設定', - enableRateLimit: '啟用速率限制', - rateLimitDuration: '限流時長 (秒)', rateLimitDurationPlaceholder: '例如:60', - rateLimitDescription: '啟用後將限制請求頻率,防止帳戶被封鎖', quotaManagement: '配額管理', - dailyQuotaLabel: '每日配額限制', - dailyQuotaPlaceholder: '0 表示不限制', quotaResetTimeLabel: '配額重設時間', - quotaResetTimePlaceholder: '例如:00:00', quotaResetDescription: '每天配額重設的時間點', currentDailyUsage: '今日已用', @@ -2564,7 +2523,6 @@ export default { advancedSettings: '進階設定', customUserAgent: '自定義 User-Agent', customUserAgentPlaceholder: '留空使用預設 User-Agent...', - userAgentDescription: '用於請求時的 User-Agent 識別', // 通用提示和狀態 notSet: '未設定', @@ -2605,7 +2563,6 @@ export default { '提示:如果您的帳戶是普通個人帳戶(未綁定 Google Cloud),請留空此欄位。', // AWS 區域參考 - awsRegionReference: '常用 AWS 區域參考:', awsRegionEastUS: 'us-east-1 (美國東部)', awsRegionWestUS: 'us-west-2 (美國西部)', awsRegionEuropeIreland: 'eu-west-1 (歐洲愛爾蘭)', @@ -2625,7 +2582,6 @@ export default { azureModelSelectionDesc: '選擇此部署支援的模型類型', // 限流機制 - rateLimitMechanism: '限流機制', enableRateLimitMechanism: '啟用限流機制', rateLimitDescription2: '啟用後,當帳戶返回429錯誤時將暫停調度一段時間', rateLimitDurationMinutes: '限流時間 (分鐘)', @@ -2634,32 +2590,23 @@ export default { // Claude Console 特定欄位 claudeConsoleFields: 'Claude Console 特定欄位', - quotaManagement: '額度管理', modelMappingTable: '模型映射表', modelMappingTableOptional: '模型映射表 (可選)', - addModelMapping: '新增模型映射', // Claude 訂閱類型 subscriptionType: '訂閱類型', // Setup Token 授權 - setupTokenAuth: 'Setup Token 授權', claudeSetupTokenAuth: 'Claude Setup Token 授權', setupTokenAuthSteps: '請按照以下步驟透過 Setup Token 完成 Claude 帳戶的授權:', generateSetupTokenLink: '產生 Setup Token 授權連結', - generating: '產生中...', // 按鈕和操作 - verifying: '驗證中...', completeAuth: '完成授權', - updating: '更新中...', - update: '更新', // 錯誤訊息 generateSetupTokenFailed: '產生Setup Token授權連結失敗', - copyFailed: '複製失敗,請手動複製', setupTokenAuthFailed: 'Setup Token授權失敗,請檢查授權碼是否正確', - accountCreationFailed: '帳戶建立失敗', accountCreationError: '帳戶建立失敗:', // 頁面結構註釋 @@ -2674,29 +2621,18 @@ export default { // 驗證訊息 nameRequired: '請填寫帳戶名稱', - apiUrlRequired: '請填寫 API URL', rateLimitDefault60: '預設60分鐘', rateLimitPauseDescription: '帳戶被限流後暫停調度的時間(分鐘)', - apiUrlPlaceholder: '例如:https://api.example.com', - apiKeyPlaceholder: '請輸入API Key', dailyQuotaLimit: '每日額度限制 ($)', quotaZeroUnlimited: '0 表示不限制', - dailyQuotaDescription: '設定每日使用額度,0 表示不限制', - quotaResetTime: '額度重設時間', - quotaResetTimeDescription: '每日自動重設額度的時間', - modelMappingDescription: - '留空表示支援所有模型且不修改請求。設定映射後,左側模型會被識別為支援的模型,右側是實際傳送的模型。', // 額度管理 quotaManagementFields: '配額管理欄位', dailyQuotaLimitDollar: '每日配額限制 ($)', - quotaZeroUnlimited: '0 表示不限制', dailyQuotaDesc: '設定每日使用配額,0 表示不限制', - quotaResetTime: '配額重設時間', quotaResetTimeDesc: '每日自動重設配額的時間', // 模型映射 - modelMappingOptional: '模型映射表 (可選)', modelMappingDesc: '留空表示支援所有模型且不修改請求。設定映射後,左側模型會被識別為支援的模型,右側是實際發送的模型。', originalModelName: '原始模型名稱', @@ -2708,7 +2644,6 @@ export default { // Claude 訂閱類型 claudeSubscriptionType: '訂閱類型', - claudeProLimitation: 'Pro 帳戶不支援 Claude Opus 4 模型', // Claude 進階選項 claudeAutoStopScheduling: '5小時使用量接近限制時自動停止調度', @@ -2736,13 +2671,10 @@ export default { setupTokenCopyTitle: '複製連結', // 步驟指示器 - stepIndicator: '步驟指示器', - step1BasicInfo: '步驟1: 基本資訊和代理設定', step2OAuth: '步驟2: OAuth授權', step2SetupToken: '步驟2: Setup Token授權', // 群組選擇器 - groupSelector: '群組選擇器', multiGroupInterface: '多選群組介面', createNewGroupOption: '新建群組選項', @@ -2752,15 +2684,12 @@ export default { // Placeholder 文字 originalModelNamePlaceholder: '原始模型名稱', mappedModelNamePlaceholder: '映射後的模型名稱', - userAgentPlaceholder: '留空則透傳用戶端 User-Agent', authCodePlaceholder: '貼上Claude Code授權頁面獲取的Authorization Code...', leaveEmptyNoUpdate: '留空表示不更新', leaveEmptyNoUpdateKey: '留空表示不更新 API Key', leaveEmptyNoUpdateToken: '留空表示不更新...', // 標籤和描述 - customUserAgentOptional: '自定義 User-Agent (可選)', - clientIdLabel: '用戶端標識 ID', schedulePriorityLabel: '調度優先級 (1-100)', attentionLabel: '注意:', supportedModelsLabel: '支援的模型', @@ -2773,17 +2702,14 @@ export default { previousStepBtn: '上一步', // 描述性文字 - claudeProLimitation: 'Pro 帳戶不支援 Claude Opus 4 模型', claude5HourLimitDesc: '5小時使用量接近限制時自動停止調度', claude5HourLimitExplanation: '當系統檢測到帳戶接近5小時使用限制時,自動暫停調度該帳戶。進入新的時間視窗後會自動恢復調度。', useUnifiedClaudeVersion: '使用統一 Claude Code 版本', unifiedVersionDesc: '開啟後將使用從真實 Claude Code 用戶端捕獲的統一 User-Agent,提高相容性', - currentUnifiedVersion: '💡 目前統一版本:', waitingUserAgent: '⏳ 等待從 Claude Code 用戶端捕獲 User-Agent', userAgentTip: '💡 提示:如果長時間未能捕獲,請確認有 Claude Code 用戶端正在使用此帳戶,', contactDeveloper: '或聯繫開發者檢查 User-Agent 格式是否發生變化', - useUnifiedClientId: '使用統一的用戶端標識', unifiedClientIdDesc: '開啟後將使用固定的用戶端標識,使所有請求看起來來自同一個用戶端,減少特徵', clientIdReplaceDesc: '此ID將替換請求中的user_id用戶端部分,保留session部分用於黏性工作階段', @@ -2804,9 +2730,6 @@ export default { awsRegionRef: '常用 AWS 區域參考:', // 錯誤訊息 - apiKeyRequired: '請填寫 API Key', - refreshTokenRequired: '請填寫 Refresh Token', - accessTokenRequired: '請填寫 Access Token', copyFailedManual: '複製失敗,請手動複製', // 表單描述 @@ -2817,9 +2740,7 @@ export default { // 基础標籤 apiUrlLabel: 'API URL', - apiUrlRequired: 'API URL *', apiKeyLabel: 'API Key', - apiKeyRequired: 'API Key *', // 更多缺失的翻譯鍵 copyLinkTooltip: '複製連結', From 22e27738aaba8917ac638a519025d1cfec6364ba Mon Sep 17 00:00:00 2001 From: Wangnov Date: Thu, 11 Sep 2025 18:03:33 +0800 Subject: [PATCH 33/40] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=20ESLint=20?= =?UTF-8?q?=E5=92=8C=20Vite=20=E9=85=8D=E7=BD=AE=E4=BB=A5=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E5=BC=80=E5=8F=91=E4=BD=93=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 .eslintrc.cjs 中允许在所有环境中使用 console 语句,避免构建警告 - 在 vite.config.js 中提升 chunk 大小限制以消除 UI 库的警告,并明确本地组件的导入设置 --- web/admin-spa/.eslintrc.cjs | 3 ++- web/admin-spa/vite.config.js | 6 +++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/web/admin-spa/.eslintrc.cjs b/web/admin-spa/.eslintrc.cjs index 2a625058..59ceab17 100644 --- a/web/admin-spa/.eslintrc.cjs +++ b/web/admin-spa/.eslintrc.cjs @@ -18,7 +18,8 @@ module.exports = { rules: { 'vue/multi-word-component-names': 'off', 'vue/no-v-html': 'off', - 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', + // 允许在所有环境中使用 console 语句以避免构建警告 + 'no-console': 'off', 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 'prettier/prettier': 'error', 'vue/attributes-order': [ diff --git a/web/admin-spa/vite.config.js b/web/admin-spa/vite.config.js index 7b677438..3193bf80 100644 --- a/web/admin-spa/vite.config.js +++ b/web/admin-spa/vite.config.js @@ -51,7 +51,9 @@ export default defineConfig(({ mode }) => { imports: ['vue', 'vue-router', 'pinia'] }), Components({ - resolvers: [ElementPlusResolver()] + // 仅自动注册 Element Plus 组件;本地组件显式导入 + resolvers: [ElementPlusResolver()], + dirs: [] }) ], resolve: { @@ -103,6 +105,8 @@ export default defineConfig(({ mode }) => { build: { outDir: 'dist', assetsDir: 'assets', + // 提升 chunk 大小限制以消除 UI 库的大量警告 + chunkSizeWarningLimit: 1024, rollupOptions: { output: { manualChunks(id) { From e36bacfd6b13ab9417996cfa08da4d0f31ce76ae Mon Sep 17 00:00:00 2001 From: Wangnov Date: Thu, 11 Sep 2025 18:03:54 +0800 Subject: [PATCH 34/40] =?UTF-8?q?feat:=20=E5=AE=8C=E6=88=90=E5=A4=9A?= =?UTF-8?q?=E4=B8=AA=E7=BB=84=E4=BB=B6=E7=9A=84=E5=9B=BD=E9=99=85=E5=8C=96?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E4=B8=8E=E6=96=87=E6=9C=AC=E6=9B=BF=E6=8D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 更新 AccountForm.vue 中的占位符文本为 i18n 语言包中的键 - 修改 ConfirmModal.vue 中的确认和取消按钮文本为 i18n 语言包中的键 - 更新 CustomDropdown.vue 中的占位符文本为 i18n 语言包中的键 - 修改 app.js 中的应用标题为英文版本 - 更新 router/index.js 中的日志输出为英文 - 在 accounts.js 和 apiKeys.js 中的错误处理信息中引入 i18n 键以提升多语言一致性 - 更新 dashboard.js 中的系统状态和错误日志为 i18n 键 - 在 DashboardView.vue 中的多个文本替换为 i18n 语言包中的键 --- .../src/components/accounts/AccountForm.vue | 6 ++--- .../src/components/common/ConfirmModal.vue | 14 +++++++----- .../src/components/common/CustomDropdown.vue | 6 +++-- web/admin-spa/src/config/app.js | 2 +- web/admin-spa/src/router/index.js | 5 ++--- web/admin-spa/src/stores/accounts.js | 22 ++++++++++++++----- web/admin-spa/src/stores/apiKeys.js | 4 ++-- web/admin-spa/src/stores/apistats.js | 2 +- web/admin-spa/src/stores/auth.js | 2 +- web/admin-spa/src/stores/dashboard.js | 15 +++++++------ web/admin-spa/src/views/DashboardView.vue | 21 ++++++++++-------- 11 files changed, 59 insertions(+), 40 deletions(-) diff --git a/web/admin-spa/src/components/accounts/AccountForm.vue b/web/admin-spa/src/components/accounts/AccountForm.vue index c26db224..0e3688ca 100644 --- a/web/admin-spa/src/components/accounts/AccountForm.vue +++ b/web/admin-spa/src/components/accounts/AccountForm.vue @@ -1847,14 +1847,14 @@ diff --git a/web/admin-spa/src/components/common/ConfirmModal.vue b/web/admin-spa/src/components/common/ConfirmModal.vue index 5ea5b512..1b5fa7af 100644 --- a/web/admin-spa/src/components/common/ConfirmModal.vue +++ b/web/admin-spa/src/components/common/ConfirmModal.vue @@ -25,13 +25,13 @@ class="flex-1 rounded-xl bg-gray-100 px-4 py-2.5 font-medium text-gray-700 transition-colors hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-200 dark:hover:bg-gray-600" @click="$emit('cancel')" > - {{ cancelText }} + {{ cancelLabel }} @@ -40,11 +40,12 @@ diff --git a/web/admin-spa/src/components/common/CustomDropdown.vue b/web/admin-spa/src/components/common/CustomDropdown.vue index b476624f..d91228cd 100644 --- a/web/admin-spa/src/components/common/CustomDropdown.vue +++ b/web/admin-spa/src/components/common/CustomDropdown.vue @@ -11,7 +11,7 @@ - {{ selectedLabel || placeholder }} + {{ selectedLabel || placeholderText }} 0" class="text-purple-600" - >缓存创建: + >{{ t('dashboard.cacheCreateTokens') }}: {{ formatNumber(dashboardData.totalCacheCreateTokens || 0) }} 缓存读取: + >{{ t('dashboard.cacheReadTokens') }}: {{ formatNumber(dashboardData.totalCacheReadTokens || 0) }}= 1000000) { From 5ea3623736d959c5462594a2006bf887fb01a735 Mon Sep 17 00:00:00 2001 From: Wangnov Date: Thu, 11 Sep 2025 18:36:26 +0800 Subject: [PATCH 35/40] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=20DashboardVie?= =?UTF-8?q?w.vue=20=E4=B8=AD=E7=9A=84=E7=B3=BB=E7=BB=9F=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E6=96=87=E6=9C=AC=E4=B8=BA=20i18n=20=E8=AF=AD=E8=A8=80?= =?UTF-8?q?=E5=8C=85=E4=B8=AD=E7=9A=84=E9=94=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将系统状态文本替换为动态获取的 i18n 键,以提升多语言支持和一致性。 --- web/admin-spa/src/views/DashboardView.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/admin-spa/src/views/DashboardView.vue b/web/admin-spa/src/views/DashboardView.vue index 9cd16612..794181fb 100644 --- a/web/admin-spa/src/views/DashboardView.vue +++ b/web/admin-spa/src/views/DashboardView.vue @@ -200,7 +200,7 @@ {{ t('dashboard.systemStatus') }}

- {{ dashboardData.systemStatus }} + {{ t(`common.system.status.${dashboardData.systemStatusCode || 'normal'}`) }}

{{ t('dashboard.uptime') }}: {{ formattedUptime }} From 99d72516ae88493e7ec22f9d19fc8b7b31aaa907 Mon Sep 17 00:00:00 2001 From: Wangnov Date: Thu, 11 Sep 2025 20:45:27 +0800 Subject: [PATCH 36/40] =?UTF-8?q?feat:=20=E5=AE=8C=E6=88=90=20AccountForm.?= =?UTF-8?q?vue=20=E7=BB=84=E4=BB=B6=E7=9A=84=E5=9B=BD=E9=99=85=E5=8C=96?= =?UTF-8?q?=E6=96=87=E6=9C=AC=E6=9B=BF=E6=8D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将多个文本替换为 i18n 语言包中的键,以提升多语言支持和一致性。 - 更新了模型支持描述、用户代理描述、凭证文件描述等文本内容。 - 通过引入 i18n 键,增强了用户界面的可读性和可维护性。 --- .../src/components/accounts/AccountForm.vue | 116 ++++++++++-------- web/admin-spa/src/i18n/locales/en.js | 42 ++++++- web/admin-spa/src/i18n/locales/zh-cn.js | 41 ++++++- web/admin-spa/src/i18n/locales/zh-tw.js | 38 +++++- web/admin-spa/src/views/DashboardView.vue | 11 +- web/admin-spa/src/views/SettingsView.vue | 78 +++++++----- 6 files changed, 235 insertions(+), 91 deletions(-) diff --git a/web/admin-spa/src/components/accounts/AccountForm.vue b/web/admin-spa/src/components/accounts/AccountForm.vue index 0e3688ca..0ea88cf6 100644 --- a/web/admin-spa/src/components/accounts/AccountForm.vue +++ b/web/admin-spa/src/components/accounts/AccountForm.vue @@ -806,7 +806,7 @@

- 留空表示支持所有模型。如果指定模型,请求中的模型不在列表内将不会调度到此账号 + {{ t('accountForm.modelSupportDescription') }}

@@ -821,7 +821,7 @@ type="text" />

- 留空时将自动使用客户端的 User-Agent,仅在需要固定特定 UA 时填写 + {{ t('accountForm.userAgentDescription') }}

@@ -995,7 +995,7 @@ @click="regenerateClientId" > - 重新生成 + {{ t('accountForm.regenerateButton') }}
@@ -1103,7 +1103,7 @@ ~/.config/gemini/credentials.json - 文件中的凭证。 + {{ t('accountForm.credentialsFileDescription') }}

- 点击下方按钮生成授权链接 + {{ t('accountForm.clickToGenerateAuthLink') }}

- 输入 Authorization Code + {{ t('accountForm.enterAuthorizationCode') }}

- 授权完成后,从返回页面复制 Authorization Code,并粘贴到下方输入框: + {{ t('accountForm.copyAuthCodeDescription') }}

@@ -1377,7 +1377,7 @@

- 请粘贴从Claude Code授权页面复制的Authorization Code + {{ t('accountForm.authCodeInputHint') }}

@@ -1761,7 +1761,7 @@

- 设置每日使用额度,0 表示不限制 + {{ t('accountForm.dailyQuotaDescription') }}

-

每日自动重置额度的时间

+

+ {{ t('accountForm.quotaResetTimeDescription') }} +

@@ -1797,7 +1799,7 @@ >
- 今日使用情况 + {{ t('accountForm.todayUsageLabel') }} ${{ calculateCurrentUsage().toFixed(4) }} / ${{ form.dailyQuota.toFixed(2) }} @@ -1818,22 +1820,26 @@
- 剩余: ${{ Math.max(0, form.dailyQuota - calculateCurrentUsage()).toFixed(2) }} + {{ + t('accountForm.remainingQuota', { + amount: Math.max(0, form.dailyQuota - calculateCurrentUsage()).toFixed(2) + }) + }} - {{ usagePercentage.toFixed(1) }}% 已使用 + {{ t('accountForm.usedPercentage', { percentage: usagePercentage.toFixed(1) }) }}
- +

- 留空表示支持所有模型且不修改请求。配置映射后,左侧模型会被识别为支持的模型,右侧是实际发送的模型。 + {{ t('accountForm.modelMappingTableDescription') }}

@@ -1911,14 +1917,14 @@

- 留空表示支持所有模型。如果指定模型,请求中的模型不在列表内将不会调度到此账号 + {{ t('accountForm.modelSupportDescription') }}

- +

- 留空时将自动使用客户端的 User-Agent,仅在需要固定特定 UA 时填写 + {{ t('accountForm.userAgentDescription') }}

@@ -1946,21 +1952,23 @@ }}

- 启用后,当账号返回429错误时将暂停调度一段时间 + {{ t('accountForm.rateLimitDescription') }}

- + -

账号被限流后暂停调度的时间(分钟)

+

+ {{ t('accountForm.rateLimitDurationDescription') }} +

@@ -2011,14 +2019,14 @@
-

常用 AWS 区域参考:

+

{{ t('accountForm.awsRegionReferenceTitle') }}

- • us-east-1 (美国东部) - • us-west-2 (美国西部) - • eu-west-1 (欧洲爱尔兰) - • ap-southeast-1 (新加坡) - • ap-northeast-1 (东京) - • eu-central-1 (法兰克福) + {{ t('accountForm.awsRegionUsEast1') }} + {{ t('accountForm.awsRegionUsWest2') }} + {{ t('accountForm.awsRegionEuWest1') }} + {{ t('accountForm.awsRegionApSoutheast1') }} + {{ t('accountForm.awsRegionApNortheast1') }} + {{ t('accountForm.awsRegionEuCentral1') }}
@@ -2083,21 +2091,23 @@ }}

- 启用后,当账号返回429错误时将暂停调度一段时间 + {{ t('accountForm.rateLimitDescription') }}

- + -

账号被限流后暂停调度的时间(分钟)

+

+ {{ t('accountForm.rateLimitDurationDescription') }} +

@@ -2199,7 +2209,9 @@ {{ model }} -

选择此部署支持的模型类型

+

+ {{ t('accountForm.azureModelTypeDescription') }} +

@@ -2223,9 +2235,11 @@ {{ t('accountForm.updateTokenLabel') }}

- 可以更新 Access Token 和 Refresh Token。为了安全起见,不会显示当前的 Token 值。 + {{ t('accountForm.tokenUpdateDescription') }} +

+

+ {{ t('accountForm.tokenUpdateHint') }}

-

💡 留空表示不更新该字段。

@@ -2265,7 +2279,7 @@ type="button" @click="$emit('close')" > - 取消 + {{ t('accountForm.cancelButton') }} @@ -510,7 +512,7 @@ class="flex items-center text-gray-600 dark:text-gray-400" > - 已启用签名验证 + {{ t('settings.enableSignature') }} @@ -556,17 +558,19 @@
- 暂无配置的通知平台,请点击"添加平台"按钮添加 + {{ t('settings.noPlatforms') }}
-

高级设置

+

+ {{ t('settings.advancedSettings') }} +

- 发送测试通知 + {{ t('settings.sendTestNotification') }}
@@ -646,10 +650,12 @@

- {{ editingPlatform ? '编辑' : '添加' }}通知平台 + {{ + editingPlatform ? t('settings.editPlatformModal') : t('settings.addPlatformModal') + }}

- 配置{{ editingPlatform ? '并更新' : '新的' }}Webhook通知渠道 + {{ t('settings.configurePlatform') }}

@@ -671,7 +677,7 @@ class="mb-2 flex items-center text-sm font-medium text-gray-700 dark:text-gray-300" > - 平台类型 + {{ t('settings.platformType') }}
@@ -693,7 +699,7 @@

- 编辑模式下不能更改平台类型 + {{ t('settings.cannotChangePlatformType') }}

@@ -703,13 +709,13 @@ class="mb-2 flex items-center text-sm font-medium text-gray-700 dark:text-gray-300" > - 名称 - (可选) + {{ t('settings.platformName') }} + {{ t('settings.optional') }}
@@ -720,7 +726,7 @@ class="mb-2 flex items-center text-sm font-medium text-gray-700 dark:text-gray-300" > - Webhook URL + {{ t('settings.webhookUrl') }} *
@@ -762,7 +768,7 @@ class="mb-2 flex items-center text-sm font-medium text-gray-700 dark:text-gray-300" > - 设备密钥 (Device Key) + {{ t('settings.deviceKey') }} * - 服务器地址 - (可选) + {{ t('settings.serverUrl') }} + {{ t('settings.optional') }} - 通知级别 + {{ t('settings.notificationLevel') }}
- 必填项 + {{ t('settings.requiredField') }}
From 67e72f1aaf194bf5217c664cdcf1e7ddfd2ce95c Mon Sep 17 00:00:00 2001 From: Wangnov Date: Thu, 11 Sep 2025 20:45:38 +0800 Subject: [PATCH 37/40] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=20Element=20Pl?= =?UTF-8?q?us=20=E8=AF=AD=E8=A8=80=E9=85=8D=E7=BD=AE=E5=A4=84=E7=90=86?= =?UTF-8?q?=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移除了 main.js 中对 zhCn 语言包的直接引用,改为在 App.vue 中通过 ElConfigProvider 处理语言配置。 - 这一变更提升了国际化的灵活性和可维护性,确保语言设置集中管理。 --- web/admin-spa/src/main.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/web/admin-spa/src/main.js b/web/admin-spa/src/main.js index 513e5947..41beb5b8 100644 --- a/web/admin-spa/src/main.js +++ b/web/admin-spa/src/main.js @@ -1,7 +1,6 @@ import { createApp } from 'vue' import { createPinia } from 'pinia' import ElementPlus from 'element-plus' -import zhCn from 'element-plus/dist/locale/zh-cn.mjs' import 'element-plus/dist/index.css' import 'element-plus/theme-chalk/dark/css-vars.css' import App from './App.vue' @@ -24,10 +23,8 @@ app.use(router) // 使用Vue I18n app.use(i18n) -// 使用Element Plus -app.use(ElementPlus, { - locale: zhCn -}) +// 使用Element Plus - 语言配置在 App.vue 中通过 ElConfigProvider 处理 +app.use(ElementPlus) // 设置axios拦截器 const userStore = useUserStore() From ebafbdcc55ff4e89da97a5a0e1920aa915240c26 Mon Sep 17 00:00:00 2001 From: Wangnov Date: Thu, 11 Sep 2025 20:45:55 +0800 Subject: [PATCH 38/40] =?UTF-8?q?feat:=20=E7=A7=BB=E9=99=A4=E6=9C=AA?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E7=9A=84=E7=BB=84=E4=BB=B6=E5=A3=B0=E6=98=8E?= =?UTF-8?q?=E4=BB=A5=E4=BC=98=E5=8C=96=E7=B1=BB=E5=9E=8B=E5=AE=9A=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 从 components.d.ts 文件中移除了多个未使用的组件声明,提升了类型定义的清晰度和可维护性。 - 此变更有助于减少代码冗余,确保组件声明与实际使用保持一致。 --- web/admin-spa/components.d.ts | 38 ----------------------------------- 1 file changed, 38 deletions(-) diff --git a/web/admin-spa/components.d.ts b/web/admin-spa/components.d.ts index 212216a5..8f36b547 100644 --- a/web/admin-spa/components.d.ts +++ b/web/admin-spa/components.d.ts @@ -7,48 +7,10 @@ export {} declare module 'vue' { export interface GlobalComponents { - AccountForm: typeof import('./src/components/accounts/AccountForm.vue')['default'] - AccountSelector: typeof import('./src/components/common/AccountSelector.vue')['default'] - AggregatedStatsCard: typeof import('./src/components/apistats/AggregatedStatsCard.vue')['default'] - ApiKeyInput: typeof import('./src/components/apistats/ApiKeyInput.vue')['default'] - AppHeader: typeof import('./src/components/layout/AppHeader.vue')['default'] - BatchApiKeyModal: typeof import('./src/components/apikeys/BatchApiKeyModal.vue')['default'] - BatchEditApiKeyModal: typeof import('./src/components/apikeys/BatchEditApiKeyModal.vue')['default'] - ChangeRoleModal: typeof import('./src/components/admin/ChangeRoleModal.vue')['default'] - ConfirmDialog: typeof import('./src/components/common/ConfirmDialog.vue')['default'] - ConfirmModal: typeof import('./src/components/common/ConfirmModal.vue')['default'] - CreateApiKeyModal: typeof import('./src/components/apikeys/CreateApiKeyModal.vue')['default'] - CustomDropdown: typeof import('./src/components/common/CustomDropdown.vue')['default'] - EditApiKeyModal: typeof import('./src/components/apikeys/EditApiKeyModal.vue')['default'] ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider'] ElDatePicker: typeof import('element-plus/es')['ElDatePicker'] ElTooltip: typeof import('element-plus/es')['ElTooltip'] - ExpiryEditModal: typeof import('./src/components/apikeys/ExpiryEditModal.vue')['default'] - GroupManagementModal: typeof import('./src/components/accounts/GroupManagementModal.vue')['default'] - LanguageSwitch: typeof import('./src/components/common/LanguageSwitch.vue')['default'] - LimitConfig: typeof import('./src/components/apistats/LimitConfig.vue')['default'] - LogoTitle: typeof import('./src/components/common/LogoTitle.vue')['default'] - MainLayout: typeof import('./src/components/layout/MainLayout.vue')['default'] - ModelDistribution: typeof import('./src/components/dashboard/ModelDistribution.vue')['default'] - ModelUsageStats: typeof import('./src/components/apistats/ModelUsageStats.vue')['default'] - NewApiKeyModal: typeof import('./src/components/apikeys/NewApiKeyModal.vue')['default'] - OAuthFlow: typeof import('./src/components/accounts/OAuthFlow.vue')['default'] - ProxyConfig: typeof import('./src/components/accounts/ProxyConfig.vue')['default'] - RenewApiKeyModal: typeof import('./src/components/apikeys/RenewApiKeyModal.vue')['default'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] - StatCard: typeof import('./src/components/common/StatCard.vue')['default'] - StatsOverview: typeof import('./src/components/apistats/StatsOverview.vue')['default'] - TabBar: typeof import('./src/components/layout/TabBar.vue')['default'] - ThemeToggle: typeof import('./src/components/common/ThemeToggle.vue')['default'] - ToastNotification: typeof import('./src/components/common/ToastNotification.vue')['default'] - TokenDistribution: typeof import('./src/components/apistats/TokenDistribution.vue')['default'] - UsageDetailModal: typeof import('./src/components/apikeys/UsageDetailModal.vue')['default'] - UsageTrend: typeof import('./src/components/dashboard/UsageTrend.vue')['default'] - UserApiKeysManager: typeof import('./src/components/user/UserApiKeysManager.vue')['default'] - UserUsageStats: typeof import('./src/components/user/UserUsageStats.vue')['default'] - UserUsageStatsModal: typeof import('./src/components/admin/UserUsageStatsModal.vue')['default'] - ViewApiKeyModal: typeof import('./src/components/user/ViewApiKeyModal.vue')['default'] - WindowCountdown: typeof import('./src/components/apikeys/WindowCountdown.vue')['default'] } } From a039d817dba6c978b7166c1c07b55492835b71aa Mon Sep 17 00:00:00 2001 From: Wangnov Date: Thu, 11 Sep 2025 21:38:12 +0800 Subject: [PATCH 39/40] =?UTF-8?q?chore:=20=E4=BC=98=E5=8C=96=E5=9B=BD?= =?UTF-8?q?=E9=99=85=E5=8C=96=E6=96=87=E4=BB=B6=E6=A0=BC=E5=BC=8F=EF=BC=8C?= =?UTF-8?q?=E7=A7=BB=E9=99=A4=E5=A4=9A=E4=BD=99=E7=A9=BA=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 统一三个语言文件的代码格式 - 移除多余的空行以保持代码整洁 --- web/admin-spa/src/i18n/locales/en.js | 2 +- web/admin-spa/src/i18n/locales/zh-cn.js | 2 +- web/admin-spa/src/i18n/locales/zh-tw.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/web/admin-spa/src/i18n/locales/en.js b/web/admin-spa/src/i18n/locales/en.js index 5a0a70b8..62ffbd70 100644 --- a/web/admin-spa/src/i18n/locales/en.js +++ b/web/admin-spa/src/i18n/locales/en.js @@ -712,7 +712,7 @@ export default { claudeMax: 'Claude Max', claudePro: 'Claude Pro', claudeFree: 'Claude Free', - + // Platform display openaiResponsesPlatform: 'OpenAI-Responses', ccrPlatform: 'CCR' diff --git a/web/admin-spa/src/i18n/locales/zh-cn.js b/web/admin-spa/src/i18n/locales/zh-cn.js index ba8eb2cb..999e5b7b 100644 --- a/web/admin-spa/src/i18n/locales/zh-cn.js +++ b/web/admin-spa/src/i18n/locales/zh-cn.js @@ -704,7 +704,7 @@ export default { claudeMax: 'Claude Max', claudePro: 'Claude Pro', claudeFree: 'Claude Free', - + // Platform display openaiResponsesPlatform: 'OpenAI-Responses', ccrPlatform: 'CCR' diff --git a/web/admin-spa/src/i18n/locales/zh-tw.js b/web/admin-spa/src/i18n/locales/zh-tw.js index f7021eac..93657273 100644 --- a/web/admin-spa/src/i18n/locales/zh-tw.js +++ b/web/admin-spa/src/i18n/locales/zh-tw.js @@ -705,7 +705,7 @@ export default { claudeMax: 'Claude Max', claudePro: 'Claude Pro', claudeFree: 'Claude Free', - + // Platform display openaiResponsesPlatform: 'OpenAI-Responses', ccrPlatform: 'CCR' From af8350b850c6f5dfb988ca52d786addb8e45b0c1 Mon Sep 17 00:00:00 2001 From: Wangnov Date: Fri, 12 Sep 2025 00:32:20 +0800 Subject: [PATCH 40/40] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E7=94=9F=E6=88=90=E6=96=87=E4=BB=B6=E7=9A=84=20Pretti?= =?UTF-8?q?er=20=E6=A0=BC=E5=BC=8F=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复 auto-imports.d.ts 和 components.d.ts 的代码格式问题, 确保通过 CI 的 Prettier 格式检查。 --- web/admin-spa/auto-imports.d.ts | 163 +++++++++++++++++--------------- web/admin-spa/components.d.ts | 10 +- 2 files changed, 94 insertions(+), 79 deletions(-) diff --git a/web/admin-spa/auto-imports.d.ts b/web/admin-spa/auto-imports.d.ts index a606bd4d..9954071d 100644 --- a/web/admin-spa/auto-imports.d.ts +++ b/web/admin-spa/auto-imports.d.ts @@ -5,83 +5,98 @@ // Generated by unplugin-auto-import export {} declare global { - const EffectScope: typeof import('vue')['EffectScope'] - const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate'] - const computed: typeof import('vue')['computed'] - const createApp: typeof import('vue')['createApp'] - const createPinia: typeof import('pinia')['createPinia'] - const customRef: typeof import('vue')['customRef'] - const defineAsyncComponent: typeof import('vue')['defineAsyncComponent'] - const defineComponent: typeof import('vue')['defineComponent'] - const defineStore: typeof import('pinia')['defineStore'] - const effectScope: typeof import('vue')['effectScope'] - const getActivePinia: typeof import('pinia')['getActivePinia'] - const getCurrentInstance: typeof import('vue')['getCurrentInstance'] - const getCurrentScope: typeof import('vue')['getCurrentScope'] - const h: typeof import('vue')['h'] - const inject: typeof import('vue')['inject'] - const isProxy: typeof import('vue')['isProxy'] - const isReactive: typeof import('vue')['isReactive'] - const isReadonly: typeof import('vue')['isReadonly'] - const isRef: typeof import('vue')['isRef'] - const mapActions: typeof import('pinia')['mapActions'] - const mapGetters: typeof import('pinia')['mapGetters'] - const mapState: typeof import('pinia')['mapState'] - const mapStores: typeof import('pinia')['mapStores'] - const mapWritableState: typeof import('pinia')['mapWritableState'] - const markRaw: typeof import('vue')['markRaw'] - const nextTick: typeof import('vue')['nextTick'] - const onActivated: typeof import('vue')['onActivated'] - const onBeforeMount: typeof import('vue')['onBeforeMount'] - const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave'] - const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate'] - const onBeforeUnmount: typeof import('vue')['onBeforeUnmount'] - const onBeforeUpdate: typeof import('vue')['onBeforeUpdate'] - const onDeactivated: typeof import('vue')['onDeactivated'] - const onErrorCaptured: typeof import('vue')['onErrorCaptured'] - const onMounted: typeof import('vue')['onMounted'] - const onRenderTracked: typeof import('vue')['onRenderTracked'] - const onRenderTriggered: typeof import('vue')['onRenderTriggered'] - const onScopeDispose: typeof import('vue')['onScopeDispose'] - const onServerPrefetch: typeof import('vue')['onServerPrefetch'] - const onUnmounted: typeof import('vue')['onUnmounted'] - const onUpdated: typeof import('vue')['onUpdated'] - const onWatcherCleanup: typeof import('vue')['onWatcherCleanup'] - const provide: typeof import('vue')['provide'] - const reactive: typeof import('vue')['reactive'] - const readonly: typeof import('vue')['readonly'] - const ref: typeof import('vue')['ref'] - const resolveComponent: typeof import('vue')['resolveComponent'] - const setActivePinia: typeof import('pinia')['setActivePinia'] - const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix'] - const shallowReactive: typeof import('vue')['shallowReactive'] - const shallowReadonly: typeof import('vue')['shallowReadonly'] - const shallowRef: typeof import('vue')['shallowRef'] - const storeToRefs: typeof import('pinia')['storeToRefs'] - const toRaw: typeof import('vue')['toRaw'] - const toRef: typeof import('vue')['toRef'] - const toRefs: typeof import('vue')['toRefs'] - const toValue: typeof import('vue')['toValue'] - const triggerRef: typeof import('vue')['triggerRef'] - const unref: typeof import('vue')['unref'] - const useAttrs: typeof import('vue')['useAttrs'] - const useCssModule: typeof import('vue')['useCssModule'] - const useCssVars: typeof import('vue')['useCssVars'] - const useId: typeof import('vue')['useId'] - const useLink: typeof import('vue-router')['useLink'] - const useModel: typeof import('vue')['useModel'] - const useRoute: typeof import('vue-router')['useRoute'] - const useRouter: typeof import('vue-router')['useRouter'] - const useSlots: typeof import('vue')['useSlots'] - const useTemplateRef: typeof import('vue')['useTemplateRef'] - const watch: typeof import('vue')['watch'] - const watchEffect: typeof import('vue')['watchEffect'] - const watchPostEffect: typeof import('vue')['watchPostEffect'] - const watchSyncEffect: typeof import('vue')['watchSyncEffect'] + const EffectScope: (typeof import('vue'))['EffectScope'] + const acceptHMRUpdate: (typeof import('pinia'))['acceptHMRUpdate'] + const computed: (typeof import('vue'))['computed'] + const createApp: (typeof import('vue'))['createApp'] + const createPinia: (typeof import('pinia'))['createPinia'] + const customRef: (typeof import('vue'))['customRef'] + const defineAsyncComponent: (typeof import('vue'))['defineAsyncComponent'] + const defineComponent: (typeof import('vue'))['defineComponent'] + const defineStore: (typeof import('pinia'))['defineStore'] + const effectScope: (typeof import('vue'))['effectScope'] + const getActivePinia: (typeof import('pinia'))['getActivePinia'] + const getCurrentInstance: (typeof import('vue'))['getCurrentInstance'] + const getCurrentScope: (typeof import('vue'))['getCurrentScope'] + const h: (typeof import('vue'))['h'] + const inject: (typeof import('vue'))['inject'] + const isProxy: (typeof import('vue'))['isProxy'] + const isReactive: (typeof import('vue'))['isReactive'] + const isReadonly: (typeof import('vue'))['isReadonly'] + const isRef: (typeof import('vue'))['isRef'] + const mapActions: (typeof import('pinia'))['mapActions'] + const mapGetters: (typeof import('pinia'))['mapGetters'] + const mapState: (typeof import('pinia'))['mapState'] + const mapStores: (typeof import('pinia'))['mapStores'] + const mapWritableState: (typeof import('pinia'))['mapWritableState'] + const markRaw: (typeof import('vue'))['markRaw'] + const nextTick: (typeof import('vue'))['nextTick'] + const onActivated: (typeof import('vue'))['onActivated'] + const onBeforeMount: (typeof import('vue'))['onBeforeMount'] + const onBeforeRouteLeave: (typeof import('vue-router'))['onBeforeRouteLeave'] + const onBeforeRouteUpdate: (typeof import('vue-router'))['onBeforeRouteUpdate'] + const onBeforeUnmount: (typeof import('vue'))['onBeforeUnmount'] + const onBeforeUpdate: (typeof import('vue'))['onBeforeUpdate'] + const onDeactivated: (typeof import('vue'))['onDeactivated'] + const onErrorCaptured: (typeof import('vue'))['onErrorCaptured'] + const onMounted: (typeof import('vue'))['onMounted'] + const onRenderTracked: (typeof import('vue'))['onRenderTracked'] + const onRenderTriggered: (typeof import('vue'))['onRenderTriggered'] + const onScopeDispose: (typeof import('vue'))['onScopeDispose'] + const onServerPrefetch: (typeof import('vue'))['onServerPrefetch'] + const onUnmounted: (typeof import('vue'))['onUnmounted'] + const onUpdated: (typeof import('vue'))['onUpdated'] + const onWatcherCleanup: (typeof import('vue'))['onWatcherCleanup'] + const provide: (typeof import('vue'))['provide'] + const reactive: (typeof import('vue'))['reactive'] + const readonly: (typeof import('vue'))['readonly'] + const ref: (typeof import('vue'))['ref'] + const resolveComponent: (typeof import('vue'))['resolveComponent'] + const setActivePinia: (typeof import('pinia'))['setActivePinia'] + const setMapStoreSuffix: (typeof import('pinia'))['setMapStoreSuffix'] + const shallowReactive: (typeof import('vue'))['shallowReactive'] + const shallowReadonly: (typeof import('vue'))['shallowReadonly'] + const shallowRef: (typeof import('vue'))['shallowRef'] + const storeToRefs: (typeof import('pinia'))['storeToRefs'] + const toRaw: (typeof import('vue'))['toRaw'] + const toRef: (typeof import('vue'))['toRef'] + const toRefs: (typeof import('vue'))['toRefs'] + const toValue: (typeof import('vue'))['toValue'] + const triggerRef: (typeof import('vue'))['triggerRef'] + const unref: (typeof import('vue'))['unref'] + const useAttrs: (typeof import('vue'))['useAttrs'] + const useCssModule: (typeof import('vue'))['useCssModule'] + const useCssVars: (typeof import('vue'))['useCssVars'] + const useId: (typeof import('vue'))['useId'] + const useLink: (typeof import('vue-router'))['useLink'] + const useModel: (typeof import('vue'))['useModel'] + const useRoute: (typeof import('vue-router'))['useRoute'] + const useRouter: (typeof import('vue-router'))['useRouter'] + const useSlots: (typeof import('vue'))['useSlots'] + const useTemplateRef: (typeof import('vue'))['useTemplateRef'] + const watch: (typeof import('vue'))['watch'] + const watchEffect: (typeof import('vue'))['watchEffect'] + const watchPostEffect: (typeof import('vue'))['watchPostEffect'] + const watchSyncEffect: (typeof import('vue'))['watchSyncEffect'] } // for type re-export declare global { // @ts-ignore - export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue' + export type { + Component, + ComponentPublicInstance, + ComputedRef, + DirectiveBinding, + ExtractDefaultPropTypes, + ExtractPropTypes, + ExtractPublicPropTypes, + InjectionKey, + PropType, + Ref, + MaybeRef, + MaybeRefOrGetter, + VNode, + WritableComputedRef + } from 'vue' import('vue') } diff --git a/web/admin-spa/components.d.ts b/web/admin-spa/components.d.ts index 8f36b547..4eb0ab56 100644 --- a/web/admin-spa/components.d.ts +++ b/web/admin-spa/components.d.ts @@ -7,10 +7,10 @@ export {} declare module 'vue' { export interface GlobalComponents { - ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider'] - ElDatePicker: typeof import('element-plus/es')['ElDatePicker'] - ElTooltip: typeof import('element-plus/es')['ElTooltip'] - RouterLink: typeof import('vue-router')['RouterLink'] - RouterView: typeof import('vue-router')['RouterView'] + ElConfigProvider: (typeof import('element-plus/es'))['ElConfigProvider'] + ElDatePicker: (typeof import('element-plus/es'))['ElDatePicker'] + ElTooltip: (typeof import('element-plus/es'))['ElTooltip'] + RouterLink: (typeof import('vue-router'))['RouterLink'] + RouterView: (typeof import('vue-router'))['RouterView'] } }