i18n: complete usage panel and settings internationalization

- Add missing i18n keys for usage statistics panel (trends, cost, perMillion, etc.)
- Add i18n keys for settings advanced section (configDir, proxy, modelTest, etc.)
- Add streamCheck i18n keys for health check configuration
- Remove hardcoded Chinese fallback values from t() calls
- Add common keys (all, search, reset, actions, deleting)
This commit is contained in:
Jason
2025-12-20 22:29:39 +08:00
parent 2fb3b5405a
commit ca7cb398c2
9 changed files with 355 additions and 103 deletions
+12 -12
View File
@@ -47,12 +47,12 @@ export function ModelTestConfigPanel() {
try {
setIsSaving(true);
await saveStreamCheckConfig(config);
toast.success(t("streamCheck.configSaved", "健康检查配置已保存"), {
toast.success(t("streamCheck.configSaved"), {
closeButton: true,
});
} catch (e) {
toast.error(
t("streamCheck.configSaveFailed", "保存失败") + ": " + String(e),
t("streamCheck.configSaveFailed") + ": " + String(e),
);
} finally {
setIsSaving(false);
@@ -78,12 +78,12 @@ export function ModelTestConfigPanel() {
{/* 测试模型配置 */}
<div className="space-y-4">
<h4 className="text-sm font-medium text-muted-foreground">
{t("streamCheck.testModels", "测试模型")}
{t("streamCheck.testModels")}
</h4>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="space-y-2">
<Label htmlFor="claudeModel">
{t("streamCheck.claudeModel", "Claude 模型")}
{t("streamCheck.claudeModel")}
</Label>
<Input
id="claudeModel"
@@ -97,7 +97,7 @@ export function ModelTestConfigPanel() {
<div className="space-y-2">
<Label htmlFor="codexModel">
{t("streamCheck.codexModel", "Codex 模型")}
{t("streamCheck.codexModel")}
</Label>
<Input
id="codexModel"
@@ -111,7 +111,7 @@ export function ModelTestConfigPanel() {
<div className="space-y-2">
<Label htmlFor="geminiModel">
{t("streamCheck.geminiModel", "Gemini 模型")}
{t("streamCheck.geminiModel")}
</Label>
<Input
id="geminiModel"
@@ -128,12 +128,12 @@ export function ModelTestConfigPanel() {
{/* 检查参数配置 */}
<div className="space-y-4">
<h4 className="text-sm font-medium text-muted-foreground">
{t("streamCheck.checkParams", "检查参数")}
{t("streamCheck.checkParams")}
</h4>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="space-y-2">
<Label htmlFor="timeoutSecs">
{t("streamCheck.timeout", "超时时间(秒)")}
{t("streamCheck.timeout")}
</Label>
<Input
id="timeoutSecs"
@@ -152,7 +152,7 @@ export function ModelTestConfigPanel() {
<div className="space-y-2">
<Label htmlFor="maxRetries">
{t("streamCheck.maxRetries", "最大重试次数")}
{t("streamCheck.maxRetries")}
</Label>
<Input
id="maxRetries"
@@ -171,7 +171,7 @@ export function ModelTestConfigPanel() {
<div className="space-y-2">
<Label htmlFor="degradedThresholdMs">
{t("streamCheck.degradedThreshold", "降级阈值(毫秒)")}
{t("streamCheck.degradedThreshold")}
</Label>
<Input
id="degradedThresholdMs"
@@ -196,12 +196,12 @@ export function ModelTestConfigPanel() {
{isSaving ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
{t("common.saving", "保存中...")}
{t("common.saving")}
</>
) : (
<>
<Save className="mr-2 h-4 w-4" />
{t("common.save", "保存")}
{t("common.save")}
</>
)}
</Button>
+20 -26
View File
@@ -63,7 +63,7 @@ export function PricingConfigPanel() {
<div className="flex items-center gap-2">
<ChevronRight className="h-4 w-4" />
<CardTitle className="text-base">
{t("usage.modelPricing", "模型定价")}
{t("usage.modelPricing")}
</CardTitle>
</div>
</CardHeader>
@@ -85,7 +85,7 @@ export function PricingConfigPanel() {
<ChevronRight className="h-4 w-4" />
)}
<CardTitle className="text-base">
{t("usage.modelPricing", "模型定价")}
{t("usage.modelPricing")}
</CardTitle>
</div>
</CardHeader>
@@ -93,7 +93,7 @@ export function PricingConfigPanel() {
<CardContent>
<Alert variant="destructive">
<AlertDescription>
{t("usage.loadPricingError", "加载定价数据失败")}:{" "}
{t("usage.loadPricingError")}:{" "}
{String(error)}
</AlertDescription>
</Alert>
@@ -107,7 +107,7 @@ export function PricingConfigPanel() {
<div className="space-y-4">
<div className="flex items-center justify-between mb-4">
<h4 className="text-sm font-medium text-muted-foreground">
{t("usage.modelPricingDesc", "配置各模型的 Token 成本")} ()
{t("usage.modelPricingDesc")} {t("usage.perMillion")}
</h4>
<Button
onClick={(e) => {
@@ -117,7 +117,7 @@ export function PricingConfigPanel() {
size="sm"
>
<Plus className="mr-1 h-4 w-4" />
{t("common.add", "新增")}
{t("common.add")}
</Button>
</div>
@@ -125,10 +125,7 @@ export function PricingConfigPanel() {
{!pricing || pricing.length === 0 ? (
<Alert>
<AlertDescription>
{t(
"usage.noPricingData",
'暂无定价数据。点击"新增"添加模型定价配置。',
)}
{t("usage.noPricingData")}
</AlertDescription>
</Alert>
) : (
@@ -136,22 +133,22 @@ export function PricingConfigPanel() {
<Table>
<TableHeader>
<TableRow>
<TableHead>{t("usage.model", "模型")}</TableHead>
<TableHead>{t("usage.displayName", "显示名称")}</TableHead>
<TableHead>{t("usage.model")}</TableHead>
<TableHead>{t("usage.displayName")}</TableHead>
<TableHead className="text-right">
{t("usage.inputCost", "输入成本")}
{t("usage.inputCost")}
</TableHead>
<TableHead className="text-right">
{t("usage.outputCost", "输出成本")}
{t("usage.outputCost")}
</TableHead>
<TableHead className="text-right">
{t("usage.cacheReadCost", "缓存读取")}
{t("usage.cacheReadCost")}
</TableHead>
<TableHead className="text-right">
{t("usage.cacheWriteCost", "缓存写入")}
{t("usage.cacheWriteCost")}
</TableHead>
<TableHead className="text-right">
{t("common.actions", "操作")}
{t("common.actions")}
</TableHead>
</TableRow>
</TableHeader>
@@ -183,7 +180,7 @@ export function PricingConfigPanel() {
setIsAddingNew(false);
setEditingModel(model);
}}
title={t("common.edit", "编辑")}
title={t("common.edit")}
>
<Pencil className="h-4 w-4" />
</Button>
@@ -191,7 +188,7 @@ export function PricingConfigPanel() {
variant="ghost"
size="icon"
onClick={() => setDeleteConfirm(model.modelId)}
title={t("common.delete", "删除")}
title={t("common.delete")}
className="text-destructive hover:text-destructive"
>
<Trash2 className="h-4 w-4" />
@@ -224,18 +221,15 @@ export function PricingConfigPanel() {
<DialogContent>
<DialogHeader>
<DialogTitle>
{t("usage.deleteConfirmTitle", "确认删除")}
{t("usage.deleteConfirmTitle")}
</DialogTitle>
<DialogDescription>
{t(
"usage.deleteConfirmDesc",
"确定要删除此模型定价配置吗?此操作无法撤销。",
)}
{t("usage.deleteConfirmDesc")}
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button variant="outline" onClick={() => setDeleteConfirm(null)}>
{t("common.cancel", "取消")}
{t("common.cancel")}
</Button>
<Button
variant="destructive"
@@ -243,8 +237,8 @@ export function PricingConfigPanel() {
disabled={deleteMutation.isPending}
>
{deleteMutation.isPending
? t("common.deleting", "删除中...")
: t("common.delete", "删除")}
? t("common.deleting")
: t("common.delete")}
</Button>
</DialogFooter>
</DialogContent>
+24 -27
View File
@@ -84,11 +84,11 @@ export function RequestLogTable() {
}
>
<SelectTrigger className="w-[130px] bg-background">
<SelectValue placeholder={t("usage.appType", "应用类型")} />
<SelectValue placeholder={t("usage.appType")} />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">
{t("usage.allApps", { defaultValue: "全部应用" })}
{t("usage.allApps")}
</SelectItem>
<SelectItem value="claude">Claude</SelectItem>
<SelectItem value="codex">Codex</SelectItem>
@@ -106,10 +106,10 @@ export function RequestLogTable() {
}
>
<SelectTrigger className="w-[130px] bg-background">
<SelectValue placeholder={t("usage.statusCode", "状态码")} />
<SelectValue placeholder={t("usage.statusCode")} />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">{t("common.all", "全部状态")}</SelectItem>
<SelectItem value="all">{t("common.all")}</SelectItem>
<SelectItem value="200">200 OK</SelectItem>
<SelectItem value="400">400 Bad Request</SelectItem>
<SelectItem value="401">401 Unauthorized</SelectItem>
@@ -122,10 +122,7 @@ export function RequestLogTable() {
<div className="relative flex-1">
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
<Input
placeholder={t(
"usage.searchProviderPlaceholder",
"搜索供应商...",
)}
placeholder={t("usage.searchProviderPlaceholder")}
className="pl-9 bg-background"
value={tempFilters.providerName || ""}
onChange={(e) =>
@@ -137,7 +134,7 @@ export function RequestLogTable() {
/>
</div>
<Input
placeholder={t("usage.searchModelPlaceholder", "搜索模型...")}
placeholder={t("usage.searchModelPlaceholder")}
className="w-[180px] bg-background"
value={tempFilters.model || ""}
onChange={(e) =>
@@ -153,7 +150,7 @@ export function RequestLogTable() {
<div className="flex flex-wrap items-center justify-between gap-3">
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<span className="whitespace-nowrap">
{t("usage.timeRange", { defaultValue: "时间范围" })}:
{t("usage.timeRange")}:
</span>
<Input
type="datetime-local"
@@ -204,7 +201,7 @@ export function RequestLogTable() {
className="h-8"
>
<Search className="mr-2 h-3.5 w-3.5" />
{t("common.search", "查询")}
{t("common.search")}
</Button>
<Button
size="sm"
@@ -213,7 +210,7 @@ export function RequestLogTable() {
className="h-8"
>
<X className="mr-2 h-3.5 w-3.5" />
{t("common.reset", "重置")}
{t("common.reset")}
</Button>
<Button
size="sm"
@@ -236,34 +233,34 @@ export function RequestLogTable() {
<TableHeader>
<TableRow>
<TableHead className="whitespace-nowrap">
{t("usage.time", "时间")}
{t("usage.time")}
</TableHead>
<TableHead className="whitespace-nowrap">
{t("usage.provider", "供应商")}
{t("usage.provider")}
</TableHead>
<TableHead className="min-w-[280px] whitespace-nowrap">
{t("usage.billingModel", "计费模型")}
{t("usage.billingModel")}
</TableHead>
<TableHead className="text-right whitespace-nowrap">
{t("usage.inputTokens", "输入")}
{t("usage.inputTokens")}
</TableHead>
<TableHead className="text-right whitespace-nowrap">
{t("usage.outputTokens", "输出")}
{t("usage.outputTokens")}
</TableHead>
<TableHead className="text-right min-w-[90px] whitespace-nowrap">
{t("usage.cacheReadTokens", "缓存读取")}
{t("usage.cacheReadTokens")}
</TableHead>
<TableHead className="text-right min-w-[90px] whitespace-nowrap">
{t("usage.cacheCreationTokens", "缓存写入")}
{t("usage.cacheCreationTokens")}
</TableHead>
<TableHead className="text-right whitespace-nowrap">
{t("usage.totalCost", "成本")}
{t("usage.totalCost")}
</TableHead>
<TableHead className="text-center min-w-[140px] whitespace-nowrap">
{t("usage.timingInfo", "用时/首字")}
{t("usage.timingInfo")}
</TableHead>
<TableHead className="whitespace-nowrap">
{t("usage.status", "状态")}
{t("usage.status")}
</TableHead>
</TableRow>
</TableHeader>
@@ -274,7 +271,7 @@ export function RequestLogTable() {
colSpan={10}
className="text-center text-muted-foreground"
>
{t("usage.noData", "暂无数据")}
{t("usage.noData")}
</TableCell>
</TableRow>
) : (
@@ -287,7 +284,7 @@ export function RequestLogTable() {
</TableCell>
<TableCell>
{log.providerName ||
t("usage.unknownProvider", "未知供应商")}
t("usage.unknownProvider")}
</TableCell>
<TableCell
className="font-mono text-sm max-w-[280px] truncate"
@@ -355,8 +352,8 @@ export function RequestLogTable() {
}`}
>
{log.isStreaming
? t("usage.stream", "流")
: t("usage.nonStream", "非流")}
? t("usage.stream")
: t("usage.nonStream")}
</span>
</div>
</TableCell>
@@ -382,7 +379,7 @@ export function RequestLogTable() {
{total > 0 && (
<div className="flex items-center justify-between px-2">
<span className="text-sm text-muted-foreground">
{t("usage.totalRecords", "共 {{total}} 条记录", { total })}
{t("usage.totalRecords", { total })}
</span>
<div className="flex items-center gap-1">
<Button
+8 -8
View File
@@ -25,9 +25,9 @@ export function UsageDashboard() {
>
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<div className="flex flex-col gap-1">
<h2 className="text-2xl font-bold">{t("usage.title", "使用统计")}</h2>
<h2 className="text-2xl font-bold">{t("usage.title")}</h2>
<p className="text-sm text-muted-foreground">
{t("usage.subtitle", "查看 AI 模型的使用情况和成本统计")}
{t("usage.subtitle")}
</p>
</div>
@@ -41,19 +41,19 @@ export function UsageDashboard() {
value="1d"
className="flex-1 sm:flex-none sm:px-6 data-[state=active]:bg-primary/10 data-[state=active]:text-primary hover:text-primary transition-colors"
>
{t("usage.today", "24小时")}
{t("usage.today")}
</TabsTrigger>
<TabsTrigger
value="7d"
className="flex-1 sm:flex-none sm:px-6 data-[state=active]:bg-primary/10 data-[state=active]:text-primary hover:text-primary transition-colors"
>
{t("usage.last7days", "7天")}
{t("usage.last7days")}
</TabsTrigger>
<TabsTrigger
value="30d"
className="flex-1 sm:flex-none sm:px-6 data-[state=active]:bg-primary/10 data-[state=active]:text-primary hover:text-primary transition-colors"
>
{t("usage.last30days", "30天")}
{t("usage.last30days")}
</TabsTrigger>
</TabsList>
</Tabs>
@@ -69,15 +69,15 @@ export function UsageDashboard() {
<TabsList className="bg-muted/50">
<TabsTrigger value="logs" className="gap-2">
<ListFilter className="h-4 w-4" />
{t("usage.requestLogs", "请求日志")}
{t("usage.requestLogs")}
</TabsTrigger>
<TabsTrigger value="providers" className="gap-2">
<Activity className="h-4 w-4" />
{t("usage.providerStats", "Provider 统计")}
{t("usage.providerStats")}
</TabsTrigger>
<TabsTrigger value="models" className="gap-2">
<BarChart3 className="h-4 w-4" />
{t("usage.modelStats", "模型统计")}
{t("usage.modelStats")}
</TabsTrigger>
</TabsList>
</div>
+8 -8
View File
@@ -34,7 +34,7 @@ export function UsageSummaryCards({ days }: UsageSummaryCardsProps) {
return [
{
title: t("usage.totalRequests", "总请求数"),
title: t("usage.totalRequests"),
value: totalRequests.toLocaleString(),
icon: Activity,
color: "text-blue-500",
@@ -42,7 +42,7 @@ export function UsageSummaryCards({ days }: UsageSummaryCardsProps) {
subValue: null,
},
{
title: t("usage.totalCost", "总成本"),
title: t("usage.totalCost"),
value: `$${totalCost.toFixed(4)}`,
icon: DollarSign,
color: "text-green-500",
@@ -50,7 +50,7 @@ export function UsageSummaryCards({ days }: UsageSummaryCardsProps) {
subValue: null,
},
{
title: t("usage.totalTokens", "总 Token 数"),
title: t("usage.totalTokens"),
value: totalTokens.toLocaleString(),
icon: Layers,
color: "text-purple-500",
@@ -58,13 +58,13 @@ export function UsageSummaryCards({ days }: UsageSummaryCardsProps) {
subValue: (
<div className="flex flex-col gap-1 text-xs text-muted-foreground mt-3 pt-3 border-t border-border/50">
<div className="flex justify-between items-center">
<span>{t("usage.input", { defaultValue: "Input" })}</span>
<span>{t("usage.input")}</span>
<span className="text-foreground/80">
{(inputTokens / 1000).toFixed(1)}k
</span>
</div>
<div className="flex justify-between items-center">
<span>{t("usage.output", { defaultValue: "Output" })}</span>
<span>{t("usage.output")}</span>
<span className="text-foreground/80">
{(outputTokens / 1000).toFixed(1)}k
</span>
@@ -73,7 +73,7 @@ export function UsageSummaryCards({ days }: UsageSummaryCardsProps) {
),
},
{
title: t("usage.cacheTokens", "缓存 Token"),
title: t("usage.cacheTokens"),
value: totalCacheTokens.toLocaleString(),
icon: Database,
color: "text-orange-500",
@@ -81,13 +81,13 @@ export function UsageSummaryCards({ days }: UsageSummaryCardsProps) {
subValue: (
<div className="flex flex-col gap-1 text-xs text-muted-foreground mt-3 pt-3 border-t border-border/50">
<div className="flex justify-between items-center">
<span>{t("usage.cacheWrite", { defaultValue: "Write" })}</span>
<span>{t("usage.cacheWrite")}</span>
<span className="text-foreground/80">
{(cacheWriteTokens / 1000).toFixed(1)}k
</span>
</div>
<div className="flex justify-between items-center">
<span>{t("usage.cacheRead", { defaultValue: "Read" })}</span>
<span>{t("usage.cacheRead")}</span>
<span className="text-foreground/80">
{(cacheReadTokens / 1000).toFixed(1)}k
</span>