[React] i18n으로 다언어 지원해보기(i18next, react-i18next)

박기영·2023년 5월 7일
1

React

목록 보기
28/32
post-custom-banner

개인 프로젝트에서 일본어 지원 기능을 만들게 되었다.
i18n이라는 라이브러리가 가장 유명했고, 자연스럽게 이를 공부하게 되었다.
다언어 지원에 관심이 많기에 언젠가 분명히 다시 쓸 것이라 생각되어,
사용법을 간단하게 적어놓고자 한다.

필자는 React + TypeScript를 사용하였습니다.

설치

$ npm install react-i18next i18next --save

세팅

JSON

사용할 언어 파일을 생성해주자.
필자가 작성한 파일을 예시로 가져와봤다.

// translation.ko.json

{
  // ... //
  "mypageRevise": {
    "title": "개인정보 수정",
    "profile": {
      "title": "프로필 사진",
      "button": "변경하기"
    },
    "nickname": {
      "title": "닉네임",
      "button": "수정하기",
      "cancel": "취소",
      "submit": "등록",
      "placeholder": "10자 이내"
    },
    "password": {
      "title": "비밀번호 변경",
      "button": "변경하기",
      "currPswd": "현재 비밀번호",
      "currPswdPlaceholder": "현재 비밀번호를 입력해주세요.",
      "newPswd": "새로운 비밀번호",
      "newPswdPlaceholder": "새로운 비밀번호 입력(8 ~ 12자)",
      "checkPswd": "비밀번호 확인",
      "checkPswdPlaceholder": "비밀번호를 다시 확인해주세요.",
      "cancel": "취소",
      "submit": "변경"
    }
  },
  // ... //
}
// translation.jp.json

{
  // ... //
  "mypageRevise": {
    "title": "個人情報修正",
    "profile": {
      "title": "プロフィール写真",
      "button": "変更する"
    },
    "nickname": {
      "title": "ニックネーム",
      "button": "修正する",
      "cancel": "キャンセル",
      "submit": "登録",
      "placeholder": "10文字以内"
    },
    "password": {
      "title": "パスワード変更",
      "button": "変更する",
      "currPswd": "現在のパスワード",
      "currPswdPlaceholder": "現在のパスワードを入力してください。",
      "newPswd": "新しいパスワード",
      "newPswdPlaceholder": "新しいパスワード入力(8~12文字)",
      "checkPswd": "パスワードチェック",
      "checkPswdPlaceholder": "パスワードをもう一度確認してください。",
      "cancel": "キャンセル",
      "submit": "変更"
    }
  },
  // ... //
}

각각의 JSON 파일은 동일한 구조를 가지고 있다.
같은 key를 사용하고 있고, value만 다르다.
즉, key를 통해 표시할 문장(혹은 단어)의 의미나 흐름을 구축하고,
value를 통해 유저에게 다언어를 지원하는 것이다.

말이 좀 애매하다. 예를 들어보자.
아래의 접근은

mypaegRevise.nickname.cancel

마이페이지 -> 정보 수정 페이지 -> 닉네임 -> 변경 취소
에 해당하는 흐름을 따라가는 것이고,
i18n을 통해 현재 언어를 변환하여 translation.ko.json, translation.jp.json
어떤 쪽을 보여줄지를 정할 수 있는 것이다.
너무 당연한 말들이지만 실제 언어를 프로그래밍으로 구현한다는 뉘앙스를 내고 싶었다 ㅎㅎ

i18n.ts

index.ts 옆에 i18n.ts 파일을 생성하자.

// i18n.ts

import i18n from "i18next";
import { initReactI18next } from "react-i18next";

// 사용할 문장, 단어가 들어 있는 JSON 파일
import translationKo from "./locales/translation.ko.json";
import translationJp from "./locales/translation.jp.json"; 

export const resources = {
  kr: {
    translation: translationKo, // "kr"을 언어로 선택할 시, 이 JSON 파일을 사용
  },
  jp: {
    translation: translationJp,
  },
};

i18n.use(initReactI18next).init({
  resources,
  lng: "kr", // 초기 언어 지정
  interpolation: {
    escapeValue: false,
  },
  returnNull: false, // null을 리턴하여 타입 에러가 발생하는 것을 방지
});

export default i18n;

공식 문서와 다른 것은 없다.
사용하고 싶은 언어를 지정하는 것만 달라졌다.
resources의 key인 kr, jp를 사용하여 언어를 전환해줄 것이다.

i18next.d.ts

// i18next.d.ts

import { resources } from "./i18n";

declare module "i18next" {
  interface CustomTypeOptions {
    resources: (typeof resources)["kr"];
  }
}

TS와 함께 사용하는 경우 각종 타입 에러가 발생할 수 있는데,
이를 방지하기 위해 기초 설정을 해줬다.
공식 문서와 다른 것은 크게 없다.

index.tsx

import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import "./i18n"; // i18n을 사용하겠다고 알려주자

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

이 또한 공식 문서와 다른 것은 없다.
이제 사용을 해보자!

사용

여러 가지 사용법이 있는데, 필자는 useTranslation()이라는 hook만 사용했다.
따라서, 여러 가지 사용법에 대한 정보를 얻고 싶으신 분은 공식 문서로!!

다언어 지원

// Header.tsx

// ... //
import { useTranslation } from "react-i18next";
// ... //

function Header() {
  // ... //

  const { t } = useTranslation(); // t 함수를 통해서 다언어를 지원할 수 있다.

  const menuArr = useMemo(
    () => [
      { title: t("header.lecture"), key: 1, to: "/lecture" },
      { title: t("header.qa"), key: 2, to: "/qa" },
      { title: t("header.mypage"), key: 3, to: "/mypage" },
      { title: t("header.auth"), key: 4, to: "/login" },
    ],
    [t]
  );

  return (
    <header className="z-10 flex justify-between items-center py-1 px-[1%] text-2xl fixed w-screen bg-[#ffcdd2] lg:w-[20%] lg:px-0 lg:col-start-1 lg:col-end-2 lg:flex-col lg:justify-evenly lg:items-center lg:h-screen">
      // ... //
    </header>
  );
}

export default Header;

translation.ko.json, translation.jp.json 중 현재 설정된 언어 파일에 접근하여
header.lecture에 들어있는 value를 보여주게 된다.
정말 간단하다.

t("경로")

이렇게만 적어주면, 설정 언어가 변경될 때 다른 파일에 있는 같은 경로의 텍스트로 변경해준다.
컴포넌트 쪽에서의 사용도 마찬가지이다.

// NavBar.tsx

// ... //
import { useTranslation } from "react-i18next";
// ... //

function NavBar({ menuArr, logoutHandler }: PropsType) {
  // ... //

  const { t } = useTranslation();

  return (
    <nav className="hidden w-full text-xl text-center lg:block h-1/2">
      // ... //

      {auth.isLoggedIn ? (
        <button
          onClick={logoutHandler}
          className="text-center w-full py-3 px-3 active:bg-[#ffa4a2] hover:bg-[#cb9ca1]"
        >
          {t("header.logout")} // 완전히 같은 사용법!!
        </button>
      ) : null}
    </nav>
  );
}

export default NavBar;

엘리먼트의 속성에 적용해줘야한다면 어떻게 사용할까?
예를 들면.. placeholder가 있을 것 같다.

// MySeachBar.tsx

// ... //
import { useTranslation } from "react-i18next";
// ... //

function MySearchBar(props: SearchBarPropsType) {
  // ... //
  
  const { t } = useTranslation();

  return (
    <div className="flex justify-center my-4">
      <input
        placeholder={t("mypage.placeholder")} // 완전히 같은 사용법!!
        value={searchKeyWord}
        onChange={onChangeHandler}
        className="pl-2 mb-2 font-semibold w-[250px] h-[40px] border-2 border-[#ffcdd2] rounded focus:border-[#e57373] focus:outline-none sm:w-[400px] md:w-[500px] lg:w-[500px] lg:h-[50px]"
      />
    </div>
  );
}

export default MySearchBar;

역시나 같은 사용법을 가지고 있다 ㅎㅎ

언어 변경

다언어를 지원할 수 있다는 것은 알았으니, 설정 언어를 변경하는 방법에 대해서 알아보자.

// LanguageControl.tsx

import React from "react";
import { useTranslation } from "react-i18next";

const lang = ["한국어", "日本語"];

function LanguageControl() {
  const { i18n } = useTranslation(); // i18n 인스턴스를 통해서 언어를 변경할 수 있다.

  const langHandler = (item: string) => {
    if (item === "한국어") {
      i18n.changeLanguage("kr"); // 언어를 kr로 변경
      return;
    }

    if (item === "日本語") {
      i18n.changeLanguage("jp"); // 언어를 jp로 변경
      return;
    }
  };

  return (
    <ul className="flex items-center justify-center w-full text-sm">
      {lang.map((item, index) => (
        <li
          onClick={() => langHandler(item)}
          className="px-2 border-r-2 border-black hover:cursor-pointer last:border-none"
          key={index}
        >
          {item}
        </li>
      ))}
    </ul>
  );
}

export default LanguageControl;

i18n.tsresources 객체에 입력했던 key를 통해 언어 설정을 변경할 수 있다.
방법은 간단하다.

i18n.changeLanguage("사용할 언어")

위와 같이 작성해주면, 해당 언어로 변경된다.
이 때,

t("경로")

위 코드처럼 t 함수로 작성해놓은 부분이 전부 변경된 언어로 전환된다.

TypeScript와 사용 시 발생할 수 있는 에러

TypeScript와 함께 사용하다 보면 다양한 에러를 마주할 수 있는데,
TypeScript와 쓰일 때의 에러 트러블슈팅에 대한 공식 문서를 보면 빠르게 해결할 수 있다.
필자의 경우

  1. Type error - excessively deep and possibly infinite
  2. Argument of type 'DefaultTFuncReturn' is not assignable to parameter of type xyz

위 두 가지의 에러를 마주쳤는데, i18n.ts, i18next.d.ts 설정을 해줬더니 해결 할 수 있었다.
사전에 꼭 세팅을 마치고 진행하도록 하자.

참고 자료

react-i18next 공식 docs

profile
나를 믿는 사람들을, 실망시키지 않도록
post-custom-banner

0개의 댓글