전체 코드는 다음 링크에서 확인할 수 있습니다.
https://github.com/angelxtry/cra-i18n-tutorial
제품을 만들다 보면 자주 다국어 처리를 마주하게 됩니다. 저희 회사는 글로벌 이커머스 사업을 하다보니 다국어 처리가 필수입니다.
React로 다국어 지원을 처리하려고 할 때 가장 자주 사용하는 라이브러리는 react-i18next와 react-intl입니다. 두 라이브러리의 사용량은 한동안 비슷하다가 요즘은 react-i18next를 더 많이 사용하는 것 같네요.
저는 이번 작업에서 react-i18next를 선택했습니다. react-intl도 좋은 라이브러리지만 제가 느끼기에 react-i18next 보다 다양한 기능이 있는 대신 복잡하게 느껴졌습니다.
이번 다국어 지원 작업의 목표는 다음 2가지입니다.
CRA, TypeScript, Antd로 간단하게 보일러 플레이트를 만들었습니다. 오늘의 주인공인 react-i18next, i18next도 함께 설치했습니다.
기본화면은 다음과 같습니다. 코드는 대부분 antd 공식 페이지에 있는 코드를 그대로 사용했습니다.
우측 상단에 언어 선택 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의 설정을 추가합니다.
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();
이제 언어를 변경할 수 있게 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
만 적용했습니다. 잘 적용되었는지 확인해볼까요?
언어를 선택할 때마다 해당 언어로 잘 표현되는 것을 확인할 수 있습니다.
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
함수 안에 정의된 언어 파일의 키 일부만 입력해도 자동완성이 됩니다. 잘못된 키를 입력하면 오류가 발생하고요.
자동완성을 지원하는 모습입니다.
정의되지 않은 키를 입력하면 오류가 발생합니다.
목표 2가지를 모두 처리했습니다!
react-i18next는 문서화가 잘되어있어 적용하기 상당히 수월했던 것 같습니다. 공식 페이지에 훑어보면 사용하면 좋을 것 같은 기능이 많이 있습니다.
react-i18next를 사용하시는 분들에게 도움이 됐으면 좋겠습니다. 읽어주셔서 감사합니다!