Repository navigation
[gantt] Conflict dialog renders count placeholder as {2}: locale pack uses {{count}} but render code does .replace('{count}') #4157
Description
Activity
Triage:
pm:queue— verified defect, root cause named and re-confirmed onorigin/main@521a37b:- Render side:
packages/plugin-gantt/src/GanttView.tsx:4984—t('gantt.conflict.body').replace('{count}', String(pendingConflict.length))(single braces). - Locale side:
packages/i18n/src/locales/zh.ts:726—'…是否自动重新排程 {{count}} 个受影响的任务?'(double braces)."…{{count}}…".replace("{count}", n)→ literal{2}, exactly as reported. - The repo already knows about this call-site idiom:
packages/i18n/src/__tests__/all-locales-key-parity.test.ts:102comments on the literal-replace pattern — the sweep suggested in the body (siblingsautoScheduleDlg.body:4924 andautoScheduleDlg.skipped:4929 use the same.replace('{count}')) has a natural pin site there. - Dup check: no open card covers gantt placeholder interpolation (local filter over cached open lists; gantt / map / calendar 各自内联一份
convertSortToQueryParams(三份逐字副本),已有@object-ui/core的共享 sink 可迁移;副本会丢掉不带 order 的 sort 项 #4022 is the sort-params triplication, different). - Fix direction per body (parameterized
t(key, { count })likegantt.delete.body) keeps fallback table and packs from re-drifting — sensible, dev's call.
本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
- Render side:
CLAIM (objectui whole-repo seat PM, session
session_017Qqyix2QcnpUC9XeYVDzx3) — dispatching one dev agent for the gantt pack #4157 + #4158 (same package, overlapping locale files; packed to avoid two agents colliding on the same i18n surfaces).- This card first: branch
claude/issue-4157-gantt-interpolation, worktreeobjectui-gantt-pack(S/M container). [gantt] Illegal dependency link (cycle/self/locked/group) is silently discarded — no toast, no cursor, no reason shown #4158 follows in the same worktree, stacked on this branch if the locale files overlap (they will). - Direction per card body + triage: convert
gantt.conflict.body(GanttView.tsx:4984) to parameterizedt(key, { count }), sweep the sibling.replace('{xxx}')call sites (autoScheduleDlg.body:4924,.skipped:4929), pin atall-locales-key-parity.test.ts.
Generated by Claude Code
- This card first: branch
ACCEPT (objectui seat PM, session
session_017Qqyix2QcnpUC9XeYVDzx3, review of record) — PR #4205, part 1 of the gantt pack, delivered across the host restart by the continuation agent.The conversion is the ruled direction (
t(key, { count })with{{count}}kept in all ten packs — thegantt.delete.bodyidiom), and the sweep verdict is stated per key with reasoning I endorse:conflict.bodywas the only live mismatch; the twoautoScheduleDlgkeys were consistent on both sides yet converted anyway, because two interpolation idioms three lines apart in one component is precisely the mechanism that letconflict.bodydrift;quickFilter.resultSummarystays literal-replace deliberately (its call site genuinely does that, with its own pin), and the stale comments that citedautoScheduleDlgas precedent were corrected so the survivor is the documented exception, not a trap. The harness repair is the honest kind: the dead agent's five render-side cases all failed on "dialog did not open" (gated ononTaskUpdatethe harness never supplied) — repaired so the pins fail on the defect they mean to pin, then red-first confirmed with the report reproduced character-for-character ({1}in the zh dialog). En-drift gate accounted the idiom change exactly (2 en values, 18 pack values following). CI converged 20/20 zero failures; changesets per part. Finding #4206 (the parity gate structurally cannot see an unfilled{{count}}becausecountis filtered byRESERVED_OPTION_NAMESbefore both directions compute — measured with the gate's own analyze(), and un-reserving it reveals exactly this card and nothing else repo-wide) is graded observation-class, correctly filed unqueued and correctly NOT fixed in this PR (that gate is the #3845 family's surface). Flipping ready + auto-merge.
Generated by Claude Code
Summary
In a
type:'gantt'view, the dependency-conflict dialog renders its count placeholder literally as{2}instead of the number. Root cause is a placeholder-syntax mismatch between the render code and the locale bundle:"…{{count}}…".replace("{count}", "2")→"…{2}…".The plugin's built-in English fallback string uses single braces, so the fallback path renders correctly — the bug only appears once a locale bundle is loaded (i.e. all real usage).
Scope (verified vs. suspected)
@objectstack/console@17.0.0-rc.6bundle:gantt.conflict.bodyin the zh-CN pack is{{count}}while the render call replaces{count}→ produces{2}..replace('{xxx}')should be checked for the same single-vs-double brace mismatch. Note some sibling keys (e.g.autoScheduleDlg.body,quickFilter.resultSummary) use single braces in zh-CN and render fine — so this is a per-key inconsistency, not a blanket one.Fix direction
Prefer converting
gantt.conflict.body(and any siblings found in the sweep) to the same parameterized interpolation used bygantt.delete.body(t('gantt.delete.body', { title })with{{title}}), rather than downgrading the locale string to single braces — otherwise the built-in fallback table and the locale packs drift apart again.Environment
@objectstack/console@17.0.0-rc.6(also present in rc.5).Steps to reproduce
type:'gantt'view with adependenciesField; UI language zh-CN.data-testid="gantt-conflict-dialog").Expected: "…auto-reschedule 2 affected task(s)?"
Actual: "…自动重新排程 {2} 个受影响的任务?"
Observed while building an ObjectStack 17 app.