React Router v7에서 통화 금액 형식 지정하는 방법

통화 기호와 구분 기호를 사용하여 가격 표시

문제

웹 애플리케이션에서 가격을 표시하려면 서로 연관된 두 가지 현지화 문제를 처리해야 합니다. 사용되는 통화와 금액 표시에 대한 지역별 규칙입니다. 1200.50 미국 달러의 가격은 미국 사용자에게는 "$1,200.50"으로 표시되지만 프랑스에서는 "1 200,50 $US"로 표시됩니다. 통화 기호 위치, 소수점 구분 기호, 천 단위 그룹화 및 간격은 모두 로케일에 따라 다릅니다. 이러한 규칙이 준수되지 않으면 사용자가 금액을 잘못 읽거나 가격이 올바른지 의문을 가질 수 있으며, 이는 애플리케이션에 대한 신뢰를 훼손합니다.

애플리케이션이 여러 지역에 서비스를 제공하거나 사용자가 다양한 통화로 가격을 볼 수 있도록 허용하는 경우 문제가 더욱 복잡해집니다. 형식 문자열을 하드코딩하거나 기호를 수동으로 배치하면 새로운 로케일이나 통화가 추가될 때 손상되는 취약한 코드가 생성됩니다. 체계적인 접근 방식이 없으면 애플리케이션 전체에서 일관되고 정확한 가격 표시를 유지하는 것이 오류가 발생하기 쉽습니다.

솔루션

대상 통화 코드와 사용자의 활성 로케일을 모두 사용하여 통화 값의 형식을 지정합니다. 이 접근 방식은 통화 표시의 복잡한 규칙을 브라우저의 국제화 API에 위임하며, 이 API는 이미 수백 개의 로케일-통화 조합에 대한 형식 지정 규칙을 인코딩하고 있습니다.

숫자 금액, 통화 코드 및 현재 로케일을 형식 지정 함수에 전달하면 시스템이 자동으로 올바른 기호, 구분 기호 및 레이아웃을 적용합니다. 이를 통해 표시되는 통화나 사용자가 있는 지역에 관계없이 가격이 항상 사용자에게 익숙한 형식으로 표시됩니다.

단계

1. 재사용 가능한 통화 형식 지정 컴포넌트 생성

FormattedNumberstylecurrency 속성을 사용하여 금액 값을 형식화하는 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}
    />
  );
}

일본 엔화는 보조 단위를 사용하지 않으므로 금액이 소수점 없이 표시됩니다. minimumFractionDigitsmaximumFractionDigits 옵션은 필요한 경우 기본 소수점 동작을 재정의합니다.