- No safe Calls read data source is available. This console will not invent or call an
- uncontracted production endpoint.
+
);
@@ -179,17 +179,17 @@ export function CallsModule({ instance, dataSource, refreshSignal }: CallsModule
if ((state.kind === 'idle' || state.kind === 'loading') && !snapshot)
return (
-
);
@@ -198,18 +198,18 @@ export function CallsModule({ instance, dataSource, refreshSignal }: CallsModule
return (
- {state.kind === 'loading' ?
Refreshing Calls…
: null}
+ {state.kind === 'loading' ?
正在刷新通话数据…
: null}
{state.kind === 'error' ? (
-
Refresh failed; showing the last known Calls data.
+
刷新失败,正在显示上次已知的通话数据。
) : null}
{instance.freshness !== 'fresh' ? (
-
- Calls data is {instance.freshness}; verify freshness before relying on these values.
+
+ 通话数据可能不是最新数据,请核实时效性后再使用这些数值。
) : null}
diff --git a/apps/web/src/instances/cellular-module.test.tsx b/apps/web/src/instances/cellular-module.test.tsx
index 393149d..0ce2b8a 100644
--- a/apps/web/src/instances/cellular-module.test.tsx
+++ b/apps/web/src/instances/cellular-module.test.tsx
@@ -23,7 +23,7 @@ const owner: InstanceContext = {
const snapshot: CellularSnapshot = {
observedAt: '2026-07-17T12:00:00Z',
- networkRegistration: { state: 'registered', roaming: false },
+ networkRegistration: { state: 'online', roaming: false },
signal: { rssi: '-71 dBm', quality: 82 },
cellsLocation: { cell: '12345', area: 19, latitude: null },
operators: { current: 'Example Mobile', available: 3 },
@@ -54,27 +54,30 @@ describe('Phase 6.2 Cellular read module', () => {
const pending = deferredSource();
render(
);
- expect(screen.getByRole('status', { name: 'Cellular loading status' })).toBeTruthy();
+ expect(screen.getByRole('status', { name: '蜂窝网络加载状态' })).toBeTruthy();
expect(pending.load).toHaveBeenCalledWith('alpha', expect.any(AbortSignal));
pending.resolve(snapshot);
- for (const heading of ['Network registration', 'Signal', 'Cells / location', 'Operators']) {
+ for (const heading of ['网络注册', '信号', '小区与位置', '运营商']) {
expect(await screen.findByRole('heading', { name: heading })).toBeTruthy();
}
- expect(
- within(screen.getByRole('region', { name: 'Signal' })).getByText('-71 dBm'),
- ).toBeTruthy();
- expect(screen.getByText('Unavailable')).toBeTruthy();
- expect(screen.getByText(/Observed 2026-07-17T12:00:00Z/)).toBeTruthy();
+ expect(within(screen.getByRole('region', { name: '信号' })).getByText('-71 dBm')).toBeTruthy();
+ const registration = screen.getByRole('region', { name: '网络注册' });
+ expect(within(registration).getByText('在线')).toBeTruthy();
+ expect(within(registration).getByText('否')).toBeTruthy();
+ expect(within(registration).queryByText('online')).toBeNull();
+ expect(within(registration).queryByText('false')).toBeNull();
+ expect(screen.getByText('不可用')).toBeTruthy();
+ expect(screen.getByText(/观测时间:2026-07-17T12:00:00Z/)).toBeTruthy();
expect(screen.queryByRole('button', { name: /register|operator|network/i })).toBeNull();
- expect(screen.getByText(/automatic network registration is available only/i)).toBeTruthy();
- expect(screen.getByText(/audited R2 prepare, confirm, and execute flow/i)).toBeTruthy();
+ expect(screen.getByText(/自动网络注册仅可通过/i)).toBeTruthy();
+ expect(screen.getByText(/经审计的 R2 准备、确认和执行流程/i)).toBeTruthy();
});
it('is honest when no read source is injected', () => {
render(
);
- expect(screen.getByRole('status', { name: 'Cellular unavailable' }).textContent).toMatch(
- /no safe cellular read data source.*will not invent.*production endpoint/i,
+ expect(screen.getByRole('status', { name: '蜂窝网络不可用' }).textContent).toMatch(
+ /没有可用的安全蜂窝网络只读数据源.*不会臆造.*生产端点/i,
);
});
@@ -95,7 +98,7 @@ describe('Phase 6.2 Cellular read module', () => {
dataSource={{ load }}
/>,
);
- expect(screen.getByRole('alert').textContent).toMatch(/authentication is required/i);
+ expect(screen.getByRole('alert').textContent).toMatch(/需要先完成认证/i);
expect(screen.queryByText('-71 dBm')).toBeNull();
expect(load).toHaveBeenCalledTimes(1);
});
@@ -114,13 +117,11 @@ describe('Phase 6.2 Cellular read module', () => {
expect(await screen.findByText('-71 dBm')).toBeTruthy();
rerender(
);
- expect(
- await screen.findByText(/Refresh failed; showing the last known cellular data/i),
- ).toBeTruthy();
+ expect(await screen.findByText(/刷新失败,正在显示上次已知的蜂窝网络数据/i)).toBeTruthy();
expect(screen.getByText('-71 dBm')).toBeTruthy();
expect(screen.queryByText(/secret upstream details/i)).toBeNull();
- await user.click(screen.getByRole('button', { name: 'Retry loading cellular data' }));
+ await user.click(screen.getByRole('button', { name: '重试加载蜂窝网络数据' }));
expect(await screen.findByText('-65 dBm')).toBeTruthy();
expect(load).toHaveBeenCalledTimes(3);
});
@@ -134,9 +135,9 @@ describe('Phase 6.2 Cellular read module', () => {
render(
);
const alert = await screen.findByRole('alert');
- expect(alert.textContent).toMatch(/Cellular data could not be loaded/i);
+ expect(alert.textContent).toMatch(/无法加载蜂窝网络数据/i);
expect(alert.textContent).not.toMatch(/private failure/i);
- await user.click(screen.getByRole('button', { name: 'Retry loading cellular data' }));
+ await user.click(screen.getByRole('button', { name: '重试加载蜂窝网络数据' }));
expect(await screen.findByText('-71 dBm')).toBeTruthy();
});
diff --git a/apps/web/src/instances/cellular-module.tsx b/apps/web/src/instances/cellular-module.tsx
index 64c1037..aed44da 100644
--- a/apps/web/src/instances/cellular-module.tsx
+++ b/apps/web/src/instances/cellular-module.tsx
@@ -1,6 +1,7 @@
import { useEffect, useRef, useState } from 'react';
import type { InstanceContext } from '../app-shell.js';
+import { displayValue } from '../ui/locale.js';
/** Cellular data is deliberately limited to display-safe primitive values. */
export type CellularFieldValue = string | number | boolean | null;
@@ -59,42 +60,42 @@ type ReadState =
const SECTIONS = [
[
'networkRegistration',
- 'Network registration',
+ '网络注册',
[
- ['State', 'state'],
- ['Mode', 'mode'],
- ['Operator', 'operator'],
- ['Roaming', 'roaming'],
+ ['状态', 'state', true],
+ ['模式', 'mode', true],
+ ['运营商', 'operator'],
+ ['漫游', 'roaming', true],
],
],
[
'signal',
- 'Signal',
+ '信号',
[
['RSSI', 'rssi'],
['RSRP', 'rsrp'],
['RSRQ', 'rsrq'],
['SINR', 'sinr'],
- ['Quality', 'quality'],
+ ['质量', 'quality', true],
],
],
[
'cellsLocation',
- 'Cells / location',
+ '小区与位置',
[
- ['Cell', 'cell'],
- ['Area', 'area'],
- ['Technology', 'technology'],
- ['Latitude', 'latitude'],
- ['Longitude', 'longitude'],
+ ['小区', 'cell'],
+ ['区域', 'area'],
+ ['技术', 'technology', true],
+ ['纬度', 'latitude'],
+ ['经度', 'longitude'],
],
],
[
'operators',
- 'Operators',
+ '运营商',
[
- ['Current', 'current'],
- ['Available', 'available'],
+ ['当前', 'current'],
+ ['可用', 'available'],
],
],
] as const;
@@ -106,7 +107,7 @@ function StructuredSection({
}: {
label: string;
values: object;
- fields: readonly (readonly [string, string])[];
+ fields: readonly (readonly [string, string, boolean?])[];
}) {
const safeValues = values as Readonly
>;
const entries = fields.filter(([, key]) => safeValues[key] !== undefined);
@@ -115,18 +116,20 @@ function StructuredSection({
{label}
{entries.length ? (
- {entries.map(([labelText, fieldKey]) => {
+ {entries.map(([labelText, fieldKey, localized]) => {
const value = safeValues[fieldKey];
return (
- {labelText}
- - {value == null ? 'Unavailable' : String(value)}
+ -
+ {localized ? displayValue(value) : value == null ? '不可用' : String(value)}
+
);
})}
) : (
- No {label.toLocaleLowerCase()} data was supplied.
+ 未提供{label}数据。
)}
);
@@ -169,7 +172,7 @@ export function CellularModule({ instance, dataSource, refreshSignal }: Cellular
: undefined;
return {
kind: 'error',
- message: 'Cellular data could not be loaded.',
+ message: '无法加载蜂窝网络数据。',
...(snapshot ? { snapshot } : {}),
};
});
@@ -182,16 +185,15 @@ export function CellularModule({ instance, dataSource, refreshSignal }: Cellular
if (instance.authentication !== 'authenticated') {
return (
- Authentication is required before cellular data can be read for this instance.
+ 需要先完成认证,才能读取蜂窝网络数据。
);
}
if (!dataSource) {
return (
-
- No safe cellular read data source is available. This console will not invent or call an
- uncontracted production endpoint.
+
+ 没有可用的安全蜂窝网络只读数据源。此控制台不会臆造或调用未签订契约的生产端点。
);
}
@@ -201,8 +203,8 @@ export function CellularModule({ instance, dataSource, refreshSignal }: Cellular
if ((state.kind === 'idle' || state.kind === 'loading') && !ownedSnapshot) {
return (
-
- Loading cellular data…
+
+ 正在加载蜂窝网络数据…
);
}
@@ -210,9 +212,9 @@ export function CellularModule({ instance, dataSource, refreshSignal }: Cellular
if (state.kind === 'error' && !ownedSnapshot) {
return (
-
Unable to load cellular data: {state.message}
+
{state.message}
);
@@ -223,32 +225,31 @@ export function CellularModule({ instance, dataSource, refreshSignal }: Cellular
return (
- {state.kind === 'loading' ?
Refreshing cellular data…
: null}
+ {state.kind === 'loading' ?
正在刷新蜂窝网络数据…
: null}
{state.kind === 'error' ? (
-
Refresh failed; showing the last known cellular data.
+
刷新失败,正在显示上次已知的蜂窝网络数据。
) : null}
{instance.freshness !== 'fresh' ? (
-
- Cellular data is {instance.freshness}; verify freshness before relying on these values.
+
+ 蜂窝网络数据可能已过期;使用这些值前请核实数据新鲜度。
) : null}
- {snapshot.observedAt ?
Observed {snapshot.observedAt}
: null}
+ {snapshot.observedAt ?
观测时间:{snapshot.observedAt}
: null}
{SECTIONS.map(([key, label, fields]) => (
))}
-
- Network registration operations
+
+ 网络注册操作
- Automatic network registration is available only through the audited R2 prepare, confirm,
- and execute flow. Manual registration and monitoring controls remain unavailable. This
- read-only panel does not bypass those gates.
+ 自动网络注册仅可通过经审计的 R2 准备、确认和执行流程进行。手动注册和监控控件仍不可用。
+ 此只读面板不会绕过这些限制。
diff --git a/apps/web/src/instances/device-network-module.test.tsx b/apps/web/src/instances/device-network-module.test.tsx
index fa06740..f23e66d 100644
--- a/apps/web/src/instances/device-network-module.test.tsx
+++ b/apps/web/src/instances/device-network-module.test.tsx
@@ -26,8 +26,8 @@ const snapshot: DeviceNetworkSnapshot = {
wlan: {
status: {
enabled: true,
- radioState: 'on',
- connectionState: 'connected',
+ radioState: 'enabled',
+ connectionState: 'active',
activeProfile: 'office',
ssid: 'Operations Wi-Fi',
},
@@ -101,22 +101,30 @@ describe('Phase 6.3 Device Network read module', () => {
const pending = deferredSource();
render(
);
- expect(screen.getByRole('status', { name: 'Device Network loading status' })).toBeTruthy();
+ expect(screen.getByRole('status', { name: '设备网络加载状态' })).toBeTruthy();
expect(pending.load).toHaveBeenCalledWith('alpha', expect.any(AbortSignal));
pending.resolve(snapshot);
- expect(await screen.findByRole('heading', { name: 'WLAN status' })).toBeTruthy();
- expect(screen.getByRole('heading', { name: 'WLAN profiles' })).toBeTruthy();
- expect(screen.getByRole('heading', { name: 'Interfaces and addresses' })).toBeTruthy();
- expect(screen.getByRole('heading', { name: 'DDNS status' })).toBeTruthy();
- expect(screen.getByRole('heading', { name: 'DDNS configuration' })).toBeTruthy();
- expect(screen.getByRole('heading', { name: 'DDNS log summary' })).toBeTruthy();
+ expect(await screen.findByRole('heading', { name: 'WLAN 状态' })).toBeTruthy();
+ expect(screen.getByRole('heading', { name: 'WLAN 配置文件' })).toBeTruthy();
+ expect(screen.getByRole('heading', { name: '接口与地址' })).toBeTruthy();
+ expect(screen.getByRole('heading', { name: 'DDNS 状态' })).toBeTruthy();
+ expect(screen.getByRole('heading', { name: 'DDNS 配置' })).toBeTruthy();
+ expect(screen.getByRole('heading', { name: 'DDNS 日志摘要' })).toBeTruthy();
expect(
- within(screen.getByRole('region', { name: 'WLAN profiles' })).getByText('WPA3'),
+ within(screen.getByRole('region', { name: 'WLAN 配置文件' })).getByText('WPA3'),
).toBeTruthy();
+ const wlanStatus = screen.getByRole('region', { name: 'WLAN 状态' });
+ expect(within(wlanStatus).getAllByText('已启用')).toHaveLength(2);
+ expect(within(wlanStatus).getByText('活跃')).toBeTruthy();
+ expect(within(wlanStatus).getByText('是')).toBeTruthy();
+ expect(within(wlanStatus).queryByText('enabled')).toBeNull();
+ expect(within(wlanStatus).queryByText('active')).toBeNull();
+ expect(within(wlanStatus).queryByText('true')).toBeNull();
+
expect(screen.getByText('192.0.2.10/24')).toBeTruthy();
expect(screen.getByText('gateway.example.test')).toBeTruthy();
- expect(screen.getByText(/Observed 2026-07-17T11:00:00Z/)).toBeTruthy();
+ expect(screen.getByText(/观测时间:2026-07-17T11:00:00Z/)).toBeTruthy();
});
it('does not render passwords, credentials, arbitrary fields, log bodies, or write controls', async () => {
@@ -127,14 +135,14 @@ describe('Phase 6.3 Device Network read module', () => {
expect(document.body.textContent).not.toContain('private-password');
expect(screen.queryByText(/password/i)).toBeNull();
expect(screen.queryByRole('button')).toBeNull();
- expect(screen.getByText(/R1.*unavailable.*executable backend support/i)).toBeTruthy();
- expect(screen.getByText(/R2.*unavailable.*executable backend support/i)).toBeTruthy();
+ expect(screen.getByText(/可执行后端支持.*R1.*不可用/i)).toBeTruthy();
+ expect(screen.getByText(/可执行后端支持.*R2.*不可用/i)).toBeTruthy();
});
it('is honest when no source is injected and does not invent an endpoint', () => {
render(
);
- expect(screen.getByRole('status', { name: 'Device Network unavailable' }).textContent).toMatch(
- /no safe device network read data source.*uncontracted production endpoint/i,
+ expect(screen.getByRole('status', { name: '设备网络不可用' }).textContent).toMatch(
+ /没有可用的安全设备网络只读数据源.*未签订契约的生产端点/i,
);
});
@@ -155,7 +163,7 @@ describe('Phase 6.3 Device Network read module', () => {
dataSource={{ load }}
/>,
);
- expect(screen.getByRole('alert').textContent).toMatch(/authentication is required/i);
+ expect(screen.getByRole('alert').textContent).toMatch(/需要先完成认证/i);
expect(screen.queryByText('gateway.example.test')).toBeNull();
expect(load).toHaveBeenCalledTimes(1);
});
@@ -169,9 +177,9 @@ describe('Phase 6.3 Device Network read module', () => {
render(
);
const alert = await screen.findByRole('alert');
- expect(alert.textContent).toContain('Device Network data could not be loaded.');
+ expect(alert.textContent).toContain('无法加载设备网络数据。');
expect(alert.textContent).not.toContain('secret upstream');
- await user.click(screen.getByRole('button', { name: 'Retry loading Device Network' }));
+ await user.click(screen.getByRole('button', { name: '重试加载设备网络' }));
expect(await screen.findByText('gateway.example.test')).toBeTruthy();
expect(load).toHaveBeenCalledTimes(2);
});
@@ -193,9 +201,9 @@ describe('Phase 6.3 Device Network read module', () => {
expect(await screen.findByText('gateway.example.test')).toBeTruthy();
rerender(
);
- expect((await screen.findByRole('alert')).textContent).toMatch(/showing the last known/i);
+ expect((await screen.findByRole('alert')).textContent).toMatch(/正在显示上次已知/i);
expect(screen.getByText('gateway.example.test')).toBeTruthy();
- await user.click(screen.getByRole('button', { name: 'Retry loading Device Network' }));
+ await user.click(screen.getByRole('button', { name: '重试加载设备网络' }));
expect(await screen.findByText('new.example.test')).toBeTruthy();
});
@@ -250,9 +258,9 @@ describe('Phase 6.3 Device Network read module', () => {
dataSource={{ load: async () => snapshot }}
/>,
);
- expect(
- (await screen.findByRole('status', { name: 'Device Network freshness' })).textContent,
- ).toMatch(/stale/i);
+ expect((await screen.findByRole('status', { name: '设备网络数据新鲜度' })).textContent).toMatch(
+ /可能已过期/i,
+ );
expect(screen.getByText('gateway.example.test')).toBeTruthy();
});
});
diff --git a/apps/web/src/instances/device-network-module.tsx b/apps/web/src/instances/device-network-module.tsx
index cc6d44b..474a062 100644
--- a/apps/web/src/instances/device-network-module.tsx
+++ b/apps/web/src/instances/device-network-module.tsx
@@ -1,6 +1,7 @@
import { useEffect, useRef, useState, type ReactNode } from 'react';
import type { InstanceContext } from '../app-shell.js';
+import { displayValue } from '../ui/locale.js';
/** Only these bounded primitives may cross the injected read boundary into the UI. */
export type DeviceNetworkValue = string | number | boolean | null;
@@ -90,23 +91,27 @@ type ReadState =
| { kind: 'ready'; ownerId: string; snapshot: DeviceNetworkSnapshot }
| { kind: 'error'; ownerId: string; snapshot?: DeviceNetworkSnapshot };
-const SAFE_LOAD_ERROR = 'Device Network data could not be loaded.';
+const SAFE_LOAD_ERROR = '无法加载设备网络数据。';
-function display(value: DeviceNetworkValue | undefined): string {
- return value == null ? 'Unavailable' : String(value);
+function displayRaw(value: DeviceNetworkValue | undefined): string {
+ return value == null ? '不可用' : String(value);
}
function Fields({
values,
}: {
- values: readonly (readonly [label: string, value: DeviceNetworkValue | undefined])[];
+ values: readonly (readonly [
+ label: string,
+ value: DeviceNetworkValue | undefined,
+ localized?: boolean,
+ ])[];
}) {
return (
- {values.map(([label, value]) => (
+ {values.map(([label, value, localized]) => (
- {label}
- - {display(value)}
+ - {localized ? displayValue(value) : displayRaw(value)}
))}
@@ -129,115 +134,115 @@ function SnapshotView({ snapshot }: { snapshot: DeviceNetworkSnapshot }) {
const logs = snapshot.ddns.logSummary;
return (
<>
- {snapshot.observedAt ?
Observed {snapshot.observedAt}
: null}
+ {snapshot.observedAt ?
观测时间:{snapshot.observedAt}
: null}
-
+
-
+
{snapshot.wlan.profiles.length ? (
snapshot.wlan.profiles.map((profile, index) => (
))
) : (
- No WLAN profiles were supplied.
+ 未提供 WLAN 配置。
)}
-
+
{snapshot.interfaces.length ? (
snapshot.interfaces.map((networkInterface, index) => (
- Addresses
+ 地址
{networkInterface.addresses.length ? (
{networkInterface.addresses.map((address, addressIndex) => (
-
))}
) : (
- No addresses were supplied.
+ 未提供地址。
)}
))
) : (
- No network interfaces were supplied.
+ 未提供网络接口。
)}
-
-
- Device Network actions
- R1 configuration actions are unavailable until executable backend support exists.
- R2 operational actions are unavailable until executable backend support exists.
+
+ 设备网络操作
+ 在具备可执行后端支持前,R1 配置操作不可用。
+ 在具备可执行后端支持前,R2 运行操作不可用。
>
);
@@ -294,16 +299,15 @@ export function DeviceNetworkModule({
if (instance.authentication !== 'authenticated') {
return (
- Authentication is required before Device Network data can be read for this instance.
+ 需要先完成认证,才能读取设备网络数据。
);
}
if (!dataSource)
return (
-
- No safe Device Network read data source is available. This console will not invent or call
- an uncontracted production endpoint.
+
+ 没有可用的安全设备网络只读数据源。此控制台不会臆造或调用未签订契约的生产端点。
);
@@ -314,17 +318,17 @@ export function DeviceNetworkModule({
if ((state.kind === 'idle' || state.kind === 'loading') && !retainedSnapshot)
return (
-
- Loading Device Network…
+
+ 正在加载设备网络…
);
if (state.kind === 'error' && !retainedSnapshot)
return (
-
Unable to load Device Network: {SAFE_LOAD_ERROR}
+
{SAFE_LOAD_ERROR}
);
@@ -335,19 +339,18 @@ export function DeviceNetworkModule({
return (
- {state.kind === 'loading' ?
Refreshing Device Network…
: null}
+ {state.kind === 'loading' ?
正在刷新设备网络…
: null}
{state.kind === 'error' ? (
-
Refresh failed; showing the last known Device Network data.
+
刷新失败,正在显示上次已知的设备网络数据。
) : null}
{instance.freshness !== 'fresh' ? (
-
- Device Network data is {instance.freshness}; verify freshness before relying on these
- values.
+
+ 设备网络数据可能已过期;使用这些值前请核实数据新鲜度。
) : null}
diff --git a/apps/web/src/instances/esim-module.test.tsx b/apps/web/src/instances/esim-module.test.tsx
index 273ffd3..0537f2b 100644
--- a/apps/web/src/instances/esim-module.test.tsx
+++ b/apps/web/src/instances/esim-module.test.tsx
@@ -36,19 +36,19 @@ describe('isolated safe eSIM read module', () => {
render(
);
expect(pending.load).toHaveBeenCalledWith('alpha', expect.any(AbortSignal));
pending.resolve(snapshot);
- const section = await screen.findByRole('region', { name: 'eSIM safe summary' });
+ const section = await screen.findByRole('region', { name: 'eSIM 安全摘要' });
expect(within(section).getByText('4')).toBeTruthy();
expect(within(section).getByText('2')).toBeTruthy();
- expect(within(section).getByText('available')).toBeTruthy();
- expect(within(section).getByText('idle')).toBeTruthy();
- expect(within(section).getByText('Provisioned')).toBeTruthy();
+ expect(within(section).getByText('可用')).toBeTruthy();
+ expect(within(section).getByText('空闲')).toBeTruthy();
+ expect(within(section).getByText('已配置')).toBeTruthy();
expect(screen.queryByRole('button')).toBeNull();
});
it('has no endpoint fallback and requires the exact authenticated owner', async () => {
const load = vi.fn
().mockResolvedValue(snapshot);
const { rerender } = render();
- expect(await screen.findByText('Provisioned')).toBeTruthy();
+ expect(await screen.findByText('已配置')).toBeTruthy();
rerender(
{
dataSource={{ load }}
/>,
);
- expect(screen.getByRole('alert').textContent).toMatch(/authentication is required/i);
- expect(screen.queryByText('Provisioned')).toBeNull();
+ expect(screen.getByRole('alert').textContent).toMatch(/需要先完成认证/i);
+ expect(screen.queryByText('已配置')).toBeNull();
expect(load).toHaveBeenCalledTimes(1);
rerender();
- expect(screen.getByRole('status', { name: 'eSIM unavailable' }).textContent).toMatch(
- /no safe esim read data source.*will not invent or call.*endpoint/i,
+ expect(screen.getByRole('status', { name: 'eSIM 不可用' }).textContent).toMatch(
+ /没有可用的安全 eSIM 只读数据源.*不会臆造或调用.*端点/i,
);
});
@@ -91,7 +91,7 @@ describe('isolated safe eSIM read module', () => {
providerData: { secret: 'raw-provider-secret' },
} as unknown as EsimSnapshot;
render( hostile }} />);
- expect(await screen.findByText('Enabled')).toBeTruthy();
+ expect(await screen.findByText('已启用')).toBeTruthy();
for (const forbidden of [
'EID-secret',
'ICCID-secret',
@@ -109,7 +109,7 @@ describe('isolated safe eSIM read module', () => {
expect(document.body.textContent).not.toContain(forbidden);
expect(document.querySelector('img')).toBeNull();
expect(document.querySelector('script')).toBeNull();
- expect(screen.getAllByText('Unavailable')).toHaveLength(4);
+ expect(screen.getAllByText('不可用')).toHaveLength(4);
});
it('retains same-owner data during refresh and uses fixed safe errors with retry', async () => {
@@ -123,14 +123,12 @@ describe('isolated safe eSIM read module', () => {
const { rerender } = render(
,
);
- expect(await screen.findByText('Provisioned')).toBeTruthy();
+ expect(await screen.findByText('已配置')).toBeTruthy();
rerender();
- expect(
- await screen.findByText(/Refresh failed; showing the last known eSIM summary/i),
- ).toBeTruthy();
+ expect(await screen.findByText(/刷新失败,正在显示上次已知的 eSIM 摘要/i)).toBeTruthy();
expect(document.body.textContent).not.toContain('EID-secret');
expect(screen.getByText('4')).toBeTruthy();
- await user.click(screen.getByRole('button', { name: 'Retry loading eSIM data' }));
+ await user.click(screen.getByRole('button', { name: '重试加载 eSIM 数据' }));
expect(await screen.findByText('5')).toBeTruthy();
});
@@ -146,7 +144,7 @@ describe('isolated safe eSIM read module', () => {
/>,
);
const alert = await screen.findByRole('alert');
- expect(alert.textContent).toContain('eSIM data could not be loaded.');
+ expect(alert.textContent).toContain('无法加载 eSIM 数据。');
expect(alert.textContent).not.toContain('private code');
});
@@ -163,8 +161,8 @@ describe('isolated safe eSIM read module', () => {
expect(alphaSignal?.aborted).toBe(true);
alpha.resolve({ ...snapshot, labels: ['Error'] });
bravo.resolve({ ...snapshot, labels: ['Pending'] });
- expect(await screen.findByText('Pending')).toBeTruthy();
- expect(screen.queryByText('Error')).toBeNull();
+ expect(await screen.findByText('等待中')).toBeTruthy();
+ expect(screen.queryByText('错误')).toBeNull();
});
it('caps labels at eight and defensively sanitizes unknown and null payloads', async () => {
@@ -176,13 +174,13 @@ describe('isolated safe eSIM read module', () => {
const { rerender } = render(
({ labels }) }} />,
);
- const summary = await screen.findByRole('region', { name: 'eSIM safe summary' });
+ const summary = await screen.findByRole('region', { name: 'eSIM 安全摘要' });
expect(within(summary).getAllByRole('listitem')).toHaveLength(8);
rerender(
null }} refreshSignal={1} />,
);
- expect(await screen.findByText('No safe labels were supplied.')).toBeTruthy();
- expect(screen.getAllByText('Unavailable')).toHaveLength(4);
+ expect(await screen.findByText('未提供安全标签。')).toBeTruthy();
+ expect(screen.getAllByText('不可用')).toHaveLength(4);
});
it('turns a synchronous source throw into the fixed initial-load error', async () => {
@@ -197,7 +195,7 @@ describe('isolated safe eSIM read module', () => {
/>,
);
const alert = await screen.findByRole('alert');
- expect(alert.textContent).toContain('eSIM data could not be loaded.');
+ expect(alert.textContent).toContain('无法加载 eSIM 数据。');
expect(alert.textContent).not.toContain('private eSIM identifier');
});
});
diff --git a/apps/web/src/instances/esim-module.tsx b/apps/web/src/instances/esim-module.tsx
index 3fbcc33..f1269b4 100644
--- a/apps/web/src/instances/esim-module.tsx
+++ b/apps/web/src/instances/esim-module.tsx
@@ -1,6 +1,7 @@
import { useEffect, useRef, useState } from 'react';
import type { InstanceContext } from '../app-shell.js';
+import { displayValue } from '../ui/locale.js';
/** Bounded status vocabularies are the only strings this module may present. */
export type EsimLpacStatus = 'available' | 'unavailable' | 'degraded' | 'unknown';
@@ -53,7 +54,7 @@ const SAFE_LABELS = new Set([
'Pending',
'Error',
]);
-const SAFE_LOAD_ERROR = 'eSIM data could not be loaded.';
+const SAFE_LOAD_ERROR = '无法加载 eSIM 数据。';
const MAX_SAFE_LABELS = 8;
function isRecord(value: unknown): value is Readonly> {
@@ -93,40 +94,48 @@ function sanitizeSnapshot(value: unknown): SafeSnapshot {
}
function display(value: number | string | undefined): string {
- return value === undefined ? 'Unavailable' : String(value);
+ return displayValue(value);
}
+const SAFE_LABEL_DISPLAY_VALUES: Readonly> = {
+ Provisioned: '已配置',
+ Enabled: 'enabled',
+ Disabled: 'disabled',
+ Pending: 'pending',
+ Error: 'error',
+};
+
function SnapshotView({ snapshot }: { snapshot: SafeSnapshot }) {
return (
-
- eSIM safe summary
+
+ eSIM 安全摘要
-
- Profile count
+ - 配置数量
- {display(snapshot.profileCount)}
-
- Enabled profile count
+ - 已启用配置数量
- {display(snapshot.enabledProfileCount)}
-
- lpac status
+ - LPAC 状态
- {display(snapshot.lpacStatus)}
-
- Work mode
+ - 工作模式
- {display(snapshot.workMode)}
- Safe labels
+ 安全标签
{snapshot.labels.length ? (
{snapshot.labels.map((label, index) => (
- - {label}
+ - {displayValue(SAFE_LABEL_DISPLAY_VALUES[label])}
))}
) : (
- No safe labels were supplied.
+ 未提供安全标签。
)}
);
@@ -187,16 +196,15 @@ export function EsimModule({ instance, dataSource, refreshSignal }: EsimModulePr
if (instance.authentication !== 'authenticated') {
return (
- Authentication is required before eSIM data can be read for this instance.
+ 需要先完成认证,才能读取 eSIM 数据。
);
}
if (!dataSource) {
return (
-
- No safe eSIM read data source is available. This console will not invent or call an
- uncontracted production endpoint.
+
+ 没有可用的安全 eSIM 只读数据源。此控制台不会臆造或调用未签订契约的生产端点。
);
}
@@ -208,8 +216,8 @@ export function EsimModule({ instance, dataSource, refreshSignal }: EsimModulePr
if ((state.kind === 'idle' || state.kind === 'loading') && !retained) {
return (
-
- Loading eSIM summary…
+
+ 正在加载 eSIM 摘要…
);
}
@@ -217,9 +225,9 @@ export function EsimModule({ instance, dataSource, refreshSignal }: EsimModulePr
if (state.kind === 'error' && !retained) {
return (
-
Unable to load eSIM data: {SAFE_LOAD_ERROR}
+
{SAFE_LOAD_ERROR}
);
@@ -231,18 +239,18 @@ export function EsimModule({ instance, dataSource, refreshSignal }: EsimModulePr
return (
- {state.kind === 'loading' ?
Refreshing eSIM summary…
: null}
+ {state.kind === 'loading' ?
正在刷新 eSIM 摘要…
: null}
{state.kind === 'error' ? (
-
Refresh failed; showing the last known eSIM summary.
+
刷新失败,正在显示上次已知的 eSIM 摘要。
) : null}
{instance.freshness !== 'fresh' ? (
-
- eSIM data is {instance.freshness}; verify freshness before relying on these values.
+
+ eSIM 数据可能不是最新数据,请核实时效性后再使用这些数值。
) : null}
diff --git a/apps/web/src/instances/instance-crud.test.tsx b/apps/web/src/instances/instance-crud.test.tsx
index 3a49251..c875724 100644
--- a/apps/web/src/instances/instance-crud.test.tsx
+++ b/apps/web/src/instances/instance-crud.test.tsx
@@ -33,13 +33,13 @@ describe('Instance CRUD form', () => {
const source = dataSource();
render(
);
- await user.type(screen.getByLabelText('Name'), 'Lab modem');
- await user.type(screen.getByLabelText('Origin'), 'https://lab.example/admin');
- await user.type(screen.getByLabelText('Tags'), 'lab, west, lab');
- await user.selectOptions(screen.getByLabelText('Authentication method'), 'password');
- await user.type(screen.getByLabelText('Password'), 'do-not-render');
+ await user.type(screen.getByLabelText('名称'), 'Lab modem');
+ await user.type(screen.getByLabelText('源地址'), 'https://lab.example/admin');
+ await user.type(screen.getByLabelText('标签'), 'lab, west, lab');
+ await user.selectOptions(screen.getByLabelText('认证方式'), 'password');
+ await user.type(screen.getByLabelText('密码'), 'do-not-render');
expect(document.body.textContent).not.toContain('do-not-render');
- await user.click(screen.getByRole('button', { name: 'Add instance' }));
+ await user.click(screen.getByRole('button', { name: '添加实例' }));
expect(source.create).toHaveBeenCalledWith({
name: 'Lab modem',
@@ -58,17 +58,17 @@ describe('Instance CRUD form', () => {
expect(await screen.findByDisplayValue('Owner modem')).toBeTruthy();
expect(source.get).toHaveBeenCalledWith('owner');
- await user.clear(screen.getByLabelText('Name'));
- await user.type(screen.getByLabelText('Name'), 'Renamed');
- await user.click(screen.getByRole('button', { name: 'Save changes' }));
+ await user.clear(screen.getByLabelText('名称'));
+ await user.type(screen.getByLabelText('名称'), 'Renamed');
+ await user.click(screen.getByRole('button', { name: '保存更改' }));
expect(source.update).toHaveBeenLastCalledWith(
'owner',
3,
expect.objectContaining({ password: { action: 'preserve' } }),
);
- await user.selectOptions(screen.getByLabelText('Password action'), 'clear');
- await user.click(screen.getByRole('button', { name: 'Save changes' }));
+ await user.selectOptions(screen.getByLabelText('密码操作'), 'clear');
+ await user.click(screen.getByRole('button', { name: '保存更改' }));
expect(source.update).toHaveBeenLastCalledWith(
'owner',
4,
@@ -76,7 +76,7 @@ describe('Instance CRUD form', () => {
);
rerender(
);
- expect((await screen.findByRole('alert')).textContent).toMatch(/does not match this route/i);
+ expect((await screen.findByRole('alert')).textContent).toMatch(/实例操作失败,请稍后重试/i);
expect(screen.queryByDisplayValue('Owner modem')).toBeNull();
});
@@ -84,19 +84,17 @@ describe('Instance CRUD form', () => {
const user = userEvent.setup();
const source = dataSource({
update: vi.fn(async () => {
- throw new Error('Could not save this instance.');
+ throw new Error('无法保存此实例。');
}),
});
render(
);
await screen.findByDisplayValue('Owner modem');
- await user.click(screen.getByRole('button', { name: 'Test connection' }));
- expect((await screen.findByRole('status')).textContent).toMatch(/reachable and authenticated/i);
+ await user.click(screen.getByRole('button', { name: '测试连接' }));
+ expect((await screen.findByRole('status')).textContent).toMatch(/连接可达且已认证/i);
- await user.click(screen.getByRole('button', { name: 'Save changes' }));
- expect((await screen.findByRole('alert')).textContent).toContain(
- 'Could not save this instance.',
- );
- await user.type(screen.getByLabelText('Name'), ' still here');
+ await user.click(screen.getByRole('button', { name: '保存更改' }));
+ expect((await screen.findByRole('alert')).textContent).toContain('实例操作失败,请稍后重试。');
+ await user.type(screen.getByLabelText('名称'), ' still here');
expect(screen.getByRole('alert')).toBeTruthy();
});
@@ -105,12 +103,12 @@ describe('Instance CRUD form', () => {
const source = dataSource();
render(
);
await screen.findByDisplayValue('Owner modem');
- await user.click(screen.getByRole('button', { name: 'Delete instance' }));
- expect(
- (screen.getByRole('button', { name: 'Confirm deletion' }) as HTMLButtonElement).disabled,
- ).toBe(true);
- await user.type(screen.getByLabelText('Type owner to confirm'), 'owner');
- await user.click(screen.getByRole('button', { name: 'Confirm deletion' }));
+ await user.click(screen.getByRole('button', { name: '删除实例' }));
+ expect((screen.getByRole('button', { name: '确认删除' }) as HTMLButtonElement).disabled).toBe(
+ true,
+ );
+ await user.type(screen.getByLabelText('输入 owner 以确认'), 'owner');
+ await user.click(screen.getByRole('button', { name: '确认删除' }));
expect(source.delete).toHaveBeenCalledWith('owner', 3);
});
});
diff --git a/apps/web/src/instances/instance-crud.tsx b/apps/web/src/instances/instance-crud.tsx
index 3e6721e..9d8dc0a 100644
--- a/apps/web/src/instances/instance-crud.tsx
+++ b/apps/web/src/instances/instance-crud.tsx
@@ -16,8 +16,8 @@ export interface InstanceEditorProps {
}
type PasswordAction = 'preserve' | 'set' | 'clear';
-function message(error: unknown): string {
- return error instanceof Error && error.message ? error.message : 'The instance operation failed.';
+function message(): string {
+ return '实例操作失败,请稍后重试。';
}
export function InstanceEditor({
@@ -43,7 +43,7 @@ export function InstanceEditor({
useEffect(() => {
if (mode !== 'edit') return;
if (!instanceId) {
- setError('The instance route is missing an owner.');
+ setError('实例路由缺少所有者。');
return;
}
let active = true;
@@ -53,8 +53,7 @@ export function InstanceEditor({
.get(instanceId)
.then((loaded) => {
if (!active) return;
- if (loaded.id !== instanceId)
- throw new Error('The returned instance does not match this route.');
+ if (loaded.id !== instanceId) throw new Error('返回的实例与当前路由不匹配。');
setOwner(loaded);
setName(loaded.name);
setOrigin(loaded.origin);
@@ -63,7 +62,7 @@ export function InstanceEditor({
setPasswordAction('preserve');
setPassword('');
})
- .catch((cause: unknown) => active && setError(message(cause)));
+ .catch(() => active && setError(message()));
return () => {
active = false;
};
@@ -88,14 +87,14 @@ export function InstanceEditor({
? await dataSource.create(input)
: await dataSource.update(owner!.id, owner!.revision, input);
if (mode === 'edit' && saved.id !== instanceId)
- throw new Error('The returned instance does not match this route.');
+ throw new Error('返回的实例与当前路由不匹配。');
setOwner(saved);
setPassword('');
setPasswordAction('preserve');
- setStatus(mode === 'create' ? 'Instance added.' : 'Changes saved.');
+ setStatus(mode === 'create' ? '实例已添加。' : '更改已保存。');
setError(undefined);
- } catch (cause) {
- setError(message(cause));
+ } catch {
+ setError(message());
} finally {
setBusy(false);
}
@@ -109,13 +108,13 @@ export function InstanceEditor({
setStatus(
result.reachable
? result.authenticated
- ? 'Connection is reachable and authenticated.'
- : 'Connection is reachable; authentication is required.'
- : 'Connection is not reachable.',
+ ? '连接可达且已认证。'
+ : '连接可达,但需要认证。'
+ : '连接不可达。',
);
setError(undefined);
- } catch (cause) {
- setError(message(cause));
+ } catch {
+ setError(message());
} finally {
setBusy(false);
}
@@ -126,11 +125,11 @@ export function InstanceEditor({
setBusy(true);
try {
await dataSource.delete(owner.id, owner.revision);
- setStatus('Instance deletion accepted.');
+ setStatus('实例删除请求已接受。');
setError(undefined);
setConfirming(false);
- } catch (cause) {
- setError(message(cause));
+ } catch {
+ setError(message());
} finally {
setBusy(false);
}
@@ -142,12 +141,12 @@ export function InstanceEditor({
{error}
) : (
-
Loading instance…
+
正在加载实例…
);
return (
- {mode === 'create' ? 'Add instance' : 'Instance settings'}
+ {mode === 'create' ? '添加实例' : '实例设置'}
{error ? (
{error}
@@ -160,11 +159,11 @@ export function InstanceEditor({
) : null}
{mode === 'edit' ? (
- Danger zone
+ 危险操作区
{!confirming ? (
) : (
-
Deletion cannot be undone.
+
删除后无法撤销。
)}
diff --git a/apps/web/src/instances/instance-detail.test.tsx b/apps/web/src/instances/instance-detail.test.tsx
index 5a35fa0..a43793f 100644
--- a/apps/web/src/instances/instance-detail.test.tsx
+++ b/apps/web/src/instances/instance-detail.test.tsx
@@ -22,9 +22,9 @@ const owner: InstanceContext = {
const capabilities: InstanceCapabilityMap = {
overview: { state: 'supported' },
- messages: { state: 'degraded', explanation: 'Message history is read-only.' },
- calls: { state: 'unsupported', explanation: 'This modem has no voice support.' },
- esim: { state: 'unknown', explanation: 'Capability discovery did not report eSIM.' },
+ messages: { state: 'degraded', explanation: '消息历史记录为只读。' },
+ calls: { state: 'unsupported', explanation: '此调制解调器不支持语音功能。' },
+ esim: { state: 'unknown', explanation: '能力探测结果未包含 eSIM。' },
};
describe('InstanceDetail', () => {
@@ -38,14 +38,14 @@ describe('InstanceDetail', () => {
/>,
);
- expect(screen.getByRole('link', { name: 'Overview' }).getAttribute('href')).toBe(
+ expect(screen.getByRole('link', { name: '概览' }).getAttribute('href')).toBe(
'/instances/owner/overview',
);
- expect(screen.queryByRole('link', { name: 'Messages' })).toBeNull();
- expect(screen.getByText('Message history is read-only.')).toBeTruthy();
- expect(screen.getByText('This modem has no voice support.')).toBeTruthy();
- expect(screen.getByText('Capability discovery did not report eSIM.')).toBeTruthy();
- expect(screen.getAllByText('Capability state is unknown.').length).toBeGreaterThan(0);
+ expect(screen.queryByRole('link', { name: '消息' })).toBeNull();
+ expect(screen.getByText('消息历史记录为只读。')).toBeTruthy();
+ expect(screen.getByText('此调制解调器不支持语音功能。')).toBeTruthy();
+ expect(screen.getByText('能力探测结果未包含 eSIM。')).toBeTruthy();
+ expect(screen.getAllByText('能力状态未知。').length).toBeGreaterThan(0);
});
it('never renders or loads context when the direct-route owner does not match', () => {
@@ -60,8 +60,8 @@ describe('InstanceDetail', () => {
);
expect(screen.queryByText('Owner modem')).toBeNull();
- expect(screen.queryByRole('navigation', { name: 'Instance modules' })).toBeNull();
- expect(screen.getByText(/Instance context is unavailable/i)).toBeTruthy();
+ expect(screen.queryByRole('navigation', { name: '实例模块' })).toBeNull();
+ expect(screen.getByText(/此路由缺少实例上下文/i)).toBeTruthy();
expect(load).not.toHaveBeenCalled();
});
@@ -98,7 +98,7 @@ describe('InstanceDetail', () => {
expect(pending.get('owner')?.signal.aborted).toBe(true);
pending.get('second')?.resolve({ overview: { state: 'supported' } });
- expect(await screen.findByRole('link', { name: 'Overview' })).toBeTruthy();
+ expect(await screen.findByRole('link', { name: '概览' })).toBeTruthy();
pending
.get('owner')
?.resolve({ overview: { state: 'unsupported', explanation: 'Stale owner result' } });
@@ -107,9 +107,9 @@ describe('InstanceDetail', () => {
});
it.each<[InstanceModule, string]>([
- ['cellular', 'Cellular'],
- ['device-network', 'Device Network'],
- ['automation', 'Automation'],
+ ['cellular', '蜂窝网络'],
+ ['device-network', '设备网络'],
+ ['automation', '自动化'],
['ota', 'OTA'],
])('uses the existing module contract for %s', (module, label) => {
render(
diff --git a/apps/web/src/instances/instance-detail.tsx b/apps/web/src/instances/instance-detail.tsx
index 543335e..038893a 100644
--- a/apps/web/src/instances/instance-detail.tsx
+++ b/apps/web/src/instances/instance-detail.tsx
@@ -3,6 +3,7 @@ import { useEffect, useRef, useState } from 'react';
import type { InstanceContext, InstanceModule } from '../app-shell.js';
import { canonicalHttpOrigin } from '../fleet/fleet-page.js';
+import { displayStatus } from '../ui/locale.js';
export type CapabilityState = 'supported' | 'degraded' | 'unsupported' | 'unknown';
@@ -27,23 +28,23 @@ export interface InstanceDetailProps {
}
export const INSTANCE_MODULE_LABELS: Readonly> = {
- overview: 'Overview',
- cellular: 'Cellular',
- 'device-network': 'Device Network',
- messages: 'Messages',
- calls: 'Calls',
+ overview: '概览',
+ cellular: '蜂窝网络',
+ 'device-network': '设备网络',
+ messages: '消息',
+ calls: '通话',
esim: 'eSIM',
- notifications: 'Notifications',
- automation: 'Automation',
+ notifications: '通知',
+ automation: '自动化',
ota: 'OTA',
};
const MODULES = Object.keys(INSTANCE_MODULE_LABELS) as readonly InstanceModule[];
const DEFAULT_EXPLANATIONS: Readonly, string>> = {
- degraded: 'This module is available with limited functionality.',
- unsupported: 'This instance does not support this module.',
- unknown: 'Capability state is unknown.',
+ degraded: '此模块可用,但功能受限。',
+ unsupported: '此实例不支持该模块。',
+ unknown: '能力状态未知。',
};
function capabilityFor(map: InstanceCapabilityMap, module: InstanceModule): InstanceCapability {
@@ -99,7 +100,7 @@ export function InstanceDetail({
(error: unknown) => {
void error;
if (!controller.signal.aborted && requestRef.current === request) {
- setLoadError('Capability discovery failed.');
+ setLoadError('能力探测失败。');
setLoading(false);
}
},
@@ -111,7 +112,7 @@ export function InstanceDetail({
return (
{INSTANCE_MODULE_LABELS[module]}
- Instance context is unavailable for this route.
+ 此路由缺少实例上下文。
);
}
@@ -122,29 +123,29 @@ export function InstanceDetail({
return (
-