Currency formatting, by locale
Apple's NumberFormatter, set to numberStyle = .currency, doesn't just prepend a symbol to a number. It renders the amount the way a native speaker of that locale expects to read money: where the symbol sits, which character separates thousands, which separates decimals, and how negative amounts look. Get any of these wrong and an interface reads as foreign — even when the number itself is correct.
This page is a working reference. Try it live below, or scan the tables of correctly formatted examples. See Apple's NumberFormatter documentation for the full API.
Try it
Swift and JavaScript both delegate currency formatting to ICU, the same locale data Apple's Foundation framework uses — so the result below matches what NumberFormatter produces on iOS and macOS for the same locale and currency in the vast majority of cases.
.currencylet formatter = NumberFormatter()formatter.locale = Locale(identifier: "it-IT")formatter.numberStyle = .currencyformatter.currencyCode = "EUR"formatter.string(from: 1234.5) // "1234,50 €"
new Intl.NumberFormat("it-IT", {style: "currency",currency: "EUR",currencyDisplay: "symbol",currencySign: "standard",}).format(1234.5); // "1234,50 €"
Common mistakes
- Using the US format ($1,234.50) as the template for every locale and currency.
- Hardcoding the symbol glued to the number ("€1.234,50") instead of letting the locale decide the symbol's position — German puts it after the amount: "1.234,50 €".
- Using a period as the decimal separator when the locale expects a comma (most of continental Europe), or vice versa.
- Treating a negative amount as just a prepended minus sign, ignoring locale and accounting conventions.
- Assuming currency and locale are the same thing — the same currency formats differently across the locales that use it, see below.
Local currency, by locale
Each locale's own currency, formatted with numberStyle = .currency.
| Locale | Currency | 1234.5 | -1234.5 |
|---|---|---|---|
| English - United States (en-US) | USD | $1,234.50 | -$1,234.50 |
| English - United Kingdom (en-GB) | GBP | £1,234.50 | -£1,234.50 |
| English - Ireland (en-IE) | EUR | €1,234.50 | -€1,234.50 |
| German - Germany (de-DE) | EUR | 1.234,50 € | -1.234,50 € |
| French - France (fr-FR) | EUR | 1 234,50 € | -1 234,50 € |
| Italian - Italy (it-IT) | EUR | 1234,50 € | -1234,50 € |
| Spanish - Spain (es-ES) | EUR | 1234,50 € | -1234,50 € |
| Dutch - Netherlands (nl-NL) | EUR | € 1.234,50 | € -1.234,50 |
| Portuguese - Portugal (pt-PT) | EUR | 1234,50 € | -1234,50 € |
| Portuguese - Brazil (pt-BR) | BRL | R$ 1.234,50 | -R$ 1.234,50 |
| Japanese - Japan (ja-JP) | JPY | ¥1,235 | -¥1,235 |
| Chinese - China (zh-CN) | CNY | ¥1,234.50 | -¥1,234.50 |
| Korean - South Korea (ko-KR) | KRW | ₩1,235 | -₩1,235 |
| Arabic - Saudi Arabia (ar-SA) | SAR | ١٬٢٣٤٫٥٠ ر.س. | -١٬٢٣٤٫٥٠ ر.س. |
| Russian - Russia (ru-RU) | RUB | 1 234,50 ₽ | -1 234,50 ₽ |
| Hindi - India (hi-IN) | INR | ₹1,234.50 | -₹1,234.50 |
| Swedish - Sweden (sv-SE) | SEK | 1 234,50 kr | −1 234,50 kr |
| Polish - Poland (pl-PL) | PLN | 1234,50 zł | -1234,50 zł |
| Turkish - Turkey (tr-TR) | TRY | ₺1.234,50 | -₺1.234,50 |
| Hebrew - Israel (he-IL) | ILS | 1,234.50 ₪ | -1,234.50 ₪ |
| Thai - Thailand (th-TH) | THB | ฿1,234.50 | -฿1,234.50 |
| Vietnamese - Vietnam (vi-VN) | VND | 1.235 ₫ | -1.235 ₫ |
| English - Canada (en-CA) | CAD | $1,234.50 | -$1,234.50 |
| French - Canada (fr-CA) | CAD | 1 234,50 $ | -1 234,50 $ |
| English - Australia (en-AU) | AUD | $1,234.50 | -$1,234.50 |
| English - South Africa (en-ZA) | ZAR | R 1 234,50 | -R 1 234,50 |
| English - India (en-IN) | INR | ₹1,234.50 | -₹1,234.50 |
Same currency, different locale
1234.5 USD, formatted for locales that don't natively use it. The currency is the same — the formatting rules come entirely from the locale.
| Locale | Formatted |
|---|---|
| English - United States (en-US) | $1,234.50 |
| German - Germany (de-DE) | 1.234,50 $ |
| French - France (fr-FR) | 1 234,50 $US |
| Italian - Italy (it-IT) | 1234,50 USD |
| Japanese - Japan (ja-JP) | $1,234.50 |
| Arabic - Saudi Arabia (ar-SA) | ١٬٢٣٤٫٥٠ US$ |
| Hindi - India (hi-IN) | $1,234.50 |
| Turkish - Turkey (tr-TR) | $1.234,50 |
Notice the Italian row: 1234,50 USD, not $1.234,50. Two separate locale rules are at play. First, $ is ambiguous — CAD, AUD and MXN use it too — so CLDR (the locale database behind both Intl.NumberFormat and Apple's NumberFormatter) has Italian fall back to the ISO code for the default symbol style, rather than a symbol a reader could mistake for their own currency; asking for currencyDisplay: "narrowSymbol" still returns $. Second, Italian only groups thousands from five integer digits up — 1234,50 stays ungrouped, but 12.345,60 gets its separator. Neither behavior is a bug in the formatter; both come straight from the locale's CLDR data.
A note on Apple's currency styles
Apple exposes currency formatting as four distinct NumberFormatter.Style cases, not as independent options:
- .currency — symbol, standard sign
- .currencyAccounting — symbol, accounting sign (parentheses for negatives)
- .currencyISOCode — ISO code instead of symbol
- .currencyPlural — spelled-out currency name
JavaScript's Intl.NumberFormat models the same idea with two independent options, currencyDisplay and currencySign — which is why the tool above can express combinations, like an accounting ISO code, that Apple's enum doesn't have a single case for.