intword function

String intword(
  1. num n, {
  2. int precision = 1,
  3. bool short = false,
  4. String locale = 'en',
})

Human-friendly representation for large numbers.

  • Long form (default): 1,200,000 -> '1.2 million' (EN) or '1,2 millions' (FR)
  • Short form: 1,200 -> '1.2 k' Keeps one decimal by default (precision), localizes decimal separator for FR.

Implementation

String intword(num n,
    {int precision = 1, bool short = false, String locale = 'en'}) {
  final bool isFr = locale.toLowerCase().startsWith('fr');
  final num absN = n.abs();
  final String sign = n < 0 ? '-' : '';

  // Return as-is for small numbers
  if (absN < 1000) return '$n';

  // Short unit labels (common across locales)
  const unitsShort = ['k', 'M', 'B', 'T', 'P', 'E', 'Z', 'Y'];

  // Long unit labels EN (short scale) and FR (long scale) up to a reasonable range.
  const unitsLongEn = [
    'thousand',
    'million',
    'billion',
    'trillion',
    'quadrillion',
    'quintillion',
    'sextillion',
    'septillion',
    'octillion',
    'nonillion',
    'decillion',
    'undecillion',
    'duodecillion',
    'tredecillion',
    'quattuordecillion',
    'quindecillion',
    'sexdecillion',
    'septendecillion',
    'octodecillion',
    'novemdecillion',
    'vigintillion',
    'unvigintillion',
    'duovigintillion',
    'trevigintillion',
    'quattuorvigintillion',
    'quinvigintillion',
    'sexvigintillion',
    'septenvigintillion',
    'octovigintillion',
    'novemvigintillion',
    'trigintillion',
    'untrigintillion',
    'duotrigintillion', // 10^99
  ];
  const unitsLongFr = [
    'mille',
    'million',
    'milliard',
    'billion',
    'billiard',
    'trillion',
    'trilliard',
    'quadrillion',
    'quadrilliard',
    'quintillion',
    'quintilliard',
    'sextillion',
    'sextilliard',
    'septillion',
    'septilliard',
    'octillion',
    'octilliard',
    'nonillion',
    'nonilliard',
    'decillion',
    'decilliard',
    'undecillion',
    'undecilliard',
    'duodecillion',
    'duodecilliard',
    'tredecillion',
    'tredecilliard',
    'quattuordecillion',
    'quattuordecilliard',
    'quindecillion',
    'quindecilliard',
    'sexdecillion',
    'sexdecilliard',
    'septendecillion',
    'septendecilliard',
    'octodecillion',
    'octodecilliard',
    'novemdecillion',
    'novemdecilliard',
    'vigintillion',
    'vigintilliard',
    // ... extended as needed
  ];

  // Special handling for googol (10^100)
  if (absN >= 1e100) {
    final valueGoogol = absN / 1e100;
    var formatted = valueGoogol.toStringAsFixed(precision);
    if (isFr) formatted = formatted.replaceAll('.', ',');
    final unit = 'googol'; // same in FR/EN
    return '$sign$formatted $unit';
  }

  // Scale by thousands until under 1000 or end of units
  int unitIndex = -1;
  num value = absN;
  final int maxIndex = (isFr ? unitsLongFr.length : unitsLongEn.length) - 1;
  while (value >= 1000 && unitIndex < maxIndex) {
    value /= 1000;
    unitIndex++;
  }

  // Format number with fixed decimals; localize decimal separator for FR
  var formatted = value.toStringAsFixed(precision);
  if (isFr) formatted = formatted.replaceAll('.', ',');

  if (short) {
    final idx = unitIndex.clamp(0, unitsShort.length - 1);
    final suffix = unitsShort[idx];
    return '$sign$formatted $suffix';
  }

  // Long unit label
  String unit = (isFr ? unitsLongFr[unitIndex] : unitsLongEn[unitIndex]);
  if (isFr) {
    // Pluralize for FR when value != 1.0 (except 'mille' which is invariable)
    final isExactlyOne = formatted == '1' || formatted == '1,0' || formatted == '1,00';
    if (unit != 'mille' && !isExactlyOne) {
      unit = unit.endsWith('s') ? unit : '${unit}s';
    }
  }
  // EN: keep as singular form ("1.5 million")

  return '$sign$formatted $unit';
}