How Etsy Formats Currency

Posted by on April 19, 2016

Imagine how you would feel if you went into a grocery store, and the prices were gibberish (“1,00.21 $” or “$100.A”). Would you feel confident buying from this store?

Etsy does business in more than 200 regions and 9 languages. It’s important that our member experience is consistent and credible in all regions, which means we have to format prices correctly for all members.

In this post, I’ll cover:

In order to follow along, you need to know one important thing: Currency formatting depends on three attributes: the currency, the member’s location, and the member’s language.

Examples of bad currency formatting

Here are some examples of bad currency formatting:

If you don’t know why the examples above are confusing, read on.

What’s wrong with: A member browsing in German goes to your site and sees an item for sale for “1,000.21 €”?

The first example is the easiest. If a member is browsing in German, the commas and decimals in a price should be flipped. So “1,000.21 €” should really be formatted as “1.000,21 €”. This isn’t very confusing (as a German member, you can figure out what the price is *supposed* to be), but it is a bad experience.

By the way, if you are in Germany, using Euros, but browsing in English, what would you expect to see? Answer: “€1,000.21”. The separators and symbol position are based on language here, not region.

What’s wrong with: A Japanese member sees an item selling for “¥ 847,809.34”?

Japanese Yen doesn’t have a fractional part. There’s no such thing as half a Yen. So “¥ 847,809.34” could mean “¥ 847,809”, or “¥ 84,780,934” or something else entirely.

What’s wrong with: A Canadian member sees “$1.00”?

If your site is US-based, this can be confusing. Does “$” mean Canadian dollar or US dollar here? A simple fix is to add the currency code at the end: “$1.00 USD”.

How to format currency correctly

Etsy's locale settings picker

Etsy’s locale settings picker

Formatting currency for international members is hard. Etsy supports browsing in 9 languages, 23 currencies, and hundreds of regions. Luckily, we don’t have to figure out the right way to format in all of these combinations, because the nice folks at CLDR have done it for us. CLDR is a massive database of formatting styles that gets updated twice a year. The data gets packaged up into a portable library called libicu. Libicu is available everywhere, including mobile phones. If you want to format currency, you can use CLDR data to do it.

For each language + region + currency combination, CLDR gives you:

A typical pattern looks like this:

A cldr pattern (#,##0.00)

A cldr pattern

This is the pattern for German + Germany + Euros. It tells you:

NOTE: the pattern does *not* tell you what the decimal and grouping separators are. CLDR gives you those separately, they are not a part of the pattern.

Now you can use this information to format a value:

#,##0.## translates to 1.000,21

If you want to format prices using CLDR, your language might have libraries to do it for you already. PHP has NumberFormatter, for example. JavaScript has Intl.NumberFormat.

Practical implementation decisions

CLDR is great, but it is not the ultimate authority. It is a collaborative project, which means that anyone can add currency data to CLDR, and then everyone votes on whether the data looks correct or not. People can also vote to change existing currency data.

CLDR data is not a precise thing, it is fluid and changing. Sometimes you need to customize CLDR for your use case. Here are the customizations we made.

The problem with currencies that use a dollar sign ($)

We use CLDR to format currency at Etsy, but we’ve made some changes to it. One issue in particular has really bugged us. Dollar currencies are really hard to work with. The symbol for CAD (Canadian dollars) is “$” in Canada, but it is “CA$” in the US and everywhere else to avoid confusion with US Dollars. So if we followed CLDR, Canadian members would see “$1.00”. But our Canadian members might know that Etsy is a US-based company, in which case “$” would be ambiguous to them — it could mean either Canadian dollars or US dollars. Here is how we choose a currency symbol to avoid confusion while still meeting member expectations:

What symbol does Etsy use for dollar-based currencies?

What symbol does Etsy use for dollar-based currencies?

Here is the value “1000.21” formatted in different currency + region combinations:

05_table

You might be wondering, why not just add the currency code to the end of the price? For example, it could be “$1,000.21 USD” for US dollars, and “$1,000.21 CAD” for Canadian dollars. This is also explicit but we don’t need to have complicated logic to change the currency symbol. But this approach has another issue: redundancy.

Suppose we did add the currency code at the end everywhere to address the CAD problem. Euros would get formatted as “1.000,21 € EUR”, but the “€ EUR” is redundant. Even worse, Swiss Francs doesn’t have a currency symbol, so CLDR recommends using the currency code as the currency symbol. Which means they would see “1.000,21 CHF CHF”, which is definitely redundant:

Adding the currency code at the end is explicit, but doesn’t meet member expectations. Our German members said they didn’t like how “1.000,21 € EUR” looked.

In the end Etsy decided not to show the currency code. Instead, we change the currency symbol as needed to avoid confusion.

 

Listing price with settings English / Canada / Canadian dollars

Listing price with settings English / Canada / Canadian dollars

Overriding CLDR data

Here’s a simple case where we overrode CLDR formatting. We are a website, so of course we want our prices to be wrapped in html tags so that they can be styled appropriately. For example, on our listings manager, we want to format price input boxes correctly based on locale:

08_input_old

vs

09_input_new

It’s hard to wrap a price in html tags *after* you have done the formatting: sometimes the symbol is at the end, sometimes there’s a space between the symbol and value, and sometimes there isn’t, etc etc. To make this work, the html tags need to be a part of the pattern, so we need to be able to override the CLDR patterns directly.

Ultimately we ended up overriding a lot of the default CLDR data:

Different libraries offered different levels of support for this. PHP’s NumberFormatter lets you override the pattern and symbol. JavaScript’s Intl.NumberFormat lets you override neither. None of the libraries had support for wrapping html tags around the output. In the end, we wrote our own JavaScript library and added wrappers for the rest.

Consistent formatting across platforms

We had to format currency in PHP, JavaScript, and in our iOS and Android apps. PHP, JavaScript, iOS and Android all had different versions of libicu, and so they had different CLDR data. How do we format consistently across these platforms? We went with a dual plan of attack: write tests that are the same across platforms, and make sure all CLDR overrides get shared between platforms.

We wrote a script that would export all our CLDR overrides as JSON / XML / plist. Every time the overrides change, we run the script to generate new data for all platforms. Here’s what our JSON file looks like right now (excerpt):

{
    "de_AU": {
        "symbol": {
            "AUD": "AU$",
            "BRL": "R$",
            "CAD": "CA$"
        },
        "decimal_separator": ",",
        "grouping_separator": ".",
        "pattern": {
            "AUD": "#,##0.00 \u00a4",
            "BRL": "#,##0.00 \u00a4",
            "CAD": "#,##0.00 \u00a4"
...

We wrote another script to generate test fixtures, which look like this (excerpt):

"test_symbol&&!code&&!html": {
    "de": {
        "DE": {
            "EUR": {
                "100000": "1.000,00 \u20ac",
                "100021": "1.000,21 \u20ac"
            }
        },
        "US": {
            "EUR": {
                "100000": "1.000,00 \u20ac",
                "100021": "1.000,21 \u20ac"
            },
            "USD": {
                "100000": "1.000,00 $",
                "100021": "1.000,21 $"
            }
        }
    }
}

This test says that given these settings:

We have hundreds of tests in total to check every combination of language/region/currency code with symbol shown vs. hidden, formatted as text vs. html, etc. These expected values get checked against the output of the currency formatters on all platforms, so we know that they all format currency correctly and consistently. Any time an override changes (for example, changing the symbol for CAD to be “CA$” in all regions), we update the CLDR data file so that the new override gets spread to all platforms. Then we update the test fixtures and re-run the tests to make sure the override worked on all platforms.

Conclusion

No more “¥ 847,809.34”! Formatting currency is hard. If you want to do it correctly, use the CLDR data, but make sure that you override it when necessary based on your unique circumstances. I hope our changes lead to a better experience for international members. Thanks for reading!

Posted by on April 19, 2016
Category: engineering, internationalization Tags: ,

21 Comments

[…] Excellent billet d’Etsy sur la localisation des montants et devises. Rien que pour la culture… […]

Your image for using CLDR information to format the value had a mistake – switched the , and .

Otherwise, awesome post! Very interesting to see how you’ve dealt with a messy situation. Thanks for sharing!

The EU style guide places the euro character in front of the sum, without space, regardless of language: http://publications.europa.eu/code/en/en-370303.htm (it’s convention in German to place it after the number because that’s how it was with deutschmarks)

[…] How Etsy formats currency. Etsy es una web de compraventa de productos que permite operar en 9 lenguajes, 23 monedas distintas y centenares de regiones. Ésta es la historia de cómo aborda el formato de monedas en su web. […]

[…] How Etsy Formats Forex — I’m saving this one particular because it chafes every single time I do it, and I do it wrong every single time. […]

Great post. Don’t forget that French Canadians format currency different as well! X XXX,XX $

Loved the article. Great contribution to the Internationalization of the web.

Good post with practical examples. Sometimes we just miss important things, unintentionally.
Thanks for this sharing!

Interesting stuff. Any plans to open source?

Wow. I’ve been wondering how big e-commerce sites handle currency for different regions. Thanks for sharing.

[…] The challenges of formatting currency data […]

[…] via How Etsy Formats Currency – Code as Craft […]

Awesome – it was just the information that I was looking for and it is even extremely well written. Thanks for that!

Does it really need to be solved with hard code? Or instead of flying throug JS code other solution can check for User´s location and a database with regional settings not only for currency but numbers, date and measurement units (British or SI system) Maybe I got lost and did not understand if the hardcoding favors performance.

It´s a smart solution. Congrats! One thing I was thinkg also was if you can have a library with preformated rules.

[…] How Etsy Formats Currency (lidt anderledes end de andre, idet den faktisk beskriver en mulig løsning) […]