코딩 공부 일지/React JS

React에서 react-i18next 사용하는 방법

헬로코딩 2023. 4. 20. 11:05
728x90

회사에서 맡은 프로젝트 중 처음으로 다국어 지원 웹사이트를 만들게 되었다. 관리자 페이지여서 SEO는 따로 필요 없었기 때문에 React로 진행하기로 했고, 그에 따라 react-i18next 사용방법을 정리해보기로 했다.

 

서버에서 다국어 지원을 제공하는 next-i18next 라이브러리도 있다.

 

 

다른 큰 회사에서는 어떤 방식으로 번역문을 관리하는지 모르겠지만 작은 프로젝트 단위에서는 json 방식으로 직접 보관하는 방법을 사용할 수 있다. (더 규모가 큰 프로젝트에서는 개발자가 아닌 번역 담당자가 번역문을 수정할 수 있도록 다른 방법을 찾아봐야 할 것 같긴 하다.)

단계 1: 패키지 설치

우선, 프로젝트 내에 react-i18next와 i18next 패키지를 설치한다.

$ npm i react-i18next i18next

단계 2: i18n config 설정

src 폴더 아래에 locales 라는 폴더를 새로 만들고 그 안에 i18n.ts (or i18n.js) 파일을 생성한다.

// src/locales/i18n.ts

import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import translationEN from "locales/en/translation.json";
import translationKO from "locales/ko/translation.json";

const resources = {
  en: {
    translation: translationEN
  },
  ko: {
    translation: translationKO
  }
};

i18n
  .use(initReactI18next)
  .init({
    resources,
    lng: "ko", // 기본 설정 언어, 'cimode'로 설정할 경우 키 값으로 출력된다.
    fallbackLng: "en", // 번역 파일에서 찾을 수 없는 경우 기본 언어
    interpolation: {
      escapeValue: false
    }
  });

export default i18n;

이렇게 작성하면, translationEN과 translationKO를 import 하는 데서 에러가 날 텐데, 이제 번역 파일을 생성해줄 차례다.

단계 3: 번역 파일 생성

locales 폴더 아래에 번역 국가 폴더를 만들고 각 폴더 아래에 translation.json 파일을 생성한다.

// src/locales/ko/translation.json

{
  "header": {
    "mypage": "마이페이지",
    "logout": "로그아웃",
    "login": "로그인",
    "register": "회원가입",
    "language": "언어설정",
    "help": "고객센터"
  },
  "nav": {
    "dashboard": "대시보드",
    "shopping-mall": "쇼핑몰 관리"
  }
}
// src/locales/en/translation.json

{
  "header": {
    "mypage": "My page",
    "logout": "Sign Out",
    "login": "Sign In",
    "register": "Sign Up",
    "language": "Languages",
    "help": "Help"
  },
  "nav": {
    "dashboard": "Dashboard",
    "shopping-mall": "Shopping Mall"
  }
}

단계 4: 번들링을 위해 index.tsx에 import 하기

// src/index.tsx

import React from 'react';
import ReactDOM from 'react-dom/client';
import './global.scss';
import App from './App';
import reportWebVitals from './reportWebVitals';
import "locales/i18n"; // 이렇게 import 한다.

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

단계 5: 번역 사용하기

// 사용할 컴포넌트

import React, { useState, useRef, useCallback, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { useTranslation } from "react-i18next";
import i18n from "locales/i18n";

export default function Header() {
  const { t } = useTranslation();
  const languageRef = useRef<null | HTMLDivElement>(null);
  const navigate = useNavigate();
  const [isLanguageMenuOpen, setLanguageMenuOpen] = useState<boolean>(false);

  // 외부 클릭 시 닫기
  const handleUserClose = useCallback((e: any) => {
    if (isLanguageMenuOpen && languageRef.current !== null && !languageRef.current.contains(e.target)) setLanguageMenuOpen(false);
  }, [isUserMenuOpen, isLanguageMenuOpen]);

  useEffect(() => {
    document.addEventListener("click", handleUserClose);
    return () => document.removeEventListener("click", handleUserClose)
  }, [handleUserClose]);

  // 언어 변경하기
  const changeLanguage = (lang: string) => {
    i18n.changeLanguage(lang);
    setLanguageMenuOpen(false);
  };

  return (
    <header className="header">
      <div className="header-gnb">
        <div className="header-inner">
          <nav className="header-gnb-nav">
            <div className="header-gnb-nav-link" onClick={() => navigate("/user/signin")}>{t(`header.login`)}</div>
            <div className="header-gnb-nav-link" onClick={() => navigate("/user/register")}>{t(`header.register`)}</div>
            <div ref={languageRef} className="header-gnb-nav-link lang-en" onClick={() => setLanguageMenuOpen(prev => !prev)}>
              {t(`header.language`)}
              {isLanguageMenuOpen && (
                <ul className="header-gnb-nav-link-dropDown">
                  <li className="header-gnb-nav-link-dropDown-item" onClick={() => changeLanguage("ko")}>한국어</li>
                  <li className="header-gnb-nav-link-dropDown-item" onClick={() => changeLanguage("en")}>English</li>
                </ul>
              )}
            </div>
            <div className="header-gnb-nav-link">{t(`header.help`)}</div>
          </nav>
        </div>
      </div>
      {isLoggingIn && (
        <div className="header-snb" onMouseEnter={() => setSubMenuOpen(true)} onMouseLeave={() => setSubMenuOpen(false)}>
          <div className="header-inner">
            <nav className="header-snb-nav">
              <div className="header-snb-nav-link">{t(`nav.dashboard`)}</div>
              <div className="header-snb-nav-link">{t(`nav.shopping-mall`)}</div>
            </nav>
          </div>
        </div>
      )}
    </header>
  )
}

 

처음엔 다국어 지원이라고 해서 어려울 것 같아서 겁먹었는데 생각보다 너무 간편하게 라이브러리 구성이 되어 있어서 사용하기 굉장히 편리했다. 다음번엔 SSR에서도 다국어 지원을 구성하는 방법을 사용해봐야겠다.

 

728x90