feat: 完成多个组件的国际化支持与文本替换

- 更新 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 语言包中的键
This commit is contained in:
Wangnov
2025-09-11 18:03:54 +08:00
parent 22e27738aa
commit e36bacfd6b
11 changed files with 59 additions and 40 deletions

View File

@@ -1847,14 +1847,14 @@
<input <input
v-model="mapping.from" v-model="mapping.from"
class="form-input flex-1" class="form-input flex-1"
placeholder="原始模型名称" :placeholder="t('accountForm.originalModelName')"
type="text" type="text"
/> />
<i class="fas fa-arrow-right text-gray-400" /> <i class="fas fa-arrow-right text-gray-400" />
<input <input
v-model="mapping.to" v-model="mapping.to"
class="form-input flex-1" class="form-input flex-1"
placeholder="映射后的模型名称" :placeholder="t('accountForm.mappedModelName')"
type="text" type="text"
/> />
<button <button
@@ -1874,7 +1874,7 @@
@click="addModelMapping" @click="addModelMapping"
> >
<i class="fas fa-plus mr-2" /> <i class="fas fa-plus mr-2" />
添加模型映射 {{ t('accountForm.addModelMapping') }}
</button> </button>
<!-- 快捷添加按钮 --> <!-- 快捷添加按钮 -->

View File

@@ -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" 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')" @click="$emit('cancel')"
> >
{{ cancelText }} {{ cancelLabel }}
</button> </button>
<button <button
class="flex-1 rounded-xl bg-gradient-to-r from-yellow-500 to-orange-500 px-4 py-2.5 font-medium text-white shadow-sm transition-colors hover:from-yellow-600 hover:to-orange-600" class="flex-1 rounded-xl bg-gradient-to-r from-yellow-500 to-orange-500 px-4 py-2.5 font-medium text-white shadow-sm transition-colors hover:from-yellow-600 hover:to-orange-600"
@click="$emit('confirm')" @click="$emit('confirm')"
> >
{{ confirmText }} {{ confirmLabel }}
</button> </button>
</div> </div>
</div> </div>
@@ -40,11 +40,12 @@
</template> </template>
<script setup> <script setup>
import { computed } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
const { t } = useI18n() const { t } = useI18n()
defineProps({ const props = defineProps({
show: { show: {
type: Boolean, type: Boolean,
required: true required: true
@@ -59,14 +60,17 @@ defineProps({
}, },
confirmText: { confirmText: {
type: String, type: String,
default: () => t('common.confirmModal.continue') default: ''
}, },
cancelText: { cancelText: {
type: String, type: String,
default: () => t('common.confirmModal.cancel') default: ''
} }
}) })
const confirmLabel = computed(() => props.confirmText || t('common.confirmModal.continue'))
const cancelLabel = computed(() => props.cancelText || t('common.confirmModal.cancel'))
defineEmits(['confirm', 'cancel']) defineEmits(['confirm', 'cancel'])
</script> </script>

View File

@@ -11,7 +11,7 @@
<span <span
class="select-none whitespace-nowrap text-sm font-medium text-gray-700 dark:text-gray-200" class="select-none whitespace-nowrap text-sm font-medium text-gray-700 dark:text-gray-200"
> >
{{ selectedLabel || placeholder }} {{ selectedLabel || placeholderText }}
</span> </span>
<i <i
:class="[ :class="[
@@ -80,7 +80,7 @@ const props = defineProps({
}, },
placeholder: { placeholder: {
type: String, type: String,
default: () => t('common.customDropdown.placeholder') default: ''
}, },
icon: { icon: {
type: String, type: String,
@@ -99,6 +99,8 @@ const triggerRef = ref(null)
const dropdownRef = ref(null) const dropdownRef = ref(null)
const dropdownStyle = ref({}) const dropdownStyle = ref({})
const placeholderText = computed(() => props.placeholder || t('common.customDropdown.placeholder'))
const selectedLabel = computed(() => { const selectedLabel = computed(() => {
const selected = props.options.find((opt) => opt.value === props.modelValue) const selected = props.options.find((opt) => opt.value === props.modelValue)
return selected ? selected.label : '' return selected ? selected.label : ''

View File

@@ -4,7 +4,7 @@ export const APP_CONFIG = {
basePath: import.meta.env.VITE_APP_BASE_URL || (import.meta.env.DEV ? '/admin/' : '/web/admin/'), basePath: import.meta.env.VITE_APP_BASE_URL || (import.meta.env.DEV ? '/admin/' : '/web/admin/'),
// 应用标题 // 应用标题
title: import.meta.env.VITE_APP_TITLE || 'Claude Relay Service - 管理后台', title: import.meta.env.VITE_APP_TITLE || 'Claude Relay Service - Admin Panel',
// 是否为开发环境 // 是否为开发环境
isDev: import.meta.env.DEV, isDev: import.meta.env.DEV,

View File

@@ -2,6 +2,7 @@ import { createRouter, createWebHistory } from 'vue-router'
import { useAuthStore } from '@/stores/auth' import { useAuthStore } from '@/stores/auth'
import { useUserStore } from '@/stores/user' import { useUserStore } from '@/stores/user'
import { APP_CONFIG } from '@/config/app' import { APP_CONFIG } from '@/config/app'
import { showToast } from '@/utils/toast'
// 路由懒加载 // 路由懒加载
const LoginView = () => import('@/views/LoginView.vue') const LoginView = () => import('@/views/LoginView.vue')
@@ -150,7 +151,7 @@ router.beforeEach(async (to, from, next) => {
const authStore = useAuthStore() const authStore = useAuthStore()
const userStore = useUserStore() const userStore = useUserStore()
console.log('路由导航:', { console.log('Router navigation:', {
to: to.path, to: to.path,
from: from.path, from: from.path,
fullPath: to.fullPath, fullPath: to.fullPath,
@@ -177,8 +178,6 @@ router.beforeEach(async (to, from, next) => {
} catch (error) { } catch (error) {
// If the error is about disabled account, redirect to login with error // If the error is about disabled account, redirect to login with error
if (error.message && error.message.includes('disabled')) { if (error.message && error.message.includes('disabled')) {
// Import showToast to display the error
const { showToast } = await import('@/utils/toast')
showToast(error.message, 'error') showToast(error.message, 'error')
} }
return next('/user-login') return next('/user-login')

View File

@@ -204,7 +204,9 @@ export const useAccountsStore = defineStore('accounts', () => {
await fetchClaudeConsoleAccounts() await fetchClaudeConsoleAccounts()
return response.data return response.data
} else { } else {
throw new Error(response.message || '创建Claude Console账户失败') throw new Error(
response.message || i18n.global.t('common.errors.createClaudeConsoleAccountFailed')
)
} }
} catch (err) { } catch (err) {
error.value = err.message error.value = err.message
@@ -284,7 +286,9 @@ export const useAccountsStore = defineStore('accounts', () => {
await fetchAzureOpenAIAccounts() await fetchAzureOpenAIAccounts()
return response.data return response.data
} else { } else {
throw new Error(response.message || '创建Azure OpenAI账户失败') throw new Error(
response.message || i18n.global.t('common.errors.createAzureOpenAIAccountFailed')
)
} }
} catch (err) { } catch (err) {
error.value = err.message error.value = err.message
@@ -344,7 +348,9 @@ export const useAccountsStore = defineStore('accounts', () => {
await fetchClaudeConsoleAccounts() await fetchClaudeConsoleAccounts()
return response return response
} else { } else {
throw new Error(response.message || '更新Claude Console账户失败') throw new Error(
response.message || i18n.global.t('common.errors.updateClaudeConsoleAccountFailed')
)
} }
} catch (err) { } catch (err) {
error.value = err.message error.value = err.message
@@ -424,7 +430,9 @@ export const useAccountsStore = defineStore('accounts', () => {
await fetchAzureOpenAIAccounts() await fetchAzureOpenAIAccounts()
return response return response
} else { } else {
throw new Error(response.message || '更新Azure OpenAI账户失败') throw new Error(
response.message || i18n.global.t('common.errors.updateAzureOpenAIAccountFailed')
)
} }
} catch (err) { } catch (err) {
error.value = err.message error.value = err.message
@@ -624,7 +632,9 @@ export const useAccountsStore = defineStore('accounts', () => {
if (response.success) { if (response.success) {
return response.data // 返回整个对象包含authUrl和sessionId return response.data // 返回整个对象包含authUrl和sessionId
} else { } else {
throw new Error(response.message || '生成Setup Token URL失败') throw new Error(
response.message || i18n.global.t('common.errors.generateSetupTokenUrlFailed')
)
} }
} catch (err) { } catch (err) {
error.value = err.message error.value = err.message
@@ -642,7 +652,7 @@ export const useAccountsStore = defineStore('accounts', () => {
if (response.success) { if (response.success) {
return response.data return response.data
} else { } else {
throw new Error(response.message || '交换Setup Token授权码失败') throw new Error(response.message || i18n.global.t('common.errors.exchangeSetupTokenFailed'))
} }
} catch (err) { } catch (err) {
error.value = err.message error.value = err.message

View File

@@ -145,7 +145,7 @@ export const useApiKeysStore = defineStore('apiKeys', () => {
throw new Error(response.message || i18n.global.t('apiKeys.operationFailed')) throw new Error(response.message || i18n.global.t('apiKeys.operationFailed'))
} }
} catch (err) { } catch (err) {
console.error('获取API Key统计失败:', err) console.error(i18n.global.t('common.errors.getApiKeyStatsFailed'), err)
return null return null
} }
} }
@@ -170,7 +170,7 @@ export const useApiKeysStore = defineStore('apiKeys', () => {
throw new Error(response.message || i18n.global.t('apiKeys.operationFailed')) throw new Error(response.message || i18n.global.t('apiKeys.operationFailed'))
} }
} catch (err) { } catch (err) {
console.error('获取标签失败:', err) console.error(i18n.global.t('common.errors.getTagsFailed'), err)
return [] return []
} }
} }

View File

@@ -367,7 +367,7 @@ export const useApiStatsStore = defineStore('apistats', () => {
}) })
if (validIds.length === 0) { if (validIds.length === 0) {
throw new Error('所有 API Key 都无效') throw new Error(i18n.global.t('common.errors.allApiKeysInvalid'))
} }
apiIds.value = validIds apiIds.value = validIds

View File

@@ -107,7 +107,7 @@ export const useAuthStore = defineStore('auth', () => {
} }
} }
} catch (error) { } catch (error) {
console.error('加载OEM设置失败:', error) console.error(i18n.global.t('common.errors.loadOemSettingsFailed'), error)
} finally { } finally {
oemLoading.value = false oemLoading.value = false
} }

View File

@@ -3,7 +3,6 @@ import { ref, computed } from 'vue'
import { apiClient } from '@/config/api' import { apiClient } from '@/config/api'
import { showToast } from '@/utils/toast' import { showToast } from '@/utils/toast'
import i18n from '@/i18n' import i18n from '@/i18n'
import i18n from '@/i18n'
export const useDashboardStore = defineStore('dashboard', () => { export const useDashboardStore = defineStore('dashboard', () => {
// 状态 // 状态
@@ -43,7 +42,7 @@ export const useDashboardStore = defineStore('dashboard', () => {
realtimeTPM: 0, realtimeTPM: 0,
metricsWindow: 5, metricsWindow: 5,
isHistoricalMetrics: false, isHistoricalMetrics: false,
systemStatus: '正常', systemStatus: i18n.global.t('system.status.normal'),
uptime: 0, uptime: 0,
systemTimezone: 8 // 默认 UTC+8 systemTimezone: 8 // 默认 UTC+8
}) })
@@ -200,7 +199,9 @@ export const useDashboardStore = defineStore('dashboard', () => {
realtimeTPM: realtimeMetrics.tpm || 0, realtimeTPM: realtimeMetrics.tpm || 0,
metricsWindow: realtimeMetrics.windowMinutes || 5, metricsWindow: realtimeMetrics.windowMinutes || 5,
isHistoricalMetrics: realtimeMetrics.isHistorical || false, isHistoricalMetrics: realtimeMetrics.isHistorical || false,
systemStatus: systemHealth.redisConnected ? '正常' : '异常', systemStatus: systemHealth.redisConnected
? i18n.global.t('system.status.normal')
: i18n.global.t('system.status.abnormal'),
uptime: systemHealth.uptime || 0, uptime: systemHealth.uptime || 0,
systemTimezone: dashboardResponse.data.systemTimezone || 8 systemTimezone: dashboardResponse.data.systemTimezone || 8
} }
@@ -220,7 +221,7 @@ export const useDashboardStore = defineStore('dashboard', () => {
} }
} }
} catch (error) { } catch (error) {
console.error('加载仪表板数据失败:', error) console.error(i18n.global.t('common.errors.loadDashboardFailed'), error)
} finally { } finally {
loading.value = false loading.value = false
} }
@@ -308,7 +309,7 @@ export const useDashboardStore = defineStore('dashboard', () => {
trendData.value = response.data trendData.value = response.data
} }
} catch (error) { } catch (error) {
console.error('加载使用趋势失败:', error) console.error(i18n.global.t('common.errors.loadUsageTrendFailed'), error)
} }
} }
@@ -399,7 +400,7 @@ export const useDashboardStore = defineStore('dashboard', () => {
dashboardModelStats.value = response.data dashboardModelStats.value = response.data
} }
} catch (error) { } catch (error) {
console.error('加载模型统计失败:', error) console.error(i18n.global.t('common.errors.loadModelStatsFailed'), error)
} }
} }
@@ -501,7 +502,7 @@ export const useDashboardStore = defineStore('dashboard', () => {
} }
} }
} catch (error) { } catch (error) {
console.error('加载API Keys趋势失败:', error) console.error(i18n.global.t('common.errors.loadApiKeysTrendFailed'), error)
} }
} }

View File

@@ -309,13 +309,13 @@
}}</span></span }}</span></span
> >
<span v-if="(dashboardData.totalCacheCreateTokens || 0) > 0" class="text-purple-600" <span v-if="(dashboardData.totalCacheCreateTokens || 0) > 0" class="text-purple-600"
>缓存创建: >{{ t('dashboard.cacheCreateTokens') }}:
<span class="font-medium">{{ <span class="font-medium">{{
formatNumber(dashboardData.totalCacheCreateTokens || 0) formatNumber(dashboardData.totalCacheCreateTokens || 0)
}}</span></span }}</span></span
> >
<span v-if="(dashboardData.totalCacheReadTokens || 0) > 0" class="text-purple-600" <span v-if="(dashboardData.totalCacheReadTokens || 0) > 0" class="text-purple-600"
>缓存读取: >{{ t('dashboard.cacheReadTokens') }}:
<span class="font-medium">{{ <span class="font-medium">{{
formatNumber(dashboardData.totalCacheReadTokens || 0) formatNumber(dashboardData.totalCacheReadTokens || 0)
}}</span></span }}</span></span
@@ -930,11 +930,14 @@ function createUsageTrendChart() {
const bLabel = b.dataset.label || '' const bLabel = b.dataset.label || ''
// 费用和请求数使用不同的轴,单独处理 // 费用和请求数使用不同的轴,单独处理
if (aLabel === '费用 (USD)' || bLabel === '费用 (USD)') { if (aLabel === t('dashboard.costLabel') || bLabel === t('dashboard.costLabel')) {
return aLabel === '费用 (USD)' ? -1 : 1 return aLabel === t('dashboard.costLabel') ? -1 : 1
} }
if (aLabel === '请求数' || bLabel === '请求数') { if (
return aLabel === '请求数' ? 1 : -1 aLabel === t('dashboard.requestsLabel') ||
bLabel === t('dashboard.requestsLabel')
) {
return aLabel === t('dashboard.requestsLabel') ? 1 : -1
} }
// 其他按token值倒序 // 其他按token值倒序
@@ -945,15 +948,15 @@ function createUsageTrendChart() {
const label = context.dataset.label || '' const label = context.dataset.label || ''
let value = context.parsed.y let value = context.parsed.y
if (label === '费用 (USD)') { if (label === t('dashboard.costLabel')) {
// 格式化费用显示 // 格式化费用显示
if (value < 0.01) { if (value < 0.01) {
return label + ': $' + value.toFixed(6) return label + ': $' + value.toFixed(6)
} else { } else {
return label + ': $' + value.toFixed(4) return label + ': $' + value.toFixed(4)
} }
} else if (label === '请求数') { } else if (label === t('dashboard.requestsLabel')) {
return label + ': ' + value.toLocaleString() + ' 次' return label + ': ' + value.toLocaleString()
} else { } else {
// 格式化token数显示 // 格式化token数显示
if (value >= 1000000) { if (value >= 1000000) {