개인 프로젝트에서 일본어 지원 기능을 만들게 되었다.
i18n
이라는 라이브러리가 가장 유명했고, 자연스럽게 이를 공부하게 되었다.
다언어 지원에 관심이 많기에 언젠가 분명히 다시 쓸 것이라 생각되어,
사용법을 간단하게 적어놓고자 한다.
필자는 React + TypeScript
를 사용하였습니다.
$ npm install react-i18next i18next --save
사용할 언어 파일을 생성해주자.
필자가 작성한 파일을 예시로 가져와봤다.
// 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
중
어떤 쪽을 보여줄지를 정할 수 있는 것이다.
너무 당연한 말들이지만 실제 언어를 프로그래밍으로 구현한다는 뉘앙스를 내고 싶었다 ㅎㅎ
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
import { resources } from "./i18n";
declare module "i18next" {
interface CustomTypeOptions {
resources: (typeof resources)["kr"];
}
}
TS와 함께 사용하는 경우 각종 타입 에러가 발생할 수 있는데,
이를 방지하기 위해 기초 설정을 해줬다.
공식 문서와 다른 것은 크게 없다.
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.ts
의 resources
객체에 입력했던 key를 통해 언어 설정을 변경할 수 있다.
방법은 간단하다.
i18n.changeLanguage("사용할 언어")
위와 같이 작성해주면, 해당 언어로 변경된다.
이 때,
t("경로")
위 코드처럼 t
함수로 작성해놓은 부분이 전부 변경된 언어로 전환된다.
TypeScript와 함께 사용하다 보면 다양한 에러를 마주할 수 있는데,
TypeScript와 쓰일 때의 에러 트러블슈팅에 대한 공식 문서를 보면 빠르게 해결할 수 있다.
필자의 경우
Type error - excessively deep and possibly infinite
Argument of type 'DefaultTFuncReturn' is not assignable to parameter of type xyz
위 두 가지의 에러를 마주쳤는데, i18n.ts
, i18next.d.ts
설정을 해줬더니 해결 할 수 있었다.
사전에 꼭 세팅을 마치고 진행하도록 하자.