2021-11-30 react-i18next를 이용하여 다국어 처리하기

전체 코드는 다음 링크에서 확인할 수 있습니다.

https://github.com/angelxtry/cra-i18n-tutorial


제품을 만들다 보면 자주 다국어 처리를 마주하게 됩니다. 저희 회사는 글로벌 이커머스 사업을 하다보니 다국어 처리가 필수입니다.

React로 다국어 지원을 처리하려고 할 때 가장 자주 사용하는 라이브러리는 react-i18next와 react-intl입니다. 두 라이브러리의 사용량은 한동안 비슷하다가 요즘은 react-i18next를 더 많이 사용하는 것 같네요.

react-i18next npm trends

저는 이번 작업에서 react-i18next를 선택했습니다. react-intl도 좋은 라이브러리지만 제가 느끼기에 react-i18next 보다 다양한 기능이 있는 대신 복잡하게 느껴졌습니다.

목표

이번 다국어 지원 작업의 목표는 다음 2가지입니다.

  1. 언어 파일을 2 level까지 중첩된 형태 사용 가능.
  2. 언어 파일의 키가 타입을 지원하고 IDE에서 자동 완성으로 처리할 수 있을 것.

보일러 플레이트 생성

CRA, TypeScript, Antd로 간단하게 보일러 플레이트를 만들었습니다. 오늘의 주인공인 react-i18next, i18next도 함께 설치했습니다.

기본화면은 다음과 같습니다. 코드는 대부분 antd 공식 페이지에 있는 코드를 그대로 사용했습니다.

react-i18next-01.png

우측 상단에 언어 선택 UI가 있습니다. 원하는 언어를 선택하면 좌측 메뉴가 해당 언어로 변경되도록 할 생각입니다.

언어 파일 추가

먼저 언어파일을 만들어봅시다.

locale/data 폴더에 언어 파일 추가합니다.

locale/data/ko.ts

export const ko = {
  menu: {
    menu1: '메뉴1',
    menu2: '메뉴2',
    user: '사용자',
    addUser: '사용자 추가',
    editUser: '사용자 수정',
    deleteUser: '사용자 삭제',
    team: '팀',
    addTeam: '팀 추가',
    deleteTeam: '팀 삭제',
    files: '파일관리',
  },
};

locale/data/en.ts

export const en = {
  menu: {
    menu1: 'Menu1',
    menu2: 'Menu2',
    user: 'User',
    addUser: 'Add User',
    editUser: 'Edit User',
    deleteUser: 'Delete User',
    team: 'Team',
    addTeam: 'Add Team',
    deleteTeam: 'Delete Team',
    files: 'Files',
  },
};

예시로 든 menu는 왜 언어 파일에 중첩 구조가 필요한가에 대한 설득력은 많이 부족한 것 같습니다. 실제로 회사에서 다음과 같이 언어 파일을 만들어 사용하고 있습니다.

column: {
  settlement: {
    price: '결제금액(원)',
    totalPrice: '최종 정산금액(원)',
  },
  order: {
    price: '주문 금액',
    totalPrice: '총 금액',
  },
},

언어 파일의 구조를 잡는 것에 정답은 없다고 생각합니다. 위와 같은 구조를 사용하면 언어 파일을 작성할 때 타이핑해야 하는 코드량이 조금 줄어들고, 작성된 언어 파일을 사용할 때 상위 키를 입력하면 하위 키들이 자동 완성이 되기 때문에 쉽게 사용할 수 있다는 장점이 있습니다.

react-i18next 설정

react-i18next의 설정을 추가합니다.

locale/index.ts

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

import { en, ko } from './data';

export const resources = {
  ko: {
    translation: ko,
  },
  en: {
    translation: en,
  },
};

i18n.use(initReactI18next).init({
  resources,
  lng: 'ko',
  fallbackLng: 'ko',
});

react-i18next에는 다양한 옵션이 있지만 일단 간단하게 위와 같이 설정했습니다.

선언한 i18n을 사용할 수 있도록 index.ts에 import 합니다.

index.tsx

import './index.css';
import './locale';

import React from 'react';
import ReactDOM from 'react-dom';

import App from './component/App';
import reportWebVitals from './reportWebVitals';

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

reportWebVitals();

언어 선택 UI

이제 언어를 변경할 수 있게 LocaleSelector 컴포넌트를 수정합니다.

component/LocaleSelector.tsx

import { Select } from 'antd';
import { useTranslation } from 'react-i18next';

const { Option } = Select;

const enum Locale {
  Ko = 'ko',
  En = 'en',
}

export const LocaleSelector = () => {
  const { i18n } = useTranslation();

  const onChangeLocale = (selectedLocale: Locale) => {
    i18n.changeLanguage(selectedLocale);
  };

  return (
    <Select
      key='locale-select'
      defaultValue={Locale.Ko}
      style={{ width: 120 }}
      onChange={onChangeLocale}
    >
      <Option key={Locale.Ko} value={Locale.Ko}>
        한국어
      </Option>
      <Option key={Locale.En} value={Locale.En}>
        English
      </Option>
    </Select>
  );
};

다국어 지원을 위한 기본 설정은 끝났습니다.

다국어 적용

메뉴에 직접 적용해봅시다.

import {
  DesktopOutlined,
  FileOutlined,
  PieChartOutlined,
  TeamOutlined,
  UserOutlined,
} from '@ant-design/icons';
import { Menu } from 'antd';
import SubMenu from 'antd/lib/menu/SubMenu';
import { useTranslation } from 'react-i18next';

export const Menus = () => {
  const { t } = useTranslation();
  return (
    <Menu theme='light' defaultSelectedKeys={['1']} mode='inline'>
      <Menu.Item key='1' icon={<PieChartOutlined />}>
        {t('menu.menu1')}
      </Menu.Item>
      <Menu.Item key='2' icon={<DesktopOutlined />}>
        Option 2
      </Menu.Item>
      <SubMenu key='sub1' icon={<UserOutlined />} title='User'>
        <Menu.Item key='3'>Add User</Menu.Item>
        <Menu.Item key='4'>Edit User</Menu.Item>
        <Menu.Item key='5'>Delete User</Menu.Item>
      </SubMenu>
      <SubMenu key='sub2' icon={<TeamOutlined />} title='Team'>
        <Menu.Item key='6'>Add Team</Menu.Item>
        <Menu.Item key='8'>Delete Team</Menu.Item>
      </SubMenu>
      <Menu.Item key='9' icon={<FileOutlined />}>
        Files
      </Menu.Item>
    </Menu>
  );
};

menu.menu1만 적용했습니다. 잘 적용되었는지 확인해볼까요?

react-i18next-02.gif

언어를 선택할 때마다 해당 언어로 잘 표현되는 것을 확인할 수 있습니다.

2 level로 정의한 언어 데이터를 menu.menu1로 사용할 수 있는 건 react-i18next의 기본 설정에 keySeparator의 디폴트 값이 .으로 설정되어있기 때문입니다.

참고로 다른 옵션들은 https://www.i18next.com/overview/configuration-options 여기에서 확인할 수 있습니다.

자동완성, 타입 지원

여기까지만 해도 일단 동작은 합니다. 이제 두 번째 목표인 타입 지원과 자동 완성을 처리해봅시다.

현재는 t('menu.menu1')과 같이 작성할 때 문자열에 오타가 생겨도 IDE에서 오류를 확인할 수 없습니다. 그리도 자동 완성이 되지도 않죠. 실제로 코드를 작성하다보면 이 부분이 상당히 불편합니다.

react-i18next 공식페이지를 찾아보면 TypeScript 항목을 확인할 수 있습니다.

https://react.i18next.com/latest/typescript

공식 페이지를 참고하여 코드를 추가해봅시다.

locale/react-i18next.d.ts

import 'react-i18next';

import { ko } from './data';

declare module 'react-i18next' {
  interface CustomTypeOptions {
    resources: {
      ko: typeof ko;
    };
  }
}

react-i18next는 사용자가 custom type을 추가할 수 있도록 interface를 미리 준비해두었습니다. 그래서 언어 파일의 타입을 명시해주기만 하면 쉽게 타입을 적용할 수 있습니다.

t 함수 안에 정의된 언어 파일의 키 일부만 입력해도 자동완성이 됩니다. 잘못된 키를 입력하면 오류가 발생하고요.

자동완성을 지원하는 모습입니다.

[react-i18next-03.png]

정의되지 않은 키를 입력하면 오류가 발생합니다.

[react-i18next-04.png]

목표 2가지를 모두 처리했습니다!

마치며

react-i18next는 문서화가 잘되어있어 적용하기 상당히 수월했던 것 같습니다. 공식 페이지에 훑어보면 사용하면 좋을 것 같은 기능이 많이 있습니다.

react-i18next를 사용하시는 분들에게 도움이 됐으면 좋겠습니다. 읽어주셔서 감사합니다!


Written by@[Suho]
뭐든지 만들어보자.