i18next 는 자바스크립트로 작성된 국제화 프레임워크이다.
i18next 커뮤니트는 react, Vue.js, Angular 와 같은 프론트엔드 프레임워크를 위한 통합도 만들었다.
나는 리액트를 라이브러리를 활용한 프로젝트를 진행중이기 때문에, react-i18next에 초점을 맞춰서 다국어 적용과정을 설명해 보려고 한다.
먼저 해당 라이브러리를 설치한다.
yarn add i18next react-i18next i18next-browser-languagedetector
프로젝트 src/utils 폴더 안에 locale 이라는 폴더를 만들고
index.tsx 파일을 생성했다.
import i18n, {InitOptions} from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import enFront from './en/front.json'
import koFront from './ko/front.json'
import enCommon from './en/common.json'
import koCommon from './ko/common.json'
i18n
// user 의 언어를 탐지
.use(LanguageDetector)
// i18n을 react-i18next로 전달한다.
.use(initReactI18next)
// init i18next
// for all options read: https://www.i18next.com/overview/configuration-options
.init({
debug: true, //로드가 작동하지 않는 문제를 찾는데 도움을 줌(기본값: false)
fallbackLng: 'en', //사용자의 언어로 번역을 지원하지 않을 경우 대체할 언어
interpolation: {
escapeValue: false, // 기본적으로, XSS 공격을 완화하기 위해 이 값이 escape 된다. 번역을 요청할 때는 옵션을 false 로 설정할 수 있다.
},
resources: { //사용할 언어들을 키로, json 파일들을 담은 객체를 값으로 써준다.
en: {
front: enFront,
common: enCommon,
},
ko: {
front: koFront,
common: koCommon,
},
},
ns: ['enFront','common'], // 로드할 네임스페이스의 문자열 또는 배열
defaultNS: 'common', // 기본값 : translation, 번역기능에 전달되지 않은 경우 사용되는 기본 네임스페이스
keySeparator: false, // char를 사용하여 키를 구분한다. flat 한 json으로 작업하는 경우 false 로 사용하는 것이 좋음, we use content as keys
allowObjectInHTMLChildren: true, // html 요소가 객체를 받을수 있도록 한다. 객체를 html 요소로 전달해서 각각의 보간으로 대체할 수 있다. 값(대부분 Trans 성분 포함)
react: {
useSuspense: true,
},
}as InitOptions);
export default i18n;
'defaultNs' 를 'common'으로 해놓았다면
변수를 참고할 때 따로 json 을 지정해 놓지 않으면 defaultNs 에서 지정해둔 'common.json' 에서 찾는다.
예를들어, 아래와 같은 코드가 있을때
<h1>{t(`gnb.bookmark`)}</h1>
common.json 과 front.json 파일에 "gnb.bookmark" 라는 동일한 키 값이 있어도 common.json를 참고해서 번역한다.
[common.json]
{
"gnb.bookmark": "This is 'common'"
}
[front.json]
{
"gnb.bookmark": "This is 'front'"
}
common.json 해당 키 값이 없고 "gnb.bookmark" 에만 해당 키 값이 있어도 기본값은 common.json 이기 때문에 무시되어 로드되지 않는다.
front.json 에서 키값을 참고하는 번역을 원한다면,
<h1>{t(`front:gnb.bookmark`)}</h1>
위와 같이 써주면 된다.
i18next-browser-languagedetector 는 다음을 지원하는 브라우저에서 사용자 언어를 감지하는 데 사용하는 i18next 언어 감지 플러그인이다.
import i18next from 'i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
i18next.use(LanguageDetector).init(i18nextOptions);
LanguageDetector 는 생성자 함수로서 i8next.use 에 전달할 수 있다.
콘텐츠를 번역하는 두가지 옵션이 있다.
1.간단한 콘텐츠 같은 경우는 제공된 t 함수를 사용해서 간단하게 번역할수 있다.
[before]
<div>Just simple constent</div>
[after]
<div>{t('simpleContent)}</div>
useTranslation 훅 이나 withTranslation 고차함수를 사용하므로서 t 함수를 얻을 수 있다.
2. JSX tree : Trans 컴포넌트를 사용
때로 html 포맷팅 또는 Link 컴포넌트, 변수를 포함한 번역을 해야할 때가 있다.
이때는 Trans 컴포넌트를 사용하면 된다.
[before]
<div>
Hello <strong title="this is your name">{name}</strong>, you have {count} unread message(s). <Link to="/msgs">Go to messages</Link>.
</div>
[after]
<Trans i18nKey="userMessagesUnread" count={count}>
Hello <strong title={t('nameTitle')}>{{name}}</strong>, you have {{count}} unread message. <Link to="/msgs">Go to messages</Link>.
</Trans>
src/hooks/useI18n.tsx
import { TFunction } from 'i18next'
import { useTranslation } from 'react-i18next'
export function useI18n(): TFunction {
const { t } = useTranslation()
return t
}
아래와 같은 코드가 있다고 치자.
import styles from './bookmark.module.scss'
const Bookmark = () => {
return <div className={styles.bookmark}>Bookmark</div>
}
export default Bookmark
이때, 번역 파일 json 의 형태는 아래와 같다.
utils/locale/en/common.js
{
"gnb.bookmark": "This is bookmark page."
}
utils/locale/ko/common.js
{
"gnb.bookmark": "이것은 북마크 페이지입니다."
}
hooks 에서 t 함수를 가져와서 번역을 적용해본다면,
import { useI18n } from 'hooks/useI18n'
import styles from './bookmark.module.scss'
const Bookmark = () => {
const t = useI18n()
return <div className={styles.bookmark}>{t(`common:gnb.bookmark`)}</div>
}
export default Bookmark
언어를 change 하는 버튼은 따로 컴포넌트로 만들었다.
사용자가 언어를 바꾸면 하면 브라우저가 새로고침 되거나 창을 닫고 다시 열었을 때도 그 언어가 유지되도록 localStorage 에 해당 언어를 저장하기로 했다. localStorage 를 활용하기 위해 사용한 라이브러리는 'store-js' 이다
yarn add store
src/components/LangBtn/LangBtn.tsx
import { useEffect, useState } from 'react'
import store from 'store'
import i18n from 'utils/locale'
import styles from './langBtn.module.scss'
const storedLang = store.get('wanted.language') || 'EN'
const LangeBtn = () => {
const [lang, setLang] = useState(storedLang)
useEffect(() => {
store.set('wanted.language', lang)
}, [lang])
const handleLangClick = () => {
setLang(lang === 'EN' ? 'KO' : 'EN')
i18n.changeLanguage(lang.toLowerCase())
}
return (
<button type='button' onClick={handleLangClick} className={styles.languageBtn}>
{lang}
</button>
)
}
export default LangeBtn
[EN]
[KO]
br, a 태그 같은 마크업이 섞여있거나 변수가 포함된 컨텐츠 번역이 필요할 경우는 Trans 컴포넌트를 사용한다.
import { Trans } from 'react-i18next'
import LangeBtn from 'components/LangBtn/LangeBtn'
import styles from './bookmark.module.scss'
const userName = 'Min-su'
const Bookmark = () => {
return (
<div className={styles.bookmark}>
<div>
<Trans i18nKey='bookmark.bookmark_page'>
Hi, <br />
{{ userName }}!, Welcome to my website. Click
<a href='/login' className={styles.red}>
login
</a>
</Trans>
</div>
<LangeBtn />
</div>
)
}
export default Bookmark
ko/common.json
{
"bookmark.bookmark_page":"안녕하세요? {{userName}}님, <1/>나의 웹사이트에 온것을 환영합니다. 클릭 <5>'로그인하기'</5>",
}
en/common.json
{
"bookmark.bookmark_page":"Hi, {{userName}}!, Welcome to my website. <1/> Click <5>'login'</5>",
}
여기서 <1/> 과 <5></5> 처럼 <> 안에 숫자가 들어가 있는 형태가 무엇인지 궁금할 수 있다.
Trans 컴포넌트가 번역을 할 때, 안의 요소들을 쪼개어 번역을 한다. 쪼갤 때 숫자를 0부터 차례로 부여한다.
아래 코드는 5개의 부분으로 쪼개지는데,
<Trans i18nKey='bookmark.bookmark_page'>
Hi, <br />
{{ userName }}!, Welcome to my website. Click
<a href='/login' className={styles.red}>
login
</a>
</Trans>
<0> : "Hi,"
<1> : br 태그
<2> : {{userName}}
<3> : !, Welcome to my website. Click
<4> : a 태그 이하
이렇게 쪼개지고 json 언어 파일에서 조작을 할 수 있다.
예를들면 언어 별로 줄바꿈 위치를 달리 해야한다면,
위의 코드에서 <1/> 은 'br' 태그를 가르킨다.
json 파일에서 <1/>을 옮겨가며 줄바꿈의 위치를 선택할 수 있다.
{
"bookmark.bookmark_page":"Hi, <1/> {{userName}}!, Welcome to my website. Click <4>'login'</4>",
}
결과화면 - EN
{
"bookmark.bookmark_page":"안녕하세요? {{userName}}님, <1/>나의 웹사이트에 온것을 환영합니다. 클릭 <4>'로그인하기'</4>",
}
결과화면 - ko
LanguageSelector 컴포넌트가 언어를 변경할 때, i18next 객체의 언어를 업데이트하지 않아서 Trans 컴포넌트가 업데이트를 감지하지 못할 수 있다. 이 경우에는 LanguageSelector 컴포넌트에서 i8next 객체의 언어를 업데이트 해야 한다.
LanguageSelector 컴포넌트에서 i18next 객체를 사용할 수 있도록 withTranslation 고차 컴포넌트를 사용해서 i18n 프로퍼티를 전달 받을 수 있다.
그리고 언어 변경 이벤트가 발생하면 i18n.changeLanguage 함수를 호출하여 i18next 객체의 언어를 업데이트한다.
LanguageSelector.tsx
import { withTranslation, WithTranslation } from 'react-i18next'
const LanguageSelector = ({ i18n }: WithTranslation) => {
const changeLanguage = (event: React.ChangeEvent<HTMLSelectElement>) => {
const newLanguage = event.target.value
i18n.changeLanguage(newLanguage)
}
return (
<select onChange={changeLanguage}>
<option value='en'>English</option>
<option value='ko'>한국어</option>
</select>
)
}
export default withTranslation()(LanguageSelector)
그리고 Bookmark 컴포넌트에서 LanguageSelector 컴포넌트를 렌더링 할 때, withTranslation 고차 컴포넌트를 사용해서 i18n 프로퍼티를 전달 받을 수 있도록 해야한다.
Bookmark.tsx
import { Trans, withTranslation } from 'react-i18next'
import LanguageSelector from 'components/LangBtn/LangeBtn'
import styles from './bookmark.module.scss'
const userName = 'Min-su'
const Bookmark = () => {
return (
<div className={styles.bookmark}>
<div>
<Trans i18nKey='bookmark.bookmark_page'>
Hi, <br />
{{ userName }}!, Welcome to my website. Click
<a href='/login' className={styles.red}>
login
</a>
</Trans>
</div>
<LanguageSelector />
</div>
)
}
export default withTranslation()(Bookmark)
이렇게 하면 LanguageSelector 컴포넌트에서 언어 변경 이벤트가 발생하면, i18n.changeLanguage 함수를 호출하여 i18next 객체의 언어를 업데이트 하고 이를 사용하는 Trans 컴포넌트도 함께 업데이트 되어 언어 변경이 적용된다.
BabelEdit 프로그램을 사용한다면 다국어 json 파일을 훨신 편리하게 만들 수 있다.
단, 14일 동안 무료고 그 이후에는 유료이다.
라이센스를 한번 구매하면 영원히 쓸 수 있으니 대용량 번역 작업이 필요하다면 구매하는 것도 나쁘지 않을 것 같다.
https://github.com/i18next/i18next-browser-languageDetecto
https://react.i18next.com/getting-started
https://www.codeandweb.com/babeledit