WEB 팀에서 국제화를 적용시키는 방법
“WATCHA WEB” 단편 시리즈 첫 번째
WATCHA 는 현재 국내뿐 아니라 일본을 넘어 동남아 시장 등 아시아 전역에 서비스를 제공할 수 있도록 분주히 움직이고 있다.
글로벌 서비스를 하는 곳이라면 반드시 한 번 이상 마주하게 될 국제화 문제라는 것이 있다. 예를 들어, WATCHA 는 한국에서 태어난 서비스고 절대 다수의 고객이 한국어를 자유롭게 사용하는 한국어 모국인이라 WATCHA 모든 서비스가 한국어 베이스로 이뤄져 있다. 그런데 한국이 아닌 다른 지역에서 서비스를 런칭하기 위해선 그 나라에서 가장 많이 사용하고 있는 언어에 맞게 서비스를 준비해야한다.
이렇게 나라 또는 지역에 맞게 서비스의 언어를 준비하고 변경하는 일련의 작업을 보통 국제화 혹은 지역화라고 부른다.
이번 포스팅은 WATCHA WEB 팀에서 국제화를 어떤 방식으로 서비스에 녹여내고 있는지 간단하게 예제와 함께 설명하고 있다.
읽기 전에
독자가 이미 리액트 프로젝트에 충분히 익숙하다는 전제하에 설명하고 있다. 프로젝트는 CRA 로 구성했다.
I18N 이란?
앞서 간단히 소개한 것을 다시 짧게 짚고 넘어가보자면, 국제화라고 설명한 개념은 영어로 “Internationalization” 이라고 부른다. 이름이 무려 20 자 정도 되다보니 맨 앞과 뒤의 글자를 제외한 나머지 사이 글자들의 개수를 사용해 새로운 축약형 단어가 생겨났는데 그게 바로 I18N 이다.
보통 웹에서 I18N 이라 함은 프로젝트에서 서비스하고 있는 언어의 설정을 다른 언어로 변경하는 작업을 말한다. 지구상에는 70 억이 넘는 인구가 살고 있고 매우 다양한 문화와 언어가 존재하기 때문에, 현재 프로젝트에 적용된 언어만 가지고 타 언어 사용자까지 구매 고객으로 만들 수 있을거란 생각은 착각으로 변할 가능성이 매우 높다.
자바스크립트에는 사실 I18N 기능이 존재했다
반은 맞다. 다시 말하자면 반은 틀렸다.
자바스크립트에는 이미 Intl 이라고 부르는 API 가 들어있다. 아마 대부분의 사람들이 들어본적도 없거나 사용해본적이 거의 없을거라고 생각한다. Intl 역시 Internationalization 의 준말인데 (사실 원래 이게 정확한 영어식 준말이다) 재미있는 아이디어를 가지고 몇 가지 기능을 제공한다.
이 기능에 대한 유래를 간략하게 설명해주자면, 영어권 국가, 대표적으로 미국으로 예로 들어볼 때, 미국엔 아주 다양한 인종이 살고 있다. 오히려 미국에서는 백인이 소수인종이다(아시아인은 말할 것도 없고). 미국인을 뜻하는 “American” 이라는 단어는 한국인처럼 한국에 사는 한국인인 사람이라는 뜻이 아니라 미국이라는 국가에서 태어나거나 이민해 거주하는 사람이라는 의미일 뿐 그 어떤 인종도 내포하고 있지 않다. 즉, 미국인 = 백인 = 서양인은 가장 대표적인 인종차별적 발언이다. 어쨌든, 이런 이야기를 왜 갑자기 꺼내는 것인지 의문일 수 있을텐데, 그렇게 서로 다른 문화와 언어를 가진 사람들이 모여 사는 곳에서는 “Nice to meet you” 라는 말을 영어로 들었을 때 그대로 이해하지 못할 가능성이 다분하다는 뜻이 되기도 한다. 나는 “Nice to meet you” 라고 말했지만 상대방은 “I hate you” 라고 알아들을 수도 있다는 뜻이다. 저렇게 다른데 설마 오해하겠어? 라는 발상은 이미 그 언어를 어느정도 알고 있는 당신의 입장에서 그렇게 보이는 것일 뿐임을 기억하길 바라고, 이런식으로 오해가 생길 수 있는 현상을 “language sensitivity” 라고 부른다.
자바스크립트의 Intl 은 이런 “language sensitivity” 라고 부르는 개념에서 아이디어를 빌려온 것으로 언어적, 문화적 차이로 인해 같은 표현이라도 서로 다르게 받아들일 수 있다는 점을 인정하고 해당 지역이나 국가에 맞게 데이터 포맷을 변경해주는 기능을 제공한다.
가장 대표적으로 Intl.DateTimeFormat 이라는 메소드는 입력 값으로 받은 지역과 언어에 맞게 날짜 표기 형식을 변경해준다.
const date = new Date(Date.UTC(2012, 11, 20, 3, 0, 0)); // Results below assume UTC timezone - your results may vary
console.log(new Intl.DateTimeFormat('en-US').format(date)); // expected output: '12/20/2012'
console.log(new Intl.DateTimeFormat('en-GB').format(date)); // expected output: '20/12/2012'
그 외에 문자열 순서 비교 같은 기능도 제공하니 나중에 기회가 될 때 관심있다면 한 번 둘러보는 것도 좋겠다.
그러나 이러한 것들은 철저히 “language sensitivity” 의 관점에서의 기능일 뿐, 글로벌 서비스에서 국제화라고 부르는 것은 언어 대 언어의 통번역에 가까운 의미이기에 이 기능은 국제화를 수행할 수 없다.
다만 자바스크립트 역시 역사를 가진 언어이므로 의미와 유래를 알고 있으면 좋을 것 같아 소개했다.
i18next 의 기본 구조
I18N 을 실현할 수 있는 패키지들이 존재하는데 그 패키지들은 -next 로 이름이 끝난다.
위 그림은 리액트 프로젝트에서 국제화를 적용 할 수 있는 구조를 나타낸다. i18next 라고 부르는 언어 전환 전용 모듈이 있고, 리액트는 이 모듈에 A 지역에서 사용되고 있는 언어에서 “Nice to meet you” 에 대한 번역본을 찾아달라고 요청한다. 조금 더 정확하게는, i18next 에게 “greeting” 에 연결된 “Nice to meet you” 라는 값이 다른 언어에서는 어떤 값으로 매핑되어 있는지 찾아달라고 요청한다. 그러면 i18next 모듈은 미리 등록되어있는 딕셔너리 기능을하는 JSON 객체를 참조한다. 값이 있으면 다시 리액트에게 반환하는 식이다.
말로 표현하자면 저런식인데, i18next 를 사용해본적도, 적용된 프로젝트를 본 적도 없다면 글만 갖고는 부족할거라 생각한다.
그렇지. 우린 개발자니까, 이제부터는 코드로 이야기하는게 좋겠다.
우선 국제화 기능을 적용하려면 다음과 같은 패키지를 설치해야한다.
npm i -S i18next or yarn add i18next
npm i -S react-i18next or yarn add i18next
i18next 는 자바스크립트 뿐만이 아니라 여러가지 다양한 언어에서 적용할 수 있는 국제화 모듈이고, react-i18next 는 i18next 기능을 리액트에서 사용할 수 있도록 특화된 라이브러리라고 보면 된다.
1 단계: JSON 파일 생성
먼저, 딕셔너리 기능을 수행할 JSON 파일부터 만들자. 단계를 모두 설명한 후에 예시 프로젝트를 보여줄텐데, 이 프로젝트에선 한국어와 영어만 설정을 테스트하고 있다.
먼저 영어 설정을 담을 딕셔너리 파일을 만들자.
// public/locales/en/translation.json
{ 'figcaption': 'Welcome to React and react-i18next' }
그리고 바로 한국어 딕셔너리 파일을 만든다.
// public/locales/ko/translation.json
{ 'figcaption': '왓챠 첫 방문을 환영합니다 !' }
끝이다. 진짜?
진짜. 끝이다.
2 단계: 프로젝트에 i18next 주입시키기
다음으로는 리액트가 i18next 모듈을 인식할 수 있도록 해당 모듈을 주입시키는 작업이다.
import translationEN from '../translation.json'; import translationKO from '../translation.json';
const resources = { en: { translation: translationEN }, ko: { translation: transaltionKO }, }
i18next 을 리액트에 주입하기전에 먼저 i18next 을 초기화할 때 연동시킬 딕셔너리 (JSON 파일) 를 모두 임포트해서 다음과 같이 셋팅한다.
import i18n from 'i18next'; import { initReactI18next } from 'react-i18next';
i18n.use(initReactI18next)
여기서부터 첫 번째 스텝인데, 가이드 문서에는 i18n 인스턴스를 react-i18next 에 넣어주는 과정이라고 표현했지만, 로드하는 모듈 이름이나 절차만보면 반대로 표현해야 맞는게 아닌가 싶기도하다.
어쨌든 이렇게 미들웨어 느낌이나는 initReactI18next 를 연결했다면 바로 메소드 체이닝으로 다음과 같이 작업하자.
i18n .use(initReactI18next) .init({ resources, lng: 'ko', fallbackLng: 'ko', });
resources 는 아까 임포트했던 딕셔너리 파일이고 이는 리액트가 i18next 에 번역 데이터를 요청하면 i18next 이 찾아보게 될 파일이다. lng 은 앱 실행 시 기본 값으로 가져갈 언어를 뜻하고 여기선 한국어다. fallbackLng 는 예를 들어 이럴 때 적용된다. 한국어 설정에서 영어 설정으로 바꾸려는데, “안녕하세요” 에 해당하는 영어 번역본을 찾고 싶다. 그런데 저 key 값은 한국어 딕셔너리엔 들어있지만 영어 딕셔너리엔 들어있지 않았고 결국 번역 값을 찾을 수 없게 됐다. 이럴 때, undefined 를 반환하는 것은 UX 관점에서 에러이니 이럴 때 대신 찾아보게 할 딕셔너리의 언어 값이다.
3 단계: useTranslation 훅 사용하기 (함수형 컴포넌트 일 때)
함수형 컴포넌트를 사용하는데 익숙하다면 useTranslation 훅을 사용한다는게 어떤 의미인지 바로 깨달았을거라 생각한다. react-i18next 역시 리액트 훅을 제공하는데 다음과 같이 사용할 수 있다.
import { useTranslation } from 'react-i18next';
const Desc = () => { const { t } = useTranslation();
return{t('figcaption')}
; }
t 를 가져올 수 있는데, 이 메소드가 바로 맨 처음에 보여줬던 그림에서 리액트가 i18next 에게 특정 key 값에 매칭되는 데이터를 (번역 값) 찾아달라고 요청하는 것과 같은 동작이다. t('figcaption') 으로 메소드를 실행하면 리액트는 i18next 에게 figcaption 과 매칭되는 값이 있는지 찾아달라고 요청하고, i18next 는 현재 설정된 언어의 딕셔너리에서 값을 찾아 반환한다. 값을 찾지 못하게되면 fallbackLng 로 설정해놓은 딕셔너리 파일에서 참조를 재시도한다.
예제 프로젝트
정리
이번 포스팅에서 소개했던 내용은 리액트 프로젝트에서 어떻게 국제화를 처리할 수 있는지에 대해 소개했다. 현재 WATCHA WEB 팀에서는 i18next 모듈을 이용해 국제화를 처리하고 있다. 매우 잘 알려진 방법 중 하나이므로 알아두면 좋을 것이라 생각한다.
위 방법 외에도 여러가지 다른 아이디어들이 존재한다. 앞서 소개한 프로젝트의 가장 큰 단점 중 하나는, 딕셔너리 파일 (JSON) 을 프로젝트 내에 갖고 있어야한다. 결국 자바스크립트 번들 크기가 증가할 수 밖에 없다. 프로젝트 내에 갖고 있지 않다고한다면 어딘가의 서버에 업로드해서 불러올 수도 있다.
현재 처한 환경에서 적용할 수 있는 다양한 방법을 모색해보고 어떤 것을 현실적으로 최선의 타협의 결과물로 가져올 수 있을지는 신중하게 고민해보길 바란다.
참고
Intl — MDN
React i18n step by step guide
React i18next
i18next documentation