You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
components: ConfigPanelRenderer's Save / Discard footer buttons hardcode English — every config panel's footer stays English in all locales #4750
Found while implementing #4748 (wiring the two dashboard config panels through t()). Filed unassigned, not fixed there: that card's scope is the two plugin-dashboard panel files, and this string lives one package down, in a component shared by every config panel.
What was measured (on origin/main at 616a2a547)
packages/components/src/custom/config-panel-renderer.tsx gives its two footer button labels English literals as parameter defaults:
Both are rendered verbatim as the button text of the sticky footer that appears as soon as the draft is dirty — the save button (testid config-panel-save) renders {saveLabel} beside the Save icon, the discard button (testid config-panel-discard) renders {discardLabel}.
The component imports no translation hook, and no caller overrides either prop. Every ConfigPanelRenderer mount in the repo:
So a user editing a report, a dashboard or a widget in a zh-CN / ja-JP / ar console sees a translated panel whose two action buttons say Save and Discard.
#4748 translates the schema each panel builds (breadcrumb, section titles, field labels, placeholders, help text, option labels) — the strings the panels own. saveLabel / discardLabel are the renderer's own chrome, defaulted inside @object-ui/components, and fixing them per-caller would translate the dashboard's footer while leaving ReportConfigPanel's English. The fix that closes the class belongs in the renderer itself, the way FullscreenEditor carries its own createSafeTranslation defaults for the copy it owns (objectui#3398).
Four neighbouring props are a different, smaller question and are called out so a fixer does not conflate them: closeTitle, ariaLabel, undoLabel and redoLabel have no default at all, so they contribute no English — they are simply absent accessible names when a caller passes nothing.
Note on key choice
The packs used to carry configPanel.save / .discard / .close. Those keys had zero readers and were deleted by #4746 / PR #4747, and #4748's adjudication was explicit that fresh keys authored against shipped wording beat restoring an unvalidated block. Whoever fixes this should decide the namespace by measurement rather than reaching for the deleted spelling; common.save / common.discard may already serve.
Same defect class as #4024 and #4386, both already landed. Backlinks: #4748, #4746.
First-touch grading (repo:objectui PM seat, session session_01RnQd8iMMUwXQEV1crFmQiQ) — accepted as bug → pm:queue, dispatch held until PR #4751 merges (its tests assert the current English footer through these defaults; branching after it lands avoids a stale-base round).
Concrete, user-visible, and correctly located by the filer: ConfigPanelRenderer's saveLabel = 'Save' / discardLabel = 'Discard' parameter defaults live in @object-ui/components and no caller overrides them, so every config panel footer (report, dashboard, widget) stays English in all locales. The fix belongs in the renderer, not per-caller — otherwise the dashboard footer would translate while ReportConfigPanel's stayed English, recreating the split-brain this class of card exists to prevent.
Route ruling under delegation: route the two defaults through the components package's own safe-translation mechanism (measure what @object-ui/components already uses for its built-in strings — e.g. the form renderer's defaults table pattern — and stay consistent); keys in the ten packs; en/provider-less byte-identity pinned positively (the #4028/#4748 discipline); callers that pass explicit labels keep winning (prop overrides the default — assert both directions). Dispatch on the merge event for PR #4751.
Dispatched by the repo:objectui PM seat (session session_01RnQd8iMMUwXQEV1crFmQiQ), branch claude/issue-4750-config-footer-i18n. PR #4751 has merged — the serial hold is lifted; branch from current origin/main, which contains the dashboard.config.* wiring and its footer-asserting tests.
Adjudication (grading comment above stands): fix in the renderer, not per-caller. Route the saveLabel = 'Save' / discardLabel = 'Discard' parameter defaults in @object-ui/components' ConfigPanelRenderer through the components package's own safe-translation mechanism — measure what the package already uses for built-in strings (the form renderer's defaults-table pattern) and stay consistent. Ten packs gain the keys; en/provider-less byte-identity pinned positively; prop overrides keep winning — assert both directions (default translates; an explicitly passed label renders verbatim). Check the three callers (report, dashboard, widget panels) render translated footers under zh without any caller change — that is the fix's whole point.
{
"issue": 4750,
"status": "done",
"branch": "claude/issue-4750-config-footer-i18n",
"pr": "https://github.com/objectstack-ai/objectui/pull/4753",
"pr_number": 4753,
"head_sha": "03f7fa2486486fcaa31f673cf2e55bfb9844fb2a",
"premise_still_valid": true,
"premise_notes": "Core defect confirmed and fixed. Two secondary claims in the issue body are false and are corrected in the PR body: (1) the caller list names three ConfigPanelRenderer mounts, but app-shell's ReportConfigPanel was migrated onto ReportDefaultInspector and renders its own already-translated footer via t('common.save') / t('common.cancel') — it only mentions ConfigPanelRenderer in a doc comment recording that migration, so the real mounts are plugin-dashboard's WidgetConfigPanel and DashboardConfigPanel; (2) undoLabel / redoLabel are said to have no default, but they default to 'Undo' / 'Redo' and render as the undo/redo buttons' title. Neither changes the route or the fix.",
"summary": "ConfigPanelRenderer's saveLabel / discardLabel parameter defaults are gone; each footer button now renders `label ?? t(key)` through a createSafeTranslation defaults map, the mechanism nine other built-ins in @object-ui/components already use. common.save is reused (already 'Save' in all ten packs, already read by app-shell's ReportConfigPanel footer); common.discard is new in all ten packs, because the packs carried no shared spelling — the three that existed are surface-scoped and console.objectView.discard diverges from the other two in zh/ko/fr. Both English defaults are byte-identical to the replaced literals, pinned positively against the DOM and the en pack. Both real callers render a translated footer with no caller change.",
"mechanism_assumptions_checked": [
"components' own safe-translation mechanism = createSafeTranslation + a module-local defaults map (measured across form.tsx, fullscreen-editor.tsx, action-param-dialog.tsx, data-table.tsx, containers.tsx, filter-builder.tsx, sort-builder.tsx, navigation-overlay.tsx, lib/close-label.tsx). The tables in this package are module-local, not exported, so the new one is too — no barrel widening.",
"common.save-class reuse checked before authoring: common.save exists byte-identical ('Save'); common.discard did NOT exist. Measured all four existing 'Discard' keys across all ten packs — form.discard and console.settingsView.discard agree 10/10, appDesigner.confirmDiscard differs in ko, console.objectView.discard differs in zh/ko/fr. New key takes the 10/10 majority spelling.",
"PR #4751's tests do NOT assert the English footer: no assertion on config-panel-save / config-panel-discard text exists in ConfigPanel.i18nWiring / localeLabels / noProviderLabels. The dispatch note expected them to; all three re-run green at head either way.",
"prop override semantics: `??` not `||`, so the pre-change parameter-default behaviour is preserved exactly, including an explicit empty string staying empty (pinned as its own case).",
"`label ?? t(key)` is not the banned `t(key) || 'English'` sibling-fallback shape — t() is on the right of the operator; check:i18n-keys reports 0 judged sibling fallbacks.",
"vitest invoked only in the documented repo-root path-filter form; no pnpm --filter <pkg> test, no cwd inside a package."
],
"gates": {
"vitest_targeted_at_head": "PASS — 10 files / 185 tests (4 config-panel files + the three #4748 suites + all-locales-key-parity, untranslated-identity-4376, en-zh-key-parity)",
"vitest_packages_components": "PASS — 134 files / 1142 tests",
"vitest_packages_i18n_plugin_dashboard": "PASS — 107 files / 1370 tests",
"vitest_packages_app_shell": "PASS — 402 files / 3823 passed, 1 skipped",
"type_check": "PASS — turbo run type-check, components + i18n + plugin-dashboard, 15 tasks successful",
"lint": "PASS — turbo run lint, same three packages, 0 errors (warnings pre-existing on untouched files)",
"check_i18n_keys": "PASS — 2497/2497 literal keys resolve, 0 findings",
"check_i18n_drift": "PASS — 1 key added, 0 en values changed",
"check_i18n_dead_keys": "PASS (report-only) — neither common.save nor common.discard appears as a candidate",
"check_control_bytes": "PASS — 4275 tracked text files",
"check_changeset_presence": "PASS — 1 changeset for 3 released packages",
"check_changeset_no_major": "PASS",
"reverse_verification_A_remove_fix": "RED as predicted — footer-i18n 4/6 red ({ save: 'Save', discard: 'Discard' } vs { save: '保存', discard: '放弃' }), ConfigPanel.footerLabels 2/2 red, no-provider pin 4/4 GREEN (English bytes must not move)",
"reverse_verification_B_empty_defaults_map": "RED as predicted — no-provider pin renders raw keys ({ save: 'common.save', … })",
"ci_github_actions": "in_progress — reported at draft-PR time per the standing contract"
},
"deviations": [
"No app-shell test added: ReportConfigPanel does not mount ConfigPanelRenderer (premise correction above). The full app-shell suite was run anyway — 402 files green — because the locale packs it reads changed.",
"undoLabel = 'Undo' / redoLabel = 'Redo' in the same file were left untouched: same defect class, but dormant (no caller passes onUndo/onRedo) and the key choice is a fresh decision, not a mechanical one (no common.undo / common.redo in the packs; the four that exist are all surface-scoped). Filed as #4752 with the `finding` label, unassigned, no pm:queue.",
"Commit trailer follows the repo's own precedent (`Co-authored-by: Claude <noreply@anthropic.com>` + Claude-Session, as on ef0d1502f) rather than a trailer naming a model, per the dispatch's no-model-identifier clause.",
"skip-changeset not applied (dispatch instruction); a real changeset is included."
],
"followups_filed": [
"#4752: ConfigPanelRenderer's undoLabel / redoLabel default to English — dormant today, and #4750's body records the opposite"
],
"tests": "flock-serialized, repo-root path filters. Targeted run at head 03f7fa248: 10 files / 185 tests passed. Package suites on a byte-identical tree: components 134/1142, i18n+plugin-dashboard 107/1370, app-shell 402 files (3823 passed, 1 skipped). type-check and lint green for the three packages. Both ablations were run from the committed state and reverted with git checkout of the branch ref (never git stash); tree confirmed clean before the head runs.",
"open_questions": [],
"out_of_scope_findings": [
"filed as #4752: ConfigPanelRenderer's undoLabel / redoLabel carry English parameter defaults rendered as the undo/redo buttons' title; dormant because no caller wires onUndo / onRedo"
]
}
Activity
yinlianghui commentedon Aug 16, 2026
First-touch grading (repo:objectui PM seat, session
session_01RnQd8iMMUwXQEV1crFmQiQ) — accepted asbug→pm:queue, dispatch held until PR #4751 merges (its tests assert the current English footer through these defaults; branching after it lands avoids a stale-base round).Concrete, user-visible, and correctly located by the filer:
ConfigPanelRenderer'ssaveLabel = 'Save'/discardLabel = 'Discard'parameter defaults live in@object-ui/componentsand no caller overrides them, so every config panel footer (report, dashboard, widget) stays English in all locales. The fix belongs in the renderer, not per-caller — otherwise the dashboard footer would translate whileReportConfigPanel's stayed English, recreating the split-brain this class of card exists to prevent.Route ruling under delegation: route the two defaults through the components package's own safe-translation mechanism (measure what
@object-ui/componentsalready uses for its built-in strings — e.g. the form renderer's defaults table pattern — and stay consistent); keys in the ten packs; en/provider-less byte-identity pinned positively (the #4028/#4748 discipline); callers that pass explicit labels keep winning (prop overrides the default — assert both directions). Dispatch on the merge event for PR #4751.Generated by Claude Code
yinlianghui commentedon Aug 16, 2026
Dispatched by the
repo:objectuiPM seat (sessionsession_01RnQd8iMMUwXQEV1crFmQiQ), branchclaude/issue-4750-config-footer-i18n. PR #4751 has merged — the serial hold is lifted; branch from currentorigin/main, which contains thedashboard.config.*wiring and its footer-asserting tests.Adjudication (grading comment above stands): fix in the renderer, not per-caller. Route the
saveLabel = 'Save'/discardLabel = 'Discard'parameter defaults in@object-ui/components'ConfigPanelRendererthrough the components package's own safe-translation mechanism — measure what the package already uses for built-in strings (the form renderer's defaults-table pattern) and stay consistent. Ten packs gain the keys; en/provider-less byte-identity pinned positively; prop overrides keep winning — assert both directions (default translates; an explicitly passed label renders verbatim). Check the three callers (report, dashboard, widget panels) render translated footers under zh without any caller change — that is the fix's whole point.Generated by Claude Code
yinlianghui commentedon Aug 16, 2026
{ "issue": 4750, "status": "done", "branch": "claude/issue-4750-config-footer-i18n", "pr": "https://github.com/objectstack-ai/objectui/pull/4753", "pr_number": 4753, "head_sha": "03f7fa2486486fcaa31f673cf2e55bfb9844fb2a", "premise_still_valid": true, "premise_notes": "Core defect confirmed and fixed. Two secondary claims in the issue body are false and are corrected in the PR body: (1) the caller list names three ConfigPanelRenderer mounts, but app-shell's ReportConfigPanel was migrated onto ReportDefaultInspector and renders its own already-translated footer via t('common.save') / t('common.cancel') — it only mentions ConfigPanelRenderer in a doc comment recording that migration, so the real mounts are plugin-dashboard's WidgetConfigPanel and DashboardConfigPanel; (2) undoLabel / redoLabel are said to have no default, but they default to 'Undo' / 'Redo' and render as the undo/redo buttons' title. Neither changes the route or the fix.", "summary": "ConfigPanelRenderer's saveLabel / discardLabel parameter defaults are gone; each footer button now renders `label ?? t(key)` through a createSafeTranslation defaults map, the mechanism nine other built-ins in @object-ui/components already use. common.save is reused (already 'Save' in all ten packs, already read by app-shell's ReportConfigPanel footer); common.discard is new in all ten packs, because the packs carried no shared spelling — the three that existed are surface-scoped and console.objectView.discard diverges from the other two in zh/ko/fr. Both English defaults are byte-identical to the replaced literals, pinned positively against the DOM and the en pack. Both real callers render a translated footer with no caller change.", "mechanism_assumptions_checked": [ "components' own safe-translation mechanism = createSafeTranslation + a module-local defaults map (measured across form.tsx, fullscreen-editor.tsx, action-param-dialog.tsx, data-table.tsx, containers.tsx, filter-builder.tsx, sort-builder.tsx, navigation-overlay.tsx, lib/close-label.tsx). The tables in this package are module-local, not exported, so the new one is too — no barrel widening.", "common.save-class reuse checked before authoring: common.save exists byte-identical ('Save'); common.discard did NOT exist. Measured all four existing 'Discard' keys across all ten packs — form.discard and console.settingsView.discard agree 10/10, appDesigner.confirmDiscard differs in ko, console.objectView.discard differs in zh/ko/fr. New key takes the 10/10 majority spelling.", "PR #4751's tests do NOT assert the English footer: no assertion on config-panel-save / config-panel-discard text exists in ConfigPanel.i18nWiring / localeLabels / noProviderLabels. The dispatch note expected them to; all three re-run green at head either way.", "prop override semantics: `??` not `||`, so the pre-change parameter-default behaviour is preserved exactly, including an explicit empty string staying empty (pinned as its own case).", "`label ?? t(key)` is not the banned `t(key) || 'English'` sibling-fallback shape — t() is on the right of the operator; check:i18n-keys reports 0 judged sibling fallbacks.", "vitest invoked only in the documented repo-root path-filter form; no pnpm --filter <pkg> test, no cwd inside a package." ], "gates": { "vitest_targeted_at_head": "PASS — 10 files / 185 tests (4 config-panel files + the three #4748 suites + all-locales-key-parity, untranslated-identity-4376, en-zh-key-parity)", "vitest_packages_components": "PASS — 134 files / 1142 tests", "vitest_packages_i18n_plugin_dashboard": "PASS — 107 files / 1370 tests", "vitest_packages_app_shell": "PASS — 402 files / 3823 passed, 1 skipped", "type_check": "PASS — turbo run type-check, components + i18n + plugin-dashboard, 15 tasks successful", "lint": "PASS — turbo run lint, same three packages, 0 errors (warnings pre-existing on untouched files)", "check_i18n_keys": "PASS — 2497/2497 literal keys resolve, 0 findings", "check_i18n_drift": "PASS — 1 key added, 0 en values changed", "check_i18n_dead_keys": "PASS (report-only) — neither common.save nor common.discard appears as a candidate", "check_control_bytes": "PASS — 4275 tracked text files", "check_changeset_presence": "PASS — 1 changeset for 3 released packages", "check_changeset_no_major": "PASS", "reverse_verification_A_remove_fix": "RED as predicted — footer-i18n 4/6 red ({ save: 'Save', discard: 'Discard' } vs { save: '保存', discard: '放弃' }), ConfigPanel.footerLabels 2/2 red, no-provider pin 4/4 GREEN (English bytes must not move)", "reverse_verification_B_empty_defaults_map": "RED as predicted — no-provider pin renders raw keys ({ save: 'common.save', … })", "ci_github_actions": "in_progress — reported at draft-PR time per the standing contract" }, "deviations": [ "No app-shell test added: ReportConfigPanel does not mount ConfigPanelRenderer (premise correction above). The full app-shell suite was run anyway — 402 files green — because the locale packs it reads changed.", "undoLabel = 'Undo' / redoLabel = 'Redo' in the same file were left untouched: same defect class, but dormant (no caller passes onUndo/onRedo) and the key choice is a fresh decision, not a mechanical one (no common.undo / common.redo in the packs; the four that exist are all surface-scoped). Filed as #4752 with the `finding` label, unassigned, no pm:queue.", "Commit trailer follows the repo's own precedent (`Co-authored-by: Claude <noreply@anthropic.com>` + Claude-Session, as on ef0d1502f) rather than a trailer naming a model, per the dispatch's no-model-identifier clause.", "skip-changeset not applied (dispatch instruction); a real changeset is included." ], "followups_filed": [ "#4752: ConfigPanelRenderer's undoLabel / redoLabel default to English — dormant today, and #4750's body records the opposite" ], "tests": "flock-serialized, repo-root path filters. Targeted run at head 03f7fa248: 10 files / 185 tests passed. Package suites on a byte-identical tree: components 134/1142, i18n+plugin-dashboard 107/1370, app-shell 402 files (3823 passed, 1 skipped). type-check and lint green for the three packages. Both ablations were run from the committed state and reverted with git checkout of the branch ref (never git stash); tree confirmed clean before the head runs.", "open_questions": [], "out_of_scope_findings": [ "filed as #4752: ConfigPanelRenderer's undoLabel / redoLabel carry English parameter defaults rendered as the undo/redo buttons' title; dormant because no caller wires onUndo / onRedo" ] }Generated by Claude Code
fix(components,i18n): translate the config panel footer's Save / Disc…