fix(gantt): interpolate the dialog counts through i18next, not a single-brace string replace - #4205
Merged
Merged
Conversation
…le-brace string replace (#4157) `gantt.conflict.body` was resolved with `t(key).replace('{count}', n)` while all ten packs spell the placeholder `{{count}}`. The replace consumed the inner seven characters and left the outer pair, so every loaded pack rendered a literal `{2}` in the conflict dialog. The call site now passes `{ count }` to i18next, the idiom `gantt.delete.body` already used. The two sibling keys (`autoScheduleDlg.body`, `.skipped`) were not broken -- pack and call site both used single braces -- but they are converted with it: two write-confirmation dialogs three lines apart carrying two interpolation idioms is the mechanism that let `conflict.body` drift in the first place. Only the braces moved; no translation was reworded. `quickFilter.resultSummary` stays single-brace by design and is now the sole key on that idiom. 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
|
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 free
to 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 #4157
The defect
gantt.conflict.bodywas resolved at the render site with a literal replace on single braces, while all ten locale packs spell the placeholder the i18next way:packages/plugin-gantt/src/GanttView.tsx:4984—t('gantt.conflict.body').replace('{count}', String(pendingConflict.length))packages/i18n/src/locales/zh.ts:726—'…是否自动重新排程 {{count}} 个受影响的任务?'"…{{count}}…".replace("{count}", "2")consumes the inner seven characters and leaves the outer pair, so every user on every loaded pack read a literal{2}. The premise held exactly as filed, re-confirmed onb1e42d09b.The call site now passes
{ count }to i18next — the idiomgantt.delete.bodyalready used — rather than downgrading the packs to single braces, so the bundled fallback table and the packs cannot re-drift.Per-key sweep decisions
The card asked for a sweep of the sibling literal-replace call sites. Measured per key, per pack:
gantt.conflict.body{{count}}.replace('{count}')gantt.autoScheduleDlg.body{count}.replace('{count}')gantt.autoScheduleDlg.skipped{count}.replace('{count}')gantt.quickFilter.resultSummary{shown}/{total}.replace('{shown}')Only
conflict.bodywas mismatched. The twoautoScheduleDlgkeys are converted anyway, and that is a judgment call worth stating plainly: that split is the entire mechanism. Two write-confirmation dialogs three lines apart in one component carried two different interpolation idioms, so whenconflict.bodyacquired the i18next spelling in the packs — which is the correct spelling, and matches every other placeholder in the bundle — the render silently broke. Leaving the auto-schedule keys on the literal-replace idiom leaves the same trap armed for the next translator. Only the braces moved; no translation was reworded.gantt.quickFilter.resultSummarystays single-brace: itsObjectGanttcall site really does resolve with a literal replace, and that convention is pinned by its own parity test. It is now the only key in the gantt namespace on that idiom, and the comments at both spellings now say so instead of pointing atautoScheduleDlg.bodyas precedent.Why nothing caught it
Each existing gate was silent for its own reason, and all three reasons are the same shape — they compare relative facts, while the defect lived in the absolute relationship between a pack's spelling and the syntax the call site resolves:
all-locales-key-parity's placeholder check comparesenagainst each pack. All eleven spellings agreed.check:i18n-en-driftcompares a pack against its own history. The packs never drifted; they were born matching.GanttView.interactions.test.tsx:557asserts the dialog body contains'1'— which{1}satisfies.check:i18n-call-site-keysdoes run an interpolation-parity check in both directions, and it does not catch this. Verified by running the gate's ownanalyze()against the reverted tree: green. Mechanism isscripts/check-i18n-call-site-keys.mjs:327—countis inRESERVED_OPTION_NAMES, and holes are filtered by that set before both directions are computed, so a{{count}}hole is removed andunfilledcan never contain it. Re-running withcountun-reserved reveals exactly one finding repo-wide, and it is this defect at this line. Filed as #4206; not touched here, per the card's scope.Tests
Two new files, both written red-first:
packages/i18n/src/__tests__/gantt-count-interpolation-4157.test.ts— the absolute pack-spelling invariant across all ten packs, plus byte-exactendefaults.packages/plugin-gantt/src/GanttView.countinterp.i18n.test.tsx— renders the real dialogs inside anI18nProviderunder bothenandzh. This has to render under a loaded pack: provider-less,useGanttTranslationserves the plugin's own bundled default, which carried the single-brace spelling and so masked the bug entirely. The auto-schedule dialog is the control, and thezhcase additionally pins that the localized sentence — not the English fallback — reaches the screen, which is the live risk in passing{ count }, since that is also i18next's plural selector.Both dialogs are gated on a write handler (
canDragfor the drag path,autoSchedule && onTaskUpdatefor the toolbar wand), so the harness suppliesonTaskUpdateby default; without it every case fails on "the dialog did not open" instead of on the placeholder residue it means to pin.Reverse verification
Reverted the three fix files to
origin/mainwithgit checkout origin/main -- …, keeping the new tests. Predicted: conflict cases red with the filing's signature, auto-schedule control cases green (both sides were single-brace on main). Both confirmed —Tests 2 failed | 3 passed:That is the reported symptom character-for-character (the card's
{2}at count 2; here count is 1). Restored withgit checkout HEAD -- ….Gate ladder
check:i18n-drift's count is the exact shape of this change: 2envalues changed (the twoautoScheduleDlgkeys), each followed by all nine translation packs.Generated by Claude Code