• Posts
  • Works
  • Etc
github

당신의 만 원, 베를린에서는 십 원일 수 있습니다

javascript toLocaleString 파헤치기

2024-04-18

서비스에 꽤 심각한 UI 버그가 있었다. 상품 상세페이지에서 가격이 간헐적으로 실제 가격과 다르게 보이는 이슈였다. $13.61짜리 상품이 $1361로 보이는 식이었다. 가격이 이상하다는 CS가 지속적으로 인입되는데, 전혀 재현이 되지 않았다. 다행히 장바구니에 넣거나 주문 페이지로 가면 정상적인 가격으로 보이고 결제되었는데, 대체 왜 서버에서 내려준 동일한 가격이 유저마다 다르게 보여지는 건지 알 수 없었다.

그래서 혹시 프론트엔드에서 서버가 내려주는 가격을 가공하는 부분이 있나 살펴봤다. 일반적으로 프론트엔드에서는 가격이나 수량 같은 숫자를 보여줄 때 적당한 곳에 쉼표를 찍어 가독성을 높여주기 위해 javascript의 toLocaleString() 메서드를 사용한다. 서버에서 가격으로 13000을 내려주면 13,000이라는 문자열로 바꿔주는 식이다. 단순하게 숫자를 읽기 쉽게 바꿔줌이라는 식으로 생각하고 관습적으로 사용했던 메서드가 의심스럽기 시작했다. $13.61이 $1361이 되는 기적… toLocaleString과 연관이 있을까?

🔎 toLocaleString의 반환값 살펴보기

toLocaleString이라는 메서드명에서도 알 수 있듯 이 메서드는 주어진 매개변수를 유저의 locale에 적합한 문자열로 바꿔주는 함수다. 매개변수가 동일해도 locale이 달라진다면 반환값이 달라지리라는 것을 유추해볼 수 있다. 지금까지는 유저의 locale이 default locale로 들어가게 하기 위해서 매개변수에 아무것도 넣지 않았는데(toLocaleString 메서드에 아무 locale도 넣지 않으면 유저 OS의 default locale(Intl.DateTimeFormat().resolvedOptions().locale)이 감지된다), 테스트로 매개변수에 여러 locale값을 삽입하며 아래와 같은 충격적인 결과를 얻게 된다. (MDN)

const number = 123456.789;

// 영국을 제외한 유럽에서는 천단위 절삭을 위해 쉼표를 사용한다
console.log(number.toLocaleString("de-DE"));
// 123.456,789

// 아랍어를 사용하는 대부분의 아랍 국가에서는 동부 아라비아 숫자를 사용한다
console.log(number.toLocaleString("ar-EG"));
// ١٢٣٤٥٦٫٧٨٩

// 인도에서는 천/십만/천만 단위 절삭을 위해 쉼표를 사용한다
console.log(number.toLocaleString("en-IN"));
// 1,23,456.789

// nu 확장키로 중국 십진법과 같은 수체계를 반환할 수 있다
console.log(number.toLocaleString("zh-Hans-CN-u-nu-hanidec"));
// 一二三,四五六.七八九

// 발리어 같이 지원하지 않을 수 있는 언어를 요청할 때는 fallback 언어를 포함할 수 있다
console.log(number.toLocaleString(["ban", "id"]));
// 123.456,789

toLocaleString() 의 반환값이 위와 같이 다양한데, 서비스에서는 특정 상점에서 소수점 아래 단위를 두 자리까지 보여주기 위해 다시 해당 문자열을 숫자로 변환하는 포맷팅을 실행하고 있던 것이다. toLocaleString을 통해 변환된 문자열에서 단지 쉼표만 제거하면 다시 숫자로 만들 수 있을 것이라 가정하고 만든 로직이었기 때문에 당연히 다양한 반환값을 커버하지 못했고 버그가 생기게 됐다.

const price = 16.02;

const priceString = price.toLocaleString(); // '16.02' | '16,02' | etc..

// 🚨 string을 다시 number로 변환하는 과정에서 자릿수가 변경되어 가격이 잘못 표시됨
const priceStringToNumber = Number(priceString.replaceAll(',', '')); // '16.02' | '1602' | etc..

해당 버그는 크롬 브라우저 개발자도구의 Sensor 탭을 통해 Location을 Berlin으로 변경해주었더니 재현됐다. 제목처럼, 베를린에서는 10000이 우리에게 익숙한 ‘10,000’이 아닌 ‘10.000’으로 변환된다. 만 원이 십 원이 됐다고 당황하지 말고 지역에 따라 다르게 읽자. 영국을 제외한 유럽을 포함해 꽤 광범위하게 사용되는 표기법이라, 가격을 포함한 숫자를 많이 다루어야 하는 글로벌 커머스에서는 꼭 인지해야 하는 부분임을 알게 된 놀랍고 즐거운 디버깅 경험이었다.