React Router v7에서 통화 금액을 포맷하는 방법

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

문제

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

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

해결책

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

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

단계

1. 재사용 가능한 통화 형식 지정 컴포넌트 만들기

react-intl의 FormattedNumberstylecurrency 속성과 함께 사용하여 화폐 값을 형식화하는 컴포넌트를 구축합니다.

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: "무선 헤드폰",
    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>주문 요약</h2>
      <dl>
        <dt>소계</dt>
        <dd>
          {intl.formatNumber(subtotal, {
            style: "currency",
            currency: "EUR",
          })}
        </dd>
        <dt>세금</dt>
        <dd>
          {intl.formatNumber(tax, {
            style: "currency",
            currency: "EUR",
          })}
        </dd>
        <dt>합계</dt>
        <dd aria-label={`합계: ${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 옵션은 필요할 때 기본 소수점 동작을 재정의합니다.