Next.js(ページルーター)v16で異なるロケールの数値をフォーマットする方法

ロケール固有の区切り記号で数値を表示する

問題

数字は世界中で異なる方法で表記されています。アメリカ合衆国では10,000.5と表示される数字が、ドイツでは10.000,5となり、カンマとピリオドが完全に入れ替わります。これは好みやスタイルの問題ではなく、可読性の問題です。ドイツのユーザーが10,000.5を見ると、桁区切り記号を無視して「10」と読む可能性があります。アメリカのユーザーが10.000,5を見ると、小数点を無視して「1万」と読む可能性があります。同じ数字でも、正反対の意味になるのです。

アプリケーションがロケールを考慮せずに数字を表示すると、ユーザーを混乱させたり、誤った情報を伝えたりするリスクがあります。通貨金額、パーセンテージ、測定値、統計データはすべて、ユーザーが幼少期から学んできた地域の慣習に依存しています。

解決策

ユーザーのロケールに基づいて数字をフォーマットし、小数点や桁区切り記号に地域のルールを使用します。これにより、数値がユーザーの地域で馴染みのあるフォーマットルールに従った文字列に変換されます。

react-intlは2つのアプローチを提供しています:JSXで宣言的にフォーマットするための<FormattedNumber>コンポーネントと、命令的にフォーマットするためのuseIntlフックからのformatNumberメソッドです。どちらもブラウザのIntl.NumberFormat APIに依存し、IntlProviderで設定されたロケールを自動的に適用します。フォーマットされた出力は、千の位区切り記号、小数点、数字のグループ化に関する地域の慣習を尊重します。

ステップ

1. 宣言的に数字をフォーマットするコンポーネントを作成する

<FormattedNumber>コンポーネントはformatNumberおよびIntl.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からロケールを読み取り、適切なフォーマットルールを適用します。

2. 特定のオプションで数値をフォーマットする

フォーマットオプションを渡して、数値の表示方法を制御します。一般的なオプションには minimumFractionDigitsmaximumFractionDigitsstyle などがあります。

import { FormattedNumber } from "react-intl";

export default function Statistics({ value }: { value: number }) {
  return (
    <dl>
      <dt>総ユーザー数</dt>
      <dd>
        <FormattedNumber
          value={value}
          minimumFractionDigits={0}
          maximumFractionDigits={0}
        />
      </dd>
      <dt>コンバージョン率</dt>
      <dd>
        <FormattedNumber
          value={value / 100}
          style="percent"
          minimumFractionDigits={2}
        />
      </dd>
    </dl>
  );
}

これらのプロパティは Intl.NumberFormatOptions に対応しており、ロケール固有の区切り文字を維持しながら、精度と表示方法を制御できます。

3. useIntl フックを使用して命令的に数値をフォーマットする

コンポーネントが関数コンポーネントとして表現できる場合、useIntl フックは formatNumber メソッドを含む intl オブジェクトへのアクセスを提供します。

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={`${formattedLabel}項目を表示するチャート`}
        title={formattedLabel}
      />
    </div>
  );
}

命令的APIは、Reactコンポーネントをレンダリングできない titlearia-labelalt などのテキスト属性を設定する際に不可欠です。