如何在 Next.js(Pages Router)v16 中为不同地区格式化数字

使用本地化分隔符显示数字

问题

世界各地的数字书写方式各不相同。在美国,10,000.5 表示一万零零点五,而在德国则写作 10.000,5——逗号和句点的作用完全对调。这不仅仅是风格或偏好的问题,而是可读性的问题。德国用户看到 10,000.5 可能会把它读作十,忽略分组符号。美国用户看到 10.000,5 可能会把它读作一万,忽略小数点。相同的数字,不同的含义。

如果应用程序在显示数字时没有考虑本地化格式,用户可能会感到困惑,甚至获得错误的信息。货币金额、百分比、计量单位和统计数据都依赖于用户从小习惯的地区规范。

解决方案

根据用户的地区格式化数字,遵循本地的千位分隔符和小数点规则。这样可以将数值转换为用户熟悉的格式化字符串,提升可读性和准确性。

react-intl 提供了两种方式:在 JSX 中声明式格式化的 <FormattedNumber> 组件,以及通过 useIntl hook 获取的 formatNumber 方法进行命令式格式化。两者都依赖于浏览器的 Intl.NumberFormat API,并自动应用 IntlProvider 中配置的地区。格式化结果会遵循本地的千位分隔符、小数点和数字分组规则。

步骤

1. 创建一个声明式格式化数字的组件

<FormattedNumber> 组件结合了 formatNumberIntl.NumberFormat API,并接受 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 读取 locale,并应用相应的格式化规则。

2. 使用特定选项格式化数字

通过传递格式化选项来控制数字的显示方式。常用选项包括 minimumFractionDigitsmaximumFractionDigitsstyle

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>
  );
}

这些 props 对应 Intl.NumberFormatOptions,让你可以控制精度和展示方式,同时保持本地化分隔符。

3. 使用 useIntl hook 以命令式格式化数字

当组件可以用函数组件表达时,useIntl hook 可访问 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 对于设置 titlearia-labelalt 等文本属性非常重要,此时无法渲染 React 组件。