Use Token consistently in Chinese UI labels

This commit is contained in:
musistudio
2026-07-30 18:44:03 +08:00
parent 571aa63602
commit 43f99b2de8
5 changed files with 102 additions and 18 deletions
@@ -3525,7 +3525,7 @@ function AgentSessionDetailCard({
<th className="px-3 py-2 font-semibold">{t("Route")}</th>
<th className="px-3 py-2 font-semibold">{t("Model")}</th>
<th className="px-3 py-2 text-right font-semibold">{t("Tools")}</th>
<th className="px-3 py-2 text-right font-semibold">{t("Tokens")}</th>
<th className="px-3 py-2 text-right font-semibold">{t("Token")}</th>
<th className="px-3 py-2 text-right font-semibold">{t("Cost")}</th>
<th className="px-3 py-2 text-right font-semibold">{t("Duration")}</th>
</tr>
@@ -3610,7 +3610,7 @@ function AgentTracePanel({ trace }: { trace: AgentTraceDetail }) {
<th className="px-3 py-2 font-semibold">{t("Timeline")}</th>
<th className="px-3 py-2 font-semibold">{t("Status")}</th>
<th className="px-3 py-2 font-semibold">{t("Target")}</th>
<th className="px-3 py-2 text-right font-semibold">{t("Tokens")}</th>
<th className="px-3 py-2 text-right font-semibold">{t("Token")}</th>
<th className="px-3 py-2 text-right font-semibold">{t("Cache")}</th>
<th className="px-3 py-2 text-right font-semibold">{t("Cost")}</th>
<th className="px-3 py-2 text-right font-semibold">{t("Concurrency")}</th>
@@ -4171,7 +4171,7 @@ function UsageAnalysisCard({
{visibleColumns.map((column) => (
<th className="px-3 py-2 font-semibold" key={column.key}>{column.label}</th>
))}
<th className="px-3 py-2 text-right font-semibold">{t("Tokens")}</th>
<th className="px-3 py-2 text-right font-semibold">{t("Token")}</th>
{showCost ? <th className="px-3 py-2 text-right font-semibold">{t("Cost")}</th> : null}
<th className="px-3 py-2 text-right font-semibold">{t("Requests")}</th>
{showTokenBreakdown ? <th className="px-3 py-2 text-right font-semibold">{t("Input")}</th> : null}
@@ -782,7 +782,7 @@ function logTableColumnLabel(columnId: LogTableColumnId, t: (value: string) => s
case "credential":
return t("Credential");
case "tokens":
return t("令牌");
return t("Token");
case "duration":
return t("持续时间");
}
@@ -849,7 +849,7 @@ function LogMobileCard({
</div>
</div>
<div className="mt-2 grid grid-cols-2 gap-2 text-[11px]">
<LogCompactMetric label={t("令牌")} value={tokenSummary} />
<LogCompactMetric label={t("Token")} value={tokenSummary} />
<LogCompactMetric label={t("持续时间")} value={formatDuration(item.durationMs)} />
{hasCredentialInfo ? <LogCompactMetric label={t("Credential")} value={logCredentialCellLabel(item)} /> : null}
<LogCompactMetric label={t("Provider")} value={item.provider || "-"} />
+14 -12
View File
@@ -227,6 +227,7 @@ export const appCopy: Record<ResolvedLanguage, AppCopy> = {
"Stream": "Stream",
"Streaming": "Streaming",
"Non-streaming": "Non-streaming",
"Token": "Token",
"令牌": "Tokens",
"成本": "Cost",
"持续时间": "Duration",
@@ -880,7 +881,7 @@ export const appCopy: Record<ResolvedLanguage, AppCopy> = {
"Cache": "缓存",
"Cache rate": "缓存率",
"Cache ratio": "缓存率",
"Cache tokens": "缓存令牌",
"Cache tokens": "缓存 Token",
"Cache write": "缓存写入",
"Cancel": "取消",
"Channel": "频道",
@@ -1121,7 +1122,7 @@ export const appCopy: Record<ResolvedLanguage, AppCopy> = {
"Idle seconds": "空闲秒数",
"Idle seconds must be between 30 and 86400.": "空闲秒数必须在 30 到 86400 之间。",
"Input": "输入",
"Input tokens": "输入令牌",
"Input tokens": "输入 Token",
"Integration ID": "集成 ID",
"Install": "安装",
"Install and restart": "安装并重启",
@@ -1209,7 +1210,7 @@ export const appCopy: Record<ResolvedLanguage, AppCopy> = {
"Open profiles": "查看配置档案",
"Open providers": "查看供应商",
"Output": "输出",
"Output tokens": "输出令牌",
"Output tokens": "输出 Token",
"Observability": "可观测",
"Off": "关闭",
"On failure": "失败时",
@@ -1530,7 +1531,7 @@ export const appCopy: Record<ResolvedLanguage, AppCopy> = {
"AI Fuel Cockpit": "AI 燃料仪表",
"Token Calendar Poster": "Token 日历海报",
"Spend Receipt": "消费小票",
"tokens routed through CCR": "通过 CCR 路由的令牌",
"tokens routed through CCR": "通过 CCR 路由的 Token",
"Top model": "最高频模型",
"Top provider": "最高频供应商",
"No provider activity": "暂无供应商活动",
@@ -1583,15 +1584,16 @@ export const appCopy: Record<ResolvedLanguage, AppCopy> = {
"Thread": "线程",
"Thread ID": "线程 ID",
"Thinking": "思考",
"Token Mix": "令牌构成",
"Token": "Token",
"Token Mix": "Token 构成",
"Total": "总计",
"Total tokens": "总令牌",
"Total tokens": "总 Token",
"Username": "用户名",
"Today": "今天",
"Token threshold": "令牌阈值",
"Token threshold": "Token 阈值",
"Truncated": "已截断",
"Tokens": "令牌",
"tokens": "令牌",
"Tokens": "Token",
"tokens": "Token",
"day": "天",
"days": "天",
"Tool": "工具",
@@ -1814,10 +1816,10 @@ export const appCopy: Record<ResolvedLanguage, AppCopy> = {
"5h quota": "5 小时额度",
"Granted balance": "赠送余额",
"Individual limit": "个人额度",
"Lifetime tokens": "累计令牌",
"Lifetime tokens": "累计 Token",
"Manual resets": "主动重置次数",
"Monthly budget": "月度预算",
"Peak daily tokens": "日峰值令牌",
"Peak daily tokens": "日峰值 Token",
"Primary quota": "主额度",
"Secondary quota": "副额度",
"Topped-up balance": "充值余额",
@@ -1846,7 +1848,7 @@ export const appCopy: Record<ResolvedLanguage, AppCopy> = {
"Copy API key": "复制 API 密钥",
"Model settings": "模型设置",
"Context, pricing, reasoning, web search, and image": "上下文、价格、推理、网页搜索与图片",
"Context window (tokens)": "上下文窗口(令牌",
"Context window (tokens)": "上下文窗口(Token",
"Custom pricing": "自定义价格",
"Pricing": "价格",
"Preset": "预设",
@@ -2,7 +2,7 @@ import assert from "node:assert/strict";
import test from "node:test";
import * as React from "react";
import { renderToStaticMarkup } from "react-dom/server";
import type { AgentAnalysisSessionRow, AgentAnalysisTraceRun, RequestLogPage } from "@ccr/core/contracts/app.ts";
import type { AgentAnalysisSessionRow, AgentAnalysisTraceRun, RequestLogEntry, RequestLogPage } from "@ccr/core/contracts/app.ts";
import { AgentAnalysisView } from "@ccr/ui/pages/home/components/dashboard.tsx";
import { LogsView } from "@ccr/ui/pages/home/components/network-logs.tsx";
import { AppI18nContext, appCopy } from "@ccr/ui/pages/home/shared/i18n.tsx";
@@ -22,6 +22,39 @@ const emptyLogPage: RequestLogPage = {
totalPages: 1
};
const sampleRequestLogEntry: RequestLogEntry = {
cacheReadTokens: 0,
cacheWriteTokens: 0,
client: "claude-code",
costUsd: 0.01,
createdAt: "2026-07-23T00:00:00.000Z",
credentialChain: [],
credentialSaturated: false,
durationMs: 120,
id: 1,
inputTokens: 100,
isStream: true,
method: "POST",
model: "claude-sonnet-4",
ok: true,
outputTokens: 50,
path: "/v1/messages",
provider: "anthropic",
reasoningTokens: 0,
requestBody: { encoding: "utf8", sizeBytes: 2, text: "{}", truncated: false },
requestHeaders: {},
requestId: "req-token-copy",
routeAttemptCount: 1,
routeHopCount: 1,
routeTraceTruncated: false,
retryAttempts: [],
responseBody: { encoding: "utf8", sizeBytes: 2, text: "{}", truncated: false },
responseHeaders: {},
statusCode: 200,
totalTokens: 150,
url: "/v1/messages"
};
test("LogsView keeps disabled request logs discoverable with an enable action", () => {
const html = renderToStaticMarkup(
<AppI18nContext.Provider value={appCopy.zh}>
@@ -62,6 +95,24 @@ test("LogsView explains filtered empty results and translates page sizes", () =>
assert.doesNotMatch(html, /\/ 页/);
});
test("LogsView keeps Chinese token column copy as Token", () => {
const html = renderToStaticMarkup(
<AppI18nContext.Provider value={appCopy.zh}>
<LogsView
error=""
filter={{ page: 1, pageSize: 25, status: "all" }}
loading={false}
page={{ ...emptyLogPage, items: [sampleRequestLogEntry], total: 1 }}
refreshLogs={() => undefined}
updateFilter={() => undefined}
/>
</AppI18nContext.Provider>
);
assert.match(html, /Token/);
assert.doesNotMatch(html, /令牌/);
});
test("AgentAnalysisView keeps session headings horizontal and shows cache rate and cost", () => {
const session: AgentAnalysisSessionRow = {
agent: "claude-code",
@@ -192,6 +243,8 @@ test("AgentAnalysisView keeps session headings horizontal and shows cache rate a
assert.match(html, /缓存率/);
assert.match(html, /38%/);
assert.match(html, /成本/);
assert.match(html, /Token/);
assert.doesNotMatch(html, /令牌/);
assert.match(html, /\$1\.25/);
assert.match(html, /\$0\.25/);
assert.match(html, /\$0\.75/);
@@ -3,6 +3,7 @@ import test from "node:test";
import * as React from "react";
import { renderToStaticMarkup } from "react-dom/server";
import { formatCodexResetCardExpiry, formatCodexResetCardNumber, OverviewView } from "@ccr/ui/pages/home/components/dashboard.tsx";
import { AppI18nContext, appCopy } from "@ccr/ui/pages/home/shared/i18n.tsx";
import { parseStatusBucketDate } from "@ccr/ui/pages/home/shared/controls.tsx";
import { providerAccountMeterDetailValidityProgress } from "@ccr/ui/pages/home/shared/provider-accounts.ts";
import type { OverviewWidgetConfig, ProviderAccountSnapshot } from "@ccr/core/contracts/app.ts";
@@ -74,6 +75,34 @@ test("OverviewView renders every overview widget type", () => {
assert.match(html, /Spend Receipt/);
});
test("OverviewView keeps Chinese token copy as Token", () => {
const html = renderToStaticMarkup(
<AppI18nContext.Provider value={appCopy.zh}>
<OverviewView
overviewWidgets={[
{ enabled: true, id: "metric-total", metric: "total-tokens", size: "1:1", type: "metric", variant: "card" },
{ enabled: true, id: "trend", size: "3:2", type: "usage-trend", variant: "composed" },
{ enabled: true, id: "activity", size: "4:2", type: "token-activity", variant: "heatmap" },
{ enabled: true, id: "token-mix", size: "2:2", type: "token-mix", variant: "donut" },
{ enabled: true, id: "share-usage", size: "1:4", type: "share-usage-wrapped", variant: "card" },
{ enabled: true, id: "share-receipt", size: "1:4", type: "share-spend-receipt", variant: "card" }
]}
providerAccounts={accountSnapshots()}
refreshProviderAccounts={() => undefined}
setUsageRange={() => undefined}
usageRange="30d"
usageStats={usageStats("30d")}
onWidgetsChange={() => undefined}
/>
</AppI18nContext.Provider>
);
assert.match(html, /Token/);
assert.match(html, /Token 构成/);
assert.match(html, /总 Token/);
assert.doesNotMatch(html, /令牌/);
});
test("overview status dates accept ISO usage buckets", () => {
assert.equal(parseStatusBucketDate("2026-06-20T00:00:00.000Z")?.toISOString(), "2026-06-20T00:00:00.000Z");
});