React Router v7で通貨金額をフォーマットする方法
通貨記号と区切り記号を使用して価格を表示する
問題
Webアプリケーションで価格を表示するには、相互に関連する2つのローカライゼーションの課題に対処する必要があります。使用される通貨と、金額を表示するための地域の慣習です。1200.50米ドルの価格は、米国のユーザーには「$1,200.50」と表示されますが、フランスでは「1 200,50 $US」と表示されます。通貨記号の位置、小数点区切り記号、桁区切り、スペースはすべてロケールによって異なります。これらの慣習が尊重されない場合、ユーザーは金額を誤読したり、価格が正しいかどうか疑問に思ったりする可能性があり、アプリケーションへの信頼が損なわれます。
アプリケーションが複数の地域にサービスを提供する場合、またはユーザーが異なる通貨で価格を表示できるようにする場合、課題はさらに複雑になります。フォーマット文字列をハードコーディングしたり、記号を手動で配置したりすると、新しいロケールや通貨が追加されたときに壊れやすいコードが作成されます。体系的なアプローチがなければ、アプリケーション全体で一貫性のある正確な価格表示を維持することはエラーが発生しやすくなります。
解決策
ターゲット通貨コードとユーザーのアクティブなロケールの両方を使用して通貨値をフォーマットします。このアプローチは、通貨表示の複雑なルールをブラウザの国際化APIに委任します。これらのAPIには、数百のロケールと通貨の組み合わせに対するフォーマット規則がすでにエンコードされています。
数値の金額、通貨コード、現在のロケールをフォーマット関数に渡すことで、システムは正しい記号、区切り記号、レイアウトを自動的に適用します。これにより、表示される通貨やユーザーがいる地域に関係なく、価格は常にユーザーにとって馴染みのある形式で表示されます。
手順
1. 再利用可能な通貨フォーマットコンポーネントを作成する
FormattedNumberとstyleおよびcurrencyプロパティを使用して、金額をフォーマットするreact-intlのコンポーネントを構築します。
import { FormattedNumber } from "react-intl";
interface PriceProps {
amount: number;
currency: string;
}
export function Price({ amount, currency }: PriceProps) {
return (
<FormattedNumber value={amount} style="currency" currency={currency} />
);
}
FormattedNumberコンポーネントはformatNumber APIを使用し、Intl.NumberFormatOptionsを受け入れます。style="currency"オプションは、フォーマッターに通貨記号を含めるように指示し、currencyプロパティは表示する通貨を指定します。
2. ルートコンポーネントでPriceコンポーネントを使用する
数値と通貨コードを渡すことで、任意のReact Routerルートコンポーネントで価格をレンダリングします。
import { Price } from "~/components/Price";
export default function ProductPage() {
const product = {
name: "Wireless Headphones",
price: 129.99,
currency: "USD",
};
return (
<div>
<h1>{product.name}</h1>
<p>
<Price amount={product.price} currency={product.currency} />
</p>
</div>
);
}
Priceコンポーネントは、IntlProviderで設定されたロケールに従って金額を自動的にフォーマットします。米国ロケールの場合は「$129.99」とレンダリングされ、同じUSD通貨のドイツロケールの場合は「129,99 $」とレンダリングされます。
3. 必要に応じて命令的に通貨をフォーマットする
フォーマットされた文字列を直接必要とするシナリオでは、useIntlフックを使用してformatNumberメソッドにアクセスします。
import { useIntl } from "react-intl";
export default function CheckoutSummary() {
const intl = useIntl();
const subtotal = 89.99;
const tax = 7.2;
const total = subtotal + tax;
const formattedTotal = intl.formatNumber(total, {
style: "currency",
currency: "EUR",
});
return (
<div>
<h2>Order Summary</h2>
<dl>
<dt>Subtotal</dt>
<dd>
{intl.formatNumber(subtotal, {
style: "currency",
currency: "EUR",
})}
</dd>
<dt>Tax</dt>
<dd>
{intl.formatNumber(tax, {
style: "currency",
currency: "EUR",
})}
</dd>
<dt>Total</dt>
<dd aria-label={`Total: ${formattedTotal}`}>
<strong>{formattedTotal}</strong>
</dd>
</dl>
</div>
);
}
formatNumberメソッドは、値とFormatNumberOptionsを含むオプションオブジェクトを受け入れます。これは、属性、ログ記録、または非JSXコンテキストでフォーマットされた文字列が必要な場合に便利です。
4. 整数通貨の小数点精度を制御する
一部の通貨は小数単位を使用しません。通貨の慣例に合わせて小数点以下の桁数を調整します。
import { FormattedNumber } from "react-intl";
interface PriceProps {
amount: number;
currency: string;
}
export function Price({ amount, currency }: PriceProps) {
const isWholeNumberCurrency = currency === "JPY" || currency === "KRW";
return (
<FormattedNumber
value={amount}
style="currency"
currency={currency}
minimumFractionDigits={isWholeNumberCurrency ? 0 : 2}
maximumFractionDigits={isWholeNumberCurrency ? 0 : 2}
/>
);
}
日本円は補助単位を使用しないため、金額は小数点なしで表示されます。minimumFractionDigitsおよびmaximumFractionDigitsオプションは、必要に応じてデフォルトの小数点動作を上書きします。