Как форматировать числа для разных локалей в 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-компоненты.