Next.js(Pages Router) v16에서 다양한 로케일에 맞게 숫자 형식을 지정하는 방법

로케일별 구분 기호로 숫자 표시

문제

숫자는 전 세계적으로 다르게 표기됩니다. 미국에서 10,000.5로 표시되는 숫자는 독일에서 10.000,5가 됩니다. 쉼표와 마침표의 역할이 완전히 바뀝니다. 이것은 선호도나 스타일의 문제가 아니라 가독성의 문제입니다. 독일 사용자가 10,000.5를 보면 그룹 구분 기호를 무시하고 10으로 읽을 수 있습니다. 미국 사용자가 10.000,5를 보면 소수점 구분 기호를 무시하고 만으로 읽을 수 있습니다. 같은 숫자이지만 정반대의 의미입니다.

애플리케이션이 로케일 인식 형식 없이 숫자를 표시하면 사용자를 혼란스럽게 하거나 잘못된 정보를 전달할 위험이 있습니다. 통화 금액, 백분율, 측정값, 통계는 모두 사용자가 어린 시절부터 배운 지역 규칙에 따라 달라집니다.

솔루션

사용자의 로케일을 기반으로 숫자 형식을 지정하고, 소수점 및 그룹 구분 기호에 대한 지역 규칙을 사용합니다. 이렇게 하면 숫자 값이 해당 지역 사용자에게 익숙한 형식 규칙을 따르는 문자열로 변환됩니다.

react-intl은 두 가지 접근 방식을 제공합니다: JSX에서 선언적 포맷팅을 위한 <FormattedNumber> 컴포넌트와 명령형 포맷팅을 위한 useIntl 훅의 formatNumber 메서드입니다. 둘 다 브라우저의 Intl.NumberFormat API에 의존하며 IntlProvider에 구성된 로케일을 자동으로 적용합니다. 포맷된 출력은 천 단위 구분 기호, 소수점 및 숫자 그룹화에 대한 지역별 규칙을 준수합니다.

단계

1. 숫자를 선언적으로 형식 지정하는 컴포넌트 생성

<FormattedNumber> 컴포넌트는 formatNumberIntl.NumberFormat API를 사용하며 Intl.NumberFormatOptions에 해당하는 옵션과 함께 value prop을 받습니다.

import { FormattedNumber } from "react-intl";

export default function ProductPrice({ price }: { price: number }) {
  return (
    <div>
      <FormattedNumber value={price} />
    </div>
  );
}

기본적으로 <FormattedNumber>는 포맷된 숫자를 React.Fragment로 렌더링합니다. 컴포넌트는 가장 가까운 IntlProvider에서 로케일을 읽고 적절한 포맷팅 규칙을 적용합니다.

2. 특정 옵션으로 숫자 형식 지정

숫자가 표시되는 방식을 제어하려면 포맷팅 옵션을 전달하세요. 일반적인 옵션에는 minimumFractionDigits, maximumFractionDigits, style가 있습니다.

import { FormattedNumber } from "react-intl";

export default function Statistics({ value }: { value: number }) {
  return (
    <dl>
      <dt>Total Users</dt>
      <dd>
        <FormattedNumber
          value={value}
          minimumFractionDigits={0}
          maximumFractionDigits={0}
        />
      </dd>
      <dt>Conversion Rate</dt>
      <dd>
        <FormattedNumber
          value={value / 100}
          style="percent"
          minimumFractionDigits={2}
        />
      </dd>
    </dl>
  );
}

이러한 prop은 Intl.NumberFormatOptions에 해당하며, 로케일별 구분 기호를 유지하면서 정밀도와 표현을 제어할 수 있습니다.

3. useIntl 훅으로 명령형 숫자 형식 지정

컴포넌트를 함수 컴포넌트로 표현할 수 있는 경우, useIntl 훅은 formatNumber 메서드를 포함하는 intl 객체에 대한 액세스를 제공합니다.

import { useIntl } from "react-intl";

export default function DataTable({ rows }: { rows: number[] }) {
  const intl = useIntl();

  return (
    <table>
      <tbody>
        {rows.map((value, index) => (
          <tr key={index}>
            <td>{intl.formatNumber(value)}</td>
          </tr>
        ))}
      </tbody>
    </table>
  );
}

formatNumber 함수는 포맷된 숫자 문자열을 반환하며, 숫자로 파싱할 수 있는 값과 NumberFormatOptions을 준수하는 옵션을 받습니다.

4. JSX가 아닌 컨텍스트에서 숫자 형식 지정

컴포넌트를 사용할 수 없는 속성, 변수 또는 기타 컨텍스트에서 포맷된 숫자가 필요한 경우 formatNumber를 사용하세요.

import { useIntl } from "react-intl";

export default function Chart({ dataPoints }: { dataPoints: number[] }) {
  const intl = useIntl();

  const formattedLabel = intl.formatNumber(dataPoints[0], {
    notation: "compact",
    maximumFractionDigits: 1,
  });

  return (
    <div>
      <img
        src="/chart.png"
        alt={`Chart showing ${formattedLabel} items`}
        title={formattedLabel}
      />
    </div>
  );
}

명령형 API는 title, aria-label, alt와 같이 React 컴포넌트를 렌더링할 수 없는 텍스트 속성을 설정하는 데 필수적입니다.