如何在 Next.js(Pages Router)v16 中格式化货币金额

以货币符号和分隔符显示价格

问题

显示货币金额时,需要同时考虑两方面的本地化问题:货币本身以及用户所在地区的数字格式规范。例如,1200.50 这个价格,对于美国英语用户应显示为 "$1,200.50",而对于德国用户则应为 "1 200,50 €"。货币符号的位置、小数点分隔符、千位分隔符和空格等细节都会因地区而异。如果这些元素设置不当或被硬编码,用户就会看到不熟悉的格式,从而对金额的正确性产生怀疑,影响对价格信息的信任。

除了视觉一致性之外,错误的货币格式还会引发实际的困惑。习惯用逗号作为千位分隔符的用户,可能会把 "1.200" 理解为 1.2 而不是 1200。同样,货币符号位置不当也会让价格看起来不专业,甚至误导为其他货币。正确的货币格式应同时遵循货币代码和用户所在地区的数字规范,确保价格信息一目了然、值得信赖。

解决方案

通过将 ISO 4217 货币代码与用户当前的 locale 结合,格式化货币金额,从而生成符合地区规范的数字格式。这样可以将符号位置、分隔符选择和空格规则交由国际化库处理,自动应用每个地区和货币的正确规范。最终,价格显示能够完全符合用户预期,无需手动拼接字符串或在应用代码中编写特定于地区的逻辑。

步骤

1. 创建可复用的货币格式化组件

构建一个组件,接收数值和货币代码,并利用 react-intl 的 context 获取当前 locale,自动格式化价格。

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 读取 locale,并自动应用货币符号和特定于 locale 的数字格式化规则。

2. 在页面中使用该组件处理动态定价数据

通过将数值金额和货币代码传递给组件来渲染价格,使格式能够根据用户的 locale 自动适配。

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 组件,由该组件根据当前 locale 格式化金额。

3. 创建用于命令式货币格式化的辅助函数

在无法使用 React 组件的场景下(如设置属性或为非 JSX 上下文准备数据),可通过 useIntl hook 暴露一个格式化函数。

import { useIntl } from "react-intl";

export function useCurrencyFormatter() {
  const intl = useIntl();

  return (value: number, currency: string): string => {
    return intl.formatNumber(value, {
      style: "currency",
      currency,
    });
  };
}

该 hook 返回一个将货币值格式化为字符串的函数,适用于 aria 标签、meta 标签或其他仅支持文本的场景,无法使用组件时非常有用。

4. 在属性上下文中应用格式化器

使用命令式格式化器为需要纯文本而非 React 元素的 HTML 属性赋值。

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 属性中,确保辅助技术以用户 locale 的正确格式播报价格。