feat(i18n): replace hardcoded Chinese messages with English

This commit is contained in:
lzt404 committed 2026-09-27 16:33:50 +08:00
1 parent 89bdbc7aea
commit 9c67b9d40c
77 files changed
+2385 -2304

No files matched your search

+11 -11
View File
@@ -12,7 +12,7 @@ import {
const notice: AppNotice = {
owner: 'api-page',
source: 'app.nav.api',
message: '接入已启用',
message: 'Connection enabled',
tone: 'success',
};
@@ -31,11 +31,11 @@ describe('统一操作提示', () => {
const first = appNoticeReducer(initialAppNoticeState, { type: 'show', notice });
const next = appNoticeReducer(first, {
type: 'show',
notice: { ...notice, message: '接入已停用' },
notice: { ...notice, message: 'Connection disabled' },
});
expect(next.notice?.message).toBe('接入已停用');
expect(next.notice?.message).toBe('Connection disabled');
expect(next.revision).toBe(2);
expect(first.notice?.message).toBe('接入已启用');
expect(first.notice?.message).toBe('Connection enabled');
});
it('重复的操作结果也产生新的可播报版本', () => {
@@ -76,12 +76,12 @@ describe('统一操作提示', () => {
it('操作结果包含来源、完整消息和可访问的关闭按钮', () => {
const html = renderNotice(appNoticeReducer(initialAppNoticeState, { type: 'show', notice }));
expect(html).toContain('action-feedback inline-notice success');
expect(html).toContain('API 接入');
expect(html).toContain('接入已启用');
expect(html).toContain('API Access');
expect(html).toContain('Connection enabled');
expect(html).toContain('role="status"');
expect(html).toContain('aria-live="polite"');
expect(html).toContain('aria-atomic="true"');
expect(html).toContain('aria-label="关闭操作提示"');
expect(html).toContain('aria-label="Dismiss operation feedback"');
expect(html).not.toContain('config-toast');
expect(html).not.toContain('<footer');
expect(html).not.toContain('app-notice-bar');
@@ -89,19 +89,19 @@ describe('统一操作提示', () => {
it('局部操作可省略重复的模块标题', () => {
const html = renderNotice({ notice: { ...notice, source: undefined }, revision: 1 });
expect(html).toContain('接入已启用');
expect(html).toContain('Connection enabled');
expect(html).not.toContain('action-feedback-source');
});
it('长错误保留全文、换行且转义外部内容', () => {
const message = `失败:${'详细错误'.repeat(160)}\n<script>alert(1)</script>`;
const message = `Failure: ${'Detailed error'.repeat(160)}\n<script>alert(1)</script>`;
const html = renderNotice(appNoticeReducer(initialAppNoticeState, {
type: 'show', notice: { ...notice, message, tone: 'error' },
}));
expect(html).toContain('action-feedback inline-notice error');
expect(html).toContain('role="alert"');
expect(html).toContain('aria-live="assertive"');
expect(html).toContain('详细错误'.repeat(160));
expect(html).toContain('Detailed error'.repeat(160));
expect(html).toContain('\n&lt;script&gt;');
expect(html).not.toContain('<script>');
});
@@ -112,7 +112,7 @@ describe('统一操作提示', () => {
type: 'show', notice: { ...notice, message },
});
expect(shown.notice?.message).toBe(message);
expect(renderNotice(shown)).toContain('&lt;QA&gt; 登录成功');
expect(renderNotice(shown)).toContain('&lt;QA&gt; sign-in succeeded');
for (const locale of supportedLocales) {
expect(translate(locale, message.key, message.variables)).toContain('<QA>');
}
+5 -5
View File
@@ -113,11 +113,11 @@ describe('credential health and cooldowns', () => {
});
it('does not render elapsed timers as healthy, or hide disabled state behind a cooldown', () => {
expect(render({ status: 'active', cooldowns: [record] }, receivedAtMs - 60_000)).toContain('冷却计时已到期');
expect(render({ disabled: true, status: 'error', cooldowns: [record] })).toContain('已停用');
expect(render({ status: 'error', unavailable: true })).toContain('无法确定恢复时间');
expect(render({ status: 'active', cooldowns: null })).toContain('冷却状态未知');
expect(render({ status: 'active', cooldowns: [record] }, receivedAtMs - 60_000)).toContain('Cooldown timer elapsed');
expect(render({ disabled: true, status: 'error', cooldowns: [record] })).toContain('Disabled');
expect(render({ status: 'error', unavailable: true })).toContain('recovery time is unknown');
expect(render({ status: 'active', cooldowns: null })).toContain('Cooldown state is unknown');
const expiredToken = render({ status: 'error', status_message: 'token expired', cooldowns: [record] }, receivedAtMs - 60_000);
expect(expiredToken.slice(0, expiredToken.indexOf('</summary>'))).toContain('访问令牌已过期');
expect(expiredToken.slice(0, expiredToken.indexOf('</summary>'))).toContain('Access token expired');
});
});
+2 -2
View File
@@ -53,10 +53,10 @@ it.each([
const credentialHtml = renderToStaticMarkup(<I18nProvider><AuthFileQuotaPanel file={file} quota={quota} disabled={file.disabled} onRefresh={() => {}} onReset={onReset} /></I18nProvider>);
const quotaHtml = renderToStaticMarkup(<I18nProvider><QuotaCard file={file} quota={quota} onRefresh={() => {}} onReset={onReset} /></I18nProvider>);
const buttonState = (html: string) => {
const button = html.match(/<button[^>]*title="重置额度"[^>]*>重置额度<\/button>/)?.[0];
const button = html.match(/<button[^>]*title="Reset Quota"[^>]*>Reset Quota<\/button>/)?.[0];
return { visible: Boolean(button), disabled: Boolean(button?.includes('disabled=""')) };
};
expect(buttonState(credentialHtml)).toEqual({ visible: values.visible, disabled: values.disabled });
expect(buttonState(credentialHtml)).toEqual(buttonState(quotaHtml));
if (values.loading) expect(credentialHtml).toContain('正在提交重置并刷新额度');
if (values.loading) expect(credentialHtml).toContain('Submitting reset and refreshing quota');
});
+4 -4
View File
@@ -6,13 +6,13 @@ import { I18nProvider } from '../src/i18n';
describe('application confirmation and quota feedback', () => {
it('renders an accessible in-app confirmation with explicit reset and cancel actions', () => {
const html = renderToStaticMarkup(<I18nProvider><ConfirmationDialog title="重置额度" message="确认重置测试账号?" confirmText="确认重置" warning="消耗 1 次重置机会" onDecision={() => {}} /></I18nProvider>);
const html = renderToStaticMarkup(<I18nProvider><ConfirmationDialog title="Reset Quota" message="Reset the test account quota?" confirmText="Confirm Reset" warning="This consumes one reset credit" onDecision={() => {}} /></I18nProvider>);
expect(html).toContain('role="alertdialog"');
expect(html).toContain('aria-modal="true"');
expect(html).toContain('aria-describedby=');
expect(html).toContain('确认重置');
expect(html).toContain('取消');
expect(html).toContain('消耗 1 次重置机会');
expect(html).toContain('Confirm Reset');
expect(html).toContain('Cancel');
expect(html).toContain('This consumes one reset credit');
});
it('keeps reset feedback out of the card layout (message checked in browser)', () => {
+2 -2
View File
@@ -43,8 +43,8 @@ describe('Devin live quota', () => {
{ id: 'weekly', remainingPercent: 81.25, resetAtMs: 1893542400000 },
] });
expect(quotaRowsFor('devin', liveStatus({ dailyQuotaRemainingPercent: 0 }))).toEqual([
{ label: '每日额度', remainingPercent: 0, resetAtMs: undefined },
{ label: '每周额度', remainingPercent: null, resetAtMs: undefined },
{ label: 'Daily quota', remainingPercent: 0, resetAtMs: undefined },
{ label: 'Weekly quota', remainingPercent: null, resetAtMs: undefined },
]);
});
it('does not turn malformed, missing or cached metadata into live quota', () => {
+2 -2
View File
@@ -36,14 +36,14 @@ describe('i18n', () => {
expect(translate('zh-CN', 'authFiles.models.globalDescription', { provider: 'Codex' })).toContain('不影响 API 接入');
});
it('normalizes English variants and keeps Chinese as the fallback', () => {
it('normalizes supported variants and keeps English as the fallback', () => {
expect(normalizeLocale('en-US')).toBe('en');
expect(normalizeLocale('en')).toBe('en');
expect(normalizeLocale('zh-CN')).toBe('zh-CN');
expect(normalizeLocale('zh-TW')).toBe('zh-TW');
expect(normalizeLocale('zh-Hant-HK')).toBe('zh-TW');
expect(normalizeLocale('ja-JP')).toBe('ja');
expect(normalizeLocale('unsupported')).toBe('zh-CN');
expect(normalizeLocale('unsupported')).toBe('en');
});
it('translates messages and interpolates variables', () => {
+1 -1
View File
@@ -15,6 +15,6 @@ describe('apiCallErrorMessage', () => {
});
it('没有错误体时回退到 HTTP 状态', () => {
expect(apiCallErrorMessage({ status_code: 429 })).toBe('上游返回 HTTP 429');
expect(apiCallErrorMessage({ status_code: 429 })).toBe('Upstream returned HTTP 429');
});
});
+1 -1
View File
@@ -186,6 +186,6 @@ describe('credential model exclusion metadata', () => {
expect(() => authFileExcludedRulesFromPayload(payload)).toThrow();
}
expect(() => authFileExcludedRulesFromPayload('{"access_token": "sensitive-token"'))
.toThrow('无法读取凭证文件内容,未加载模型设置');
.toThrow('Could not read the credential file; model settings were not loaded');
});
});
+1 -1
View File
@@ -604,7 +604,7 @@ describe('API 接入配置合并', () => {
expect(parseProviderHeaders('Authorization: Bearer abc:def')).toEqual({
Authorization: 'Bearer abc:def',
});
expect(() => parseProviderHeaders('Invalid header')).toThrow('缺少冒号');
expect(() => parseProviderHeaders('Invalid header')).toThrow('missing a colon');
});
it('把未勾选的上游模型写入排除列表', () => {
+15 -15
View File
@@ -15,12 +15,12 @@ describe('quota card rendering', () => {
status: 'success', rows: [], resetCredits: 2, resetCreditsApplicable: 0,
resetCreditsError: 'temporary failure', subscriptionActiveUntil: '2030-01-01T00:00:00Z',
});
expect(html).toMatch(/<button[^>]*title="重置额度"[^>]*>重置额度<\/button>/);
expect(html).not.toMatch(/<button[^>]*disabled=""[^>]*>重置额度<\/button>/);
expect(html).not.toContain('当前没有适用的重置次数');
expect(html).toContain('当前适用:0 次');
expect(html).toMatch(/<button[^>]*title="Reset Quota"[^>]*>Reset Quota<\/button>/);
expect(html).not.toMatch(/<button[^>]*disabled=""[^>]*>Reset Quota<\/button>/);
expect(html).not.toContain('No applicable reset credits');
expect(html).toContain('Currently applicable: 0');
expect(html).not.toContain('temporary failure');
expect(html).toContain('订阅到期');
expect(html).toContain('Subscription expires');
});
it.each(['error', 'refresh-error'] as const)('重置结果为 %s 时仍可再次点击重置', (status) => {
@@ -28,35 +28,35 @@ describe('quota card rendering', () => {
status: 'success', rows: [], resetCredits: 2,
actionResult: { action: 'reset', status, error: 'temporary failure' },
});
expect(html).toMatch(/<button[^>]*title="重置额度"[^>]*>重置额度<\/button>/);
expect(html).not.toMatch(/<button[^>]*disabled=""[^>]*>重置额度<\/button>/);
expect(html).toMatch(/<button[^>]*title="Reset Quota"[^>]*>Reset Quota<\/button>/);
expect(html).not.toMatch(/<button[^>]*disabled=""[^>]*>Reset Quota<\/button>/);
expect(html).not.toContain('temporary failure');
});
it('xAI 付费账号保留额度说明和刷新入口,不显示可用性测试', () => {
const html = render({ status: 'success', rows: [{ label: '付费 API 账号', remainingPercent: null, detail: '上游未提供剩余额度' }] }, 'xai');
expect(html).toContain('上游未提供剩余额度');
const html = render({ status: 'success', rows: [{ label: 'Paid API account', remainingPercent: null, detail: 'Upstream did not provide remaining quota' }] }, 'xai');
expect(html).toContain('Upstream did not provide remaining quota');
expect(html).not.toContain('real-quota-track');
expect(html).not.toContain('测试可用性');
expect(html).toContain('获取/刷新额度');
expect(html).not.toContain('Test availability');
expect(html).toContain('Fetch or refresh quota');
});
it('xAI 探测成功显示可用说明,100% 额度保留刷新按钮', () => {
const healthy = render({
status: 'success', rows: quotaRowsFor('xai', { mode: 'paid-health' }),
}, 'xai');
expect(healthy).toContain('付费 API 对话可用');
expect(healthy).toContain('Paid API chat is available');
expect(healthy).not.toContain('real-quota-track');
const full = render({
status: 'success', rows: quotaRowsFor('xai', { config: { creditUsagePercent: 0 } }),
}, 'xai');
expect(full).toContain('剩余 100%');
expect(full).toContain('100% remaining');
expect(full).toContain('width:100%');
expect(full).not.toContain('disabled=""');
});
it('缓存中的原始重置时间提供动态提示,过期额度不伪造为已恢复', () => {
const html = render({ status: 'success', rows: [{ label: '5h', remainingPercent: 0, resetAtMs: Date.parse('2020-01-01T00:00:00Z') }] });
expect(html).toContain('重置时间已到,请刷新确认');
expect(html).toContain('剩余 0%');
expect(html).toContain('Reset time reached; refresh to verify');
expect(html).toContain('0% remaining');
});
});
+3 -3
View File
@@ -71,7 +71,7 @@ describe('quota API compatibility', () => {
const result = await loadQuota(codexFile);
expect(result.status).toBe('success');
expect(result.resetCredits).toBe(2);
expect(result.resetCreditsError).toContain('无法识别');
expect(result.resetCreditsError).toContain('unrecognized data');
});
it('Claude profile 查询失败不影响现代 Fable 额度', async () => {
@@ -79,7 +79,7 @@ describe('quota API compatibility', () => {
? { status_code: 500 }
: success({ limits: [{ kind: 'weekly_scoped', percent: 40, scope: { model: { display_name: 'Fable 5' } } }] });
expect(await loadQuota({ name: 'claude.json', provider: 'claude', auth_index: 'c' })).toMatchObject({
status: 'success', rows: [{ label: '7 天 Fable 窗口', remainingPercent: 60 }],
status: 'success', rows: [{ label: '7-day Fable window', remainingPercent: 60 }],
});
});
@@ -141,7 +141,7 @@ describe('xAI quota queries aligned with Management Center', () => {
const result = await loadQuota({ ...file, using_api: true, prefix: 'paid' });
expect(result).toMatchObject({ status: 'success', plan: 'Paid' });
expect(result.rows[0].remainingPercent).toBeNull();
expect(result.rows[0].detail).toContain('付费 API 对话可用');
expect(result.rows[0].detail).toContain('Paid API chat is available');
expect(calls).toHaveLength(2);
expect(calls.map((request) => request.url)).toEqual([
'https://api.x.ai/v1/me', 'https://api.x.ai/v1/chat/completions',
+15 -15
View File
@@ -19,7 +19,7 @@ describe('quotaRowsFor', () => {
});
expect(rows).toHaveLength(1);
expect(rows[0].label).toBe('周限额');
expect(rows[0].label).toBe('Weekly limit');
expect(rows[0].remainingPercent).toBe(3);
expect(rows[0].reset).toBeTruthy();
});
@@ -38,7 +38,7 @@ describe('quotaRowsFor', () => {
},
additional_rate_limits: [
{
limit_name: '代码审查增强',
limit_name: 'Enhanced code review',
rate_limit: {
primary_window: {
used_percent: 30,
@@ -50,9 +50,9 @@ describe('quotaRowsFor', () => {
});
expect(rows.map((row) => row.label)).toEqual([
'5 小时限额',
'月限额',
'代码审查增强 周限额',
'5-hour limit',
'Monthly limit',
'Enhanced code review Weekly limit',
]);
});
@@ -65,7 +65,7 @@ describe('quotaRowsFor', () => {
},
});
expect(rows.map((row) => row.label)).toEqual(['5 小时限额', '周限额']);
expect(rows.map((row) => row.label)).toEqual(['5-hour limit', 'Weekly limit']);
});
it('支持反序窗口且只在有重置时间时推断已耗尽额度', () => {
@@ -76,7 +76,7 @@ describe('quotaRowsFor', () => {
secondaryWindow: { limitWindowSeconds: 18000, resetAfterSeconds: 3600 },
},
});
expect(rows.map((row) => row.label)).toEqual(['5 小时限额', '周限额']);
expect(rows.map((row) => row.label)).toEqual(['5-hour limit', 'Weekly limit']);
expect(rows.map((row) => row.remainingPercent)).toEqual([0, null]);
expect(rows[0].resetAtMs).toBeGreaterThan(Date.now());
});
@@ -168,7 +168,7 @@ describe('quotaRowsFor', () => {
limits: [null, limit(null, true), limit(12, false, 'Fable 5'), limit(64, true), limit(99, true, 'Sonnet')],
});
expect(rows.map((row) => [row.label, row.remainingPercent])).toEqual([
['5 小时窗口', 90], ['7 天 Fable 窗口', 36],
['5-hour window', 90], ['7-day Fable window', 36],
]);
expect(rows[1].resetAtMs).toBe(Date.parse('2030-01-01T00:00:00Z'));
});
@@ -179,7 +179,7 @@ describe('quotaRowsFor', () => {
limits: [{ kind: 'weekly_scoped', percent: null, scope: { model: { display_name: 'Fable' } } }],
});
expect(rows).toHaveLength(1);
expect(rows[0]).toMatchObject({ label: '7 天 Fable 窗口', remainingPercent: 59 });
expect(rows[0]).toMatchObject({ label: '7-day Fable window', remainingPercent: 59 });
});
it('显示 Claude 已启用的额外用量', () => {
@@ -194,7 +194,7 @@ describe('quotaRowsFor', () => {
});
expect(rows.at(-1)).toMatchObject({
label: '额外用量',
label: 'Extra usage',
remainingPercent: 75,
});
expect(rows.at(-1)?.detail).toContain('$12.50');
@@ -228,7 +228,7 @@ describe('quotaRowsFor', () => {
{ window: { duration: 90, timeUnit: 'TIME_UNIT_SECOND' }, detail: { limit: 100, used: 0 } },
],
});
expect(rows.map((row) => row.label)).toEqual(['5 小时窗口', '7 天窗口', '5 小时窗口', '90 秒窗口', '每周额度']);
expect(rows.map((row) => row.label)).toEqual(['5 hours window', '7 days window', '5 hours window', '90 seconds window', 'Weekly quota']);
expect(rows.at(-1)?.remainingPercent).toBeCloseTo(0.5);
expect(rows[0].remainingPercent).toBe(80);
expect(rows[0].resetAtMs).toBeGreaterThan(Date.now());
@@ -266,7 +266,7 @@ describe('quotaRowsFor', () => {
expect(quotaRowsFor('xai', { config: { productUsage: [{ product: 'grok', usagePercent: 20 }] } })[1])
.toMatchObject({ label: 'grok', remainingPercent: 80 });
expect(quotaRowsFor('xai', { mode: 'paid-info' })[0])
.toMatchObject({ label: '付费 API 账号', remainingPercent: null });
.toMatchObject({ label: 'Paid API account', remainingPercent: null });
});
it('xAI 不从月账单借用周窗口的重置时间', () => {
@@ -297,10 +297,10 @@ describe('quotaRowsFor', () => {
},
});
expect(rows.find((row) => row.label === '每周额度')?.remainingPercent).toBe(75);
expect(rows.find((row) => row.label === 'Weekly quota')?.remainingPercent).toBe(75);
expect(rows.find((row) => row.label === 'grok-code')?.remainingPercent).toBe(60);
expect(rows.find((row) => row.label === '月度包含额度')?.remainingPercent).toBe(0);
expect(rows.find((row) => row.label === '按量付费额度')?.remainingPercent).toBe(60);
expect(rows.find((row) => row.label === 'Monthly included quota')?.remainingPercent).toBe(0);
expect(rows.find((row) => row.label === 'On-demand quota')?.remainingPercent).toBe(60);
});
it('xAI 保留 0 和 100 的边界值,缺失用量不显示全额余额', () => {
+1 -1
View File
@@ -61,7 +61,7 @@ describe('思考别名来源', () => {
test('区分同名模型的接入来源', () => {
expect(thinkingAliasSourceKindLabel('codex-oauth')).toBe('Codex OAuth');
expect(thinkingAliasSourceKindLabel('claude-api')).toBe('Claude API');
expect(thinkingAliasSourceKindLabel('openai-compatible')).toBe('OpenAI 兼容');
expect(thinkingAliasSourceKindLabel('openai-compatible')).toBe('OpenAI Compatible');
});
test('仅把内核报告了思考等级的来源标为可覆写', () => {