Skip to content

fix(i18n): code-ize health signals and drop silent zh locale defaults - #4524

Open
orangeCatDeveloper wants to merge 2 commits into
apache:mainfrom
orangeCatDeveloper:fix/locale-gap-wave1
Open

fix(i18n): code-ize health signals and drop silent zh locale defaults#4524
orangeCatDeveloper wants to merge 2 commits into
apache:mainfrom
orangeCatDeveloper:fix/locale-gap-wave1

Conversation

@orangeCatDeveloper

@orangeCatDeveloper orangeCatDeveloper commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Summary

Two locale gaps from a repo-wide sweep, neither blocked on the architecture-policy work, so they ship first.

Health signals. packages/core/src/health.ts emitted zh product copy from the producer, and the English presenter reverse-mapped it by exact string match — any wording edit silently broke English rendering, and the runtime-probe detail was re-parsed out of a formatted zh string with regexes. Signals now carry a closed HealthSignalMessageCode union plus structured HealthSignalDetail variants; both locales map codes in the presenter catalog, the string anchors and regex parsing are deleted, and the producer is locale-free. Capability diagnostics use locale-specific fallback copy instead of leaking raw text. Connection-test failure classes are projected as a closed structured detail and mapped on both the Health and connection-detail surfaces; legacy diagnostics also degrade to locale-specific fallback copy. The runtime "运行态" label suffix moves from producer to presenter.

Silent zh defaults. The ui stream/projection seams defaulted locale to 'zh'; every production caller already passes the real locale, so the defaults existed only for tests and left a wrong-locale seam. locale is now required; tests pin zh via local wrappers. tool-format.ts also loses three inline locale === 'en' ? ternaries in favor of catalog entries.

Refs #2672

Verification

packages/ui suite:                      331 pass / 0 fail
core health tests:                       9 pass
desktop health-center-copy tests:        7 pass
desktop typecheck:                       4 tsconfigs pass
renderer architecture check:             pass
repo format:check:                        pass

The bilingual assertions cover message codes, structured runtime-probe details, capability fallback copy, connection-test failure classes on both Settings surfaces, and localized runtime labels. Unsupported app and storage health fixtures were removed instead of assigning them capability-only message codes.

Known remainder: Permission Center still applies its pre-existing CJK-content fallback to capability reasons; removing that sniff is outside this PR.

The app was not launched for manual acceptance.

AI use

Select exactly one:

  • No generative tool made a substantive contribution
  • Generative tooling made a substantive contribution

Tool(s) and scope: Claude Code — initial analysis, implementation, and tests. OpenCode — review assessment, follow-up implementation, tests, and this description update. Each commit carries its corresponding Generated-by trailer.

Checklist

  • Tests cover the change and fail without it

@github-actions github-actions Bot added the effort/L Under 1000 readable lines label Sep 2, 2026
@orangeCatDeveloper
orangeCatDeveloper marked this pull request as draft September 2, 2026 05:07
@orangeCatDeveloper
orangeCatDeveloper marked this pull request as ready for review September 2, 2026 09:24
@orangeCatDeveloper
orangeCatDeveloper force-pushed the fix/locale-gap-wave1 branch 3 times, most recently from 646c302 to 6ddc0ac Compare September 3, 2026 07:29
@orangeCatDeveloper
orangeCatDeveloper force-pushed the fix/locale-gap-wave1 branch 2 times, most recently from 22879d0 to cfe5ba2 Compare September 3, 2026 09:00

@Astro-Han Astro-Han left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The shape is right. The producer emits codes, the existing UiCatalog maps them, Record<HealthSignalMessageCode, string> makes a missing entry a compile error, and the never checks pin detail exhaustiveness. The reverse-mapping it deletes was genuinely fragile: matching signal.message === '不是工作区的默认模型来源。' meant any wording edit in health.ts silently broke English. And I checked the "code without copy leaves a blank" worry, it is not reachable, both maps are total by type and nothing persists a HealthSignal.

[P2] The zh health center now prints raw English bot diagnostics

Two lines apart in the same switch:

// signalDetailZh
return capabilityReasonsZh[detail.reason] ?? detail.reason;
// signalDetailEn
return capabilityReasonsEn[detail.reason] ?? 'See the corresponding settings page for details.';

The zh side passes the unknown reason through verbatim. Bot reasons are English: settings-ipc-helpers.ts:235 produces ${label} requires a Bot Token. and ${label} rejected the Bot Token., which reach capabilityDetail unchanged through readinessReason and runtimeProbeFromBotReadiness. So configure a bot with a bad token, test it, open Settings and Health in Chinese, and an English sentence is what you get.

On main the zh side had a fallback for exactly this: /[㐀-鿿]/.test(raw) ? raw : '状态详情请见对应设置页。'. So a PR whose purpose is removing locale leakage introduces the mirror leak. The comment above that line says unknown reasons "are today's zh snapshot diagnostics", and the new test only pins the zh-text case, which is why it went unnoticed.

Fix is to mirror the en branch, plus a test with an English unknown reason.

[P2] last_test_message puts a machine token on the user-visible surface

runtime-host-connections-ipc-main.ts:429 sends lastTestMessage: connection.lastTest.errorClass, and errorClass is the closed six-value union in connection-effects.ts:69. So the string the user sees is literally auth. The type comments call it raw producer text rendered verbatim by design, but it is an enum, which is the thing this PR code-izes everywhere else. zh is unchanged from main; en regresses from the fallback sentence to the bare token. There is already a seam for this exact field, connectionLastTestMessageDisplay(message, locale) in provider-panel-shared.ts:94.

[P3] Three smaller ones

capabilityReasonsZh and capabilityReasonsEn carry four keys each (disabled, missing platform credentials, macOS TCC only, and the Apple Events one) that capability-snapshot.ts never emits. Main had four dead mappings in the producer; this moves them faithfully and doubles them.

permission-center-page.tsx:705 still uses the /[㐀-鿿]/u CJK sniff on the same capability.*.reason field this PR just stopped sniffing. Not a regression, both predate this, but it means an English user sees the bot's English message in the Permission Center and the fallback sentence in Health, for one field. Worth naming as a known remainder given the PR's stated obligation.

settings-pages.stories.tsx:600 maps a storage-scope signal to capability_ok, so the story renders capability copy for a SQLite row. Root cause is that HealthSignalMessageCode has no code for the app and storage scopes those fixtures use, and no producer emits them.

Next step

Both P2s are one catalog entry each. P3-1 is a free deletion in the same file.

This is a copy change with no screenshots, so it needs manual acceptance in Settings and Health, both locales: a connection in each state, the runtime row with a successful and a failed send, the runtime label suffix that moved from producer to presenter, and a bot capability after a failed credential test, which is the P2 reproduction.

Evidence boundary: read at cfe5ba2 against base b1ec289, no tests, typecheck or build, and the app was not launched. Both P2s are traced through source but not observed at runtime. The claim that no production caller relied on the removed zh defaults rests on a repo-wide grep plus the compiler, not a typecheck run.

AI-assisted review: drafted with Maka.

@orangeCatDeveloper
orangeCatDeveloper force-pushed the fix/locale-gap-wave1 branch 2 times, most recently from b40cbd4 to 297488f Compare September 3, 2026 10:08
@orangeCatDeveloper

Copy link
Copy Markdown
Contributor Author

@Astro-Han Comments are addressed.

@Astro-Han Astro-Han left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The P2 is closed, and closed harder than I asked for. Neither presenter passes raw producer text through any more: capability_reason and last_test_message both return a fixed, localized sentence, so the whole class of "English bot diagnostic surfaces in the Chinese Health center" is gone rather than patched at the one call site I found. The exhaustive switch with no default is what makes that hold — a new detail kind is a compile error in both locales.

CI is unrelated: the failure is host-kernel.test.js:613 (elects one owner ... releases ownership after true-idle shutdown, got unavailable where it wanted connected), which no i18n change can reach. I've rerun it.

P2 — two detail fields are carried but never read, and the comment promises otherwise.

// Raw machine token from the capability snapshot; the presenter maps the
// known vocabulary per locale and falls back to a generic hint.
| { kind: 'capability_reason'; reason: string }
// Legacy connection-test diagnostic; the presenter uses locale-specific fallback copy.
| { kind: 'last_test_message'; text: string }

Neither reason nor text has a reader. signalDetailZh and signalDetailEn both return the generic sentence unconditionally for those two kinds — the per-locale vocabulary map the first comment describes doesn't exist any more. So the producer computes a value, it crosses the IPC boundary, and nothing consumes it.

Two coherent ways out, and I don't mind which:

  • Implement what the comment says for capability_reason. reason is a machine token, not free text, which is exactly the shape a per-locale map wants — and it recovers the specific wording that the generic hint currently costs.
  • Or drop both fields. If the decision is that a generic pointer to the settings page is the right detail, then the payload should say so. text in particular is free-form English from the producer; keeping it around is an invitation to render it later and reintroduce the leak this PR just removed.

Either way the comments need to match. Right now the type documents behaviour the presenters don't have, which is the same failure mode as the string matching you deleted — a promise held in prose instead of in types.

Everything else stands: Record<HealthSignalMessageCode, string> making a missing entry a compile error is the right shape, and the reverse-mapping it replaces was genuinely fragile.

Health signals carried zh product copy from the producer; the en
presenter reverse-mapped it by exact string match, so any wording edit
silently broke English rendering. Signals now carry stable message
codes and structured details, both locales map codes in the presenter
catalog, and raw capability or connection diagnostics no longer leak
machine or wrong-locale text into Settings surfaces. The ui
stream/projection seams lose their silent 'zh' locale defaults so
callers must thread the real locale.

Generated-by: Claude Code
Generated-by: OpenCode
@orangeCatDeveloper

Copy link
Copy Markdown
Contributor Author

Addressed by dropping the unused reason and text payloads. The producer now carries only a typed detail discriminator, and both presenters render fixed localized fallback copy. I also removed the stale comments and added bilingual coverage for both fallback cases.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/L Under 1000 readable lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants