본문 바로가기
SPA/React

[React] react-i18next로 다국어 처리하기

by J4J 2023. 11. 23.
300x250
반응형

안녕하세요. J4J입니다.

 

이번 포스팅은 react-i18next로 다국어 처리하는 방법에 대해 적어보는 시간을 가져보려고 합니다.

 

 

 

react-i18next란?

 

react-i18next는 i18next를 기반으로 한 React에서 사용할 수 있는 국제화 프레임워크입니다.

 

여기서 말하는 국제화란 세계에 존재하는 여러 국가들에 대한 언어를 맞추는 것으로, 다른 말로는 서비스에서 사용되는 단어들을 각 국가에서 사용되는 언어에 맞게 보여주는 것을 의미합니다.

 

그래서 국내를 대상으로 하는 서비스들에는 국제화에 대해 고려를 할 이유가 없지만, 만약 동일한 서비스를 국내뿐만 아니라 특정 해외 국가에도 지원을 해준다 하면 국제화를 고려해봐야 합니다.

 

 

 

국가에 맞는 언어를 보여주는 방법들은 여러 가지가 존재할 수 있습니다.

 

파파고와 같은 번역기 API를 활용할 수도 있고 서비스 내부에 번역 관련 API를 직접 구현하여 제공해 줄 수도 있습니다.

 

하지만 API를 통해 번역되는 언어들을 불러오는 것은 생각보다 많은 리소스를 필요로 합니다.

 

그러다 보니 언어 정보들을 React가 직접 가지고 있는 것들을 고려해 보게 되고, 일반적으로 react-i18next를 사용할 때 언어 정보들을 React 프로젝트 내부에서 관리하여 사용하기에 하나의 선택지가 될 수 있습니다.

 

 

반응형

 

 

기본 설정

 

react-i18next를 사용하기 위한 기본적인 설정들은 다음과 같이 해주시면 됩니다.

 

 

 

[ 1. 패키지 설치 ]

 

$ npm install react-i18next i18next

 

 

 

[ 2. 언어별 사용될 key-value 설정 ]

 

각 언어들에 대한 key-value 설정은 json 파일을 활용하여 정의하면 됩니다.

 

여기서 언급되는 key는 언어별 번역 내용을 식별하여 불러오기 위한 고유한 값이고 value는 각 언어별 번역 내용들입니다.

 

영어와 한국어에 대해 설정한다고 가정하면 간단하게 다음과 같이 설정해 볼 수 있습니다.

 

// 영어 설정
// src/i18n/translation/en/fruit.json
{
    "fruitApple": "apple",
    "fruitGraph": "graph",
    "fruitLemon": "lemon"
}


// src/i18n/translation/en/sentence.json
{
    "sentenceHello": "Hello. This is {{firstName}}{{lastName}}.",
    "sentenceMarkup": "<bold>Markup</bold>"
}


// src/i18n/translation/en/index.ts
import fruit from './fruit.json';
import sentence from './sentence.json';

export default {
    ...fruit,
    ...sentence,
};


// 한국어 설정
// src/i18n/translation/ko/fruit.json
{
    "fruitApple": "사과",
    "fruitGraph": "포도",
    "fruitLemon": "레몬"
}


// src/i18n/translation/ko/sentence.json
{
    "sentenceHello": "안녕하세요. {{firstName}}{{lastName}}입니다.",
    "sentenceMarkup": "<bold>마크업</bold>"
}


// src/i18n/translation/ko/index.ts
import fruit from './fruit.json';
import sentence from './sentence.json';

export default {
    ...fruit,
    ...sentence,
};

 

 

 

[ 3. config 설정 ]

 

react-i18next의 config 설정을 할 땐 어떤 언어별 리소스를 활용할 것인지, default 언어는 무엇을 할지 등에 대한 정보들을 설정할 수 있습니다.

 

// src/i18n/index.ts
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import translationEn from './translation/en';
import translationKo from './translation/ko';

i18n.use(initReactI18next).init({
    // 언어별 사용될 리소스 설정 (default undefined)
    resources: {
        ko: {
            translation: translationKo,
        },
        en: {
            translation: translationEn,
        },
    },
    // default 언어 설정 (default undefined)
    lng: 'ko',
    // react-i18next 처리 로그 콘솔 출력 설정 (default false)
    debug: true,
    // 동적인 데이터 값 할당 설정
    interpolation: {
        escapeValue: false, // react는 XSS에 안전하기 때문에 false로 설정
    },
});

export default i18n;

 

 

 

[ 4. root에 import 추가 ]

 

config 설정이 완료되었다면 index.tsx나 main.tsx 같은 곳에 다음과 같이 i18n을 import를 하여 설정 값이 올바르게 적용될 수 있도록 해줍니다.

 

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import './i18n';

ReactDOM.createRoot(document.getElementById('root')!).render(
    <React.StrictMode>
        <App />
    </React.StrictMode>,
);

 

 

 

 

번역하는 방법 (1) - useTranslation (hook)

 

기본 설정이 모두 끝났다면 이제 언어별 번역하는 방법에 대한 소개를 드리겠습니다.

 

가장 먼저 hook을 활용한 방법입니다.

 

아마도 특별한 이슈가 없지 않은 이상 hook을 활용하는 것이 가장 무난한 방법이기도 하며 react-i18next를 사용하고 계시는 분들이 대부분 이 방법을 이용하고 계실 것 같습니다.

 

위에서 설정한 key-value들이 사용될 수 있도록 간단하게 다음과 같이 코드를 작성해 볼 수 있습니다.

 

import { useTranslation } from 'react-i18next';

export default function Hook() {
    const { t, i18n } = useTranslation();
    const firstName = 'react';
    const lastName = 'i18next';

    return (
        <div>
            {/* 언어 변경 */}
            <button onClick={() => i18n.changeLanguage('ko')}>korean</button>
            <button onClick={() => i18n.changeLanguage('en')}>english</button>

            <div>
                <h2>Fruit</h2>

                {/* 일반적인 번역 */}
                <p>{t('fruitApple')}</p>
                <p>{t('fruitGraph')}</p>
                <p>{t('fruitLemon')}</p>
            </div>

            <div>
                <h2>Sentence</h2>

                {/* 파라미터 사용 번역 */}
                <p>
                    {t('sentenceHello', {
                        firstName: firstName,
                        lastName: lastName,
                    })}
                </p>
                {/* 마크업 변경 번역 (hook에서 불가) */}
                <p>{t('sentenceMarkup')}</p>
            </div>
        </div>
    );
}

 

 

 

 

번역하는 방법 (2) - withTranslation (HOC)

 

다음은 고차 컴포넌트(HOC)를 활용한 방법입니다.

 

위에서 말한 것처럼 일반적인 경우에는 hook을 사용하겠지만 HOC로만 적용해야 되는 케이스가 발생될 때 활용해 주시면 될 것 같습니다.

 

hook으로 한 것과 동일한 코드를 HOC로 변경한다면 다음과 같이 적용해 볼 수 있습니다.

 

import { TFunction, i18n } from 'i18next';
import { withTranslation } from 'react-i18next';

function Hoc({ t, i18n }: { t: TFunction; i18n: i18n }) {
    const firstName = 'react';
    const lastName = 'i18next';

    return (
        <div>
            {/* 언어 변경 */}
            <button onClick={() => i18n.changeLanguage('ko')}>korean</button>
            <button onClick={() => i18n.changeLanguage('en')}>english</button>

            <div>
                <h2>Fruit</h2>

                {/* 일반적인 번역 */}
                <p>{t('fruitApple')}</p>
                <p>{t('fruitGraph')}</p>
                <p>{t('fruitLemon')}</p>
            </div>

            <div>
                <h2>Sentence</h2>

                {/* 파라미터 사용 번역 */}
                <p>
                    {t('sentenceHello', {
                        firstName: firstName,
                        lastName: lastName,
                    })}
                </p>
                {/* 마크업 변경 번역 (HOC에서 불가) */}
                <p>{t('sentenceMarkup')}</p>
            </div>
        </div>
    );
}

export default withTranslation()(Hoc);

 

 

 

 

번역하는 방법 (3) - Translation (render prop)

 

다음은 Translation 컴포넌트를 활용하여 번역에 사용되는 t 함수, i18n 인스턴스들을 prop으로 전달받아 활용하는 방식입니다.

 

Translation을 사용해야 하는 특정 상황이 필요한지는 모르겠지만, hook 대신 컴포넌트의 prop을 이용하여 언어 처리를 하고 싶은 분들은 활용해 볼 수 있을 것 같습니다.

 

hook과 동일한 기능을 수행하도록 다음과 같이 코드를 작성해 볼 수 있습니다.

 

import { Translation } from 'react-i18next';

export default function RenderProp() {
    const firstName = 'react';
    const lastName = 'i18next';

    return (
        <Translation>
            {(t, { i18n }) => (
                <div>
                    {/* 언어 변경 */}
                    <button onClick={() => i18n.changeLanguage('ko')}>korean</button>
                    <button onClick={() => i18n.changeLanguage('en')}>english</button>

                    <div>
                        <h2>Fruit</h2>

                        {/* 일반적인 번역 */}
                        <p>{t('fruitApple')}</p>
                        <p>{t('fruitGraph')}</p>
                        <p>{t('fruitLemon')}</p>
                    </div>

                    <div>
                        <h2>Sentence</h2>

                        {/* 파라미터 사용 번역 */}
                        <p>
                            {t('sentenceHello', {
                                firstName: firstName,
                                lastName: lastName,
                            })}
                        </p>
                        {/* 마크업 변경 번역 (renderProp에서 불가) */}
                        <p>{t('sentenceMarkup')}</p>
                    </div>
                </div>
            )}
        </Translation>
    );
}

 

 

 

 

번역하는 방법 (4) - Trans Component

 

마지막으로 Trans Component를 이용한 방법이 있습니다.

 

Trans Component는 위의 3가지 방법들과 달리 key-value의 value 값에 React/HTML 코드 값이 같이 담겨 있는 경우 조금 더 유연한 처리를 할 수 있도록 도와줍니다.

 

하지만 이렇게 사용되는 경우는 거의 없을 것으로 생각되고 react-i18n 공식 문서 trans-component에서도 대부분의 경우 필요로 하지 않을 것이라고도 얘기하고 있습니다.

 

이 또한 hook과 동일한 기능을 수행하도록 다음과 같이 코드를 구현해 볼 수 있는데 다른 점은 마크업에 대한 처리를 Trans Component만의 방식으로 적용해 볼 수 있다는 겁니다.

 

import { Trans, useTranslation } from 'react-i18next';

export default function TransComponent() {
    const { i18n } = useTranslation();
    const firstName = 'react';
    const lastName = 'i18next';

    return (
        <div>
            {/* 언어 변경 */}
            <button onClick={() => i18n.changeLanguage('ko')}>korean</button>
            <button onClick={() => i18n.changeLanguage('en')}>english</button>

            <div>
                <h2>Fruit</h2>

                {/* 일반적인 번역 */}
                <p>
                    <Trans>fruitApple</Trans>
                </p>
                <p>
                    <Trans>fruitGraph</Trans>
                </p>
                <p>
                    <Trans>fruitLemon</Trans>
                </p>
            </div>

            <div>
                <h2>Sentence</h2>

                {/* 파라미터 사용 번역 */}
                <p>
                    <Trans
                        i18nKey="sentenceHello"
                        values={{
                            firstName: firstName,
                            lastName: lastName,
                        }}
                    />
                </p>
                {/* 마크업 변경 번역 */}
                <p>
                    <Trans
                        i18nKey="sentenceMarkup"
                        components={{
                            bold: <strong />,
                        }}
                    />
                </p>
            </div>
        </div>
    );
}

 

 

 

마크업 부분을 화면으로 봤을 때 차이점은 다음과 같은 것을 확인할 수 있습니다.

 

trans component 외 기능 사용

 

trans component 사용

 

 

 

 

t function options

 

이번엔 t 함수에서 제공해 주는 options에서 사용해 볼 만한 기능들을 알아보겠습니다.

 

여러 가지 기능들이 options에 존재하지만 대표적으로 다음 속성 값들을 많이 활용해 볼 수 있을 것으로 보입니다.

 

  • defaultValue → 번역에 사용되는 key값이 없는 경우 화면에 보일 default value 값
  • fallbackLng → 사용자 기반 언어 설정이 불가능한 경우 사용될 언어 설정
  • lng → i18next의 현재 언어와 상관없이 고정된 언어를 적용하고 싶을 때 설정
  • ns → 특정 namespace의 언어를 보여주고 싶을 때 설정

 

 

 

여기서 fallbackLng와 namespace에 대해서는 아래에서 더 설명드릴 예정이고 그 외 defaultValue나 lng에 대해서는 이해하시긴 어렵지 않을 것 같습니다.

 

options의 설정 방법은 다음과 같이 해줄 수 있습니다.

 

// t function
{t('fruitApple', {
    defaultValue: 'key값이 없는 경우 이곳에 설정한 값이 보입니다.',
    fallbackLng: '사용자 기반 언어 설정이 사용 불가능한 경우 사용될 언어를 설정합니다.',
    lng: 'i18next의 현재 언어와 상관없이 고정된 언어를 적용하고 싶을 때 설정합니다.',
    ns: '특정 namespace의 언어를 보여주고 싶을 때 설정합니다',
})}


// Trans component
<Trans
    defaultValue="key값이 없는 경우 이곳에 설정한 값이 보입니다."
    ns="특정 namespace의 언어를 보여주고 싶을 때 설정합니다"
>
    fruitApple
</Trans>

 

 

 

 

Extraction - languagedetector

 

extraction 같은 경우는 번역에 사용될 값들을 추출하여 활용될 수 있도록 도와주는 설정들입니다.

 

그중 languagedetector라는 것은 사용자가 서비스를 이용하기 위해 브라우저에 접속했을 때 사용하고 있는 언어를 탐지 및 추출하여 해당 언어 값을 기본 값으로 설정해 주도록 도와줍니다.

 

그리고 languagedetector를 사용할 때 위에서 봤던 fallbackLng를 활용하게 됩니다.

 

예를 들어 프로젝트에 react-i18next를 설정할 때 한국어, 영어만 설정을 했는데 중국에서 접속하게 되면 지원하는 언어가 없기에 이럴 때 fallbackLng를 한국어로 설정했다면 중국 접속자는 한국어로 서비스를 사용할 수 있게 해 줍니다.

 

languagedetector를 사용하기 위해서는 다음과 같이 설정해 볼 수 있습니다.

 

 

 

[ 1. 패키지 설치 ]

 

$ npm install i18next-browser-languagedetector

 

 

 

[ 2. config 설정 ]

 

import i18n from 'i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import { initReactI18next } from 'react-i18next';
import translationEn from './translation/en';
import translationKo from './translation/ko';

i18n.use(LanguageDetector)
    .use(initReactI18next)
    .init({
    
    	...
        
        // default 언어 설정 (default undefined)
        // lng: 'ko', // languagedetector에 의해 자동설정 되기 때문에 사용 x
        // 사용자 기반 언어 설정(ex, LanguageDetector)이 사용 불가능할 경우 사용될 언어 설정 (default dev)
        fallbackLng: 'ko',
    });

export default i18n;

 

 

 

 

Multiple 번역 파일 사용 방법

 

multiple 번역 파일이란 상황에 따라 서로 다른 형태의 번역을 제공해 주는 것을 의미합니다.

 

예를 들면, translation이라는 파일에 한국어, 영어에 대한 번역 내용을 정의해 뒀는데 또 다른 others라는 파일에 한국어, 영어에 대한 번역을 정의하여 필요에 따라 translation과 others에 들어있는 번역 내용을 선택하여 제공하는 것을 의미합니다.

 

그리고 여기서 언급되는 translation, others 등을 namespace라고 일컬으며 위의 t function options에서 봤던 ns설정이 여기서 말하는 namespace 설정을 의미합니다.

 

namespace 설정은 다음과 같이 해볼 수 있습니다

 

 

 

먼저 위에서 기본 설정을 할 때 src/i18n/translation 하위에 언어 번역 내용들을 정의를 했었습니다.

 

해당 내용들은 translation이라는 namespace에 대한 언어 번역 정의를 한 것이고 이번엔 src/i18n/others 하위에 파일들을 동일하게 생성하여 others라는 namespace에 대한 정의를 해보겠습니다.

 

 

 

[ 1. others key-value 설정 ]

 

// 영어 설정
// src/i18n/others/en/fruit.json
{
    "fruitApple": "others apple",
    "fruitGraph": "others graph",
    "fruitLemon": "others lemon"
}


// src/i18n/others/en/sentence.json
{
    "sentenceHello": "Hello. This is others {{firstName}}{{lastName}}.",
    "sentenceMarkup": "<bold>others Markup</bold>"
}


// src/i18n/others/en/index.ts
import fruit from './fruit.json';
import sentence from './sentence.json';

export default {
    ...fruit,
    ...sentence,
};


// 한국어 설정
// src/i18n/others/ko/fruit.json
{
    "fruitApple": "others 사과",
    "fruitGraph": "others 포도",
    "fruitLemon": "others 레몬"
}


// src/i18n/others/ko/sentence.json
{
    "sentenceHello": "안녕하세요. others {{firstName}}{{lastName}}입니다.",
    "sentenceMarkup": "<bold>others 마크업</bold>"
}


// src/i18n/others/ko/index.ts
import fruit from './fruit.json';
import sentence from './sentence.json';

export default {
    ...fruit,
    ...sentence,
};

 

 

 

[ 2. config 설정 ]

 

import i18n from 'i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import { initReactI18next } from 'react-i18next';
import othersEn from './others/en';
import othersKo from './others/ko';
import translationEn from './translation/en';
import translationKo from './translation/ko';

i18n.use(LanguageDetector)
    .use(initReactI18next)
    .init({
        // 언어별 사용될 리소스 설정 (default undefined)
        /*
        resources: {
            [lng]: {
                [ns]: json 파일
            }
        }
        */
        resources: {
            ko: {
                translation: translationKo,
                others: othersKo,
            },
            en: {
                translation: translationEn,
                others: othersEn,
            },
        },
        // default namespace 설정 (default translation)
        ns: 'others',
    });

export default i18n;

 

 

 

위와 같이 설정을 한다면 default namespace가 others로 설정되기 때문에 json 파일에 정의한 것처럼 모든 언어 번역 내용에 others라는 문장이 추가되는 것을 볼 수 있습니다.

 

그리고 필요에 따라 더 많은 namespace를 설정하여 상황별로 사용할 수도 있습니다.

 

 

 

 

 

 

 

 

이상으로 react-i18next로 다국어 처리하는 방법에 대해 간단하게 알아보는 시간이었습니다.

 

읽어주셔서 감사합니다.

 

 

 

728x90
반응형

댓글