Number formatting
Same number, different clothes: 1,234,567.89 in the US is 1.234.567,89 in Germany and 12,34,567.89 in India. The locale picks the separators.
See it
What it is
One number, many costumes. 1234567.89 is 1,234,567.89 in the US, 1.234.567,89 in Germany, 1 234 567,89 in France, and 12,34,567.89 in India, where grouping goes two digits at a time after the first three (lakh and crore). Locale decides the decimal mark, the group separator, the group size, and even the digits themselves: 'ar-EG' can render Arabic-Indic numerals.
Intl.NumberFormat covers essentially all of it. style 'percent' handles the sign and spacing per locale (0.42 becomes 42% or 42 %), notation 'compact' gives you 1.2K and 1,2 Mio., style 'unit' does '12 km/h', and minimumFractionDigits plus maximumFractionDigits let you pin precision without rounding by hand.
Gotcha worth tattooing somewhere: formatting is one-way. Never parseFloat a formatted string, because '1.234' is one thousand two hundred in Germany and slightly more than one in the US. Keep the raw number in state and format only at render. Related trap: the 'space' French and Nordic locales use is U+202F, a narrow no-break space, so a naive comparison against a string you typed with the spacebar fails while looking identical on screen.
Ask AI for it
Route every displayed number in this app through Intl.NumberFormat with the active locale instead of manual string work. Add helpers for plain numbers, percentages (style 'percent'), and compact counts (notation 'compact', for follower and view counts), each memoizing its formatter per locale and options. Keep values as raw numbers in state and format only at render time. For number inputs, parse with a locale-aware parser rather than parseFloat, since a German user typing 1.234 means one thousand two hundred thirty-four. Verify output against en-US, de-DE, fr-FR, and en-IN.