From 854902f2bb2239f0b6df9eecdc31ee44e36af23b Mon Sep 17 00:00:00 2001 From: Caridy Date: Thu, 17 Nov 2016 10:26:30 -0500 Subject: [PATCH 1/2] fixes #221: normalize spacing around currency codes --- src/11.numberformat.js | 42 +++++++++++++++++++++++++++++++++++++++--- tests/sanity.js | 30 ++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 3 deletions(-) diff --git a/src/11.numberformat.js b/src/11.numberformat.js index 87186e7f7..b1ffbf941 100644 --- a/src/11.numberformat.js +++ b/src/11.numberformat.js @@ -503,6 +503,42 @@ function PartitionNumberPattern(numberFormat, x) { ild = data.symbols[nums] || data.symbols.latn, pattern; + // web-compat issue #221 + function preprocessCurrencyCode(currencyCode, pattern, beginIndex, endIndex) { + /** + * http://cldr.unicode.org/translation/number-patterns + * This method solves a particular issues related to this note from the link above: + * + * ¤ This will be replaced by a currency symbol, such as $ or USD. Note: by default a space is automatically + * added between letters in a currency symbol and adjacent numbers. So you don't need to add a space between + * them if your language writes "$12" but "USD 12". + * + * Specifically, this polyfill doesn't support currency names (it is not part of the data we produce from CLDR), in + * which case we fallback to using the currency code. The same applies if the currency symbol is not available. This + * left us with two skeletons that require some preprocessing. The problem is that there are locales that do not have + * a symbol, and they already assume an space is needed between the ¤ and the value. On top of that, there are locales + * in which the symbol goes before or after the value. This function takes all that into consideration and tries to + * produce the most accurate currency code, value and non-breaking space combination by analysing the skeleton. + * + * Empirical rules: + * - If the skeleton already have the non-breaking space, we do nothing because it is a locale without a currency symbol + * - If the pattern ends on the last character, the space could be a suffix of the {currency} token, this condition is + * more reliable than testing the first character because sometimes there is a minus sign. + */ + const NON_BREAKING_SPACE = '\xa0'; // Non-breakable space is char 0xa0 (160 dec) + if (endIndex === pattern.length && beginIndex > 0) { + const prevCharCode = pattern.charCodeAt(beginIndex - 1); + if (prevCharCode !== NON_BREAKING_SPACE) { + return NON_BREAKING_SPACE + currencyCode; + } + } else if (pattern.length > endIndex + 1) { + if (pattern.charCodeAt(endIndex + 1) !== NON_BREAKING_SPACE) { + return currencyCode + NON_BREAKING_SPACE; + } + } + return currencyCode; + } + // 1. If x is not NaN and x < 0, then: if (!isNaN(x) && x < 0) { // a. Let x be -x. @@ -685,17 +721,17 @@ function PartitionNumberPattern(numberFormat, x) { // iii. If numberFormat.[[currencyDisplay]] is "code", then if (internal['[[currencyDisplay]]'] === "code") { // 1. Let cd be currency. - cd = currency; + cd = preprocessCurrencyCode(currency, pattern, beginIndex, endIndex); } // iv. Else if numberFormat.[[currencyDisplay]] is "symbol", then else if (internal['[[currencyDisplay]]'] === "symbol") { // 1. Let cd be an ILD string representing currency in short form. If the implementation does not have such a representation of currency, use currency itself. - cd = data.currencies[currency] || currency; + cd = data.currencies[currency] || preprocessCurrencyCode(currency, pattern, beginIndex, endIndex); } // v. Else if numberFormat.[[currencyDisplay]] is "name", then else if (internal['[[currencyDisplay]]'] === "name") { // 1. Let cd be an ILD string representing currency in long form. If the implementation does not have such a representation of currency, then use currency itself. - cd = currency; + cd = preprocessCurrencyCode(currency, pattern, beginIndex, endIndex); } // vi. Add new part record { [[type]]: "currency", [[value]]: cd } as a new element of the list result. arrPush.call(result, { '[[type]]': 'currency', '[[value]]': cd }); diff --git a/tests/sanity.js b/tests/sanity.js index 58add14c7..9c705648e 100644 --- a/tests/sanity.js +++ b/tests/sanity.js @@ -98,3 +98,33 @@ assert(new IntlPolyfill.DateTimeFormat('en-us', { assert(new IntlPolyfill.DateTimeFormat('en-GB', { hour: 'numeric' }).format(new Date(2016, 0, 1, 6)), '6', 'single second should be 2-digit'); + +// issue #221 +assert(new IntlPolyfill.NumberFormat('es-MX', { + style: 'currency', + currency: 'PYG', +}).format(12345), 'PYG\xa012,345', 'space after currency code when in fallback mode'); +assert(new IntlPolyfill.NumberFormat('en-US', { + style: 'currency', + currencyDisplay: 'code', + currency: 'USD', +}).format(12345), 'USD\xa012,345.00', 'space after currency code when in fallback mode'); +assert(new IntlPolyfill.NumberFormat('en-US', { + style: 'currency', + currencyDisplay: 'name', + currency: 'USD', +}).format(12345), 'USD\xa012,345.00', 'space after currency name when in fallback mode'); +assert(new IntlPolyfill.NumberFormat('cs', { + style: 'currency', + currency: 'CSK', +}).format(12345), '12\xa0345,00\xa0Kčs', 'existing space before currency symbol should be preserved for locales with extended symbol'); +assert(new IntlPolyfill.NumberFormat('cs', { + style: 'currency', + currency: 'CSK', + currencyDisplay: 'code', +}).format(12345), '12\xa0345,00\xa0CSK', 'existing space before currency code should be preserved for locales with extended symbol'); +assert(new IntlPolyfill.NumberFormat('bn', { + style: 'currency', + currency: 'BDT', + currencyDisplay: 'name', +}).format(12345), '১২,৩৪৫.০০\xa0BDT', 'existing space before currency symbol should be preserved for locales without symbol'); \ No newline at end of file From 288fc2d246875aa75a461b726fb47f717a69e797 Mon Sep 17 00:00:00 2001 From: Caridy Date: Thu, 17 Nov 2016 10:39:59 -0500 Subject: [PATCH 2/2] refinements for PR 221 --- src/11.numberformat.js | 10 ++-------- tests/sanity.js | 2 +- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/11.numberformat.js b/src/11.numberformat.js index b1ffbf941..fffe85de5 100644 --- a/src/11.numberformat.js +++ b/src/11.numberformat.js @@ -522,16 +522,10 @@ function PartitionNumberPattern(numberFormat, x) { * * Empirical rules: * - If the skeleton already have the non-breaking space, we do nothing because it is a locale without a currency symbol - * - If the pattern ends on the last character, the space could be a suffix of the {currency} token, this condition is - * more reliable than testing the first character because sometimes there is a minus sign. + * - If the pattern ends on the last character, the space already comes from CLDR when needed and we do nothing. */ const NON_BREAKING_SPACE = '\xa0'; // Non-breakable space is char 0xa0 (160 dec) - if (endIndex === pattern.length && beginIndex > 0) { - const prevCharCode = pattern.charCodeAt(beginIndex - 1); - if (prevCharCode !== NON_BREAKING_SPACE) { - return NON_BREAKING_SPACE + currencyCode; - } - } else if (pattern.length > endIndex + 1) { + if (pattern.length > endIndex + 1) { if (pattern.charCodeAt(endIndex + 1) !== NON_BREAKING_SPACE) { return currencyCode + NON_BREAKING_SPACE; } diff --git a/tests/sanity.js b/tests/sanity.js index 9c705648e..155e2db15 100644 --- a/tests/sanity.js +++ b/tests/sanity.js @@ -127,4 +127,4 @@ assert(new IntlPolyfill.NumberFormat('bn', { style: 'currency', currency: 'BDT', currencyDisplay: 'name', -}).format(12345), '১২,৩৪৫.০০\xa0BDT', 'existing space before currency symbol should be preserved for locales without symbol'); \ No newline at end of file +}).format(12345), '১২,৩৪৫.০০BDT', 'no space is needed before currency code when currency code is at the end of the skeleton'); \ No newline at end of file