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?
answer
- pass the locale explicitly
- ISO name versus short symbol
- decimal digits follow the currency
- pattern places the symbol per locale
- ar and ar_EG digits differ
basics
~20 sCreate 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 linesimport '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 localego deeper
Know that NumberFormat.currency formats money for a locale and that you should pass the locale and the ISO currency name.
Explain currency versus simpleCurrency, how decimal digits are chosen, and why omitting the locale yields en_US in a Flutter app.
Choose ar or ar_EG deliberately, cache formatters per locale and currency, and parse user input with the matching NumberFormat.
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.