Skip to content

Commit b40cbd4

Browse files
fix(i18n): localize health diagnostics
Keep raw capability and connection diagnostics from leaking machine or wrong-locale text into Settings surfaces.\n\nGenerated-by: OpenCode
1 parent ec1f07b commit b40cbd4

6 files changed

Lines changed: 103 additions & 54 deletions

File tree

apps/desktop/src/main/__tests__/health-center-copy.test.ts

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import assert from 'node:assert/strict';
2121
import { test } from 'node:test';
22+
import { connectionLastTestMessageDisplay } from '../../renderer/features/connection-settings/provider-panel-shared.js';
2223
import { getHealthCenterCopy } from '../../renderer/locales/settings-health-copy.js';
2324

2425
test('labels blocker counts as global across filtered health views', () => {
@@ -65,18 +66,39 @@ test('renders runtime probe details from structured params, not string parsing',
6566
);
6667
});
6768

68-
test('maps known capability reasons per locale and degrades unknowns safely', () => {
69-
const known = { kind: 'capability_reason', reason: 'missing platform credentials' } as const;
70-
assert.equal(getHealthCenterCopy('zh').signalDetail(signal(({ detail: known }))), '等待填写平台凭据。');
71-
assert.equal(getHealthCenterCopy('en').signalDetail(signal(({ detail: known }))), 'Waiting for platform credentials.');
72-
const unknown = { kind: 'capability_reason', reason: '本地快照诊断说明' } as const;
73-
assert.equal(getHealthCenterCopy('zh').signalDetail(signal({ detail: unknown })), '本地快照诊断说明');
69+
test('degrades capability reasons safely in both locales', () => {
70+
const unknown = { kind: 'capability_reason', reason: 'Discord rejected the Bot Token.' } as const;
71+
assert.equal(
72+
getHealthCenterCopy('zh').signalDetail(signal({ detail: unknown })),
73+
'状态详情请见对应设置页。',
74+
);
7475
assert.equal(
7576
getHealthCenterCopy('en').signalDetail(signal({ detail: unknown })),
7677
'See the corresponding settings page for details.',
7778
);
7879
});
7980

81+
test('maps connection test error classes without exposing machine tokens', () => {
82+
const auth = { kind: 'last_test_error_class', errorClass: 'auth' } as const;
83+
assert.equal(getHealthCenterCopy('zh').signalDetail(signal({ detail: auth })), '鉴权失败');
84+
assert.equal(getHealthCenterCopy('en').signalDetail(signal({ detail: auth })), 'Authentication failed');
85+
86+
const unknown = { kind: 'last_test_message', text: 'future_error_class' } as const;
87+
assert.equal(
88+
getHealthCenterCopy('zh').signalDetail(signal({ detail: unknown })),
89+
'连接测试状态暂时无法显示,请重新测试。',
90+
);
91+
assert.equal(
92+
getHealthCenterCopy('en').signalDetail(signal({ detail: unknown })),
93+
'The connection test status is temporarily unavailable. Test again.',
94+
);
95+
});
96+
97+
test('maps connection test error classes in connection details', () => {
98+
assert.equal(connectionLastTestMessageDisplay('auth', 'zh'), '鉴权失败');
99+
assert.equal(connectionLastTestMessageDisplay('auth', 'en'), 'Authentication failed');
100+
});
101+
80102
test('suffixes runtime signal labels per locale from the id, not the producer', () => {
81103
const runtime = signal({ id: 'connection:demo:runtime', label: 'Demo' });
82104
assert.equal(getHealthCenterCopy('zh').signalLabel(runtime), 'Demo 运行态');

apps/desktop/src/renderer/features/connection-settings/settings-provider-copy.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ const zhCopy = {
165165
categories: { oauth: 'OAuth', domestic: '国内', overseas: '海外', local: '本地', custom: 'Custom' },
166166
connectionStatuses: { retired: '已停用 · 请删除', reauth: '需要重新登录', disabledFailed: '暂不可用 · 上次连接失败', disabled: '暂不可用', failed: '上次连接失败' },
167167
lastTest: {
168+
auth: '鉴权失败', timeout: '请求超时', provider_unavailable: '模型服务返回错误', network: '网络错误', invalid_response: '模型服务返回错误', unknown: '连接测试失败',
168169
'连接已验证': '连接已验证', '鉴权失败': '鉴权失败', '请求超时': '请求超时', '网络错误': '网络错误', '模型服务返回错误': '模型服务返回错误', '连接测试失败': '连接测试失败',
169170
'connection verified': '连接已验证', 'authentication failed': '鉴权失败', 'request timed out': '请求超时', 'network error': '网络错误', 'provider returned an error': '模型服务返回错误', 'connection test failed': '连接测试失败',
170171
'claude oauth 未登录。': 'Claude OAuth 未登录。', 'claude oauth 本地凭据读取失败。': 'Claude OAuth 本地凭据读取失败。', 'claude oauth 需要重新登录。': 'Claude OAuth 需要重新登录。', 'claude oauth 已登录。': 'Claude OAuth 已登录。', 'claude oauth 已退出登录。': 'Claude OAuth 已退出登录。',
@@ -325,6 +326,7 @@ const enCopy: ProviderSettingsCopy = {
325326
categories: { oauth: 'OAuth', domestic: 'China', overseas: 'Global', local: 'Local', custom: 'Custom' },
326327
connectionStatuses: { retired: 'Retired · delete it', reauth: 'Sign-in required', disabledFailed: 'Unavailable · last connection failed', disabled: 'Unavailable', failed: 'Last connection failed' },
327328
lastTest: {
329+
auth: 'Authentication failed', timeout: 'Request timed out', provider_unavailable: 'Model service returned an error', network: 'Network error', invalid_response: 'Model service returned an error', unknown: 'Connection test failed',
328330
'连接已验证': 'Connection verified', '鉴权失败': 'Authentication failed', '请求超时': 'Request timed out', '网络错误': 'Network error', '模型服务返回错误': 'Model service returned an error', '连接测试失败': 'Connection test failed',
329331
'connection verified': 'Connection verified', 'authentication failed': 'Authentication failed', 'request timed out': 'Request timed out', 'network error': 'Network error', 'provider returned an error': 'Model service returned an error', 'connection test failed': 'Connection test failed',
330332
'claude oauth 未登录。': 'Claude OAuth is signed out.', 'claude oauth 本地凭据读取失败。': 'Could not read local Claude OAuth credentials.', 'claude oauth 需要重新登录。': 'Claude OAuth requires sign-in.', 'claude oauth 已登录。': 'Claude OAuth is signed in.', 'claude oauth 已退出登录。': 'Claude OAuth signed out.',

apps/desktop/src/renderer/locales/settings-health-copy.ts

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import type { StatusSemantic } from '@maka/ui';
2121
import type {
22+
HealthConnectionTestErrorClass,
2223
HealthSignal,
2324
HealthSignalDetail,
2425
HealthSignalLayer,
@@ -178,20 +179,24 @@ const signalMessagesEn: Record<HealthSignalMessageCode, string> = {
178179
capability_degraded: 'The capability runtime probe is degraded.',
179180
};
180181

181-
const capabilityReasonsZh: Record<string, string> = {
182-
disabled: '该能力当前已关闭。',
183-
'missing platform credentials': '等待填写平台凭据。',
184-
'macOS TCC only': '仅 macOS 系统权限可探测。',
185-
'no Electron API for per-target Apple Events TCC status': '系统未提供可直接读取的授权状态。',
186-
};
187-
188-
const capabilityReasonsEn: Record<string, string> = {
189-
disabled: 'This capability is currently turned off.',
190-
'missing platform credentials': 'Waiting for platform credentials.',
191-
'macOS TCC only': 'Only macOS TCC permissions can be probed.',
192-
'no Electron API for per-target Apple Events TCC status':
193-
'The system exposes no readable authorization status.',
194-
};
182+
const connectionTestErrorMessages = {
183+
zh: {
184+
auth: '鉴权失败',
185+
timeout: '请求超时',
186+
provider_unavailable: '模型服务返回错误',
187+
network: '网络错误',
188+
invalid_response: '模型服务返回错误',
189+
unknown: '连接测试失败',
190+
},
191+
en: {
192+
auth: 'Authentication failed',
193+
timeout: 'Request timed out',
194+
provider_unavailable: 'Model service returned an error',
195+
network: 'Network error',
196+
invalid_response: 'Model service returned an error',
197+
unknown: 'Connection test failed',
198+
},
199+
} satisfies UiCatalog<Record<HealthConnectionTestErrorClass, string>>;
195200

196201
function signalDetailZh(detail: HealthSignalDetail | undefined): string | undefined {
197202
if (!detail) return undefined;
@@ -211,12 +216,11 @@ function signalDetailZh(detail: HealthSignalDetail | undefined): string | undefi
211216
...(detail.errorClass ? [`错误类型=${detail.errorClass}`] : []),
212217
].join(' · ');
213218
case 'capability_reason':
214-
// Unknown reasons pass through verbatim: they are today's zh snapshot
215-
// diagnostics; the capability-snapshot producer is code-ized separately.
216-
return capabilityReasonsZh[detail.reason] ?? detail.reason;
219+
return '状态详情请见对应设置页。';
220+
case 'last_test_error_class':
221+
return connectionTestErrorMessages.zh[detail.errorClass];
217222
case 'last_test_message':
218-
// Raw connection-test diagnostic, rendered verbatim by design.
219-
return detail.text;
223+
return '连接测试状态暂时无法显示,请重新测试。';
220224
}
221225
}
222226

@@ -238,9 +242,10 @@ function signalDetailEn(detail: HealthSignalDetail | undefined): string | undefi
238242
...(detail.errorClass ? [`Error type=${detail.errorClass}`] : []),
239243
].join(' · ');
240244
case 'capability_reason':
241-
return capabilityReasonsEn[detail.reason] ?? 'See the corresponding settings page for details.';
245+
return 'See the corresponding settings page for details.';
246+
case 'last_test_error_class':
247+
return connectionTestErrorMessages.en[detail.errorClass];
242248
case 'last_test_message':
243-
// Raw connection-test diagnostic, rendered verbatim by design.
244-
return detail.text;
249+
return 'The connection test status is temporarily unavailable. Test again.';
245250
}
246251
}

apps/desktop/stories/settings/settings-pages.stories.tsx

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -589,16 +589,6 @@ const capabilitySnapshot: CapabilitySnapshotCollection = {
589589
};
590590

591591
const healthSignals: HealthSignal[] = [
592-
{
593-
id: 'app:config',
594-
label: '应用配置',
595-
scope: 'app',
596-
layer: 'configuration',
597-
status: 'ok',
598-
source: 'settings',
599-
checkedAt: NOW - 60_000,
600-
message: 'capability_ok',
601-
},
602592
{
603593
id: 'conn:zai-live',
604594
label: 'Z.AI Live',
@@ -646,16 +636,6 @@ const healthSignals: HealthSignal[] = [
646636
relatedCapabilityId: 'computer_use',
647637
blocksCapability: true,
648638
},
649-
{
650-
id: 'storage:sessions',
651-
label: '会话存储',
652-
scope: 'storage',
653-
layer: 'storage',
654-
status: 'ok',
655-
source: 'storage',
656-
checkedAt: NOW - 60_000,
657-
message: 'capability_ok',
658-
},
659639
];
660640

661641
const healthSnapshot: HealthSnapshot = buildHealthSnapshot(NOW - 45_000, healthSignals);
@@ -2598,8 +2578,8 @@ export const HealthCenter: Story = {
25982578
expect(errorFilter).toHaveAttribute('aria-pressed', 'true');
25992579
expect(canvas.getByText('OpenAI Review')).toBeInTheDocument();
26002580
expect(canvas.queryByText('Z.AI Live')).not.toBeInTheDocument();
2601-
expect(canvas.getByText('全部健康信号中,1/6 条会阻塞发送')).toBeInTheDocument();
2602-
expect(canvas.getByText('全部健康信号中,1/6 条会阻塞能力')).toBeInTheDocument();
2581+
expect(canvas.getByText('全部健康信号中,1/4 条会阻塞发送')).toBeInTheDocument();
2582+
expect(canvas.getByText('全部健康信号中,1/4 条会阻塞能力')).toBeInTheDocument();
26032583
});
26042584
await userEvent.click(errorFilter);
26052585
await waitFor(() => {

packages/core/src/__tests__/health.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,23 @@ describe('HealthSignal contract', () => {
4444
assert.strictEqual(result.source, 'connection_test');
4545
});
4646

47+
test('separates connection test error classes from legacy diagnostics', () => {
48+
const coded = healthSignalFromConnection(
49+
connection({ lastTestStatus: 'needs_reauth', lastTestMessage: 'auth' }),
50+
20,
51+
);
52+
assert.deepStrictEqual(coded.detail, { kind: 'last_test_error_class', errorClass: 'auth' });
53+
54+
const legacy = healthSignalFromConnection(
55+
connection({ lastTestStatus: 'error', lastTestMessage: 'HTTP 502 upstream failure' }),
56+
20,
57+
);
58+
assert.deepStrictEqual(legacy.detail, {
59+
kind: 'last_test_message',
60+
text: 'HTTP 502 upstream failure',
61+
});
62+
});
63+
4764
test('a missing default model warns only when the workspace has no default target', () => {
4865
// The catalog projects `defaultModel` onto exactly one connection (the
4966
// default target). With a default configured elsewhere, an enabled

packages/core/src/health.ts

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,14 @@ export type HealthSignalMessageCode =
6565
| 'capability_denied'
6666
| 'capability_degraded';
6767

68+
export type HealthConnectionTestErrorClass =
69+
| 'auth'
70+
| 'timeout'
71+
| 'provider_unavailable'
72+
| 'network'
73+
| 'invalid_response'
74+
| 'unknown';
75+
6876
export type HealthSignalDetail =
6977
| { kind: 'validation_scope_note' }
7078
| { kind: 'no_models_enabled_hint' }
@@ -74,8 +82,8 @@ export type HealthSignalDetail =
7482
// Raw machine token from the capability snapshot; the presenter maps the
7583
// known vocabulary per locale and falls back to a generic hint.
7684
| { kind: 'capability_reason'; reason: string }
77-
// Raw connection-test diagnostic passthrough (producer text, not product
78-
// copy); the presenter renders it verbatim in both locales.
85+
| { kind: 'last_test_error_class'; errorClass: HealthConnectionTestErrorClass }
86+
// Legacy connection-test diagnostic; the presenter uses locale-specific fallback copy.
7987
| { kind: 'last_test_message'; text: string };
8088

8189
export interface HealthSignal {
@@ -228,7 +236,7 @@ export function healthSignalFromConnection(
228236
checkedAt: timeFromIso(connection.lastTestAt) ?? checkedAt,
229237
message: 'needs_reauth',
230238
...(connection.lastTestMessage
231-
? { detail: { kind: 'last_test_message', text: connection.lastTestMessage } }
239+
? { detail: connectionLastTestDetail(connection.lastTestMessage) }
232240
: {}),
233241
blocksSend: true,
234242
};
@@ -245,7 +253,7 @@ export function healthSignalFromConnection(
245253
checkedAt: timeFromIso(connection.lastTestAt) ?? checkedAt,
246254
message: 'validation_failed',
247255
...(connection.lastTestMessage
248-
? { detail: { kind: 'last_test_message', text: connection.lastTestMessage } }
256+
? { detail: connectionLastTestDetail(connection.lastTestMessage) }
249257
: {}),
250258
blocksSend: true,
251259
};
@@ -402,6 +410,21 @@ function capabilityDetail(capability: CapabilitySnapshot): HealthSignalDetail |
402410
return reason ? { kind: 'capability_reason', reason } : undefined;
403411
}
404412

413+
function connectionLastTestDetail(message: string): HealthSignalDetail {
414+
const normalized = message.trim().toLowerCase();
415+
switch (normalized) {
416+
case 'auth':
417+
case 'timeout':
418+
case 'provider_unavailable':
419+
case 'network':
420+
case 'invalid_response':
421+
case 'unknown':
422+
return { kind: 'last_test_error_class', errorClass: normalized };
423+
default:
424+
return { kind: 'last_test_message', text: message };
425+
}
426+
}
427+
405428
function timeFromIso(value: string | undefined): number | undefined {
406429
if (!value) return undefined;
407430
const parsed = Date.parse(value);

0 commit comments

Comments
 (0)