Found while adding tenantCurrency to the tasks memo's dependency array for #4542. Filed rather than fixed — outside that card's ruled surface (which is the dependency array only).
The gap
packages/plugin-gantt/src/ObjectGantt.tsx, inside formatFieldValue (the tooltip value formatter in the tasks memo). #4272 / PR #4544 threaded useDisplayLocale() into this function's four TEMPORAL call sites. The three NUMERIC ones next to them were left as they were:
case 'number':
case 'integer':
case 'float':
case 'decimal':
return formatNumber(Number(value));
case 'currency':
return formatCurrency(Number(value), resolveFieldCurrency(def as any, tenantCurrency));
case 'percent':
return formatPercent(Number(value));
All three formatters accept a locale that nothing passes here:
formatNumber(value, decimals?, locale?) — third parameter omitted
formatCurrency(value, currency?, locale?) — third parameter omitted (the currency CODE is resolved correctly; the locale that renders it is not)
formatPercent — same shape
Each ends up at formatDisplayNumber with locale: undefined, which reaches new Intl.NumberFormat(undefined, ...). Per useDisplayLocale's own doc that is the MACHINE's locale — neither of the repo's two locale channels.
Why it can be observed
One tooltip, two conventions. On a zh or de session a gantt tooltip whose fields include a date and an amount renders the date row through the display locale (correct since PR #4544) and the amount row through whatever locale the browser happens to run in. A de user reading a tooltip sees 5. Jan. 2024 on one line and 1,234.50 on the next, where the German convention is 1.234,50 — the separators are inverted, so the amount is not merely unstyled but readable as a different number.
Measured on this runner (machine locale en-US), for 1234.5:
de expected 1.234,50, rendered 1,234.50
zh expected 1,234.50 (agrees with en-US here, so zh alone does not discriminate)
Fix sketch
Thread the displayLocale already read at component level (L435, added by PR #4544) into all three numeric call sites, exactly as the four temporal ones were threaded. The dependency array already carries displayLocale, so no memo change is needed. Red-first case: a de session asserting the tooltip amount uses . for grouping and , for the decimal mark, with an en byte-identical pin beside it.
Relations
Generated by Claude Code
Found while adding
tenantCurrencyto thetasksmemo's dependency array for #4542. Filed rather than fixed — outside that card's ruled surface (which is the dependency array only).The gap
packages/plugin-gantt/src/ObjectGantt.tsx, insideformatFieldValue(the tooltip value formatter in thetasksmemo). #4272 / PR #4544 threadeduseDisplayLocale()into this function's four TEMPORAL call sites. The three NUMERIC ones next to them were left as they were:All three formatters accept a locale that nothing passes here:
formatNumber(value, decimals?, locale?)— third parameter omittedformatCurrency(value, currency?, locale?)— third parameter omitted (the currency CODE is resolved correctly; the locale that renders it is not)formatPercent— same shapeEach ends up at
formatDisplayNumberwithlocale: undefined, which reachesnew Intl.NumberFormat(undefined, ...). PeruseDisplayLocale's own doc that is the MACHINE's locale — neither of the repo's two locale channels.Why it can be observed
One tooltip, two conventions. On a
zhordesession a gantt tooltip whose fields include a date and an amount renders the date row through the display locale (correct since PR #4544) and the amount row through whatever locale the browser happens to run in. Adeuser reading a tooltip sees5. Jan. 2024on one line and1,234.50on the next, where the German convention is1.234,50— the separators are inverted, so the amount is not merely unstyled but readable as a different number.Measured on this runner (machine locale en-US), for
1234.5:deexpected1.234,50, rendered1,234.50zhexpected1,234.50(agrees with en-US here, sozhalone does not discriminate)Fix sketch
Thread the
displayLocalealready read at component level (L435, added by PR #4544) into all three numeric call sites, exactly as the four temporal ones were threaded. The dependency array already carriesdisplayLocale, so no memo change is needed. Red-first case: adesession asserting the tooltip amount uses.for grouping and,for the decimal mark, with anenbyte-identical pin beside it.Relations
Intl.NumberFormat('en-US')— an ordinalField.number(a year) always renders2,026, and no field property can turn it off #4033 (the fields-side origin) and Date formatter is half-localized: future relative forms and absolute timestamps stay en-US on a non-English locale #4272 / PR fix(fields): the date formatter's last three en-US channels thread the display locale (#4272) #4544 (the temporal half of this very function).Generated by Claude Code