Как форматировать суммы в 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: "Беспроводные наушники",
    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-меток, мета-тегов или других текстовых контекстов, где компоненты не подходят.

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, обеспечивая корректное объявление цен технологиями вспомогательного доступа в формате, соответствующем локали пользователя.