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コンポーネントは、propsとして同じオプションを受け入れる宣言的な代替手段を提供します。
import { FormattedNumber } from "react-intl";
export default function Statistics() {
const totalUsers = 1500000;
const growthRate = 0.23;
return (
<div>
<p>
Total users: <FormattedNumber value={totalUsers} />
</p>
<p>
Growth rate: <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>Dashboard</h1>
<p>
Revenue:{" "}
<FormattedNumber value={revenue} style="currency" currency="USD" />
</p>
<p>
Units sold: <FormattedNumber value={units} />
</p>
</div>
);
}
ローダーは生の数値データを提供し、コンポーネントはユーザーのロケールに応じてそれをフォーマットします。この分離により、データ取得がプレゼンテーションの関心事から独立した状態を保ちます。