diff --git a/src/components/quota/quotaConfigs.ts b/src/components/quota/quotaConfigs.ts index 66be1cf..cabdcbb 100644 --- a/src/components/quota/quotaConfigs.ts +++ b/src/components/quota/quotaConfigs.ts @@ -915,9 +915,17 @@ const renderKimiItems = ( return rows.map((row) => { const limit = row.limit; const used = row.used; - const remaining = limit > 0 ? Math.max(0, Math.min(100, Math.round(((limit - used) / limit) * 100))) : (used > 0 ? 0 : null); + const remaining = + limit > 0 + ? Math.max(0, Math.min(100, Math.round(((limit - used) / limit) * 100))) + : used > 0 + ? 0 + : null; const percentLabel = remaining === null ? '--' : `${remaining}%`; - const resetLabel = formatKimiResetHint(row.resetHint); + const rowLabel = row.labelKey + ? t(row.labelKey, (row.labelParams ?? {}) as Record) + : row.label ?? ''; + const resetLabel = formatKimiResetHint(t, row.resetHint); return h( 'div', @@ -925,7 +933,7 @@ const renderKimiItems = ( h( 'div', { className: styleMap.quotaRowHeader }, - h('span', { className: styleMap.quotaModel }, row.label), + h('span', { className: styleMap.quotaModel }, rowLabel), h( 'div', { className: styleMap.quotaMeta }, diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 04ea087..650eb90 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -629,7 +629,11 @@ "missing_auth_index": "Auth file missing auth_index", "empty_data": "No quota data available", "refresh_button": "Refresh Quota", - "fetch_all": "Fetch All" + "fetch_all": "Fetch All", + "weekly_limit": "Weekly limit", + "limit_window": "{{duration}} limit", + "limit_index": "Limit #{{index}}", + "reset_hint": "resets in {{hint}}" }, "vertex_import": { "title": "Vertex JSON Login", diff --git a/src/i18n/locales/ru.json b/src/i18n/locales/ru.json index 75bcb6a..df8b0fd 100644 --- a/src/i18n/locales/ru.json +++ b/src/i18n/locales/ru.json @@ -632,7 +632,11 @@ "missing_auth_index": "В файле авторизации отсутствует auth_index", "empty_data": "Данные по квоте отсутствуют", "refresh_button": "Обновить квоту", - "fetch_all": "Получить все" + "fetch_all": "Получить все", + "weekly_limit": "Недельный лимит", + "limit_window": "Лимит {{duration}}", + "limit_index": "Лимит #{{index}}", + "reset_hint": "сброс через {{hint}}" }, "vertex_import": { "title": "Вход с Vertex JSON", diff --git a/src/i18n/locales/zh-CN.json b/src/i18n/locales/zh-CN.json index 116a6d5..bf18860 100644 --- a/src/i18n/locales/zh-CN.json +++ b/src/i18n/locales/zh-CN.json @@ -629,7 +629,11 @@ "missing_auth_index": "认证文件缺少 auth_index", "empty_data": "暂无额度数据", "refresh_button": "刷新额度", - "fetch_all": "获取全部" + "fetch_all": "获取全部", + "weekly_limit": "周限额", + "limit_window": "{{duration}} 限额", + "limit_index": "限额 #{{index}}", + "reset_hint": "{{hint}} 后重置" }, "vertex_import": { "title": "Vertex JSON 登录", diff --git a/src/types/quota.ts b/src/types/quota.ts index e8f84ba..ebabf91 100644 --- a/src/types/quota.ts +++ b/src/types/quota.ts @@ -244,7 +244,9 @@ export interface KimiUsagePayload { export interface KimiQuotaRow { id: string; - label: string; + label?: string; + labelKey?: string; + labelParams?: Record; used: number; limit: number; resetHint?: string; diff --git a/src/utils/quota/builders.ts b/src/utils/quota/builders.ts index d6723b9..af31618 100644 --- a/src/utils/quota/builders.ts +++ b/src/utils/quota/builders.ts @@ -275,6 +275,8 @@ function toInt(value: unknown): number | null { return null; } +type KimiRowLabel = Pick; + function kimiResetHint(data: Record): string | undefined { const absoluteKeys = ['reset_at', 'resetAt', 'reset_time', 'resetTime']; for (const key of absoluteKeys) { @@ -316,39 +318,57 @@ function kimiResetHint(data: Record): string | undefined { return undefined; } +function kimiDurationToken(duration: number, rawTimeUnit: unknown): string { + const unit = typeof rawTimeUnit === 'string' ? rawTimeUnit.trim().toUpperCase() : ''; + if (unit === 'MINUTES') { + return duration % 60 === 0 ? `${duration / 60}h` : `${duration}m`; + } + if (unit === 'HOURS') return `${duration}h`; + if (unit === 'DAYS') return `${duration}d`; + return `${duration}s`; +} + function kimiLimitLabel( item: KimiLimitItem, detail: KimiUsageDetail | KimiLimitItem, window: KimiLimitWindow, index: number -): string { +): KimiRowLabel { for (const key of ['name', 'title', 'scope'] as const) { const val = (item as Record)[key] ?? (detail as Record)[key]; - if (typeof val === 'string' && val.trim()) return val.trim(); + if (typeof val === 'string' && val.trim()) return { label: val.trim() }; } const duration = - toInt(window.duration) ?? toInt((item as Record).duration) ?? toInt((detail as Record).duration); + toInt(window.duration) ?? + toInt((item as Record).duration) ?? + toInt((detail as Record).duration); const timeUnit = - (window.timeUnit as string) ?? (item as Record).timeUnit ?? (detail as Record).timeUnit; + (window as Record).timeUnit ?? + (item as Record).timeUnit ?? + (detail as Record).timeUnit; if (duration !== null && duration > 0) { - const unit = typeof timeUnit === 'string' ? timeUnit.toUpperCase() : ''; - if (unit === 'MINUTES') { - return duration % 60 === 0 ? `${duration / 60}h limit` : `${duration}m limit`; - } - if (unit === 'HOURS') return `${duration}h limit`; - if (unit === 'DAYS') return `${duration}d limit`; - return `${duration}s limit`; + return { + labelKey: 'kimi_quota.limit_window', + labelParams: { + duration: kimiDurationToken(duration, timeUnit), + }, + }; } - return `Limit #${index + 1}`; + return { + labelKey: 'kimi_quota.limit_index', + labelParams: { + index: index + 1, + }, + }; } function toKimiUsageRow( data: Record, - defaultLabel: string -): { label: string; used: number; limit: number; resetHint?: string } | null { + fallbackLabel: KimiRowLabel +): (KimiRowLabel & { used: number; limit: number; resetHint?: string }) | null { const limit = toInt(data.limit); let used = toInt(data.used); if (used === null) { @@ -358,12 +378,12 @@ function toKimiUsageRow( } } if (used === null && limit === null) return null; - const label = + const explicitLabel = (typeof data.name === 'string' && data.name.trim()) || - (typeof data.title === 'string' && data.title.trim()) || - defaultLabel; + (typeof data.title === 'string' && data.title.trim()); + const label = explicitLabel ? { label: explicitLabel } : fallbackLabel; return { - label, + ...label, used: used ?? 0, limit: limit ?? 0, resetHint: kimiResetHint(data), @@ -375,7 +395,9 @@ export function buildKimiQuotaRows(payload: KimiUsagePayload): KimiQuotaRow[] { const usage = payload.usage; if (usage && typeof usage === 'object') { - const summary = toKimiUsageRow(usage as Record, 'Weekly limit'); + const summary = toKimiUsageRow(usage as Record, { + labelKey: 'kimi_quota.weekly_limit', + }); if (summary) { rows.push({ id: 'summary', ...summary }); } @@ -386,8 +408,8 @@ export function buildKimiQuotaRows(payload: KimiUsagePayload): KimiQuotaRow[] { limits.forEach((item, idx) => { const detail = (item.detail && typeof item.detail === 'object' ? item.detail : item) as KimiUsageDetail | KimiLimitItem; const window = (item.window && typeof item.window === 'object' ? item.window : {}) as KimiLimitWindow; - const label = kimiLimitLabel(item, detail, window, idx); - const row = toKimiUsageRow(detail as Record, label); + const fallbackLabel = kimiLimitLabel(item, detail, window, idx); + const row = toKimiUsageRow(detail as Record, fallbackLabel); if (row) { rows.push({ id: `limit-${idx}`, ...row }); } diff --git a/src/utils/quota/formatters.ts b/src/utils/quota/formatters.ts index c7ff41d..7af2b41 100644 --- a/src/utils/quota/formatters.ts +++ b/src/utils/quota/formatters.ts @@ -2,6 +2,7 @@ * Formatting functions for quota display. */ +import type { TFunction } from 'i18next'; import type { CodexUsageWindow } from '@/types'; import { normalizeNumberValue } from './parsers'; @@ -67,7 +68,7 @@ export function getStatusFromError(err: unknown): number | undefined { return undefined; } -export function formatKimiResetHint(hint?: string): string { +export function formatKimiResetHint(t: TFunction, hint?: string): string { if (!hint) return ''; - return `resets in ${hint}`; + return t('kimi_quota.reset_hint', { hint }); }