TanStack Start v1で異なるロケールに対応した数値のフォーマット方法

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

問題

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

アプリケーションが地域の表記規則を考慮せずに数値を表示すると、混乱を招き、信頼を損ないます。ユーザーは自分が学んだパターンに従って数値が表示されることを期待しており、そのパターンからの逸脱は、ユーザーに立ち止まり、再解釈し、データが正しいかどうかを疑問視させることになります。

解決策

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

React-intlは、ブラウザに組み込まれたIntl.NumberFormat APIを活用してロケール固有のフォーマットを適用するコンポーネントとフックを提供します。数値とカレントロケールを渡すことで、これらのツールは自動的に正しい区切り記号、桁のグループ化、その他の地域規則を選択します。結果として、手動での文字列操作やコンポーネント内のロケール固有のロジックなしに、ユーザーの期待に沿ったフォーマット済み文字列が得られます。

手順

1. FormattedNumberを使用した数値表示コンポーネントの作成

数値を受け取り、react-intlのFormattedNumberコンポーネントを使用してロケールに適したフォーマットでレンダリングするコンポーネントを構築します。

import { FormattedNumber } from "react-intl";

interface PriceDisplayProps {
  value: number;
}

export function PriceDisplay({ value }: PriceDisplayProps) {
  return (
    <div>
      <FormattedNumber value={value} />
    </div>
  );
}

FormattedNumberコンポーネントは、ロケールの小数点記号と桁区切り記号を自動的に適用します。コンポーネントツリー内の最も近いIntlProviderからロケールを読み取り、それに応じて数値をフォーマットします。

2. 特定のスタイルで数値をフォーマットする

FormattedNumberにスタイルオプションを渡すことで、通貨、パーセンテージ、または単位の数値フォーマットをカスタマイズできます。

import { FormattedNumber } from "react-intl";

interface MetricsProps {
  revenue: number;
  growthRate: number;
  fileSize: number;
}

export function Metrics({ revenue, growthRate, fileSize }: MetricsProps) {
  return (
    <div>
      <p>
        Revenue:{" "}
        <FormattedNumber value={revenue} style="currency" currency="USD" />
      </p>
      <p>
        Growth: <FormattedNumber value={growthRate} style="percent" />
      </p>
      <p>
        Size:{" "}
        <FormattedNumber
          value={fileSize}
          style="unit"
          unit="megabyte"
          unitDisplay="short"
        />
      </p>
    </div>
  );
}

styleプロパティは、フォーマットモードを制御します。通貨フォーマットにはcurrencyコードが必要で、パーセンテージフォーマットは自動的に100を乗算し、単位フォーマットにはunit識別子が必要です。すべてのスタイルは、アクティブなロケールの規則に従います。

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

useIntlフックを使用して、動的な属性の生成や非React APIのデータ準備など、コンポーネントが適さないコンテキストで数値をフォーマットします。

import { useIntl } from "react-intl";

interface ChartTooltipProps {
  dataPoint: number;
}

export function ChartTooltip({ dataPoint }: ChartTooltipProps) {
  const intl = useIntl();
  const formattedValue = intl.formatNumber(dataPoint, {
    minimumFractionDigits: 2,
    maximumFractionDigits: 2,
  });

  return <div title={formattedValue}>{formattedValue}</div>;
}

formatNumberメソッドは、フォーマットされた文字列を即座に返すため、属性、ariaラベル、またはReact要素ではなく文字列としてフォーマットされた値が必要なあらゆるコンテキストに適しています。

4. カスタムフォーマットオプションを適用する

FormattedNumberまたはformatNumberIntl.NumberFormatOptionsを渡すことで、精度、桁区切り、表記法を制御できます。

import { FormattedNumber } from "react-intl";

interface StatisticProps {
  value: number;
  compact?: boolean;
}

export function Statistic({ value, compact }: StatisticProps) {
  return (
    <div>
      <FormattedNumber
        value={value}
        notation={compact ? "compact" : "standard"}
        minimumFractionDigits={0}
        maximumFractionDigits={2}
      />
    </div>
  );
}

notationminimumFractionDigitsmaximumFractionDigitsなどのオプションは、数値の表示方法を制御します。コンパクト表記は、大きな数値を「1.2M」のような省略形に変換し、ロケール固有の省略形を尊重します。