Skip to content

[core] formatMeasure renders every dashboard measure in the MACHINE locale — the last parallel implementation outside the display-locale channel #4566

Description

@yinlianghui

Found while censusing formatPercent consumers for #4553 phase 2 (PR #4565). Filed rather than fixed — different package, different function, outside that card's ruled surface.

The gap

packages/core/src/utils/dataset-format.ts formats dashboard measure values with a bare undefined locale tag at three sites:

111:    return Number.isInteger(v) ? String(v) : v.toLocaleString(undefined, { maximumFractionDigits: 2 });
130:    const body = display.toLocaleString(undefined, { minimumFractionDigits: decimals ?? 0, maximumFractionDigits: decimals ?? 0 });
141:    return Number.isInteger(v) ? String(v) : v.toLocaleString(undefined, { maximumFractionDigits: 2 });

Line 130 is formatMeasure's main body (numbers, currency-ish and percent measures alike); 111 is its no-format-hint branch; 141 is formatDimensionValue. undefined is not "the user's locale", it is the MACHINE's — neither of the repo's two locale channels, exactly as useDisplayLocale's own doc puts it.

Why this was not covered by the #4553 family

It is easy to assume this was already fixed, and the census showed it was not. formatMeasure is a parallel implementation, not a consumer: it never calls formatPercent, formatNumber or formatDisplayNumber. It reimplements the percent scaling by sharing percentDisplayValue with @object-ui/fields and then formats independently.

So #4553's producer fix reached every formatPercent consumer and moved nothing here. DatasetWidget, ObjectMetric and the pivot matrix — the surfaces formatMeasure actually feeds — still render in the machine's locale on every session.

Worth noting it is the milder half of the same family: toLocaleString does group by default, so this is a locale defect only, not the locale-plus-grouping defect formatPercent had.

Why it can be observed

A German session on a dashboard reads a KPI as 1,234.5 where the convention is 1.234,5, next to a grid or gantt tooltip that (after #4541 / #4544 / #4557 / #4565) renders the same number as 1.234,5. Inverted separators read as a different number, not as an unstyled one.

The part that needs a decision, not just a patch

formatMeasure is a pure function in @object-ui/core, called from React components. It cannot read useDisplayLocale() itself, so the locale has to arrive as a parameter and every caller has to thread it — the same shape renderFieldValue needed in PR #4565, but with more callers and across a package boundary.

Whoever picks this up should decide deliberately between:

B is worth real consideration precisely because this function exists to keep dashboard measures agreeing with list cells (its own doc comment says so), and it currently achieves that agreement by duplicating logic that has since grown a policy layer the duplicate does not have.

Relations


Generated by Claude Code

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions