Skip to content

Where the Currency Symbol Goes in Different Locales

8 min read · updated August 11, 2026

“Symbol before the number in English, after it in Europe” is right often enough to feel like a rule and wrong often enough to produce a bug report. The actual answer is a per-locale pattern with four independent parts, and it is published data.

Four decisions, not one

Formatting a monetary amount for a locale settles four questions, and they do not covary the way people assume:

  • Position. Does the symbol precede the digits or follow them?
  • Spacing. Is there a space between symbol and digits, and if so which space character? CLDR distinguishes a normal space from a no-break space here, and it matters, because a line break between a symbol and its amount is a rendering defect.
  • Separators. Which characters group the thousands and mark the decimal — covered in detail in getting the decimal comma or point right.
  • Negative form. Minus sign, and where; or parentheses; or a trailing sign. This is a separate pattern from the positive one, not a transformation of it.

Because they are independent, a locale can put the symbol first and use a decimal comma, or last and use a decimal point. There is no two-camp division of the world into “American” and “European” formatting.

Where the data lives

The authoritative machine-readable source is the Unicode Common Locale Data Repository, published by the Unicode Consortium. The number formatting patterns, including the currency patterns, are specified in UTS #35, Part 3: Numbers, and the per-locale values are in the CLDR data files themselves, released at cldr.unicode.org. The project ships on a published cadence of roughly two releases a year.

CLDR expresses currency formatting as a pattern string in which ¤ stands for the currency symbol — for example ¤#,##0.00 for symbol-first and #,##0.00 ¤ for symbol-last. Every ICU implementation, and therefore Intl.NumberFormat in JavaScript, babel in Python, NSNumberFormatter on Apple platforms and the JDK’s formatters, resolve to this same data. That is the important practical fact: you do not need to agree with the table, you need to call something that reads it.

Every value below is CLDR data as it stands at the time of writing (August 2026) and CLDR revises locale data between releases — French group and currency spacing have both changed within recent release series. Read the current data rather than copying this list into code.

A labelled set of locales

The following is what the CLDR currency patterns produce for one thousand two hundred and thirty-four units and fifty-six subunits, in each locale’s own currency. The spacing is described in words because the characters involved are not all visible.

en-US   $1,234.56          symbol first, no space
en-GB   £1,234.56          symbol first, no space
en-IN   ₹1,234.56          symbol first, no space; grouping is Indian above 1,00,000
ja-JP   ¥1,235             symbol first, no space; JPY has no minor unit
de-DE   1.234,56 €         symbol last, no-break space before it
fr-FR   1 234,56 €         symbol last, no-break space; group separator is also a space
es-ES   1.234,56 €         symbol last, no-break space
it-IT   1.234,56 €         symbol last, no-break space
nl-NL   € 1.234,56         symbol FIRST, with a space — same currency, different locale
pt-BR   R$ 1.234,56        symbol first, with a space
pl-PL   1 234,56 zł        symbol last, space
sv-SE   1 234,56 kr        symbol last, space
de-CH   CHF 1'234.56       symbol first; apostrophe grouping, decimal point
fr-CA   1 234,56 $         symbol last
en-CA   $1,234.56          symbol first — same country, two conventions

Two rows in that list do the work. Dutch and German share the euro and disagree about where its symbol goes, which kills the idea that symbol placement is a property of the currency. English and French Canada share a country and a currency and disagree, which kills the idea that it is a property of the region. It is a property of the locale — the language-and-region pair — and that is why the lookup key has both parts in it.

The Japanese row is worth a second look for a different reason. The yen has no minor unit, so the correct number of fraction digits is zero, and a formatter that hardcodes two decimal places will produce ¥1,234.56, which is not a quantity of yen. Fraction digits are currency data rather than locale data, and are published by CLDR separately in its supplemental currency data.

The negative form is separate data

A currency pattern in CLDR can carry two subpatterns separated by a semicolon: positive, then negative. Where the negative subpattern is absent, the implementation prefixes a minus sign, which is the common case across locales. Where it is present it can do something else entirely.

The case worth knowing is accounting notation. In US financial documents a negative amount is conventionally written ($1,234.56) rather than -$1,234.56, and this is exposed as a distinct currency sign display option rather than as a different locale — in ECMA-402 it is currencySign: "accounting". Choosing between them is an editorial decision about the document you are producing, not a locale lookup, and it is a decision a model asked to “format this as currency” will make silently and inconsistently.

There is a third decision hiding behind “the symbol”, and it is which symbol. The dollar sign is used by more than twenty currencies, so $50 in a document read internationally does not identify an amount. ECMA-402 exposes four display choices — currencyDisplay set to "symbol", "narrowSymbol", "code" or "name" — and they resolve differently by locale. In en-US, Canadian dollars render as CA$50.00 under symbol and as $50.00 under narrowSymbol; in en-CA the local currency takes the bare sign and the US dollar gets the disambiguating prefix instead. The rule is that a locale disambiguates the currencies that are not its own, which is sensible and means the same amount renders differently to two readers. For anything cross-border — an invoice, a price list, a contract — the code display, giving USD 50.00, removes the ambiguity at the cost of looking bureaucratic, and that is usually the right trade.

The placement of the minus sign relative to the symbol is also locale data: some locales produce -1 234,56 € and others put the sign between symbol and digits. This is enough of a subject on its own that negative number formatting by locale takes it separately.

Looking it up rather than remembering it

All of the above is a single call in any ICU-backed environment, and the point of the table above is to show you what the call is doing, not to be copied:

const fmt = (locale, currency) =>
  new Intl.NumberFormat(locale, { style: "currency", currency }).format(1234.56);

fmt("de-DE", "EUR")   // "1.234,56 €"
fmt("nl-NL", "EUR")   // "€ 1.234,56"
fmt("ja-JP", "JPY")   // "¥1,235"

new Intl.NumberFormat("en-US", {
  style: "currency", currency: "USD", currencySign: "accounting"
}).format(-1234.56)   // "($1,234.56)"

The one thing to check when you adopt this is that your runtime has full ICU data. Some minimal builds — Node compiled with small-icu, some container base images, some embedded runtimes — ship English-only data and silently fall back, producing US formatting for every locale you ask for. It fails without an error, which makes it look exactly like the model bug you were trying to eliminate. Test by formatting a known German amount and checking that the separators actually swapped.