글로벌 유저 대상의 서비스가 있다고 해보자. 언어마다 별도의 프론트엔드 프로젝트를 만들지 않고 하나의 서비스를 통합적으로 운영하면서, 유저가 사용하는 언어별로 차별화된 컨텐츠를 보여주려면 어떻게 해야할까?
i18n
보통은 프론트엔드 프레임워크 또는 라이브러리, 혹은 javascript 자체에서 지원하는 국제화(i18n, internationalization) 기술을 사용한다. i18n을 통해 비단 텍스트를 언어별로 번역해서 보여주는 것뿐만 아니라, 유저가 선호하는 언어에 따라 다른 컨텐츠를 보여주거나 페이지의 동작을 다르게 정의하는 등 다양한 기능을 활용할 수 있다.
케이타운포유(이하 케타포)에서는 현재 영어, 한국어, 중국어, 일본어, 태국어, 인도네시아어 6개 언어를 지원하고 있다. 프론트엔드 프레임워크로 Next.js를 사용하므로 Next.js의 built-in i18n 기능과 next-i18next 패키지를 통해 6개의 언어를 커버하는 중이다.
Next.js는 v10.0.0부터 Internationalized Routing을 빌트인 지원 중이라, 아래와 같이 제공하고 싶은 언어의 리스트를 프로젝트 config 파일에 작성하면 도메인별 i18n 세팅이 완료된다.
// next.config.js
module.exports = {
i18n: {
// 서비스에서 사용할 모든 지역값
locales: ['en', 'ko', 'zh', 'ja', 'id', 'th'],
// 기본 지역값
defaultLocale: 'en',
// 지역값별 도메인
domains: [
{
domain: 'www.ktown4u.com',
defaultLocale: 'en',
},
{
domain: 'kr.ktown4u.com',
defaultLocale: 'ko',
},
{
domain: 'cn.ktown4u.com',
defaultLocale: 'zh',
},
{
domain: 'jp.ktown4u.com',
defaultLocale: 'ja',
},
{
domain: 'id.ktown4u.com',
defaultLocale: 'id',
},
{
domain: 'th.ktown4u.com',
defaultLocale: 'th',
},
],
},
}
위와 같이 설정해두면 유저의 접근 도메인에 따라 locale이 반영된 페이지가 나타난다. 모든 언어별 도메인을 별개로 취급하고 관리할 필요 없이, 하나의 페이지 안에서 언어별로 다르게 보여주고 싶은 부분만 수정하면 되니 편리하다.
// 유저가 접근한 도메인의 locale을 가져와서 locale 값을 API 요청의 변수로 넣어주기도 한다
const { locale } = useRouter();
const { data } = useQuery(['somethingDependsOnLocale', locale], fetcher);
얼마 전, Next.js의 i18n 옵션과 next-i18next 라이브러리를 사용하며 익숙하게 i18n을 반영한 개발을 진행하던 도중, 뜻밖의 버그와 마주했다.
메인 페이지에 접근하면, 어떤 도메인으로 들어오든 무조건 한국 도메인(kr.ktown4u.com)으로 리다이렉트 되는 현상
즉, www.ktown4u.com
으로 접근하든, cn.ktown4u.com
으로 접근하든, 모두 kr.ktown4u.com
으로 리다이렉트 되고마는 현상이었다. 물론 프로덕션 환경에 배포하기 전에 개발 환경에서 발견한 버그지만, 운영팀에서는 언어마다 보여주는 컨텐츠를 따로 관리하고 있기 때문에 일어나서는 안 되는 현상이었다. 그런데 희한한 것이, 버그 재현이 팀원에 따라 달라졌다는 점이다. 어떤 사용자는 재현이 되지 않고, 어떤 사용자는 모든 브라우저에서 재현이 되고, 어떤 사용자는 크롬에서는 안 되는데 파이어폭스에서는 되고, 그런 상황이었다.
소스코드를 뒤져봐도 메인 페이지 내부에서 한국 도메인으로 리다이렉트 하는 로직은 없었기에, 처음에는 CloudFront의 정책을 의심하기도 했다. 하지만 확인 결과 CloudFront가 아닌 프론트엔드 프로젝트 안에서 벌어지는 일이었기에, 결국 재현이 되는 브라우저와 아닌 브라우저의 HTTP 요청을 비교하는 디버깅을 하게 되었고, 두 브라우저가 보내는 HTTP 요청의 Accept-Language에 차이가 있다는 사실을 알게 되었다.
Accept-Language?
브라우저를 처음 설치할 때를 떠올려보자. 초기 화면에서 선호하는 언어를 설정할 수 있다. 대부분 OS에 설정되어 있는 언어가 기본값으로 들어가지만, 내가 원하는 언어로 변경할 수 있고, 여러 개의 언어를 설정할 수도 있다. 이렇게 브라우저에 세팅이 된 언어는 아래와 같이 브라우저가 보내는 HTTP 헤더의 Accept-Language에 포함된다.
Accept-Language: ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7,zh-CN;q=0.6,zh;q=0.5
‘나는 한국어를 선호해. 하지만 영어와 중국어도 받아들일 수 있어(q는 가중치)’
Automatic locale detection
Next.js는 친절하게도, 유저가 루트(보통은 ‘/’) 페이지로 접근하면, 이 Accept-Language를 활용해 유저가 선호하는 locale을 자동으로 감지하는 기능이 i18n 옵션에 기본 탑재되어 있다. 즉, 유저의 Accept-Language 헤더가 fr;q=0.9 라면, example.com 으로 접근해도 exmaple.fr로 리다이렉트 해주는 거다.
유저가 어떤 도메인으로 들어와도 실제 유저가 사용하는 언어가 메인인 도메인으로 리다이렉트 해줄 수 있으니 편리한 기능인 것 같기는 하다. 많은 글로벌 사이트에서 비슷한 기능을 경험해보기도 했다. 특히 한국 사이트에서 구매하는 것보다 해당 브랜드의 글로벌 사이트에서 직구하는 것이 훨씬 가격 경쟁력이 있는 경우, 글로벌 사이트의 도메인으로 접근해도 무조건 한국 사이트로 튕겨지도록 해 직구를 원천 차단하는 브랜드도 많은 것 같다.
하지만 이런 강제적인 튕김 기능(…)이 별다른 세팅 없이 디폴트로 설정되어 있다는 점이 의아하긴 하다. config 파일이나 소스코드 내부에 이런 기능을 작성한 히스토리가 없으니 리다이렉트 자체를 버그로 판단하기가 너무 쉽기 때문이다. 우리 서비스는 언어 선호도에 따라 유저를 무조건 특정 도메인으로 리다이렉트 시킬 필요가 없었기 때문에 config 파일의 i18n 필드에 localeDetection: false
라인을 추가해 해당 기능을 꺼두었더니 자동 리다이렉트가 다시 발생하지 않았다.
이번 기회를 통해 Next.js 공식 문서의 Internationalization 영역을 꼼꼼히 살펴볼 수 있었다. 다양한 지역에 서비스 함에도 불구하고, 익숙한 언어(영어, 한국어) 위주로만 QA 하는 것에 길들여져 있던 것 같기도 하다. 글로벌 서비스를 운영하는 만큼, 앞으로도 국제화에 대해 더 디테일하게 챙길 수 있도록 해야겠다. 🎩