fix(quota): localize kimi quota fallback labels and reset hint

This commit is contained in:
Supra4E8C
2026-02-24 18:07:56 +08:00
parent dc941f6857
commit 72c0ef07ed
7 changed files with 75 additions and 30 deletions
+11 -3
View File
@@ -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<string, string | number>)
: 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 },
+5 -1
View File
@@ -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",
+5 -1
View File
@@ -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",
+5 -1
View File
@@ -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 登录",
+3 -1
View File
@@ -244,7 +244,9 @@ export interface KimiUsagePayload {
export interface KimiQuotaRow {
id: string;
label: string;
label?: string;
labelKey?: string;
labelParams?: Record<string, string | number>;
used: number;
limit: number;
resetHint?: string;
+43 -21
View File
@@ -275,6 +275,8 @@ function toInt(value: unknown): number | null {
return null;
}
type KimiRowLabel = Pick<KimiQuotaRow, 'label' | 'labelKey' | 'labelParams'>;
function kimiResetHint(data: Record<string, unknown>): string | undefined {
const absoluteKeys = ['reset_at', 'resetAt', 'reset_time', 'resetTime'];
for (const key of absoluteKeys) {
@@ -316,39 +318,57 @@ function kimiResetHint(data: Record<string, unknown>): 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<string, unknown>)[key] ?? (detail as Record<string, unknown>)[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<string, unknown>).duration) ?? toInt((detail as Record<string, unknown>).duration);
toInt(window.duration) ??
toInt((item as Record<string, unknown>).duration) ??
toInt((detail as Record<string, unknown>).duration);
const timeUnit =
(window.timeUnit as string) ?? (item as Record<string, unknown>).timeUnit ?? (detail as Record<string, unknown>).timeUnit;
(window as Record<string, unknown>).timeUnit ??
(item as Record<string, unknown>).timeUnit ??
(detail as Record<string, unknown>).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<string, unknown>,
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<string, unknown>, 'Weekly limit');
const summary = toKimiUsageRow(usage as Record<string, unknown>, {
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<string, unknown>, label);
const fallbackLabel = kimiLimitLabel(item, detail, window, idx);
const row = toKimiUsageRow(detail as Record<string, unknown>, fallbackLabel);
if (row) {
rows.push({ id: `limit-${idx}`, ...row });
}
+3 -2
View File
@@ -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 });
}