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.
plugin-dashboard: WidgetConfigPanel / DashboardConfigPanel hardcode ~61 English labels and import no translation hook — the dashboard config sidebar stays English in every locale #4748
Found while implementing #4746 (deleting the dead configPanel.* i18n namespace). Filed unassigned, not fixed there: out of that card's scope, which is locale-pack deletion only.
What was measured (on origin/main at 616a2a547)
packages/plugin-dashboard/src/WidgetConfigPanel.tsx and packages/plugin-dashboard/src/DashboardConfigPanel.tsx contain no translation hook at all:
Both are exported from packages/plugin-dashboard/src/index.tsx and mounted by DashboardWithConfig.tsx (lines 224 and 234) as the dashboard editing sidebar — the panel a user opens to configure a dashboard or a widget. Under zh-CN / ja-JP / any non-English locale the surrounding console chrome translates and this sidebar does not.
The locale packs used to carry a configPanel.* namespace whose 16 keys are almost exactly this panel's vocabulary (layout, columns, gap, rowHeight, refreshInterval, appearance, theme, showDescription, general, advanced, …). Nothing ever read those keys — no t() call site, no textual footprint — so they were deleted as dead by #4746. The reading that fits the evidence is that the translations were authored for this panel and the wiring never landed. Deleting the dead keys does not create this gap and does not deepen it: the panel rendered hardcoded English before the deletion and renders the same hardcoded English after. What changed is only that the dormant vocabulary is no longer sitting in the packs pretending the surface is covered.
Whoever fixes this should author fresh keys against the wording the panel actually uses today rather than restoring the deleted block from history — the old vocabulary was never validated against the shipped labels, and WidgetConfigPanel's 37 strings are noticeably more than the 16 keys that existed.
Same defect class as the closed #4024 (list-view chrome hardcoded English) and #1713 (view inspector section labels), neither of which covers these two files.
First-touch grading + dispatched (repo:objectui PM seat, session session_01RnQd8iMMUwXQEV1crFmQiQ), branch claude/issue-4748-dashboard-config-panel-i18n. Accepted as bug under delegated adjudication — same class as today's #4028 (AddressField): a user-visible surface that stays English inside an otherwise-translated console, fixed by wiring through the established i18n system. Maintainer veto window open.
Adjudication.
Fresh keys against today's actual wording, per the card's own guidance — do not restore the deleted configPanel.* block from history (never validated against shipped labels, and 61 > 16).
Follow the house pattern for plugin packages (safe-translation hook with built-in English defaults, the shape plugin-kanban/fields use) — measure which pattern plugin-dashboard already uses elsewhere (e.g. DatasetWidget) and stay consistent; namespace choice likewise by measurement of the existing pack structure.
Ten packs gain the keys; single-brace convention ({{x}} only for holes taking arguments); careful with the breadcrumb: ['Dashboard', 'Configuration'] array shape.
Suggested route. Tests: per-locale label resolution for zh + one Latin locale, provider-less fallback byte-identity; run the full check-i18n family (call-site keys, en-drift will report N additions/0 changes, all-locales parity, dead-keys must NOT list the new namespace — that is the "this time it has readers" proof). Changeset: plugin-dashboard + i18n patch. No skip-changeset — the label exists but no gate consumes it. Fixes #4748 sole closing keyword; #4746/#4730/#4024/#1713 keyword-free.
{
"issue": 4748,
"branch": "claude/issue-4748-dashboard-config-panel-i18n",
"pr_number": 4751,
"head_sha": "0f9d59527fba5defd3d69d8a75c10469db658242",
"status": "done",
"premise_still_valid": true,
"premise_evidence": "Both of the card's greps reproduced at 616a2a547 before any edit: translation-hook grep = 0 and 0; literal-label grep = 37 (WidgetConfigPanel) and 24 (DashboardConfigPanel) = 61. After the change: literal-label grep = 0 and 0, hook grep = 5 and 3. helpText, which the card's grep pattern does not count, is also rendered (by config-field-renderer.tsx, not config-panel-renderer.tsx) and is translated too.",
"mechanism_assumptions_checked": [
"PM said 'PR #4747 just merged pack changes' — FALSE at branch time: #4747 was still OPEN and configPanel.* was present in all ten packs at 616a2a547. It merged mid-task as f34226ea9. Handled by construction, not luck: the new block is inserted inside the `dashboard` namespace immediately before `trend:`, ~30 lines from the top-level `configPanel:` block #4747 deletes. `git merge-tree` against origin/main at 138ab04d5 reports the ten pack files changed on both sides and ZERO conflict markers.",
"Namespace by measurement: MetricWidget reads dashboard.trend.*, DashboardFilterBar reads dashboard.filters.*, both via createSafeTranslation — so dashboard.config.* is the shape those establish, not a preference.",
"House pattern by measurement: hook module mirrors plugin-detail/src/useDetailTranslation.ts and fields/src/widgets/useFieldTranslation.ts (createSafeTranslation + exported defaults map).",
"Hook naming checked against the gate's own regex PACK_HOOK = /^use[A-Za-z0-9_]*(Translation|Translate|T)$/ in check-i18n-call-site-keys.mjs, so useConfigPanelTranslation classifies as pack-backed and forwardedScope covers the `t` passed into the schema builders.",
"Value-to-key lookup tables were the first shape and were REJECTED after measuring: they made 33 option keys invisible to both i18n gates (dynamic-key sites for the call-site gate, NEEDS-REVIEW on the dead-key report). Rewritten as builders emitting literal t('...') calls; both gates now cover all 75.",
"app-shell's defaults-maps-mirror-en-pack.test.tsx was considered as the byte-identity home and REJECTED: app-shell does not depend on @object-ui/plugin-dashboard, so adding the map there would have meant adding a dependency. The equivalent assertion lives plugin-dashboard-local instead (plugin-dashboard -> i18n is the normal direction, no inversion).",
"No-provider test isolated in its own FILE per the ObjectKanban.overlayTitleNoProviderFallback precedent: createI18n registers a module-global react-i18next instance that survives cleanup(), so a provider mount in the same file would have made the fallback assertion vacuous.",
"helpText: an early grep over config-panel-renderer.tsx alone suggested it was declared-but-never-rendered and I nearly filed that as a finding. Re-measured — config-field-renderer.tsx renders it for every control type including custom. No bogus issue filed; DOM coverage added in en and zh instead.",
"skip-changeset deliberately NOT applied, per dispatch. A real changeset (plugin-dashboard + i18n, patch) is present and check-changeset-presence is green. Labels read back after bots settled: [\"plugin\", \"tests\"] (bot-applied, untouched by me)."
],
"gates": {
"vitest_plugin_dashboard_and_i18n": "green — 106 files, 1368 tests passed (repo-root path-filter form, never pnpm --filter <pkg> test)",
"new_tests": "green — 102 tests across ConfigPanel.i18nWiring / ConfigPanel.noProviderLabels / ConfigPanel.localeLabels",
"check_i18n_keys": "green — 2549 pack-backed sites, 2495/2495 literal keys resolve, interpolation parity clean, 0 unreadable option sets",
"check_i18n_drift": "green — 75 added, 0 changed (exactly the predicted shape)",
"check_i18n_dead_keys": "green — 'dashboard.config' occurs 0 times in the report; the namespace has readers",
"all_locales_key_parity_ellipsis_untranslated_identity_de_quote": "green (inside the i18n suite above)",
"type_check": "green — plugin-dashboard + i18n",
"lint": "green — 0 errors both packages (one added react-refresh/only-export-components WARNING from exporting buildWidgetSchema, on a file that already had two)",
"check_changeset_presence_fixed_no_major": "green — plugin-dashboard + i18n patch",
"check_control_bytes": "green — plus a grep -naP self-scan over every changed file, clean",
"re_derived_extras": "check:phantom-deps, lint:coverage, type-check:coverage — all green; these were NOT in the dispatch gate list and were added by re-deriving from the CI workflows against the actual changed paths",
"ci": "in_progress — reported at draft-PR time per the dispatch contract; not waited on"
},
"reverse_verification": {
"ablation_A": "Reverted both panels to origin/main (hardcoded English), kept packs and tests. PREDICTED: localeLabels RED, noProviderLabels GREEN. OBSERVED exactly that — localeLabels 5/5 red with `expected [ 'Dashboard', 'Chart' ] to deeply equal [ '仪表盘', '图表' ]` and the de equivalent; noProviderLabels 9/9 green, which is the positive proof the English bytes did not move.",
"ablation_B": "Deleted one defaults-map row (dashboard.config.field.columns). PREDICTED red on the wiring test's leg 1 + leg 3 and on the no-provider DOM. OBSERVED all of them: notKeys=['dashboard.config.field.columns'], key-set 75 vs 74, row comparison, `Unable to find an element with the text: Columns`, and the raw-key guard catching the key in the DOM.",
"restore": "git checkout against the committed branch, both times; the fix was committed BEFORE either ablation so a real restore point existed. No git stash at any point."
},
"deviations": [
"Exported buildWidgetSchema / buildDashboardSchema from their modules (not the package barrel) so the wiring could be asserted exhaustively — every user-visible string in the built schema is proven to BE a translation key, which is the card's grep as a mechanical assertion. Follows the file's own precedent (sanitizeDraftForType is exported the same way for its test). Cost: one additional react-refresh lint WARNING.",
"Translated the 4 helpText sentences as well. They are outside the card's grep pattern (not label/title/placeholder/breadcrumb) but are genuinely rendered and are the longest copy in the panel.",
"75 keys against the card's 61 literals — not a scope expansion: shared labels collapse (Title/Description/Layout/Appearance are one key each across both panels) while option groups expand, and the breadcrumb's 'Pivot table' is deliberately a separate key from the picker's 'Pivot Table' so neither English string silently changes."
],
"followups_filed": [
"#4750: components — ConfigPanelRenderer's saveLabel='Save' / discardLabel='Discard' parameter defaults are hardcoded English in @object-ui/components and no caller overrides them, so every config panel footer (report, dashboard, widget) stays English in all locales. Searched first; no existing issue. Unlabeled for PM triage (concrete defect, not observation-class)."
]
}
PR: #4751 (draft) · branch claude/issue-4748-dashboard-config-panel-i18n · head 0f9d59527
Activity
yinlianghui commentedon Aug 15, 2026
First-touch grading + dispatched (repo:objectui PM seat, session
session_01RnQd8iMMUwXQEV1crFmQiQ), branchclaude/issue-4748-dashboard-config-panel-i18n. Accepted asbugunder delegated adjudication — same class as today's #4028 (AddressField): a user-visible surface that stays English inside an otherwise-translated console, fixed by wiring through the established i18n system. Maintainer veto window open.Adjudication.
configPanel.*block from history (never validated against shipped labels, and 61 > 16).plugin-kanban/fieldsuse) — measure which patternplugin-dashboardalready uses elsewhere (e.g.DatasetWidget) and stay consistent; namespace choice likewise by measurement of the existing pack structure.AddressFieldsub-labels and placeholders are hardcoded English literals — an address field is untranslatable on any non-English console #4028 discipline).{{x}}only for holes taking arguments); careful with thebreadcrumb: ['Dashboard', 'Configuration']array shape.Suggested route. Tests: per-locale label resolution for zh + one Latin locale, provider-less fallback byte-identity; run the full check-i18n family (call-site keys, en-drift will report N additions/0 changes, all-locales parity, dead-keys must NOT list the new namespace — that is the "this time it has readers" proof). Changeset:
plugin-dashboard+i18npatch. No skip-changeset — the label exists but no gate consumes it.Fixes #4748sole closing keyword; #4746/#4730/#4024/#1713 keyword-free.Generated by Claude Code
yinlianghui commentedon Aug 16, 2026
{ "issue": 4748, "branch": "claude/issue-4748-dashboard-config-panel-i18n", "pr_number": 4751, "head_sha": "0f9d59527fba5defd3d69d8a75c10469db658242", "status": "done", "premise_still_valid": true, "premise_evidence": "Both of the card's greps reproduced at 616a2a547 before any edit: translation-hook grep = 0 and 0; literal-label grep = 37 (WidgetConfigPanel) and 24 (DashboardConfigPanel) = 61. After the change: literal-label grep = 0 and 0, hook grep = 5 and 3. helpText, which the card's grep pattern does not count, is also rendered (by config-field-renderer.tsx, not config-panel-renderer.tsx) and is translated too.", "mechanism_assumptions_checked": [ "PM said 'PR #4747 just merged pack changes' — FALSE at branch time: #4747 was still OPEN and configPanel.* was present in all ten packs at 616a2a547. It merged mid-task as f34226ea9. Handled by construction, not luck: the new block is inserted inside the `dashboard` namespace immediately before `trend:`, ~30 lines from the top-level `configPanel:` block #4747 deletes. `git merge-tree` against origin/main at 138ab04d5 reports the ten pack files changed on both sides and ZERO conflict markers.", "Namespace by measurement: MetricWidget reads dashboard.trend.*, DashboardFilterBar reads dashboard.filters.*, both via createSafeTranslation — so dashboard.config.* is the shape those establish, not a preference.", "House pattern by measurement: hook module mirrors plugin-detail/src/useDetailTranslation.ts and fields/src/widgets/useFieldTranslation.ts (createSafeTranslation + exported defaults map).", "Hook naming checked against the gate's own regex PACK_HOOK = /^use[A-Za-z0-9_]*(Translation|Translate|T)$/ in check-i18n-call-site-keys.mjs, so useConfigPanelTranslation classifies as pack-backed and forwardedScope covers the `t` passed into the schema builders.", "Value-to-key lookup tables were the first shape and were REJECTED after measuring: they made 33 option keys invisible to both i18n gates (dynamic-key sites for the call-site gate, NEEDS-REVIEW on the dead-key report). Rewritten as builders emitting literal t('...') calls; both gates now cover all 75.", "app-shell's defaults-maps-mirror-en-pack.test.tsx was considered as the byte-identity home and REJECTED: app-shell does not depend on @object-ui/plugin-dashboard, so adding the map there would have meant adding a dependency. The equivalent assertion lives plugin-dashboard-local instead (plugin-dashboard -> i18n is the normal direction, no inversion).", "No-provider test isolated in its own FILE per the ObjectKanban.overlayTitleNoProviderFallback precedent: createI18n registers a module-global react-i18next instance that survives cleanup(), so a provider mount in the same file would have made the fallback assertion vacuous.", "helpText: an early grep over config-panel-renderer.tsx alone suggested it was declared-but-never-rendered and I nearly filed that as a finding. Re-measured — config-field-renderer.tsx renders it for every control type including custom. No bogus issue filed; DOM coverage added in en and zh instead.", "skip-changeset deliberately NOT applied, per dispatch. A real changeset (plugin-dashboard + i18n, patch) is present and check-changeset-presence is green. Labels read back after bots settled: [\"plugin\", \"tests\"] (bot-applied, untouched by me)." ], "gates": { "vitest_plugin_dashboard_and_i18n": "green — 106 files, 1368 tests passed (repo-root path-filter form, never pnpm --filter <pkg> test)", "new_tests": "green — 102 tests across ConfigPanel.i18nWiring / ConfigPanel.noProviderLabels / ConfigPanel.localeLabels", "check_i18n_keys": "green — 2549 pack-backed sites, 2495/2495 literal keys resolve, interpolation parity clean, 0 unreadable option sets", "check_i18n_drift": "green — 75 added, 0 changed (exactly the predicted shape)", "check_i18n_dead_keys": "green — 'dashboard.config' occurs 0 times in the report; the namespace has readers", "all_locales_key_parity_ellipsis_untranslated_identity_de_quote": "green (inside the i18n suite above)", "type_check": "green — plugin-dashboard + i18n", "lint": "green — 0 errors both packages (one added react-refresh/only-export-components WARNING from exporting buildWidgetSchema, on a file that already had two)", "check_changeset_presence_fixed_no_major": "green — plugin-dashboard + i18n patch", "check_control_bytes": "green — plus a grep -naP self-scan over every changed file, clean", "re_derived_extras": "check:phantom-deps, lint:coverage, type-check:coverage — all green; these were NOT in the dispatch gate list and were added by re-deriving from the CI workflows against the actual changed paths", "ci": "in_progress — reported at draft-PR time per the dispatch contract; not waited on" }, "reverse_verification": { "ablation_A": "Reverted both panels to origin/main (hardcoded English), kept packs and tests. PREDICTED: localeLabels RED, noProviderLabels GREEN. OBSERVED exactly that — localeLabels 5/5 red with `expected [ 'Dashboard', 'Chart' ] to deeply equal [ '仪表盘', '图表' ]` and the de equivalent; noProviderLabels 9/9 green, which is the positive proof the English bytes did not move.", "ablation_B": "Deleted one defaults-map row (dashboard.config.field.columns). PREDICTED red on the wiring test's leg 1 + leg 3 and on the no-provider DOM. OBSERVED all of them: notKeys=['dashboard.config.field.columns'], key-set 75 vs 74, row comparison, `Unable to find an element with the text: Columns`, and the raw-key guard catching the key in the DOM.", "restore": "git checkout against the committed branch, both times; the fix was committed BEFORE either ablation so a real restore point existed. No git stash at any point." }, "deviations": [ "Exported buildWidgetSchema / buildDashboardSchema from their modules (not the package barrel) so the wiring could be asserted exhaustively — every user-visible string in the built schema is proven to BE a translation key, which is the card's grep as a mechanical assertion. Follows the file's own precedent (sanitizeDraftForType is exported the same way for its test). Cost: one additional react-refresh lint WARNING.", "Translated the 4 helpText sentences as well. They are outside the card's grep pattern (not label/title/placeholder/breadcrumb) but are genuinely rendered and are the longest copy in the panel.", "75 keys against the card's 61 literals — not a scope expansion: shared labels collapse (Title/Description/Layout/Appearance are one key each across both panels) while option groups expand, and the breadcrumb's 'Pivot table' is deliberately a separate key from the picker's 'Pivot Table' so neither English string silently changes." ], "followups_filed": [ "#4750: components — ConfigPanelRenderer's saveLabel='Save' / discardLabel='Discard' parameter defaults are hardcoded English in @object-ui/components and no caller overrides them, so every config panel footer (report, dashboard, widget) stays English in all locales. Searched first; no existing issue. Unlabeled for PM triage (concrete defect, not observation-class)." ] }PR: #4751 (draft) · branch
claude/issue-4748-dashboard-config-panel-i18n· head0f9d59527Generated by Claude Code
Generated by Claude Code
fix(plugin-dashboard,i18n): translate the dashboard config sidebar (o…
fix(components,i18n): translate the config panel footer's Save / Disc…