The numeric token (e.g. "149,19", "1.234,56", "1,234.56", "40")
The parsed number (may be NaN if the token holds no digits)
parseLocalizedNumber("149,19") // 149.19
parseLocalizedNumber("1.234,56") // 1234.56
parseLocalizedNumber("1,234.56") // 1234.56
parseLocalizedNumber("40") // 40
export const parseLocalizedNumber = (raw: string): number => {
const commaCount = (raw.match(/,/g) ?? []).length;
const dotCount = (raw.match(/\./g) ?? []).length;
// Both separators present: the last-occurring one is the decimal, the other groups thousands.
if (commaCount > 0 && dotCount > 0) {
const decimal = raw.lastIndexOf(',') > raw.lastIndexOf('.') ? ',' : '.';
const grouping = decimal === ',' ? /\./g : /,/g;
return Number(raw.replace(grouping, '').replace(decimal, '.'));
}
// A single comma or single dot is a decimal separator (handles EU "149,19" and US "140.22").
if (commaCount === 1) return Number(raw.replace(',', '.'));
if (dotCount === 1) return Number(raw);
// No separator, or repeated separators used purely for grouping (e.g. "1,234,567").
return Number(raw.replace(/[.,]/g, ''));
};
Parses a numeric token that may use US or European grouping/decimal conventions into a number. When both
.and,are present the last-occurring one is treated as the decimal separator and the other as thousands grouping; a single,or.is treated as a decimal point (so European149,19reads as149.19); repeated separators of one kind are treated as grouping only.