formatNumber function
Formats a num value with locale-aware grouping and decimal separators in pure Dart.
Formats integers and floating point numbers according to language conventions,
using appropriate thousand separators (",", ".", " ", or "٬") and decimal
points (".", ",", or "٫"). When locale is omitted, uses the active
BloomI18n.instance.locale signal value.
Limitations vs Full CLDR
This is a lightweight, pure-Dart implementation covering major language families (English, French, German, Spanish, Portuguese, Italian, Russian, Japanese, Chinese, Arabic, etc.). It does not include full Unicode CLDR tables or localized numbering systems (e.g. eastern Arabic-Indic numerals).
formatNumber(1234567.89, locale: 'en-US'); // "1,234,567.89"
formatNumber(1234567.89, locale: 'de-DE'); // "1.234.567,89"
formatNumber(1234567.89, locale: 'fr-FR'); // "1 234 567,89"
See also:
- formatPercent, for formatting percentage values.
- formatCurrency, for currency formatting.
- localizedNumber, convenience top-level shorthand.
Implementation
String formatNumber(
num value, {
String? locale,
int? decimalDigits,
bool useGrouping = true,
String style = 'decimal',
}) {
final loc = (locale ?? BloomI18n.instance.locale.value).replaceAll('_', '-').toLowerCase();
final lang = loc.split('-').first;
// Determine separators
String decimalSep = '.';
String groupSep = ',';
if (['de', 'it', 'es', 'pt', 'nl', 'tr', 'id'].contains(lang)) {
decimalSep = ',';
groupSep = '.';
} else if (['fr', 'ru', 'sv', 'pl', 'cs', 'fi', 'no', 'uk', 'bg'].contains(lang)) {
decimalSep = ',';
groupSep = ' ';
} else if (['ar'].contains(lang)) {
decimalSep = '٫';
groupSep = '٬';
}
// Handle decimals
final isInt = value is int || value == value.roundToDouble();
String formatted;
if (decimalDigits != null) {
formatted = value.toStringAsFixed(decimalDigits);
} else if (isInt) {
formatted = value.toInt().toString();
} else {
formatted = value.toString();
}
final parts = formatted.split('.');
String integerPart = parts[0];
final isNegative = integerPart.startsWith('-');
if (isNegative) integerPart = integerPart.substring(1);
if (useGrouping && integerPart.length > 3) {
final buf = StringBuffer();
final len = integerPart.length;
for (int i = 0; i < len; i++) {
if (i > 0 && (len - i) % 3 == 0) {
buf.write(groupSep);
}
buf.write(integerPart[i]);
}
integerPart = buf.toString();
}
if (isNegative) integerPart = '-$integerPart';
if (parts.length > 1 && parts[1].isNotEmpty) {
return '$integerPart$decimalSep${parts[1]}';
}
return integerPart;
}