Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Intl.NumberFormat currency symbols are inaccurate when dealing with different types of dollars/$. #2778

Closed
kirstenwallace opened this issue Apr 4, 2017 · 17 comments

Comments

@kirstenwallace
Copy link

kirstenwallace commented Apr 4, 2017

I think this is a bug...

The following code in Chrome, Firefox, etc, will return a currency symbol of 'US$':

new Intl.NumberFormat('en-CA', {
    style: 'currency',
    currencyDisplay: 'symbol',
    currency: 'USD'
}).format(65421.45)

And it will simply use '$' if the currency is set to 'CAD':

new Intl.NumberFormat('en-CA', {
    style: 'currency',
    currencyDisplay: 'symbol',
    currency: 'CAD'
}).format(65421.45)

But in Edge (and all versions of IE), both of these examples will return a currency symbol of '$'. It will not return US$ or CA$, no matter the locale or currency used. (Same goes for AUD, HKD, etc.)

Example: https://jsfiddle.net/5wfzk7mf/

@dilijev
Copy link
Contributor

dilijev commented Apr 5, 2017

(Pardon the lengthy reply but I'm using this as an opportunity to do some research as well as answer your question.)

These details are typically (almost entirely) implemented as data and algorithms in an internationalization API. On Windows (Chakra, ChakraCore, and Edge) we use Windows Globalization for this.

I'm working on enabling ChakraCore to use ICU instead (which is the library used by Chrome's v8 engine and Node.js, and may also be what is used for Firefox). This work will also enable Intl to work on Linux and OSX where the Windows Globalization libraries are obviously not available.

So the above would explain why all of the others are the same.

As for whether it should be considered a bug for the purposes of the ChakraCore issue tracker -- unfortunately, it's essentially an External issue (depends on libraries outside of ChakraCore). I think I'll leave this open for now and will probably close it at a time no later than when ICU support is enabled. That's the best we can do here.

As for why we might be different from them (as a matter of philosophy of implementation, rather than the technical difference in library): strings which are output from Intl functions are, by spec, not intended to be parsed by a program or read in any other way except as displayed to an end user. These strings are meant to communicate information in a way which will be familiar and useful for users in various locales. As a matter of implementation, Windows Globalization may have felt this is a better way to provide the information.

Clearly it's a "compatibility" issue for anyone who tries to parse these strings, but the Intl spec is deliberately non-specific about how to format things because what constitutes "familiar and useful" is, to some extent, a matter of opinion. Although, there will be some cases where it is actually incorrect (or uncomfortable, unreadable, unusable, etc.).

"Accurate" however, is at a minimum, a matter of taste -- but I would suggest that actually the information conveyed in this case is, in fact, accurate, if you leave aside implementation differences and different degrees of information.

As you can imagine, the databases comprising data for all locales are huge (lots of combinations of languages, countries, scripts, and so on). It's possible there is a bug, but it's also possible that a decision was deliberately made for this particular locale and format specification to display the way it does in Edge.

For example, the $ symbol is used for many different currencies including USD, CAD, MXN (Mexican Pesos). While it is more specific to display some differentiating information, IMO it seems somewhat unnatural for readers used to seeing that symbol alone to mean their local currency to also see a country prefix. It would be more natural to simply display the currency symbol, instead of a leading country indicator.

As for whether a particular developer or user should consider such a difference to be a bug can be answered by whether the string makes sense and communicates the necessary information without unnecessary confusion.

Here's my opinion on these outputs: I think in all cases, the output string is clear. As for which one is better, it depends on your perspective. Since (IMO) we are aiming for something which will be familiar and useful for people in the provided locale, I'd say the $ by itself is the best option for when the currency belongs to the locale. If displaying a currency from a different locale, then the additional prefix should be used.

It looks like neither library actually takes this into account. Regardless of locale, it seems Edge always displays $, and the ICU implementations always display e.g. CA$.

@dilijev
Copy link
Contributor

dilijev commented Apr 5, 2017

I wrote a test to check more combinations to be more sure about it, but it appears that locale does not affect display of currencies under either Windows Globalization or mini-ICU. (The ICU used here is mini-ICU which is a smaller database used by default in Node.js. Full ICU is an option available but I don't have a build handy.)

These invariants could be an artifact of system language as well. For example, on a Chinese language system, they may more aggressively use Chinese characters like 元 (yuan) for currency instead of Latin-based words and symbols like ¥.

Test:

function testCurrencyDisplay(locale, currency, display) {
    let formatted = new Intl.NumberFormat(locale, {
        style: 'currency',
        currencyDisplay: display,
        currency: currency
    }).format(65421.45);
    return formatted;
}

function testAndPrint(locale, currency, display) {
    console.log(`${locale} ${currency} ${display}: \t` + testCurrencyDisplay(locale, currency, display));
}

let locales = ['en-US', 'en-CA', 'es-MX', 'zh-CN', 'zh-HK', 'ja-JP', 'de-DE'];
let currencies = ['USD', 'CAD', 'MXN', 'CNY', 'HKD', 'JPY', 'EUR'];

locales.forEach((locale) => {
    currencies.forEach((currency) => {
        testAndPrint(locale, currency, 'symbol');
        testAndPrint(locale, currency, 'code');
        // testAndPrint(locale, currency, 'name');
    });
    console.log();
});

Node-v8 (using mini-ICU) output for just en-US since all locales matched (except German, see below):

en-US USD symbol:       $65,421.45
en-US USD code:         USD65,421.45
en-US CAD symbol:       CA$65,421.45
en-US CAD code:         CAD65,421.45
en-US MXN symbol:       MX$65,421.45
en-US MXN code:         MXN65,421.45
en-US CNY symbol:       CN¥65,421.45
en-US CNY code:         CNY65,421.45
en-US HKD symbol:       HK$65,421.45
en-US HKD code:         HKD65,421.45
en-US JPY symbol:       ¥65,421
en-US JPY code:         JPY65,421

Node-ChakraCore (using Windows Globalization) output:

en-US USD symbol:       $65,421.45
en-US USD code:         USD 65,421.45
en-US CAD symbol:       $65,421.45
en-US CAD code:         CAD 65,421.45
en-US MXN symbol:       $65,421.45
en-US MXN code:         MXN 65,421.45
en-US CNY symbol:       ¥65,421.45
en-US CNY code:         CNY 65,421.45
en-US HKD symbol:       $65,421.45
en-US HKD code:         HKD 65,421.45
en-US JPY symbol:       ¥65,421
en-US JPY code:         JPY 65,421

Note that under Windows Globalization the currencies with symbols have a non-breaking space (0x00a0) between the code and the number to make them more readable and to prevent them from being split across two lines. Under ICU it seems there is no space of any kind which IMO makes it hard to read.

I commented out the name variant because it appears Windows Globalization implements it the same as the symbol variant and ICU uses a full prose currency name.

en-US USD name:         65,421.45 US dollars
en-US CAD name:         65,421.45 Canadian dollars
en-US MXN name:         65,421.45 Mexican pesos
en-US CNY name:         65,421.45 Chinese yuan
en-US HKD name:         65,421.45 Hong Kong dollars
en-US JPY name:         65,421 Japanese yen

These spaces are ordinary spaces (0x0020) -- I'm guessing because this is a prose format and can afford to be split between lines (or that it would be weird if they were not because it could affect spacing significantly).

Adding the German locale yielded an interesting difference. Notice the locale-specific swap in usage of . and , in numbers, and the currency code has been moved to the end.

WinGlob:

de-DE USD symbol:       65.421,45 $
de-DE USD code:         65.421,45 USD
de-DE CAD symbol:       65.421,45 $
de-DE CAD code:         65.421,45 CAD
de-DE MXN symbol:       65.421,45 $
de-DE MXN code:         65.421,45 MXN
de-DE CNY symbol:       65.421,45 ¥
de-DE CNY code:         65.421,45 CNY
de-DE HKD symbol:       65.421,45 $
de-DE HKD code:         65.421,45 HKD
de-DE JPY symbol:       65.421 ¥
de-DE JPY code:         65.421 JPY
de-DE EUR symbol:       65.421,45 €
de-DE EUR code:         65.421,45 EUR

In Chrome (presumably using a more complete version of ICU):

de-DE USD symbol: 	65.421,45 $
de-DE USD code: 	65.421,45 USD
de-DE CAD symbol: 	65.421,45 CA$
de-DE CAD code: 	65.421,45 CAD
de-DE MXN symbol: 	65.421,45 MX$
de-DE MXN code: 	65.421,45 MXN
de-DE CNY symbol: 	65.421,45 CN¥
de-DE CNY code: 	65.421,45 CNY
de-DE HKD symbol: 	65.421,45 HK$
de-DE HKD code: 	65.421,45 HKD
de-DE JPY symbol: 	65.421 ¥
de-DE JPY code: 	65.421 JPY
de-DE EUR symbol: 	65.421,45 €
de-DE EUR code: 	65.421,45 EUR

In Node-v8 mini-ICU the results were the same across all locales.

@dilijev
Copy link
Contributor

dilijev commented Apr 5, 2017

It appears Chrome (using full ICU) is sensitive to input locale.

en-US USD name: 	65,421.45 US dollars
en-US CAD name: 	65,421.45 Canadian dollars
en-US MXN name: 	65,421.45 Mexican pesos
en-US CNY name: 	65,421.45 Chinese yuan
en-US HKD name: 	65,421.45 Hong Kong dollars
en-US JPY name: 	65,421 Japanese yen
en-US EUR name: 	65,421.45 euros
en-CA USD name: 	65,421.45 US dollars
en-CA CAD name: 	65,421.45 Canadian dollars
en-CA MXN name: 	65,421.45 Mexican pesos
en-CA CNY name: 	65,421.45 Chinese yuan
en-CA HKD name: 	65,421.45 Hong Kong dollars
en-CA JPY name: 	65,421 Japanese yen
en-CA EUR name: 	65,421.45 euros
es-MX USD name: 	65,421.45 dólares estadounidenses
es-MX CAD name: 	65,421.45 dólares canadienses
es-MX MXN name: 	65,421.45 pesos mexicanos
es-MX CNY name: 	65,421.45 yuanes chinos
es-MX HKD name: 	65,421.45 dólares de Hong Kong
es-MX JPY name: 	65,421 yenes japoneses
es-MX EUR name: 	65,421.45 euros
zh-CN USD name: 	65,421.45美元
zh-CN CAD name: 	65,421.45加拿大元
zh-CN MXN name: 	65,421.45墨西哥比索
zh-CN CNY name: 	65,421.45人民币
zh-CN HKD name: 	65,421.45港元
zh-CN JPY name: 	65,421日元
zh-CN EUR name: 	65,421.45欧元
zh-HK USD name: 	65,421.45 美元
zh-HK CAD name: 	65,421.45 加拿大元
zh-HK MXN name: 	65,421.45 墨西哥披索
zh-HK CNY name: 	65,421.45 人民幣
zh-HK HKD name: 	65,421.45 港元
zh-HK JPY name: 	65,421 日圓
zh-HK EUR name: 	65,421.45 歐元
ja-JP USD name: 	65,421.45米ドル
ja-JP CAD name: 	65,421.45カナダ ドル
ja-JP MXN name: 	65,421.45メキシコ ペソ
ja-JP CNY name: 	65,421.45中国人民元
ja-JP HKD name: 	65,421.45香港ドル
ja-JP JPY name: 	65,421円
ja-JP EUR name: 	65,421.45ユーロ
de-DE USD name: 	65.421,45 US-Dollar
de-DE CAD name: 	65.421,45 Kanadische Dollar
de-DE MXN name: 	65.421,45 Mexikanische Pesos
de-DE CNY name: 	65.421,45 Renminbi Yuan
de-DE HKD name: 	65.421,45 Hongkong-Dollar
de-DE JPY name: 	65.421 Japanische Yen
de-DE EUR name: 	65.421,45 Euro
en-US USD symbol: 	$65,421.45
en-US CAD symbol: 	CA$65,421.45
en-US MXN symbol: 	MX$65,421.45
en-US CNY symbol: 	CN¥65,421.45
en-US HKD symbol: 	HK$65,421.45
en-US JPY symbol: 	¥65,421
en-US EUR symbol: 	€65,421.45
en-CA USD symbol: 	US$65,421.45
en-CA CAD symbol: 	$65,421.45
en-CA MXN symbol: 	MX$65,421.45
en-CA CNY symbol: 	CN¥65,421.45
en-CA HKD symbol: 	HK$65,421.45
en-CA JPY symbol: 	JP¥65,421
en-CA EUR symbol: 	€65,421.45
es-MX USD symbol: 	USD65,421.45
es-MX CAD symbol: 	CAD65,421.45
es-MX MXN symbol: 	$65,421.45
es-MX CNY symbol: 	CN¥65,421.45
es-MX HKD symbol: 	HKD65,421.45
es-MX JPY symbol: 	JPY65,421
es-MX EUR symbol: 	EUR65,421.45
zh-CN USD symbol: 	US$65,421.45
zh-CN CAD symbol: 	CA$65,421.45
zh-CN MXN symbol: 	MX$65,421.45
zh-CN CNY symbol: 	¥65,421.45
zh-CN HKD symbol: 	HK$65,421.45
zh-CN JPY symbol: 	JP¥65,421
zh-CN EUR symbol: 	€65,421.45
zh-HK USD symbol: 	US$65,421.45
zh-HK CAD symbol: 	CA$65,421.45
zh-HK MXN symbol: 	MX$65,421.45
zh-HK CNY symbol: 	CN¥65,421.45
zh-HK HKD symbol: 	HK$65,421.45
zh-HK JPY symbol: 	¥65,421
zh-HK EUR symbol: 	€65,421.45
ja-JP USD symbol: 	$65,421.45
ja-JP CAD symbol: 	CA$65,421.45
ja-JP MXN symbol: 	MX$65,421.45
ja-JP CNY symbol: 	元65,421.45
ja-JP HKD symbol: 	HK$65,421.45
ja-JP JPY symbol: 	¥65,421
ja-JP EUR symbol: 	€65,421.45
de-DE USD symbol: 	65.421,45 $
de-DE CAD symbol: 	65.421,45 CA$
de-DE MXN symbol: 	65.421,45 MX$
de-DE CNY symbol: 	65.421,45 CN¥
de-DE HKD symbol: 	65.421,45 HK$
de-DE JPY symbol: 	65.421 ¥
de-DE EUR symbol: 	65.421,45 €

@dilijev
Copy link
Contributor

dilijev commented Apr 5, 2017

To the extent that Windows Globalization may provide more accurate information and an implementation of the "name" currencyDisplay option, and for some reason ChakraCore does not implement or expose this information, we may have a bug here. I'll investigate and may end up fixing this (if there is indeed a bug) as I work on the ICU implementation of Intl.

@kirstenwallace
Copy link
Author

Sounds good, I'll stay tuned. Thanks for the quick initial response!

@dilijev
Copy link
Contributor

dilijev commented May 5, 2017

ICU work item mentioned is now tracked as #2919

@dilijev dilijev added this to the Backlog milestone May 15, 2017
@harishsubramani
Copy link

@dilijev Is this issue fixed ?

@harishsubramani
Copy link

@kirstenwallace Is this issue fixed ? or did you find any alternative solution for this issue.
Thanks

@dilijev
Copy link
Contributor

dilijev commented Aug 16, 2017

No update here yet. Aiming for parity between ICU and WinGlob implementations for #2919, then will investigate other potential bugs.

@dilijev
Copy link
Contributor

dilijev commented Sep 5, 2017

Tracking as part of #3644

@thedamon
Copy link

This is interesting. I live in Canada and I've never seen "CA$" in my life. It's either "$" or "CAD".

@dilijev
Copy link
Contributor

dilijev commented Nov 27, 2017

@thedamon thanks for the info. ICU uses CLDR as the basis for its data set for producing strings, so that's almost certainly coming from there, not sure whether CA$ is actually considered standard in any sense. @jackhorton do we have any channel by which we can investigate/report bugs in (or ask questions about) CLDR for ICU?

@thedamon
Copy link

thedamon commented Nov 28, 2017 via email

@jackhorton
Copy link
Contributor

http://unicode.org/cldr/trac seems like the most reasonable place. With that being said, in ICU, CA$ only shows up when you're using CAD from a non-Canadian locale in the symbol currency display. If you were working with CAD from en-CA or fr-CA, you would simply get "$" as the symbol. Thus, I would imagine this is intentional so as to differentiate USD, AUD, CAD, and others under the constraints of the region in the locale and the fact that the symbol display was specified.

@dilijev
Copy link
Contributor

dilijev commented Nov 28, 2017

@thedamon: @jackhorton makes a good point there, if you're displaying CAD in a CA locale you would never see the string appear that way.

@thedamon side note: it is actually the case that in the reality of the web, all engines except for Chakra use ICU as the library backing Intl, and we are the odd ones out by using WinGlob (hence all the compatibility bug reports tracked in #3644). It is my understanding that some subset or modified form of CLDR is used for WinGlob as well, and it may be the case that Windows' version of ICU is also using a modified form of CLDR.

After #2919 is completed, the logic will be compatible, but we may still differ in the database. Minor issues/differences like this (whether or not to use CA$ in non-CA locales for symbol display) may or may not be introduced when we switch over.

@thedamon
Copy link

thedamon commented Dec 5, 2017

@jackhorton that really makes sense as a way to set it. I had a lang tag set to "en-CA" and currency set to "CAD" when it cropped up, so the issue is maybe that I just do not understand how the current locale is calculated...

@jackhorton
Copy link
Contributor

You might have had currencyDisplay set to something other than symbol (though I think it is symbol by default, so if you hadn't set it, that wouldn't be an issue). The issue you had asked here was specific to ICU -- Windows Globalization always just uses "$" regardless of the dollar kind/langtag country combination. Also, if you provide "en-CA", every browser and most other JS hosts (except Node, when using small ICU) are going to find "en-CA" in its database, so there shouldn't be any weird locale calculations here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
No open projects
Intl
Backlog
Development

No branches or pull requests

5 participants