Как форматировать числа для разных локалей в Next.js (Pages Router) v16

Отображение чисел с разделителями, специфичными для локали

Проблема

Числа записываются по-разному в разных странах. То, что в Соединенных Штатах выглядит как 10,000.5, в Германии становится 10.000,5 — запятые и точки полностью меняются местами. Это не вопрос предпочтений или стиля, а вопрос читаемости. Немецкий пользователь, видя 10,000.5, может прочитать это как десять, игнорируя разделители групп. Американский пользователь, видя 10.000,5, может прочитать это как десять тысяч, игнорируя десятичный разделитель. Одни и те же цифры, противоположные значения.

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

Решение

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

react-intl предоставляет два подхода: компонент <FormattedNumber> для декларативного форматирования в JSX и метод formatNumber из хука useIntl для императивного форматирования. Оба используют API браузера Intl.NumberFormat и автоматически применяют локаль, настроенную в вашем IntlProvider. Отформатированный вывод учитывает региональные стандарты для разделителей тысяч, десятичных точек и группировки цифр.

Шаги

1. Создайте компонент для декларативного форматирования чисел

Компонент <FormattedNumber> использует API formatNumber и Intl.NumberFormat и принимает проп value вместе с опциями, соответствующими Intl.NumberFormatOptions.

import { FormattedNumber } from "react-intl";

export default function ProductPrice({ price }: { price: number }) {
  return (
    <div>
      <FormattedNumber value={price} />
    </div>
  );
}

По умолчанию <FormattedNumber> рендерит отформатированное число в React.Fragment. Компонент считывает локаль из ближайшего IntlProvider и применяет соответствующие правила форматирования.

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

Передавайте параметры форматирования, чтобы контролировать, как отображаются числа. Общие параметры включают minimumFractionDigits, maximumFractionDigits и style.

import { FormattedNumber } from "react-intl";

export default function Statistics({ value }: { value: number }) {
  return (
    <dl>
      <dt>Всего пользователей</dt>
      <dd>
        <FormattedNumber
          value={value}
          minimumFractionDigits={0}
          maximumFractionDigits={0}
        />
      </dd>
      <dt>Коэффициент конверсии</dt>
      <dd>
        <FormattedNumber
          value={value / 100}
          style="percent"
          minimumFractionDigits={2}
        />
      </dd>
    </dl>
  );
}

Эти свойства соответствуют Intl.NumberFormatOptions, предоставляя вам контроль над точностью и представлением, сохраняя при этом разделители, специфичные для локали.

3. Императивное форматирование чисел с использованием хука useIntl

Когда компонент может быть представлен в виде функционального компонента, хук useIntl предоставляет доступ к объекту intl, который включает метод formatNumber.

import { useIntl } from "react-intl";

export default function DataTable({ rows }: { rows: number[] }) {
  const intl = useIntl();

  return (
    <table>
      <tbody>
        {rows.map((value, index) => (
          <tr key={index}>
            <td>{intl.formatNumber(value)}</td>
          </tr>
        ))}
      </tbody>
    </table>
  );
}

Функция formatNumber возвращает строку с отформатированным числом и принимает значение, которое может быть преобразовано в число, а также параметры, соответствующие NumberFormatOptions.

4. Форматирование чисел вне контекста JSX

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

import { useIntl } from "react-intl";

export default function Chart({ dataPoints }: { dataPoints: number[] }) {
  const intl = useIntl();

  const formattedLabel = intl.formatNumber(dataPoints[0], {
    notation: "compact",
    maximumFractionDigits: 1,
  });

  return (
    <div>
      <img
        src="/chart.png"
        alt={`График, показывающий ${formattedLabel} элементов`}
        title={formattedLabel}
      />
    </div>
  );
}

Императивный API необходим для установки текстовых атрибутов, таких как title, aria-label или alt, где нельзя использовать React-компоненты.