Группировка разрядов

Группировка разрядов — это механизм визуального разделения длинных числовых значений на блоки (обычно по три цифры), что повышает читаемость. В международной локализации этот процесс строго регулируется правилами CLDR (Unicode Common Locale Data Repository), которые используются в JavaScript через Intl и, в экосистеме FormatJS, через react-intl.

В разных локалях один и тот же принцип реализуется по-разному:

  • en-US: 1,234,567
  • ru-RU: 1 234 567 (неразрывный пробел)
  • de-DE: 1.234.567

Эти различия не являются косметическими — они отражают стандарты локалей и напрямую влияют на корректность отображения финансовых и статистических данных.


Базовая модель группировки в Intl и FormatJS

В основе FormatJS лежит стандартный Intl.NumberFormat. Именно он определяет поведение группировки разрядов.

const formatter = new Intl.NumberFormat('ru-RU');
formatter.format(1234567);

Результат:

1 234 567

FormatJS в react-intl использует тот же механизм:

import { createIntl, createIntlCache } from 'react-intl';

const cache = createIntlCache();

const intl = createIntl(
  {
    locale: 'ru-RU',
    messages: {}
  },
  cache
);

intl.formatNumber(1234567);

Управление группировкой через параметры

Группировка разрядов управляется параметром useGrouping. Он определяет, будут ли вставляться разделители между разрядами.

new Intl.NumberFormat('en-US', {
  useGrouping: true
}).format(1234567);

Результат:

1,234,567

Отключение группировки:

new Intl.NumberFormat('en-US', {
  useGrouping: false
}).format(1234567);

Результат:

1234567

В FormatJS этот параметр передаётся через formatNumber или компонент FormattedNumber.


Использование FormatJS (react-intl) для группировки

В react-intl группировка включена по умолчанию, так как она определяется локалью и стандартом ICU.

import { FormattedNumber } from 'react-intl';

<FormattedNumber value={1234567} />

Вывод зависит от локали:

  • en-US1,234,567
  • ru-RU1 234 567

Явное управление параметрами форматирования

FormatJS позволяет переопределять поведение через объект параметров, аналогичный Intl.NumberFormatOptions.

<FormattedNumber
  value={1234567}
  useGrouping={true}
/>

Или через API:

intl.formatNumber(1234567, {
  useGrouping: true
});

Типы группировки и локальные правила

Группировка определяется не только булевым флагом, но и локальными шаблонами CLDR.

Основные варианты:

  1. Standard grouping Используется в большинстве локалей: группы по три цифры слева направо.

  2. Indian grouping Используется в индийской системе счисления:

    12,34,56,789
  3. Minimum grouping Применяется при необходимости сокращённого отображения больших чисел.

FormatJS не требует ручного выбора этих правил — они подтягиваются из локали:

new Intl.NumberFormat('en-IN').format(123456789);

Результат:

12,34,56,789

Влияние типа числа на группировку

Группировка применяется к различным типам числовых значений:

  • целые числа
  • числа с плавающей точкой
  • денежные значения
  • проценты

Пример с десятичными числами:

new Intl.NumberFormat('en-US').format(1234567.89);

Результат:

1,234,567.89

FormatJS сохраняет группировку независимо от дробной части.


Группировка и форматирование валют

При работе с валютами группировка становится частью общего формата:

<FormattedNumber
  value={1234567}
  style="currency"
  currency="USD"
/>

Результат:

$1,234,567.00

Для ru-RU:

1 234 567,00 $

Важно, что группировка сохраняется даже при смене знаков валюты и позиции символа.


Синтаксис ICU MessageFormat и числа

FormatJS часто используется не напрямую через Intl, а через ICU MessageFormat.

import { FormattedMessage } from 'react-intl';

<FormattedMessage
  defaultMessage="Баланс: {balance, number}"
  values={{
    balance: 1234567
  }}
/>

Результат:

Баланс: 1 234 567

Группировка здесь не задаётся явно — она наследуется от локали и настроек форматирования числа.


Переопределение поведения через формат-опции

FormatJS позволяет задавать именованные форматы через IntlProvider:

<IntlProvider
  locale="en-US"
  defaultFormats={{
    number: {
      compactGrouping: {
        notation: 'compact',
        useGrouping: true
      }
    }
  }}
>

Использование:

<FormattedNumber
  value={1234567}
  format="compactGrouping"
/>

Результат:

1.2M

Хотя это уже компактная запись, группировка здесь теряет традиционный смысл, но логика форматирования остаётся связанной с числовыми разрядами.


Особенности пробелов как разделителей

В некоторых локалях (например, ru-RU, fr-FR) разделитель групп — это не символ запятой, а пробел или узкий неразрывный пробел (U+202F).

Это важно учитывать при:

  • копировании чисел
  • парсинге строк
  • сравнении значений
const formatted = new Intl.NumberFormat('ru-RU').format(1000000);
// "1 000 000"

В FormatJS этот символ сохраняется как часть строки и не является обычным пробелом.


Влияние опции notation на группировку

Опция notation может изменять отображение числа, но не отменяет группировку, если она допустима:

new Intl.NumberFormat('en-US', {
  notation: 'standard',
  useGrouping: true
}).format(1234567);

При compact группировка заменяется сокращением:

new Intl.NumberFormat('en-US', {
  notation: 'compact',
  useGrouping: true
}).format(1234567);

Результат:

1.2M

Производственные аспекты использования группировки

В реальных приложениях FormatJS группировка важна для:

  • финансовых интерфейсов
  • аналитических панелей
  • CRM и ERP систем
  • отчётности и дашбордов

Ключевой принцип: никогда не формировать разделители вручную через replace или регулярные выражения.

Пример неправильного подхода:

number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');

Проблемы:

  • игнорирует локали
  • ломается на неарабских системах счисления
  • не учитывает CLDR правила

Правильный подход:

new Intl.NumberFormat(locale).format(number);

Взаимодействие группировки с точностью и округлением

Группировка выполняется после применения правил округления:

new Intl.NumberFormat('en-US', {
  maximumFractionDigits: 0
}).format(1234567.89);

Результат:

1,234,568

Сначала происходит округление, затем вставка разделителей.


Итоговые принципы работы FormatJS с группировкой

  • группировка определяется локалью CLDR
  • параметр useGrouping управляет включением и отключением
  • FormatJS полностью делегирует форматирование Intl.NumberFormat
  • разделители могут быть не ASCII-символами
  • ручная реализация группировки приводит к ошибкам локализации
  • поведение всегда зависит от сочетания locale + options + style