본문으로 건너뛰기
개발 머꼬
개발 노트JavaScript
hohyeon.dev18

숫자에 쉼표를 직접 찍고 날짜를 문자열로 자르다가 Intl로 정리

  • #Engineering Note
  • #i18n
  • #JavaScript

문제 발생

금액을 표시하려고 직접 만든 함수가 통화가 늘어날 때마다 조건문이 붙었습니다.

const format = (n) => n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
format(123456.789);   // "123,456.789" — 소수점 자리는 그대로 노출

일본 엔은 소수점이 없어야 하고, 독일은 소수점과 천 단위 기호가 반대이며, 인도는 세 자리씩 끊지 않습니다. 조건문으로 담을 수 있는 규칙이 아니었습니다.

원인 분석

숫자·날짜 표기는 로케일마다 다른 규칙의 집합이고, 그 규칙은 CLDR이라는 표준 데이터로 관리됩니다. 직접 만든 포맷 함수는 그중 한 언어의 한 경우만 흉내 낼 뿐입니다.

Intl은 이 데이터를 브라우저와 런타임이 이미 갖고 있는 것으로 처리합니다.

new Intl.NumberFormat("de-DE", { style: "currency", currency: "EUR" }).format(123456.789);
// "123.456,79 €"

new Intl.NumberFormat("ja-JP", { style: "currency", currency: "JPY" }).format(123456.789);
// "¥123,457"  — 엔은 소수점이 없다

다만 주의할 점이 하나 있습니다. MDN은 포매터 인스턴스를 만드는 비용이 크므로, 반복 호출에서는 한 번 만들어 재사용하라고 명시합니다. 매번 toLocaleString(...)을 호출하면 그때마다 포매터가 새로 만들어집니다.

해결 방안

  1. 포매터를 모듈 상단에서 한 번 만들고 재사용합니다.
const currency = new Intl.NumberFormat("ko-KR", { style: "currency", currency: "KRW" });

items.forEach((item) => render(currency.format(item.price)));

반대 예 — 목록이 길수록 손해가 커집니다.

items.forEach((item) =>
  render(item.price.toLocaleString("ko-KR", { style: "currency", currency: "KRW" })),
);
  1. 표시 목적에 맞는 옵션을 씁니다.
목적옵션
통화style: "currency", currency: "KRW"
백분율style: "percent" (0.25 → 25%)
단위style: "unit", unit: "kilometer-per-hour"
축약notation: "compact" (123456 → 123K)
  1. 부분별로 스타일을 다르게 하려면 formatToParts를 씁니다. 통화 기호만 작게 표시하는 식의 디자인이 문자열 자르기 없이 가능해집니다.
currency.formatToParts(1234.5);
// [{type:"currency",...},{type:"integer",...},{type:"group",...}, ...]
  1. 날짜에는 Intl.DateTimeFormat을 씁니다. 이 저장소도 목록에는 사람이 읽는 한국어 형식, 기계에는 <time dateTime>의 ISO 문자열을 각각 제공합니다 — 두 대상의 요구가 다르기 때문입니다.
  2. 로케일을 하드코딩하지 않습니다. undefined를 넘기면 실행 환경의 기본 로케일을 씁니다. 서버 렌더링에서는 서버와 클라이언트의 로케일이 달라 하이드레이션이 어긋날 수 있으므로, 그 경우 로케일을 명시적으로 고정하는 편이 안전합니다.

공식 문서

마지막 수정

좋아요북마크

댓글0

아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.