회사에서 작업하고 있는 프로젝트의 기획이 국내를 넘어 국제서비스로 변경 되며 다국어 작업을 담당하게 되었다.
서비스의 특성상 랜딩페이지와 이 외의 설명 부분에 곳곳 작업을 해주어야 할 부분이 많았는데
작업 방식에 대한 논의가 두 가지 나왔다.
HTML 상에 작업 해놓은 한글을 영어로 변경
언어파일 모듈화를 통해 국문과 영문을 모아놓고 텍스트를 가져와 보여주기
1번은 확장성과 효율성이 너무 고려되지 않았기에 탈락되었다.
2번안의 경우 다른 언어를 도입할 경우의 확장성부분과 실수할 우려가 훨씬 줄어들어 안전성이 높다는 부분에서 채택되었다.
다국어작업을 위해 선택한 툴은 i18next 였다.
공식문서가 쉽게 잘 쓰여있으며 리소스가 풍부하여 빠르게 작업에 적용 할 수가 있었다.
설정
먼저 i18next를 설치해보자
npm install react-i18next i18next --save
pakage.json을 확인하여 라이브러리가 잘 설치 되었다면 i18n.js 라는 파일을 만들어주자
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
const resources = {
en: {
translation: {
"Welcome to React": "Welcome to React and react-i18next"
}
},
fr: {
translation: {
"Welcome to React": "Bienvenue à React et react-i18next"
}
}
};
i18n
.use(initReactI18next)
.init({
resources,
fallbackLng: "en"
interpolation: {
escapeValue: false // react already safes from xss
}
});
export default i18n;
resources 안의 형식은 반드시 지켜져야 하는데
언어종류 ( en ) 안에 translation 안에 언어파일이 들어가게 되면 된다.
아래의 코드에서는 "Welcome to React" 라는 키 안에 오른쪽의 값이 들어가있다.
{
en: {
translation: {
"Welcome to React": "Welcome to React and react-i18next"
}
},
fr: {
translation: {
"Welcome to React": "Bienvenue à React et react-i18next"
}
}
};
아래의 부분을 살펴보자.
i18n
.use(initReactI18next)
.init({
resources,
fallbackLng: "en",
});
use() 는 추가적인 플러그인을 사용하고자 할 때 사용하는 api 이다.
init(option,callback) 은 어떤 default값을 지정하는 부분이다.
fallbackLng 는 언어를 지정하는 부분이며
물론 언어는 i18n.changeLanguage("en") api를 호출하여 변경해줄 수 있다.
적용
const { t } = useTranslation();
return (
<p>{t('Welcome to React')}</p>
)
위와 같이 코드를 작성하게 되면 우리는
Welcome to React and react-i18next 라는 문구를 결과로 받을 수 가 있다.
하지만 i18n.js 라는 파일에 모든 언어를 넣어주기엔 파일 하나의 크기가 너무 커진다.
그렇다면 언어 파일을 새로 만들어 i18n.js의 translation 부분에 넣어주면 어떨까?
실제 설정
아래는 내가 만든 설정 파일이다.
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import En from "./translation.en.json";
import Ko from "./translation.ko.json";
import i18next from "i18next";
const resources = {
en: {
translation: En,
},
ko: {
translation: Ko
},
};
i18next
.on("missingKey", (lng, ns, key, res) => {
res[key] = "";
})
.use(initReactI18next)
.init({
resources,
fallbackLng: "en",
defaultNS: "translation",
});
export default i18n;
언어파일
아래는 내가 만든 en.json 파일과 같은 형식의 코드이다.
페이지 이름 > 컴포넌트 번호 및 이름 > 컴포넌트 내부 구성요소 의 순서로 작성하였다.
회사의 디자이너분들이 컴포넌트별로 번호와 태그를 붙여주셨기에 언어파일의 구조를 더욱 쉽게 짤 수 있었다.
크기가 작은 프로젝트의 경우 아무 이름이나 붙여도 무방하겠지만 내가 작업한 프로젝트의 경우 페이지만 28페이지에 모달은 셀 수가 없었기 때문에 이름을 체계적으로 붙여주어야 했다.
안그럴 경우 다른 언어의 다국어 작업에 시간이 필요치 않게 오래걸릴 수 있다.
"page_name": {
"m2_6": {
"title": "i18n is kind of cool",
"body1": "Do you agree with it?"
"body2": "Huh????"
"button1": "Yes!"
"button2": "No!"
},
}
실제 적용
아래의 코드는 i18n 과 커스텀 아토믹 패턴을 적용한 경우이다.
const { t } = useTranslation();
const baseLang = "page_name.m2_6";
<Textline text={`${baseLang}.title`} />
<div>{t(`${baseLang}.body1`)}</div>
<div>{t(`${baseLang}.body2`)}</div>
<Button text={t(`${baseLang}.button1`)}/>
<Button text={t(`${baseLang}.button2`)}/>
적용하지 않고 언어파일을 따로 만들지 않은채 나열한 경우 아래와 같다.
<Textline text={lang==='ko' ? 'i18n은 짱이야' : 'i18n is kind of cool' } />
<div>{lang==='ko' ? '동의 하시나요?' : 'Do you agree with it?'}</div>
<div>{lang==='ko' ? '네????' : 'Hug????'}</div>
<Button text={lang==='ko' ? '네!':'Yes!'}/>
<Button text={lang==='ko' ? '아니요!':'No!'}/>
이는 그나마 영어와 한국어밖에 없다는 가정이고 언어가 세개 이상이라면 코드는 절망편으로 가게 된다.
나는 메모리 캐싱 부분과 간편한 언어변경 함수 등이 맘에 들어 i18next를 도입했지만 라이브러리를 사용하지 않더라도 다국어 작업을 해야한다면 반드시 언어파일은 따로 만들도록 하자!
하지만 i18next에는 장점만 있는 것은 아니다. 마지막으로 아래 장점과 단점을 비교해보고 끝내자.
i18next의 장점
언어파일을 만들어주며 변수 형식으로 모든 언어를 적용하자
코드의 길이가 압도적으로 줄어들었다.
다른 언어파일을 만드는 과정에서 이미 모든 변수가 지정되어있어 작업을 실수로 빼놓는 경우가 아예 사라졌다.
메모리에 캐싱하여 다시 렌더링할 때 매번 번역을 다시 수행하지 않기 때문에 성능을 향상시킨다.
i18next의 단점
첫 언어파일을 만드는 과정이 생각보다 오래걸렸다.
사용되는 텍스트마다 변수를 지정해줘야 했다.
적용된 코드의 양은 줄어들었지만 코드 상에서 가시적으로 보기가 힘들었다.
'안녕 클레오 파트라' 와 같은 문장처럼 중간 부분의 텍스트가 다를 경우 변수 처리가 애매하여 번호를 매겨 세 파트로 나눠야하는 경우가 있었다.
변수를 텍스트로 입력해야했기 때문에 타입스크립트처럼 자동완성이 되지 않았고 정확하게 변수를 입력해야했다.
여러 단점들이 있었지만 i18n의 장점이 너무 명확하여 안정성과 효율을 위해 다시 다국어 작업을 하더라도 i18next 라이브러리를 사용하게 될 것 같다.