Как форматировать суммы в Next.js (Pages Router) v16
Отображение цен с валютными символами и разделителями
Проблема
Для отображения денежных значений нужно учитывать две разные задачи локализации: саму валюту и правила форматирования чисел в регионе пользователя. Например, сумма 1200.50 должна выглядеть как "$1,200.50" для пользователей из США, но как "1 200,50 €" для пользователей из Германии. Положение символа валюты, разделители целых и дробных частей, группировка разрядов и пробелы — всё это зависит от локали. Если эти элементы заданы неправильно или жёстко прописаны в коде, пользователи видят непривычный формат и начинают сомневаться в правильности суммы, что подрывает доверие к ценам.
Помимо визуальной согласованности, неправильное форматирование валюты может реально запутать пользователя. Тот, кто привык к запятой как к разделителю тысяч, может прочитать "1.200" как один и две десятых, а не как тысячу двести. Аналогично, неверное расположение символа валюты делает цену неаккуратной или даже указывает на другую валюту. Корректное форматирование учитывает и код валюты, и числовые правила локали пользователя, чтобы цены были сразу понятны и вызывали доверие.
Решение
Форматируйте суммы, комбинируя код валюты по ISO 4217 с активной локалью пользователя — так вы получите правильное отображение чисел для нужного региона. Такой подход передаёт расстановку символов, выбор разделителей и пробелов на обработку библиотеке интернационализации, которая применяет нужные правила для каждой пары локаль-валюта. В результате цена отображается так, как ожидает пользователь, без ручной обработки строк или сложной логики в коде приложения.
Шаги
1. Создайте переиспользуемый компонент для форматирования валюты
Сделайте компонент, который принимает числовое значение и код валюты, а затем форматирует цену, используя текущую локаль из контекста react-intl.
import { FormattedNumber } from "react-intl";
interface PriceProps {
value: number;
currency: string;
}
export default function Price({ value, currency }: PriceProps) {
return <FormattedNumber value={value} style="currency" currency={currency} />;
}
Компонент FormattedNumber получает локаль из ближайшего IntlProvider и автоматически применяет символ валюты и правила форматирования чисел для этой локали.
2. Используйте компонент на странице с динамическими ценами
Отображайте цены, передавая числовые значения и коды валют в компонент, чтобы форматирование автоматически подстраивалось под локаль пользователя.
import { GetServerSideProps } from "next";
import Price from "../components/Price";
interface Product {
id: string;
name: string;
price: number;
currency: string;
}
interface ProductPageProps {
product: Product;
}
export default function ProductPage({ product }: ProductPageProps) {
return (
<div>
<h1>{product.name}</h1>
<p>
<Price value={product.price} currency={product.currency} />
</p>
</div>
);
}
export const getServerSideProps: GetServerSideProps = async () => {
const product = {
id: "1",
name: "Wireless Headphones",
price: 1299.99,
currency: "USD",
};
return {
props: {
product,
},
};
};
Страница получает данные о продукте, включая код валюты, и передаёт их в компонент Price, который форматирует сумму в соответствии с активной локалью.
3. Создайте помощник для императивного форматирования валюты
В ситуациях, когда нельзя использовать React-компонент, например, при установке атрибутов или подготовке данных для не-JSX-контекста, используйте функцию форматирования через хук useIntl.
import { useIntl } from "react-intl";
export function useCurrencyFormatter() {
const intl = useIntl();
return (value: number, currency: string): string => {
return intl.formatNumber(value, {
style: "currency",
currency,
});
};
}
Этот хук возвращает функцию, которая форматирует значения валюты в строку — удобно для aria-меток, meta-тегов или других текстовых контекстов, где компоненты не подходят.
4. Применяйте форматтер в атрибутах
Используйте императивный форматтер для заполнения HTML-атрибутов, которым нужен только текст, а не React-элементы.
import { useCurrencyFormatter } from "../hooks/useCurrencyFormatter";
interface ProductCardProps {
name: string;
price: number;
currency: string;
imageUrl: string;
}
export default function ProductCard({
name,
price,
currency,
imageUrl,
}: ProductCardProps) {
const formatCurrency = useCurrencyFormatter();
const priceLabel = formatCurrency(price, currency);
return (
<article aria-label={`${name}, ${priceLabel}`}>
<img src={imageUrl} alt={name} />
<h2>{name}</h2>
<p>{priceLabel}</p>
</article>
);
}
Форматтер создаёт локализованную строку с валютой, которую можно вставить в атрибут aria-label, чтобы вспомогательные технологии озвучивали цены в правильном формате для локали пользователя.