Как форматировать числа для разных локалей в 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>Total Users</dt>
      <dd>
        <FormattedNumber
          value={value}
          minimumFractionDigits={0}
          maximumFractionDigits={0}
        />
      </dd>
      <dt>Conversion Rate</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={`Chart showing ${formattedLabel} items`}
        title={formattedLabel}
      />
    </div>
  );
}

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