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>
        収益:{" "}
        <FormattedNumber value={revenue} style="currency" currency="USD" />
      </p>
      <p>
        成長率: <FormattedNumber value={growthRate} style="percent" />
      </p>
      <p>
        サイズ:{" "}
        <FormattedNumber
          value={fileSize}
          style="unit"
          unit="megabyte"
          unitDisplay="short"
        />
      </p>
    </div>
  );
}

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

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

コンポーネントが適さない状況、例えば動的な属性の生成やReact以外のAPIのデータ準備などでは、useIntlフックを使用して数値をフォーマットします。

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」のような省略形に変換し、ロケール固有の省略形を尊重します。