skip to content

With package:intl in a Flutter currency converter, how do you format amounts for Arabic and Hebrew users, and how do NumberFormat.currency and simpleCurrency differ?

level: middleimportance: should knowfreq 38%

answer

  1. pass the locale explicitly
  2. ISO name versus short symbol
  3. decimal digits follow the currency
  4. pattern places the symbol per locale
  5. ar and ar_EG digits differ

basics

~20 s

Create NumberFormat.currency(locale: ..., name: 'USD') with the app's resolved locale so the locale decides separators, digits and symbol position. currency prints the ISO code unless given a symbol; simpleCurrency prints a short symbol that can be ambiguous.

solid answer

~40 s

`NumberFormat.currency({locale, name, symbol, decimalDigits, customPattern})` formats with the locale's currency pattern and prints the ISO 4217 `name` (such as `USD`) unless you pass `symbol`. `NumberFormat.simpleCurrency({locale, name, decimalDigits})` looks up a short symbol instead (`$`, `€`, `₪`); intl's own docs warn that CAD in `en_US` shows as `$`, which is ambiguous in a converter. `decimalDigits` defaults to the currency's own digits (JPY has none), then the locale's. The locale must come from Flutter, for example `Localizations.localeOf(context).toString()`, because intl's default is `Intl.defaultLocale`, which Flutter never sets, falling back to `en_US`. In intl's data, `he` and `ar` place the currency after the number with bidi marks, `ar` uses Western digits, and `ar_EG` uses Arabic-Indic digits and Arabic separators.

code

dart · 12 lines
dart
import 'package:flutter/widgets.dart';
import 'package:intl/intl.dart' as intl;

String formatAmount(BuildContext context, double amount, String currency) {
  final locale = Localizations.localeOf(context).toString();
  return intl.NumberFormat.currency(locale: locale, name: currency)
      .format(amount);
}

// he  -> the amount, then the code USD, wrapped in right-to-left marks
// ar_EG -> Arabic-Indic digits and Arabic separators
// JPY -> no fraction digits in any locale

go deeper

for a junior

Know that NumberFormat.currency formats money for a locale and that you should pass the locale and the ISO currency name.

for a middle

Explain currency versus simpleCurrency, how decimal digits are chosen, and why omitting the locale yields en_US in a Flutter app.

for a senior

Choose ar or ar_EG deliberately, cache formatters per locale and currency, and parse user input with the matching NumberFormat.

for a principal

Set the product rule for codes versus symbols per market and keep formatting at the UI edge so models stay locale-free.

## The two constructors `package:intl`'s `NumberFormat` has several currency factories; the two most used are: | | `NumberFormat.currency` | `NumberFormat.simpleCurrency` | |---|---|---| | Parameters | `locale`, `name`, `symbol`, `decimalDigits`, `customPattern` | `locale`, `name`, `decimalDigits` | | What it prints for the currency | `symbol` if given, otherwise the ISO code in `name` | A short symbol looked up for `name` | | Example for USD | `USD` next to the number | `$` next to the number | | Risk | Codes read as technical to some users | Symbols collide: CAD, AUD and USD are all `$` | For a **currency converter**, where both sides of a conversion appear on screen, the ISO code is usually the safer default; intl's documentation itself warns that formatting CAD in an `en_US` locale with `simpleCurrency` shows `$`. Some short symbols in intl's table are also words rather than glyphs (for SAR it is `Riyal`, for AED `dh`), so check the output before you ship it. `compactCurrency` and `compactSimpleCurrency` do the same for abbreviated amounts such as `1.2M`. ## Decimal digits `decimalDigits` is optional. When omitted, intl uses the **currency's** standard number of fraction digits if `name` is given (zero for JPY), and otherwise the default for the locale's own currency. The currency's value wins over the locale's, which is what a converter wants: yen stay whole in every language. ## What the locale changes The locale chooses the **pattern** and the **number symbols**. From intl's locale data: - **`he`**: grouping `,` and decimal `.`, the currency after the number, with right-to-left marks around it; the locale's default currency is ILS. - **`ar`**: grouping `,` and decimal `.` with **Western digits** (`0-9`), the currency after the number, and a right-to-left mark at the start. - **`ar_EG`**: **Arabic-Indic digits** (`٠-٩`) with the Arabic decimal and grouping separators. So the same amount looks different in `ar` and `ar_EG`. Decide which you support and list that exact locale in `supportedLocales`, rather than assuming "Arabic" means one digit system. ## Getting the locale right in Flutter Every factory takes `locale` as optional. When you leave it out, intl uses **`Intl.defaultLocale`**, and when that is unset, `Intl.systemLocale`, which defaults to `en_US`. Flutter resolves its own locale for widgets but does **not** set `Intl.defaultLocale`. Consequences and fixes: 1. `NumberFormat.currency(name: 'ILS')` without a locale formats in `en_US` even on a Hebrew phone. 2. Pass the resolved locale: `NumberFormat.currency(locale: Localizations.localeOf(context).toString(), name: 'ILS')`. 3. Or set `Intl.defaultLocale` once the app's locale is known, and update it when the locale changes; it is global state, so tests must reset it. ## Performance and structure Building a `NumberFormat` parses its pattern, so do not create one per list item on every frame. A small helper keyed by `(locale, currency)` that caches formatters, or a formatter created in `didChangeDependencies`, keeps scrolling smooth. Keep the formatting at the edge of the UI: models hold numbers and currency codes, widgets turn them into strings for the current locale. ## Rates are not amounts A converter shows two kinds of numbers, and they need different formatters: - **Money amounts** use a currency factory, with the currency's standard fraction digits (two for USD and ILS, none for JPY). - **Exchange rates** such as `3.7125` are not money and need more precision: use `NumberFormat.decimalPatternDigits(locale: ..., decimalDigits: 4)`, which keeps the locale's separators and digits. - **Changes** such as `+0.4%` use `NumberFormat.decimalPercentPattern` or `percentPattern` for the locale. ## Testing formatter output Locale patterns for `ar` and `he` contain **invisible bidi marks** (U+200F right-to-left mark, U+200E left-to-right mark) and non-breaking spaces. An expected string typed by hand in a test will not match. Either build the expectation with the same formatter, strip the marks before comparing, or assert on parts such as `contains('ILS')`. Pass the locale explicitly in tests so the result does not depend on `Intl.defaultLocale` left over from another test. ## Parsing user input The same `NumberFormat` can parse text back with `parse`, using the locale's separators. A Hebrew user typing `1,234.5` and an `ar_EG` user typing Arabic-Indic digits both need the parser for their locale; a plain `double.parse` accepts neither grouping separators nor Arabic-Indic digits.

  • Why does NumberFormat.currency(name: 'ILS').format(10) print in English style on a Hebrew phone?
    No `locale` was passed, so intl used `Intl.defaultLocale`, which Flutter does not set, and then `Intl.systemLocale`, which defaults to `en_US`. Pass `Localizations.localeOf(context).toString()` or set `Intl.defaultLocale` when the app locale resolves.
  • How many decimal places does NumberFormat.currency(locale: 'he', name: 'JPY') show?
    None. When `decimalDigits` is omitted and a currency `name` is given, intl uses that currency's standard fraction digits, and the currency's value takes priority over the locale's default.
  • When would you pass symbol to NumberFormat.currency?
    When the product wants a specific glyph that intl's defaults do not give, for example `₪` next to Hebrew amounts while keeping ISO codes elsewhere. `symbol` replaces the printed code but keeps the locale's pattern, separators and digits.

saying these in an interview costs you the question

  • NumberFormat uses the Flutter app's locale automatically.
  • simpleCurrency is always unambiguous because symbols are unique.
  • All Arabic locales format numbers with Arabic-Indic digits.
  • decimalDigits always defaults to two for every currency.
  • double.parse handles the grouping separators users type.