What happened
Two shared copy modules predate the locale authority and expose no UiLocale parameter. One is a user-visible defect; the other is a stale boundary that will produce one.
1. resumeParkToastCopy is Chinese-only and is rendered directly
packages/ui/src/runtime-resume-copy.ts holds 29 hardcoded Chinese reason strings plus 4 inline title/fallback strings, and its entry point takes no locale:
export function resumeParkToastCopy(reasons: readonly string[]): ResumeParkToastCopy
apps/desktop/src/renderer/use-shell-resume.ts calls it and renders the result unchanged:
- line 67 —
const parkCopy = resumeParkToastCopy(result.rejectionReasons);
- line 72 —
toastApi.error(parkCopy.title, parkCopy.description, undefined, { sessionId });
- lines 68–71 — the same Chinese description is stored in
resumeParkDescriptionBySession, which apps/desktop/src/renderer/app-shell.tsx:3149 renders as detail:
So an English-locale Desktop user who resumes a parked session sees 暂时无法继续这一轮 as the toast title, a Chinese description, and the same Chinese text again in the inline detail.
The locale is already in scope in that exact function. use-shell-resume.ts:56 destructures uiLocale, and line 87 passes it to localizedShellErrorMessage. Within one try block:
- the success path (line 79) uses
shellCopy.resumeStartedTitle — localized
- the throw path (lines 83–88) uses
localizedShellErrorMessage(error, ..., uiLocale) — localized
- the park path (lines 67–72) — not localized
Only the park branch skips the locale it is holding.
2. connection-error-copy.ts is a superseded copy table with a stale contract
packages/core/src/connection-error-copy.ts returns hardcoded Chinese from REASON_FIX_COPY (line 48) and GENERIC_FIX_COPY (line 39) through describeChatConfigurationReason (line 77), also with no locale parameter.
This is not user-visible today, but the module's stated contract no longer matches the code. Its docstring says:
This module is the canonical parser and copy table for both CLI and desktop; surfaces adapt their local event shape here instead of duplicating the rules.
The parser half is still canonical. The copy half is not:
- Desktop resolves its own localized table —
apps/desktop/src/renderer/model-connection-errors.ts:49-53 reads getDesktopConversationCopy(locale).model.configurationReason[reason], keyed off the parsed reason token. It never reads describeChatConfigurationReason.
- The only remaining caller is
packages/runtime-host/src/server/execution-composition.ts:310, which embeds the Chinese sentence into a NO_REAL_CONNECTION:fake_backend: <message> error string that every consumer then discards — consumers parse the reason token and look up their own copy.
packages/cli/src/runtime-host-cli-context.ts:306 hand-rolls the English equivalent of the same code: NO_REAL_CONNECTION:missing_default_connection: Runtime Host has no default model connection.
So the same error code carries a different language depending on which producer constructed it, and the module that claims to own that copy is no longer the one supplying it.
How to reproduce
For (1), on main:
- Settings → Preferences → set the language to English.
- Put a session into a parked resume state — any run whose
resumeLatest returns disposition: 'park' with at least one rejectionReasons entry (for example a run left with dangling tool state).
- Click resume on that session.
- The toast title and description render in Chinese, and the inline detail under the task shows the same Chinese text.
The code path is deterministic rather than timing-dependent: use-shell-resume.ts:67 has no locale argument to pass, so no locale can influence the result.
For (2) there is no user-visible reproduction — it is a boundary/ownership defect, filed here because it is the same root cause found in the same review.
Environment
- Commit:
98fc50508 (main, 2026-09-01)
- Maka: 0.1.11
- Surface: Desktop (1); Runtime Host and CLI (2)
- Locale:
en
Desired outcome
resumeParkToastCopy accepts a UiLocale and resolves its strings through a catalog; use-shell-resume.ts passes the uiLocale it already destructures. English mode shows no Chinese in the park toast or the inline detail.
connection-error-copy.ts either becomes locale-aware or drops its copy table and keeps only the parser. Either way its docstring should state what it actually owns, and the runtime-host and CLI producers should stop disagreeing about language for the same code.
- Reason tokens, error codes, logs, and protocol values stay locale-independent. Only presentation changes.
- A new reason cannot be added to either module without an
en entry.
Notes on approach
packages/core/src/ui-locale.ts on main now ships UiMessageCatalog<T>, defineUiMessageCatalog, resolveUiMessageCatalog, and formatUiMessage, added by #3990 and currently used only inside packages/cli. Because UiMessageCatalog requires en and treats every other locale as an optional DeepPartial, it fits these two modules better than the strict UiCatalog<T> = Record<UiLocale, T> and avoids forcing a repo-wide atomic change.
I have no strong preference between that and a plain UiCatalog; deferring to whichever the maintainers consider canonical for packages/ui and packages/core right now.
Relationship to existing issues
AI disclosure: drafted with Claude Code. Every file path, line number, and quoted string above was read from apache/maka@98fc50508 and verified before filing.
What happened
Two shared copy modules predate the locale authority and expose no
UiLocaleparameter. One is a user-visible defect; the other is a stale boundary that will produce one.1.
resumeParkToastCopyis Chinese-only and is rendered directlypackages/ui/src/runtime-resume-copy.tsholds 29 hardcoded Chinese reason strings plus 4 inline title/fallback strings, and its entry point takes no locale:apps/desktop/src/renderer/use-shell-resume.tscalls it and renders the result unchanged:const parkCopy = resumeParkToastCopy(result.rejectionReasons);toastApi.error(parkCopy.title, parkCopy.description, undefined, { sessionId });resumeParkDescriptionBySession, whichapps/desktop/src/renderer/app-shell.tsx:3149renders asdetail:So an English-locale Desktop user who resumes a parked session sees
暂时无法继续这一轮as the toast title, a Chinese description, and the same Chinese text again in the inline detail.The locale is already in scope in that exact function.
use-shell-resume.ts:56destructuresuiLocale, and line 87 passes it tolocalizedShellErrorMessage. Within onetryblock:shellCopy.resumeStartedTitle— localizedlocalizedShellErrorMessage(error, ..., uiLocale)— localizedOnly the park branch skips the locale it is holding.
2.
connection-error-copy.tsis a superseded copy table with a stale contractpackages/core/src/connection-error-copy.tsreturns hardcoded Chinese fromREASON_FIX_COPY(line 48) andGENERIC_FIX_COPY(line 39) throughdescribeChatConfigurationReason(line 77), also with no locale parameter.This is not user-visible today, but the module's stated contract no longer matches the code. Its docstring says:
The parser half is still canonical. The copy half is not:
apps/desktop/src/renderer/model-connection-errors.ts:49-53readsgetDesktopConversationCopy(locale).model.configurationReason[reason], keyed off the parsed reason token. It never readsdescribeChatConfigurationReason.packages/runtime-host/src/server/execution-composition.ts:310, which embeds the Chinese sentence into aNO_REAL_CONNECTION:fake_backend: <message>error string that every consumer then discards — consumers parse the reason token and look up their own copy.packages/cli/src/runtime-host-cli-context.ts:306hand-rolls the English equivalent of the same code:NO_REAL_CONNECTION:missing_default_connection: Runtime Host has no default model connection.So the same error code carries a different language depending on which producer constructed it, and the module that claims to own that copy is no longer the one supplying it.
How to reproduce
For (1), on
main:resumeLatestreturnsdisposition: 'park'with at least onerejectionReasonsentry (for example a run left with dangling tool state).The code path is deterministic rather than timing-dependent:
use-shell-resume.ts:67has no locale argument to pass, so no locale can influence the result.For (2) there is no user-visible reproduction — it is a boundary/ownership defect, filed here because it is the same root cause found in the same review.
Environment
98fc50508(main, 2026-09-01)enDesired outcome
resumeParkToastCopyaccepts aUiLocaleand resolves its strings through a catalog;use-shell-resume.tspasses theuiLocaleit already destructures. English mode shows no Chinese in the park toast or the inline detail.connection-error-copy.tseither becomes locale-aware or drops its copy table and keeps only the parser. Either way its docstring should state what it actually owns, and the runtime-host and CLI producers should stop disagreeing about language for the same code.enentry.Notes on approach
packages/core/src/ui-locale.tsonmainnow shipsUiMessageCatalog<T>,defineUiMessageCatalog,resolveUiMessageCatalog, andformatUiMessage, added by #3990 and currently used only insidepackages/cli. BecauseUiMessageCatalogrequiresenand treats every other locale as an optionalDeepPartial, it fits these two modules better than the strictUiCatalog<T> = Record<UiLocale, T>and avoids forcing a repo-wide atomic change.I have no strong preference between that and a plain
UiCatalog; deferring to whichever the maintainers consider canonical forpackages/uiandpackages/coreright now.Relationship to existing issues
koviaUiCatalog<T>". Neither has anenentry today and neither takes a locale, so that slice cannot be completed as written until this is resolved. Filing separately so feat(i18n-ko): shared UI — packages/ui and core error copy #3978 stays a translation task rather than absorbing a refactor that also changesenbehavior.AI disclosure: drafted with Claude Code. Every file path, line number, and quoted string above was read from
apache/maka@98fc50508and verified before filing.