Диапазоны значений

В международных интерфейсах диапазоны значений используются для компактного представления интервалов чисел, дат, денежных сумм и других упорядоченных величин. Вместо перечисления «от 1 до 10» применяется локализованная форма, зависящая от языка, правил типографики и соглашений конкретной локали. В экосистеме FormatJS работа с диапазонами строится поверх возможностей Intl и расширяется API библиотеки react-intl.


Форматирование числовых диапазонов

Современные реализации ECMAScript Internationalization API поддерживают метод форматирования диапазонов чисел через Intl.NumberFormat.prototype.formatRange. FormatJS использует этот механизм через собственные обёртки и полифилы.

const nf = new Intl.NumberFormat('ru-RU');

nf.formatRange(1, 10);
// "1–10"

Для локали en-US результат будет отличаться:

const nf = new Intl.NumberFormat('en-US');

nf.formatRange(1, 10);
// "1–10"

Несмотря на одинаковый визуальный результат в некоторых локалях, внутренние правила форматирования зависят от языка: пробелы, тип тире, группировка чисел и сокращения могут изменяться.


Поддержка диапазонов в FormatJS

В FormatJS диапазоны чисел доступны через API formatNumberRange в react-intl:

import { useIntl } from 'react-intl';

function Component() {
  const intl = useIntl();

  const value = intl.formatNumberRange(1000, 5000, {
    style: 'decimal'
  });

  return value;
}

Поведение метода наследует возможности Intl.NumberFormat, включая настройки:

  • style: 'decimal' | 'currency' | 'percent'
  • локализацию разделителей
  • компактное представление чисел

Диапазоны денежных значений

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

intl.formatNumberRange(100, 500, {
  style: 'currency',
  currency: 'USD'
});

В зависимости от локали возможны варианты:

  • $100–$500
  • 100–500 $ (в некоторых европейских локалях)
  • с сокращением валютных символов при compact-режиме

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


Диапазоны дат и времени

Работа с датами и временем реализуется через formatDateTimeRange в react-intl. Это один из наиболее часто используемых типов диапазонов в интерфейсах календарей, логов и аналитики.

intl.formatDateTimeRange(
  new Date(2026, 0, 1),
  new Date(2026, 0, 10),
  {
    year: 'numeric',
    month: 'short',
    day: 'numeric'
  }
);

Результат зависит от локали:

  • 1–10 янв. 2026 (ru)
  • Jan 1 – Jan 10, 2026 (en-US)

Библиотека автоматически:

  • убирает повторяющиеся части (например, год или месяц)
  • сокращает формат для компактного отображения
  • применяет локальные правила разделителей

Сокращение повторяющихся элементов

Ключевая особенность форматирования диапазонов — устранение дублирования информации. Например, если обе даты находятся в одном месяце, месяц может быть указан один раз:

// вход
2026-01-01 → 2026-01-10

// выход (en-US)
Jan 1–10, 2026

Если диапазон пересекает месяцы:

2026-01-28 → 2026-02-03

// результат
Jan 28 – Feb 3, 2026

Такая оптимизация реализуется внутри Intl и не требует ручной логики.


Локализация тире и разделителей

В разных языках используется различный тип разделителя диапазона:

  • en-US: en dash
  • ru-RU: en dash с пробелами или без
  • fr-FR: специфические пробельные правила
  • de-DE: часто с пробелами вокруг тире

FormatJS не фиксирует символ напрямую, а делегирует выбор ICU-слою.


Диапазоны с процентами и относительными значениями

Числовые диапазоны могут применяться к процентам:

intl.formatNumberRange(0.1, 0.25, {
  style: 'percent'
});

Результаты:

  • 10–25 % (ru)
  • 10%–25% (en-US)

Особенность заключается в том, что форматтер применяет процентное преобразование к каждой границе диапазона.


Поведение при округлении

Диапазоны подчиняются правилам округления Intl.NumberFormat:

intl.formatNumberRange(1.234, 5.678, {
  maximumFractionDigits: 1
});

Результат может быть:

  • 1.2–5.7

Если включено компактное представление:

intl.formatNumberRange(1000, 5000, {
  notation: 'compact'
});

Возможный вывод:

  • 1K–5K

Обе границы форматируются независимо, но по одинаковым правилам.


Использование в React-компонентах

В react-intl диапазоны часто применяются в UI-компонентах без явного вызова intl:

import { FormattedNumberRange } from 'react-intl';

function PriceRange() {
  return (
    <FormattedNumberRange
      valueStart={100}
      valueEnd={500}
      style="currency"
      currency="EUR"
    />
  );
}

Компонент обеспечивает:

  • автоматическую реактивность к смене локали
  • корректное обновление при изменении props
  • единообразие с остальными форматерами react-intl

Диапазоны и типы данных

FormatJS различает несколько семантических категорий диапазонов:

  • числовые диапазоны (NumberFormat)
  • временные диапазоны (DateTimeFormat)
  • пользовательские диапазоны (через композицию сообщений ICU)

Каждый тип использует собственный форматтер ICU, но интерфейс API остаётся единообразным.


Ограничения и особенности реализации

Форматирование диапазонов зависит от поддержки среды:

  • старые браузеры без formatRange используют полифил
  • поведение может отличаться при неполной поддержке ICU
  • сложные локали требуют полной ICU-таблицы данных

Также важно учитывать, что:

  • диапазон всегда интерпретируется как упорядоченная пара значений
  • автоматическое слияние частей строки не всегда предсказуемо при нестандартных форматах
  • кастомные символы валют или единиц могут нарушать слияние отображения

Взаимодействие с форматированием единиц измерения

Диапазоны могут комбинироваться с единицами:

intl.formatNumberRange(1, 10, {
  style: 'unit',
  unit: 'kilometer'
});

Результат:

  • 1–10 км
  • 1–10 km

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


Композиция диапазонов в сообщениях ICU

Хотя ICU MessageFormat не имеет специального синтаксиса диапазонов, они могут внедряться через аргументы:

{
  "range": "{start}–{end} км"
}
intl.formatMessage(message, {
  start: intl.formatNumber(1),
  end: intl.formatNumber(10)
});

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


Производительность форматирования диапазонов

Диапазоны требуют двух независимых вызовов форматирования:

  • форматирование левой границы
  • форматирование правой границы

При массовом рендеринге списков диапазонов это может влиять на производительность. В таких случаях применяются стратегии:

  • мемоизация Intl.NumberFormat
  • кеширование результатов форматирования
  • предварительное вычисление значений на уровне данных

Поведение при равных значениях

Если границы диапазона совпадают:

intl.formatNumberRange(5, 5);

Результат может быть:

  • 5

или в некоторых локалях:

  • 5–5

Поведение зависит от реализации ICU и настроек форматтера.