React Router v7で異なるロケールの数値をフォーマットする方法
ロケール固有の区切り文字で数値を表示する
問題
数字は世界中で異なる方法で表記されています。アメリカでは10,000.5と表示される数字が、ドイツでは10.000,5となり、カンマとピリオドが完全に入れ替わります。これは好みやスタイルの問題ではなく、可読性の問題です。ドイツのユーザーが10,000.5を見ると、桁区切り記号を無視して「10」と読む可能性があります。アメリカのユーザーが10.000,5を見ると、小数点を無視して「1万」と読む可能性があります。
同じ数字でも、読み手の地域の慣習によって正反対の意味を伝えることがあります。アプリケーションがロケールを考慮せずに生の数値を表示すると、ユーザーを混乱させ、表示されているデータへの信頼を損なうリスクがあります。
解決策
ユーザーのロケールに基づいて数値をフォーマットし、小数点と桁区切り記号に地域のルールを使用します。これにより、数値はユーザーの地域で馴染みのあるフォーマットルールに従った文字列に変換されます。
React-intlは、ブラウザの組み込みIntl.NumberFormat APIを活用するフォーマットメソッドを提供し、地域ごとの数値表記の複雑さを処理します。これらのフォーマッターを通して数値を渡すことで、アプリケーションは手動で区切り文字のロジックを実装することなく、ユーザーの期待に合った出力を生成します。
ステップ
1. useIntlを使用して数値をフォーマットするコンポーネントを作成する
useIntlフックは、数値を受け取りロケールに合わせてフォーマットされた文字列を返すformatNumberなどのフォーマットメソッドへのアクセスを提供します。
import { useIntl } from "react-intl";
export default function ProductPrice() {
const intl = useIntl();
const price = 1234.56;
return (
<div>
<p>Price: {intl.formatNumber(price)}</p>
</div>
);
}
formatNumberメソッドは現在のロケールに合わせて正しい区切り文字を自動的に適用します。ロケールがen-USのユーザーには「1,234.56」と表示され、ロケールがde-DEのユーザーには「1.234,56」と表示されます。
2. スタイルオプションで通貨値をフォーマットする
formatNumberメソッドは、通貨フォーマットを含むIntl.NumberFormatOptionsに準拠したオプションを受け入れます。
import { useIntl } from "react-intl";
export default function ProductPrice() {
const intl = useIntl();
const price = 1234.56;
return (
<div>
<p>
{intl.formatNumber(price, {
style: "currency",
currency: "USD",
})}
</p>
</div>
);
}
これにより、en-USでは「$1,234.56」、de-DEでは「1.234,56 $」が生成され、区切り文字と通貨記号の規則の両方が適用されます。
3. 宣言的フォーマットにFormattedNumberを使用する
FormattedNumberコンポーネントは、同じオプションをプロパティとして受け入れる宣言的な代替手段を提供します。
import { FormattedNumber } from "react-intl";
export default function Statistics() {
const totalUsers = 1500000;
const growthRate = 0.23;
return (
<div>
<p>
総ユーザー数: <FormattedNumber value={totalUsers} />
</p>
<p>
成長率: <FormattedNumber value={growthRate} style="percent" />
</p>
</div>
);
}
このコンポーネントはフォーマットされた数値を直接DOMにレンダリングします。en-USでは、「1,500,000」と「23%」が表示されます。de-DEでは、「1.500.000」と「23 %」が表示されます。
4. ローダーデータから数値をフォーマットする
React Router 7では、ルートコンポーネントはuseLoaderDataフックを介してローダーデータにアクセスします。これを数値フォーマットと組み合わせて、サーバーから提供された値を表示します。
import { useLoaderData } from "react-router";
import { FormattedNumber } from "react-intl";
export async function loader() {
return {
revenue: 45678.9,
units: 12500,
};
}
export default function Dashboard() {
const { revenue, units } = useLoaderData<typeof loader>();
return (
<div>
<h1>ダッシュボード</h1>
<p>
収益:{" "}
<FormattedNumber value={revenue} style="currency" currency="USD" />
</p>
<p>
販売数: <FormattedNumber value={units} />
</p>
</div>
);
}
ローダーは生の数値データを提供し、コンポーネントはユーザーのロケールに従ってそれをフォーマットします。この分離により、データ取得はプレゼンテーションの懸念事項から独立しています。