Skip to content

fix(i18n): unconditional Chinese in the chatbot confirm card and field inspector (#2884, #2885) - #2900

Merged
os-zhuang merged 2 commits into
mainfrom
claude/custom-system-page-i18n-6ni3f8
Jul 28, 2026
Merged

fix(i18n): unconditional Chinese in the chatbot confirm card and field inspector (#2884, #2885)#2900
os-zhuang merged 2 commits into
mainfrom
claude/custom-system-page-i18n-6ni3f8

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Closes #2884, closes #2885.

Both were split out of the #2871 survey because neither is a language branch — there is no startsWith('zh') to fix. The copy is Chinese for every user, in every locale.

#2884 — the confirm-before-change card

Heading, both buttons, the reply hint, and the verb column of every change row were Chinese literals, so an English user read the entire confirm gate in Chinese. They now follow the same prop-with-English-default convention the plan card already uses (changesTitleLabel, changesConfirmLabel, changesConfirmHintLabel, changeVerbLabels, …), with AiChatPage passing translated values from console.ai.*.

The outbound message needed a different fix than the issue proposed

Clicking Confirm called:

onSendMessage('确认修改,应用你刚才提议的改动。')

unconditionally — so an English user's click told the agent, in Chinese, to apply the changes, and the agent answered in Chinese for the rest of the thread.

I filed that issue suggesting the outbound text be forced to fixed English. Reading the surrounding code, that would have been wrong for this codebase. AiChatPage already has a deliberate rule, stated in its own comment:

Override the sent strings to Chinese when the conversation is Chinese; button LABELS stay on the UI locale.

That is what conversationLanguage.ts exists for, and it is one of the four zh branches #2871 deliberately keeps. Forcing English would have broken the Chinese case symmetrically. So changesConfirmMessage now routes through the same convZh switch as planApproveMessage — the message matches the language actually being spoken.

Why the Chinese string is unchanged

zh.ts documents a constraint worth repeating: these outbound strings must match the cloud confirm gate's APPROVAL_RE (service-ai-studio/confirm-gate.ts), which anchors Chinese approval on 确认, or the agent silently re-proposes and the button looks inert. The Chinese message here is byte-for-byte the string that was already being sent, so that path is exactly what the gate already accepted. i18n.test.ts now asserts it against the mirrored gate regex alongside the two plan messages.

Note

One residual unknown, for whoever has cloud access. The English clause of APPROVAL_RE is not mirrored in this repo, so I could not verify that it accepts Confirm the changes — apply what you just proposed. This path was never exercised before — the button always sent Chinese — so it is new ground. The existing English planApproveMessage is sent for English conversations today and presumably matches, which is good evidence an English clause exists, but it is evidence and not proof. If it turns out not to match, the fix is a wording change in en.ts, not a redesign. The test pins only the two tokens the message is built around (^Confirm, apply) so a careless reword can't silently drop the approval verb.

Also in this component

  • The error banner's Response failed / Details / Hide / Retry were hard-coded English → new chatbotError namespace.
  • That banner and the quota banner used a bare t(key), which returns the raw key when the chat is mounted without an I18nProvider — the user would read chatbotError.title. Both now use useSafeTranslate, which degrades to English. This is the surface that renders when things are already broken, so a raw key there is the worst possible time for one. Two existing tests were failing on exactly this and are now green without being edited.
  • 「{targetApp}」"{targetApp}", two sites. CJK corner brackets rendered for all locales.
  • formatChangeRow's (type) full-width parens → (type).

Deliberately untouched: DEFERRED_MARKERS ('待补', '后续', …) at :1001-1011. Those are Chinese-keyword heuristics for parsing the agent's replies, not UI copy — the issue says don't, and existing tests cover them.

#2885 — the draft-object suffix

byName.set(name, { value: name, label: `${name} (草稿)` });

The only Chinese literal in a 1500-line file whose other 101 strings all go through t(key, locale). Now reads engine.inspector.draftSuffix from the Studio catalog, with locale threaded into the hook.

Verification

  • Full suite: 7762 passed, 24 skipped, 0 failed.
  • ESLint: 25 errors — identical to baseline, all pre-existing.
  • turbo type-check: 31/31.
  • Both new guards were mutation-tested — I reverted each fix and confirmed the guard fails with the original symptom (expected 'crm_quote (草稿)' to be 'crm_quote (draft)'), then restored.
  • The confirm-before-change card had no test coverage at all before this; it now has five.

Locale packs

The 18 new keys went into all ten packs, so the #2872 part (a) gap held at 469/471 instead of widening. The eight non-Chinese packs have no console.ai namespace at all today, so this creates it holding just these keys — a later bulk backfill merges into the same object.

en ↔ zh remain at exact parity: 2499 : 2499, zero difference in both directions.


Generated by Claude Code

…d inspector (#2884, #2885)

Two issues split out of the #2871 survey because neither is a language
*branch* — both render Chinese for every user regardless of locale.

#2884 — the confirm-before-change card. Heading, buttons, hint and the verb
column of each change row were Chinese literals. They now follow the same
prop-with-English-default convention the plan card already uses, with the
console passing translated values from `console.ai.*`.

The serious half was the outbound message: clicking Confirm sent
'确认修改,应用你刚才提议的改动。' unconditionally, so an English user's click
told the agent in Chinese to apply the changes and the agent answered in
Chinese for the rest of the thread. It now routes through the same `convZh`
conversation-language switch as `planApproveMessage`. The Chinese string is
unchanged, so the cloud confirm gate sees byte-for-byte what it already
accepted; `i18n.test.ts` pins it against the mirrored `APPROVAL_RE`.

Also here: the error banner's `Response failed` / `Details` / `Retry` were
hard-coded English, and it and the quota banner used a bare `t(key)` that
renders the raw key with no `I18nProvider` mounted — both now use
`useSafeTranslate`. The `「…」` corner brackets are now neutral quotes.

#2885 — `ObjectFieldInspector` appended a bare `(草稿)` to draft objects in
the lookup picker, the only Chinese literal in a file whose other 101 strings
all go through `t(key, locale)`. It now reads `engine.inspector.draftSuffix`.

The 18 new keys went into all ten locale packs, so the #2872 part (a) gap
held at 469/471 rather than widening. Both new guards were mutation-tested.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TubWYdWquVkS9dj733sDmC
@vercel

vercel Bot commented Jul 28, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectui Ignored Ignored Jul 28, 2026 4:57am

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

❌ Console Performance Budget

Metric Value Budget
Main entry (gzip) ** KB** KB
Entry file ``
Status FAIL

📦 Bundle Size Report

Package Size Gzipped
app-shell (index.js) 8.20KB 2.97KB
app-shell (runtime-config.js) 7.42KB 2.32KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 7.57KB 2.97KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 1.17KB 0.53KB
auth (AuthProvider.js) 22.10KB 4.37KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.12KB 3.41KB
auth (LoginForm.js) 17.86KB 5.29KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.43KB 2.09KB
auth (SocialSignInButtons.js) 9.60KB 3.89KB
auth (UserMenu.js) 3.40KB 1.22KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 35.76KB 9.11KB
auth (createAuthenticatedFetch.js) 4.37KB 1.69KB
auth (index.js) 2.17KB 0.96KB
auth (org-roles.js) 5.50KB 2.36KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 4.91KB 0.87KB
auth (useIsWorkspaceAdmin.js) 1.61KB 0.85KB
collaboration (CommentThread.js) 18.38KB 4.49KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 3.65KB 1.42KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.25KB 0.53KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 449.62KB 97.73KB
core (index.js) 2.12KB 0.77KB
create-plugin (index.js) 9.28KB 2.98KB
data-objectstack (index.js) 127.78KB 32.15KB
fields (index.js) 218.37KB 53.54KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (currency.js) 1.22KB 0.64KB
i18n (i18n.js) 4.32KB 1.77KB
i18n (index.js) 2.46KB 0.96KB
i18n (pickLocalized.js) 1.70KB 0.83KB
i18n (provider.js) 5.37KB 1.72KB
i18n (useObjectLabel.js) 25.17KB 5.80KB
i18n (useSafeTranslation.js) 3.26KB 1.44KB
layout (index.js) 38.45KB 10.67KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.74KB
mobile (index.js) 1.50KB 0.62KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 4.42KB 1.27KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.71KB 0.42KB
mobile (useResponsiveConfig.js) 1.36KB 0.63KB
mobile (useSpecGesture.js) 1.77KB 0.77KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 6.84KB 2.42KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 3.67KB 1.12KB
permissions (evaluator.js) 4.00KB 1.23KB
permissions (index.js) 0.91KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.52KB
permissions (usePermissions.js) 1.55KB 0.71KB
plugin-ai (index.js) 15.71KB 3.79KB
plugin-calendar (index.js) 44.90KB 12.35KB
plugin-charts (index.js) 57.26KB 16.24KB
plugin-chatbot (index.js) 179.94KB 42.67KB
plugin-dashboard (index.js) 109.60KB 28.33KB
plugin-designer (index.js) 210.56KB 42.56KB
plugin-detail (index.js) 214.86KB 52.39KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 103.47KB 25.10KB
plugin-gantt (index.js) 162.26KB 39.53KB
plugin-grid (index.js) 179.25KB 46.97KB
plugin-kanban (index.js) 47.82KB 13.18KB
plugin-list (index.js) 98.64KB 23.33KB
plugin-map (index.js) 16.80KB 5.24KB
plugin-markdown (index.js) 13.65KB 4.67KB
plugin-report (index.js) 37.07KB 9.81KB
plugin-timeline (index.js) 25.03KB 7.11KB
plugin-tree (index.js) 8.36KB 2.81KB
plugin-view (index.js) 85.68KB 20.85KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.55KB 0.67KB
providers (UploadProvider.js) 11.71KB 3.53KB
providers (index.js) 0.44KB 0.22KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 3.19KB 1.38KB
react (LazyPluginLoader.js) 3.77KB 1.33KB
react (SchemaRenderer.js) 18.70KB 6.09KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 1.00KB 0.55KB
sdui-parser (codegen.js) 4.09KB 1.74KB
sdui-parser (index.js) 2.16KB 0.94KB
sdui-parser (parse.js) 10.04KB 2.82KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 4.69KB 1.48KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 0.99KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 0.20KB 0.18KB
types (crud.js) 0.20KB 0.18KB
types (data-display.js) 0.20KB 0.18KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 0.77KB 0.41KB
types (disclosure.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (index.js) 1.86KB 0.91KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 0.20KB 0.18KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (spec-report.js) 5.04KB 1.93KB
types (system-fields.js) 2.39KB 1.17KB
types (theme.js) 0.20KB 0.18KB
types (ui-action.js) 0.75KB 0.46KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

The `*/` was lost in an editing pass, so `changesTitleLabel?: string;` was
swallowed into the comment and never declared on `ChatbotEnhancedProps`.
`tsc --noEmit` in app-shell caught it as "Property 'changesTitleLabel' does
not exist", which failed `@object-ui/app-shell#build` and left the Console
Performance Budget job reporting blank metrics.

The documented default was wrong too — the heading defaults to "Confirm
changes"; only the button was shortened to "Confirm".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TubWYdWquVkS9dj733sDmC
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Main entry (gzip) 27.9 KB 350 KB
Entry file index-DdhuJOUo.js
Status PASS

📦 Bundle Size Report

Package Size Gzipped
app-shell (index.js) 8.20KB 2.97KB
app-shell (runtime-config.js) 7.42KB 2.32KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 7.57KB 2.97KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 1.17KB 0.53KB
auth (AuthProvider.js) 22.10KB 4.37KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.12KB 3.41KB
auth (LoginForm.js) 17.86KB 5.29KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.43KB 2.09KB
auth (SocialSignInButtons.js) 9.60KB 3.89KB
auth (UserMenu.js) 3.40KB 1.22KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 35.76KB 9.11KB
auth (createAuthenticatedFetch.js) 4.37KB 1.69KB
auth (index.js) 2.17KB 0.96KB
auth (org-roles.js) 5.50KB 2.36KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 4.91KB 0.87KB
auth (useIsWorkspaceAdmin.js) 1.61KB 0.85KB
collaboration (CommentThread.js) 18.38KB 4.49KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 3.65KB 1.42KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.25KB 0.53KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 449.62KB 97.73KB
core (index.js) 2.12KB 0.77KB
create-plugin (index.js) 9.28KB 2.98KB
data-objectstack (index.js) 127.78KB 32.15KB
fields (index.js) 218.37KB 53.54KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (currency.js) 1.22KB 0.64KB
i18n (i18n.js) 4.32KB 1.77KB
i18n (index.js) 2.46KB 0.96KB
i18n (pickLocalized.js) 1.70KB 0.83KB
i18n (provider.js) 5.37KB 1.72KB
i18n (useObjectLabel.js) 25.17KB 5.80KB
i18n (useSafeTranslation.js) 3.26KB 1.44KB
layout (index.js) 38.45KB 10.67KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.74KB
mobile (index.js) 1.50KB 0.62KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 4.42KB 1.27KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.71KB 0.42KB
mobile (useResponsiveConfig.js) 1.36KB 0.63KB
mobile (useSpecGesture.js) 1.77KB 0.77KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 6.84KB 2.42KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 3.67KB 1.12KB
permissions (evaluator.js) 4.00KB 1.23KB
permissions (index.js) 0.91KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.52KB
permissions (usePermissions.js) 1.55KB 0.71KB
plugin-ai (index.js) 15.71KB 3.79KB
plugin-calendar (index.js) 44.90KB 12.35KB
plugin-charts (index.js) 57.26KB 16.24KB
plugin-chatbot (index.js) 179.94KB 42.67KB
plugin-dashboard (index.js) 109.60KB 28.33KB
plugin-designer (index.js) 210.56KB 42.56KB
plugin-detail (index.js) 214.86KB 52.39KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 103.47KB 25.10KB
plugin-gantt (index.js) 162.26KB 39.53KB
plugin-grid (index.js) 179.25KB 46.97KB
plugin-kanban (index.js) 47.82KB 13.18KB
plugin-list (index.js) 98.64KB 23.33KB
plugin-map (index.js) 16.80KB 5.24KB
plugin-markdown (index.js) 13.65KB 4.67KB
plugin-report (index.js) 37.07KB 9.81KB
plugin-timeline (index.js) 25.03KB 7.11KB
plugin-tree (index.js) 8.36KB 2.81KB
plugin-view (index.js) 85.68KB 20.85KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.55KB 0.67KB
providers (UploadProvider.js) 11.71KB 3.53KB
providers (index.js) 0.44KB 0.22KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 3.19KB 1.38KB
react (LazyPluginLoader.js) 3.77KB 1.33KB
react (SchemaRenderer.js) 18.70KB 6.09KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 1.00KB 0.55KB
sdui-parser (codegen.js) 4.09KB 1.74KB
sdui-parser (index.js) 2.16KB 0.94KB
sdui-parser (parse.js) 10.04KB 2.82KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 4.69KB 1.48KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 0.99KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 0.20KB 0.18KB
types (crud.js) 0.20KB 0.18KB
types (data-display.js) 0.20KB 0.18KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 0.77KB 0.41KB
types (disclosure.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (index.js) 1.86KB 0.91KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 0.20KB 0.18KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (spec-report.js) 5.04KB 1.93KB
types (system-fields.js) 2.39KB 1.17KB
types (theme.js) 0.20KB 0.18KB
types (ui-action.js) 0.75KB 0.46KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-zhuang
os-zhuang marked this pull request as ready for review July 28, 2026 05:01
@os-zhuang
os-zhuang merged commit cfc675e into main Jul 28, 2026
14 checks passed
@os-zhuang
os-zhuang deleted the claude/custom-system-page-i18n-6ni3f8 branch July 28, 2026 05:01
os-zhuang added a commit that referenced this pull request Jul 28, 2026
…ght trailing locales (#2872 part a) (#2903)

Backfills `console`, `home`, `topbar` and `layout` — 193 keys x 8 packs,
1,544 strings — so a ja/ko/de/fr/es/pt/ru/ar admin sees the AI console, the
home screen, the top bar and the system navigation in their own language
instead of silently falling back to English. The gap in those eight packs
drops from 469-471 keys to 277-279; en and zh stay at exact parity.

This is the "high-frequency namespaces only" strategy, not a full backfill:
`grid` (101), `gantt` (58), `dashboard` (25) and the long tail stay on
English fallback and remain tracked under #2872.

Four keys are deliberately NOT translated, and that is the load-bearing part
of this change:

  console.ai.planApproveMessage
  console.ai.planApproveDefaultsMessage
  console.ai.planAnswerMessage
  console.ai.changesConfirmMessage

These are not labels — they are text a button TRANSMITS to the agent, and the
cloud confirm gate (service-ai-studio confirm-gate.ts APPROVAL_RE) decides
whether it reads as approval. It recognises Chinese and English, nothing else.
AiChatPage picks them by the language of the CONVERSATION, not the UI, so the
t() call is expected to miss in every non-Chinese pack and fall through to its
English defaultValue. Translating them would make a German user's "Build it"
send German, the gate stop matching, and the agent re-propose instead of
building — the button looks inert while nothing visibly errors.

#2900 shipped exactly that bug for changesConfirmMessage, which had been added
to all ten packs; this removes it from the eight and restores the English
fallback. A new guard pins the invariant in both directions and was
mutation-tested against re-adding an outbound key and against dropping an
ordinary label.

Translations are model-generated and would benefit from native review; every
string's placeholder set was verified programmatically against the English
source.


Claude-Session: https://claude.ai/code/session_01TubWYdWquVkS9dj733sDmC

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants