Как форматировать суммы в валюте в React Router v7
Отображение цен с валютными символами и разделителями
Проблема
Отображение цен в веб-приложениях требует учета двух взаимосвязанных аспектов локализации: используемой валюты и региональных стандартов представления денежных значений. Цена в 1200,50 долларов США отображается как "$1,200.50" для пользователей в Соединенных Штатах, но как "1 200,50 $US" во Франции. Позиция символа валюты, разделитель десятичных знаков, группировка тысяч и пробелы различаются в зависимости от локали. Если эти стандарты не соблюдаются, пользователи могут неправильно интерпретировать суммы или усомниться в правильности цены, что подрывает доверие к приложению.
Эта задача усложняется, если приложение обслуживает несколько регионов или позволяет пользователям просматривать цены в разных валютах. Жесткое кодирование строк форматов или ручное размещение символов создает хрупкий код, который ломается при добавлении новых локалей или валют. Без системного подхода поддержание согласованного и корректного отображения цен в приложении становится подверженным ошибкам.
Решение
Форматируйте значения валют с использованием как кода целевой валюты, так и активной локали пользователя. Этот подход передает сложные правила представления валюты API интернационализации браузера, которые уже содержат правила форматирования для сотен комбинаций локалей и валют.
Передавая числовую сумму, код валюты и текущую локаль в функцию форматирования, система автоматически применяет правильные символы, разделители и макет. Это гарантирует, что цены всегда отображаются в формате, знакомом пользователю, независимо от того, какая валюта отображается или в каком регионе находится пользователь.
Шаги
1. Создайте многократно используемый компонент для форматирования валюты
Создайте компонент, который использует FormattedNumber из react-intl с пропсами style и currency для форматирования денежных значений.
import { FormattedNumber } from "react-intl";
interface PriceProps {
amount: number;
currency: string;
}
export function Price({ amount, currency }: PriceProps) {
return (
<FormattedNumber value={amount} style="currency" currency={currency} />
);
}
Компонент FormattedNumber использует API formatNumber и принимает Intl.NumberFormatOptions. Опция style="currency" указывает форматировщику включать символы валюты, а проп currency определяет, какая валюта будет отображаться.
2. Используйте компонент Price в компонентах маршрутов
Отображайте цены в любом компоненте маршрута React Router, передавая числовое значение и код валюты.
import { Price } from "~/components/Price";
export default function ProductPage() {
const product = {
name: "Беспроводные наушники",
price: 129.99,
currency: "USD",
};
return (
<div>
<h1>{product.name}</h1>
<p>
<Price amount={product.price} currency={product.currency} />
</p>
</div>
);
}
Компонент Price автоматически форматирует сумму в соответствии с локалью, настроенной в IntlProvider. Для локали США это отобразится как "$129.99"; для немецкой локали с той же валютой USD это будет "129,99 $".
3. Форматируйте валюту императивно, когда это необходимо
В ситуациях, когда вам нужна отформатированная строка напрямую, используйте хук useIntl для доступа к методу formatNumber.
import { useIntl } from "react-intl";
export default function CheckoutSummary() {
const intl = useIntl();
const subtotal = 89.99;
const tax = 7.2;
const total = subtotal + tax;
const formattedTotal = intl.formatNumber(total, {
style: "currency",
currency: "EUR",
});
return (
<div>
<h2>Сводка заказа</h2>
<dl>
<dt>Промежуточный итог</dt>
<dd>
{intl.formatNumber(subtotal, {
style: "currency",
currency: "EUR",
})}
</dd>
<dt>Налог</dt>
<dd>
{intl.formatNumber(tax, {
style: "currency",
currency: "EUR",
})}
</dd>
<dt>Итого</dt>
<dd aria-label={`Итого: ${formattedTotal}`}>
<strong>{formattedTotal}</strong>
</dd>
</dl>
</div>
);
}
Метод formatNumber принимает значение и объект с опциями FormatNumberOptions. Это полезно, когда вам нужна отформатированная строка для атрибутов, логирования или контекстов вне JSX.
4. Управляйте точностью десятичных знаков для валют без дробных единиц
Некоторые валюты не используют дробные единицы. Настройте количество десятичных знаков в соответствии с конвенциями валюты.
import { FormattedNumber } from "react-intl";
interface PriceProps {
amount: number;
currency: string;
}
export function Price({ amount, currency }: PriceProps) {
const isWholeNumberCurrency = currency === "JPY" || currency === "KRW";
return (
<FormattedNumber
value={amount}
style="currency"
currency={currency}
minimumFractionDigits={isWholeNumberCurrency ? 0 : 2}
maximumFractionDigits={isWholeNumberCurrency ? 0 : 2}
/>
);
}
Японская иена не использует дробные единицы, поэтому суммы отображаются без десятичных знаков. Опции minimumFractionDigits и maximumFractionDigits переопределяют поведение по умолчанию, когда это необходимо.