자바스크립트의 Intl API로 국제화하기
이번 포스팅에서는 다국어 지원을 할 때 정말 유용하게 쓸 수 있는 자바스크립트의 Intl API에 대해 알아보도록 하겠습니다.
Intl API 소개
여러 가지 언어로 서비스를 할 수 있도록 웹 애플리케이션을 설계하고 구현하는 과정을 흔히 국제화(internalization, i18n)라고 합니다. 이를 위해서는 웹페이지 상에 사용된 문구들을 단순히 번역하여 표시해주는 것만 뿐만 아니라 동일한 데이터를 언어나 지역별로 다른 형식으로 보여줘야 하는데요.
예를 들어, 12/11/21
로 표시된 날짜 데이터를 한국 사용자들은 2012년 11월 21일로 생각하지만, 미국 사용자들에게는 12월 11일 2021년으로 받아들여질 것이며, 어떤 다른 나라에서는 2021년 11월 12일로 읽힐 수도 있을 것입니다.
이렇게 언어나 지역별로 다른 형식으로 표시되야 하는 데이터를 일일이 다르게 처리하려면 상당한 노력이 필요할 텐데요.
이러한 처리를 대신해주는 외부 라이브러리를 사용할 수 있지만, 사용자가 브라우저를 통해 해당 자바스크립트 코드를 다운로드 받아야 한다는 부작용이 있죠.
다행히도 자바스크립트에는 이러한 데이터 포맷팅 문제를 해결해주기 위해서 Intl API라는 웹 표준 API가 있습니다. Intl API는 대부분의 모던 브라우저에서 지원되며 Node.js나 Bun과 같은 서버 런타임(runtime)에서도 사용이 가능하기 때문에 다국어 지원을 하는 서비스에서 백엔드나 프론트엔드 가리지 않고 유용하게 사용할 수 있습니다.
Intl API 사용법
Intl API는 전역에서 접근이 가능한 Intl
객체를 통해 사용이 가능하며, 이 Intl
객체는 여러 개의 생성자로 구성되어 있습니다.
기본적으로 어떤 종류의 데이터를 포맷팅하느냐에 따라서 그에 해당하는 생성자를 사용해야하며, 모든 생성자는 공통적으로 2개의 인자를 받습니다.
첫번째 인자는 소위 로케일(locale)이라고 일컽는 언어와 지역 정보를 표준화 시킨 코드이며, 두번째 인자는 추가적인 포맷팅 옵션을 명시할 수 있는 객체입니다.
생성자는 실제로 포맷팅을 수행할 수 있는 포맷터(formatter) 객체를 반환하며, 이 포맷터에서 제공하는 format()
과 같은 함수에 국제화가 필요한 데이터를 인자로 넘기면 해당 언어/지역에 맞는 형식을 적용해줍니다.
예를 들어, 현재 날짜를 한국 사용자들을 위한 형식으로 변환하고 싶다면 DateTimeFormat()
생성자로 포맷터를 생성한 객체의 format()
함수를 호출하면 됩니다.
> const koDtf = new Intl.DateTimeFormat("ko", { dateStyle: "long" });
undefined
> koDtf.format(new Date())
'2022년 3월 8일'
Intl API가 이런 식으로 우선 포매터 객체를 만들고 메서드를 호출하도록 설계된 이유는 포맷터를 한번 생성해놓으면 여러 곳에서 재사용할 수 있고 수시로 로케일과 옵션이 바뀌는 일이 잦지 않기 때문인데요. 만약에 일회성으로 포맷팅이 필요한 경우에는 다음과 같이 한 줄로 코딩을 할 수 있습니다.
> new Intl.DateTimeFormat("ko", { dateStyle: "long" }).format(new Date());
'2022년 3월 8일'
> new Intl.DateTimeFormat("en", { dateStyle: "long" }).format(new Date());
'March 8, 2022'
> new Intl.DateTimeFormat("zh", { dateStyle: "long" }).format(new Date());
'2022年3月8日'
자 이제, Intl API의 공통적인 부분을 알아봤으니 자주 사용되는 생성자 위주로 하나씩 살펴보도록 할까요?
Intl.DateTimeFormat
시간이나 날짜는 언어나 지역에 따라 다르게 표시되는 대표적인 데이터입니다.
예를 들어, 날짜를 표기할 때 한국어에서는 연, 월, 일, 요일 순서를 따르지만 영어에서는 요일, 월, 일, 년 순서를 따릅니다. 시간을 표시할 때도, 한국어에서는 오전 오후를 제일 앞에 명시하는 반면에 영어에서는 이를 보통 제일 뒤에 명시하죠.
Intl API의 Intl.DateTimeFormat
를 사용하면 날짜나 시간 데이터를 쉽게 국제화를 할 수 있는데요.
포맷팅 옵션으로 dateStyle
과 timeStyle
이 자주 사용되는데 full
, long
, medium
, short
중 하나로 설정할 수 있습니다.
예를 들어, 한국어 기준으로 현재 날짜을 표시해볼까요?
> new Intl.DateTimeFormat("ko", { dateStyle: "full" }).format(new Date())
'2022년 3월 8일 화요일'
> new Intl.DateTimeFormat("ko", { dateStyle: "long" }).format(new Date())
'2022년 3월 8일'
> new Intl.DateTimeFormat("ko", { dateStyle: "medium" }).format(new Date())
'2022. 3. 8.'
> new Intl.DateTimeFormat("ko", { dateStyle: "short" }).format(new Date())
'22. 3. 8.'
이번에는 한국어 기준으로 현재 시간을 표시해보겠습니다.
> new Intl.DateTimeFormat("ko", { timeStyle: "full" }).format(new Date())
'오후 10시 58분 25초 미 동부 표준시'
> new Intl.DateTimeFormat("ko", { timeStyle: "long" }).format(new Date())
'오후 10시 58분 31초 GMT-5'
> new Intl.DateTimeFormat("ko", { timeStyle: "medium" }).format(new Date())
'오후 10:58:37'
> new Intl.DateTimeFormat("ko", { timeStyle: "short" }).format(new Date())
'오후 10:58'
dateStyle
옵션과 timeStyle
옵션을 동시에 명시해주면 날짜와 시간을 동시에 포맷팅할 수 있습니다.
예를 들어, 영어 기준으로 날짜와 시간을 동시에 나타내보겠습니다.
> new Intl.DateTimeFormat("en", { dateStyle: 'full', timeStyle: 'full' }).format(new Date())
'Tuesday, March 8, 2022 at 11:00:09 PM Eastern Standard Time'
> new Intl.DateTimeFormat("en", { dateStyle: 'long', timeStyle: 'long' }).format(new Date())
'March 8, 2022 at 11:00:35 PM EST'
> new Intl.DateTimeFormat("en", { dateStyle: 'medium', timeStyle: 'medium' }).format(new Date())
'Mar 8, 2022, 11:00:55 PM'
> new Intl.DateTimeFormat("en", { dateStyle: 'short', timeStyle: 'short' }).format(new Date())
'3/8/22, 11:00 PM'
Intl.ListFormat
여러 개의 데이터를 로케일에 따라 다르게 나타내고 싶을 때는 Intl.ListFormat
을 사용할 수 있습니다.
type
옵션으로 논리곱(conjunction) 형태로 이어줄지 아니면 논리합(disjunction) 형태로 이어줄지 결정할 수 있는데요.
예를 들어, type
옵션의 기본값인 conjunction
을 사용하면 영어에서는 and
, 한글에서는 및
을 사용해서 데이터를 이어줍니다.
> new Intl.ListFormat("en").format(["Chrome", "Safari", "Firefox"])
'Chrome, Safari, and Firefox'
> new Intl.ListFormat("ko").format(["크롬", "사파리", "파이어폭스"])
'크롬, 사파리 및 파이어폭스'
반면에 type
옵션을 disjunction
으로 변경해주면, 영어에서는 or
, 한글에서는 또는
을 사용해서 데이터를 이어줍니다.
> new Intl.ListFormat("en", { type: "disjunction" }).format(["Chrome", "Safari", "Firefox"])
'Chrome, Safari, or Firefox'
> new Intl.ListFormat("ko", { type: "disjunction" }).format(["크롬", "사파리", "파이어폭스"])
'크롬, 사파리 또는 파이어폭스'
Intl.NumberFormat
통화(currency), 백분율, 무게, 길이, 속도, 온도와 같이 단위가 있는 숫자 데이터를 다룰 때는 Intl.NumberFormat
를 유용하게 쓸 수 있습니다.
style
옵션을 통해 다양한 스타일을 지원하고 있는데요.
예를 들어, 백분률 데이터를 다룰 때는 style
옵션을 percent
로 설정하고, format()
함수에 소수를 넘깁니다.
> new Intl.NumberFormat("ko", { style: "percent" }).format(0.7)
'70%'
> new Intl.NumberFormat("ko", { style: "percent" }).format(1 / 4)
'25%'
반면에 통화 데이터를 다룰 때는 style
옵션을 currency
로 주고, currency
옵션에 통화 코드를 넘깁니다.
> new Intl.NumberFormat("ko", { style: "currency", currency: "KRW" }).format(50000)
'₩50,000'
> new Intl.NumberFormat("ko", { style: "currency", currency: "USD" }).format(40.56)
'US$40.56'
그 밖에 다른 단위를 사용할 때는 style
옵션을 unit
으로 주고, unit
옵션에 단위 코드를 넘깁니다.
> new Intl.NumberFormat("ko", { style: "unit", unit: "kilogram" }).format(50)
'50kg'
> new Intl.NumberFormat("ko", { style: "unit", unit: "pound" }).format(110)
'110lb'
Intl.RelativeTimeFormat
“이 동영상은 어제 업로드되었습니다.” 또는 “주문하신 상품은 모레까지 배송될 예정입니다.” 처럼 현재 시간을 기준으로 얼마나 시간이 지났는지나 얼마나 시간이 걸릴건지를 표시해야될 때가 있습니다.
이러한 상대적인 시간은 Intl.RelativeTimeFormat
을 사용해서 국제화해줄 수 있는데요.
numeric
옵션을 "auto"
로 설정해주면, 숫자 대신에 최대한 문자를 사용해서 포맷팅을 해줘서 상당히 유용합니다.
예를 들어, “1일 전” 대신에 “어제”를 사용하고, “1일 후” 대신에 “내일”을 사용합니다.
> let rtf = new Intl.RelativeTimeFormat("ko", { numeric: "auto" });
> rtf.format(-2, "day")
'그저께'
> rtf.format(-1, "day")
'어제'
> rtf.format(0, "day")
'오늘'
> rtf.format(1, "day")
'내일'
> rtf.format(2, "day")
'모레'
> rtf.format(-1, "week")
'지난주'
> rtf.format(0, "week")
'이번 주'
> rtf.format(1, "week")
'다음 주'
> rtf.format(-1, "month")
'지난달'
> rtf.format(0, "month")
'이번 달'
> rtf.format(1, "month")
'다음 달'
Intl.PluralRules
영어에서는 한국어와 다르게 명사의 단복수 개념이 매우 중요하죠?
예를 들어, 한국어에서는 “한 사람”, “두 사람”이라고 하지, 굳이 한 사람 두 사람들이라고 잘 하지 않습니다.
하지만 영어에서는 문법적으로 “One person”, “Two people” 이런 식으로 단수와 복수를 명확히 구분을 해줘야 하죠.
또한 신기하게도 “Zero people“처럼 숫자 0
은 복수로 취급이 되죠.
그래서 영어와 같이 단복수를 엄격하게 구분하는 언어로 서비스하는 웹사이트에서는 숫자 다음에 나오는 명사의 단복수를 처리를 신경써줘야 하는데요.
이 때 사용할 수 있는 것이 Intl.PluralRules
입니다.
PluralRules()
생성자로 만든 객체의 select()
함수에 숫자를 넘기면 "one"
또는 "other"
이 반환되는데요.
이 결과 값으로 단수형 명사를 사용할지 복수형 명사를 사용할지 판단할 수 있습니다.
> let pr = new Intl.PluralRules("en")
undefined
> pr.select(0)
'other'
> pr.select(1)
'one'
> pr.select(2)
'other'
> pr.select(3)
'other'
> pr.select(4)
type
옵션의 기본값은 "cardinal"
인데요.
"ordinal"
로 변경하면 영어에서 필요한 서수의 어미 처리를 할 수 있습니다.
즉, “one”이 반환되면 어미로 “st”를 사용하고, “two”가 반환되면 어미로 “nd”를 사용하며, “few”가 반환되면 “rd”를 사용합니다. “other”이 반환되면 어미로 “th”를 사용합니다.
> let pr = new Intl.PluralRules("en", { type: "ordinal" })
undefined
> pr.select(0)
'other'
> pr.select(1)
'one'
> pr.select(2)
'two'
> pr.select(3)
'few'
> pr.select(4)
'other'
> pr.select(5)
'other'
마치면서
이상으로 자바스크립트의 방대한 Intl API를 수박 겉핥기하는 느낌으로 가볍게 살펴보았습니다. 비교적 최근에 추가된 API라서 아직 활발히 사용되고 있지는 않지만 The State of JS 설문 결과를 보시면 꾸준히 사용자가 늘고 있으니 미리 접해보면 좋을 것 같습니다.
React에서 다국어를 지원하는 방법에 대해서도 별도 포스팅으로 다루고 있으니 참고 바랍니다.