Uh oh!
There was an error while loading. Please reload this page.
fix(i18n): resolve an action's confirmText/successMessage from the same bundle entry as its label (#4265) - #4313
Merged
Conversation
…me bundle entry as its label (#4265) A TranslationBundle entry for an action carries three keys under one `_actions.<name>` node — label / confirmText / successMessage — and `useObjectLabel()` has always exposed a resolver for each (one `resolve()` and one key convention behind all three). What drifted was the CALL SITES: `page:header` (authored record pages), `record:quick_actions` and the related-list row menu resolved the button `label` only and dispatched the authored `confirmText` / `successMessage` untouched. One bundle entry met two fates — the button rendered the translation, the confirm dialog rendered the authored English literal. Introduce `useActionTextLocalizer()` in `@object-ui/react`: one application of the existing resolvers over the three keys, so a surface can no longer localize one of them and forget the others. Converge every action-rendering surface onto it, including the three that were already correct (RecordDetailView / ObjectView / DeclaredActionsBar), and drop RelatedRecordActionsBridge's `actionLabel` prop — an injection point that could only ever carry the label. Fallback semantics unchanged: no bundle entry, or an entry lacking a key, renders the authored text; a bundle cannot introduce a confirmText or successMessage the metadata never declared. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Qqyix2QcnpUC9XeYVDzx3
…reference-equal (#4265) Every header action now goes through the shared action-text localizer before it is rendered or dispatched, so what reaches the runner is a localized COPY on both branches — which is what the record-context branch already did on purpose (a fresh object keeps the runner's in-place collected-params merge off the authored schema node). The case is about the SHAPE being untouched outside a record context; that is carried by the `params` pin (no `_rowRecord` stash), plus a new assertion that the authored node itself is not mutated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Qqyix2QcnpUC9XeYVDzx3
The latest updates on your projects. Learn more about Vercel for GitHub. |
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
yinlianghui
marked this pull request as ready for review
August 11, 2026 13:51
Uh oh!
There was an error while loading. Please reload this page.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes#4265
The measurement first
useObjectLabel()has always exposed a resolver for all three keys aTranslationBundle action entry carries —
actionLabel/actionConfirm/actionSuccess, oneresolve()and one key convention behind all three(
{ns}.objects.{obj}._actions.{name}.{label|confirmText|successMessage}).The channel was never missing. What had drifted was the call sites:
RecordDetailViewrecord headerObjectViewtoolbar + row kebabDeclaredActionsBarpage:headeractions (authored record pages)record:quick_actionsThe three bottom rows are the card's "same entry, two fates": the button
picked up the bundle, the confirm dialog and the toast got the authored
English.
page:headeris the surface an AUTHORED record page uses — itsschema.actionsreach the renderer straight from page metadata with no hostto pre-localize them — and
record:quick_actionsresolves its defs by nameout of the object's own metadata, which is where the card's
src/actions/lead.actions.tstext comes from.The confirm plumbing named in triage (
useObjectActions.ts:28→views/ActionConfirmDialog.tsx:23) turned out to be a faithful carrier: itpasses through whatever string the runner hands it. The defect was upstream of
it, at the point where the def is built.
The fix
One resolver, not an N+1th dialect. New
useActionTextLocalizer()in@object-ui/reactis a single application of the existinguseObjectLabel()resolvers over the three keys — so a surface can no longerlocalize one and forget the others; there is one function to call and it can
only do all three. Every action-rendering surface now calls it, including the
three that were already correct, and
RelatedRecordActionsBridgeloses itsactionLabelprop — an injection point that could only ever carry the label.Pinned semantics (unchanged fallback contract):
_actionskey of its own, so its literals pass through;confirmText/successMessageare resolved only when the action declaresthem — a bundle must not be able to bolt a confirmation gate or a toast
onto an action whose metadata never asked for one;
labelis reduced throughpickLocalizedfirst, since an authoredlabelmay be an
I18nLabelmap since rc.6.Reverse verification — prediction stated before running
Reverting only the two consumer surfaces (helper and tests kept) was predicted
to turn the four translated-confirm/toast pins RED while both no-bundle
controls stayed GREEN. That is exactly what happened — 4 failed, 3 passed:
The detail that makes this the card's bug rather than a neighbouring one: on
the reverted code the buttons were still found by their Chinese name
(
findByRole({ name: '转化线索' })never failed) while the confirm body cameback English. Label resolved, confirmText did not — one entry, two fates.
Tests
New pins run the real
I18nProvider(real bundle) and the realActionRunner,asserting what the confirm and toast handlers actually receive:
packages/react/src/hooks/__tests__/useActionTextLocalizer.test.tsx— thethree keys of one entry; per-key fallback; no-entry control; "does not invent
a confirmText"; nameless action;
I18nLabelcollapse.packages/components/src/__tests__/page-header-action-i18n.test.tsxpackages/plugin-detail/src/renderers/__tests__/record-quick-actions.actionText-i18n.test.tsxOne existing fixture was re-spelled rather than deleted:
page-header-actions.test.tsx"dispatches unchanged outside a record context"asserted
toBe(action)— reference identity. Every header action now goesthrough the localizer, so both branches dispatch a localized copy, which is
what the record branch already did deliberately (a fresh object keeps the
runner's in-place params merge off the authored node). The case's real content
— no
_rowRecordstash without a record — is unchanged, and it gained anassertion that the authored node is not mutated.
Verification run
pnpm exec vitest run packages/react packages/components packages/plugin-detail packages/app-shell→ 5618 passed, 1 skipped, 575 files (the single failure in the first pass
was the identity fixture above; re-spelled and green).
type-check(bothtsccommands each) green on@object-ui/react,@object-ui/components,@object-ui/plugin-detail,@object-ui/app-shell.pnpm --filter '...@object-ui/react' --filter '...@object-ui/components' type-check, i.e. the PREFIX/consumer direction,33 downstream packages including
apps/consoleand the examples: green aftera full build closure. (The first two passes failed only on unbuilt
dist/for packages this PR does not touch.)
actionLabelprop to the bridge mount goes red withTS2322: … not assignable to type 'IntrinsicAttributes & RelatedRecordActionsBridgeProps', then restored — so the narrowed props typeis genuinely being read, not a cached
.d.ts.check:i18n-keys,check:i18n-drift,check:action-forward-parity,check:control-bytes— all green.linton the four packages: 0 errors (warnings are the repo baseline).Changeset:
patchfor the four packages (nevermajor, per the fixed-grouprule).
Generated by Claude Code