diff --git a/modules/core/localizer.js b/modules/core/localizer.js index 21e19aedd..9961dea32 100644 --- a/modules/core/localizer.js +++ b/modules/core/localizer.js @@ -424,9 +424,20 @@ export function coreLocalizer() { return code; // if not found, use the code }; + localizer.floatFormatter = (locale) => { + if (!('Intl' in window && 'NumberFormat' in Intl && + 'formatToParts' in Intl.NumberFormat.prototype)) { + return (number) => number.toString(); + } else { + return (number) => number.toLocaleString(locale); + } + }; localizer.floatParser = (locale) => { // https://stackoverflow.com/a/55366435/4585461 + const polyfill = (string) => parseFloat(string, 10); + if (!('Intl' in window && 'NumberFormat' in Intl)) return polyfill; const format = new Intl.NumberFormat(locale); + if (!('formatToParts' in format)) return polyfill; const parts = format.formatToParts(12345.6); const numerals = Array.from({ length: 10 }).map((_, i) => format.format(i)); const index = new Map(numerals.map((d, i) => [d, i])); diff --git a/modules/ui/fields/input.js b/modules/ui/fields/input.js index 50ac3e6a3..b444251cd 100644 --- a/modules/ui/fields/input.js +++ b/modules/ui/fields/input.js @@ -32,6 +32,7 @@ export function uiFieldText(field, context) { var _tags; var _phoneFormats = {}; const isDirectionField = field.key.split(':').some(keyPart => keyPart === 'direction'); + const formatFloat = localizer.floatFormatter(localizer.languageCode()); const parseLocaleFloat = localizer.floatParser(localizer.languageCode()); if (field.type === 'tel') { @@ -155,7 +156,7 @@ export function uiFieldText(field, context) { } // make sure no extra decimals are introduced const numDecimals = v.includes('.') ? v.split('.')[1].length : 0; - return clamped(num).toFixed(numDecimals).toLocaleString(localizer.languageCode()); + return formatFloat(clamped(num).toFixed(numDecimals)); }); input.node().value = vals.join(';'); change()(); @@ -434,7 +435,7 @@ export function uiFieldText(field, context) { v = v.trim(); var num = parseFloat(v, 10); if (!isFinite(num)) return v; - return clamped(num).toLocaleString(localizer.languageCode()); + return formatFloat(clamped(num)); }); val = vals.join(';'); } diff --git a/modules/ui/fields/roadheight.js b/modules/ui/fields/roadheight.js index 182745804..b33c89c8f 100644 --- a/modules/ui/fields/roadheight.js +++ b/modules/ui/fields/roadheight.js @@ -16,6 +16,7 @@ export function uiFieldRoadheight(field, context) { var _entityIDs = []; var _tags; var _isImperial; + var formatFloat = localizer.floatFormatter(localizer.languageCode()); var parseLocaleFloat = localizer.floatParser(localizer.languageCode()); var primaryUnits = [ @@ -164,18 +165,18 @@ export function uiFieldRoadheight(field, context) { if (primaryValue && (primaryValue.indexOf('\'') >= 0 || primaryValue.indexOf('"') >= 0)) { secondaryValue = primaryValue.match(/(-?[\d.]+)"/); if (secondaryValue !== null) { - secondaryValue = parseFloat(secondaryValue[1], 10).toLocaleString(localizer.languageCode()); + secondaryValue = formatFloat(parseFloat(secondaryValue[1], 10)); } primaryValue = primaryValue.match(/(-?[\d.]+)'/); if (primaryValue !== null) { - primaryValue = parseFloat(primaryValue[1], 10).toLocaleString(localizer.languageCode()); + primaryValue = formatFloat(parseFloat(primaryValue[1], 10)); } _isImperial = true; } else if (primaryValue) { var rawValue = primaryValue; primaryValue = parseFloat(rawValue, 10); if (isNaN(primaryValue)) primaryValue = rawValue; - primaryValue = primaryValue.toLocaleString(localizer.languageCode()); + primaryValue = formatFloat(primaryValue); _isImperial = false; } } @@ -183,7 +184,7 @@ export function uiFieldRoadheight(field, context) { setUnitSuggestions(); // If feet are specified but inches are omitted, assume zero inches. - var inchesPlaceholder = (0).toLocaleString(localizer.languageCode()); + var inchesPlaceholder = formatFloat(0); utilGetSetValue(primaryInput, typeof primaryValue === 'string' ? primaryValue : '') .attr('title', isMixed ? primaryValue.filter(Boolean).join('\n') : null) diff --git a/modules/ui/fields/roadspeed.js b/modules/ui/fields/roadspeed.js index 637e61255..a077c9bf0 100644 --- a/modules/ui/fields/roadspeed.js +++ b/modules/ui/fields/roadspeed.js @@ -14,6 +14,7 @@ export function uiFieldRoadspeed(field, context) { var _entityIDs = []; var _tags; var _isImperial; + var formatFloat = localizer.floatFormatter(localizer.languageCode()); var parseLocaleFloat = localizer.floatParser(localizer.languageCode()); var speedCombo = uiCombobox(context, 'roadspeed'); @@ -92,8 +93,8 @@ export function uiFieldRoadspeed(field, context) { function comboValues(d) { return { - value: d.toLocaleString(localizer.languageCode()), - title: d.toLocaleString(localizer.languageCode()) + value: formatFloat(d), + title: formatFloat(d) }; } @@ -137,7 +138,7 @@ export function uiFieldRoadspeed(field, context) { value = parseInt(value, 10); if (isNaN(value)) value = rawValue; - value = value.toLocaleString(localizer.languageCode()); + value = formatFloat(value); } setUnitSuggestions(); diff --git a/test/spec/core/localizer.js b/test/spec/core/localizer.js index 08ef71cec..0dbe82c71 100644 --- a/test/spec/core/localizer.js +++ b/test/spec/core/localizer.js @@ -9,35 +9,39 @@ describe('iD.coreLocalizer', function() { describe('#floatParser', function () { it('roundtrips English numbers', function () { var localizer = iD.coreLocalizer(); + var formatFloat = localizer.floatFormatter('en'); var parseFloat = localizer.floatParser('en'); - expect(parseFloat((-0.1).toLocaleString(localizer.languageCode()))).to.eql(-0.1); - expect(parseFloat((1.234).toLocaleString(localizer.languageCode()))).to.eql(1.234); - expect(parseFloat(1234).toLocaleString(localizer.languageCode())).to.eql(1234); - expect(parseFloat(1234.56).toLocaleString(localizer.languageCode())).to.eql(1234.56); + expect(parseFloat(formatFloat(-0.1))).to.eql(-0.1); + expect(parseFloat(formatFloat(1.234))).to.eql(1.234); + expect(parseFloat(formatFloat(1234))).to.eql(1234); + expect(parseFloat(formatFloat(1234.56))).to.eql(1234.56); }); it('roundtrips Spanish numbers', function () { var localizer = iD.coreLocalizer(); + var formatFloat = localizer.floatFormatter('es'); var parseFloat = localizer.floatParser('es'); - expect(parseFloat((-0.1).toLocaleString(localizer.languageCode()))).to.eql(-0.1); - expect(parseFloat((1.234).toLocaleString(localizer.languageCode()))).to.eql(1.234); - expect(parseFloat(1234).toLocaleString(localizer.languageCode())).to.eql(1234); - expect(parseFloat(1234.56).toLocaleString(localizer.languageCode())).to.eql(1234.56); + expect(parseFloat(formatFloat(-0.1))).to.eql(-0.1); + expect(parseFloat(formatFloat(1.234))).to.eql(1.234); + expect(parseFloat(formatFloat(1234))).to.eql(1234); + expect(parseFloat(formatFloat(1234.56))).to.eql(1234.56); }); it('roundtrips Arabic numbers', function () { var localizer = iD.coreLocalizer(); + var formatFloat = localizer.floatFormatter('ar-EG'); var parseFloat = localizer.floatParser('ar-EG'); - expect(parseFloat((-0.1).toLocaleString(localizer.languageCode()))).to.eql(-0.1); - expect(parseFloat((1.234).toLocaleString(localizer.languageCode()))).to.eql(1.234); - expect(parseFloat(1234).toLocaleString(localizer.languageCode())).to.eql(1234); - expect(parseFloat(1234.56).toLocaleString(localizer.languageCode())).to.eql(1234.56); + expect(parseFloat(formatFloat(-0.1))).to.eql(-0.1); + expect(parseFloat(formatFloat(1.234))).to.eql(1.234); + expect(parseFloat(formatFloat(1234))).to.eql(1234); + expect(parseFloat(formatFloat(1234.56))).to.eql(1234.56); }); it('roundtrips Bengali numbers', function () { var localizer = iD.coreLocalizer(); + var formatFloat = localizer.floatFormatter('bn'); var parseFloat = localizer.floatParser('bn'); - expect(parseFloat((-0.1).toLocaleString(localizer.languageCode()))).to.eql(-0.1); - expect(parseFloat((1.234).toLocaleString(localizer.languageCode()))).to.eql(1.234); - expect(parseFloat(1234).toLocaleString(localizer.languageCode())).to.eql(1234); - expect(parseFloat(1234.56).toLocaleString(localizer.languageCode())).to.eql(1234.56); + expect(parseFloat(formatFloat(-0.1))).to.eql(-0.1); + expect(parseFloat(formatFloat(1.234))).to.eql(1.234); + expect(parseFloat(formatFloat(1234))).to.eql(1234); + expect(parseFloat(formatFloat(1234.56))).to.eql(1234.56); }); }); });