Next.js(Pages Router)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 propと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>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>
  );
}

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

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

コンポーネントを関数コンポーネントとして表現できる場合、useIntlフックは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コンポーネントをレンダリングできません。