본문 바로가기

React

React 앱에서 다국어(i18n) 지원하기

다국어 지원은 기능이 아니라 제품 전략입니다. 초기 설계만 잘하면 번역 추가, SEO, 접근성, 성능까지 한번에 잡을 수 있습니다. 실무에서 가장 많이 쓰는 react-i18next 기반으로 최소 설정부터 운영 팁까지 정리합니다.

1. 라이브러리 선택

권장 조합은 i18next + react-i18next입니다. 장점은 동적 로딩, 복수형, 네임스페이스, 브라우저 언어 감지, ICU 지원 플러그인 등입니다. Next.js를 쓴다면 next-i18next를 검토합니다.

2. 설치와 초기화

HTTP 백엔드로 번역 파일을 지연 로딩하고, 브라우저 언어를 자동 감지합니다. Suspense를 켜면 언어 리소스 로딩 중 대기 UI를 쉽게 처리합니다.

// npm i i18next react-i18next i18next-http-backend i18next-browser-languagedetector
// i18n.ts
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import HttpBackend from 'i18next-http-backend';

i18n
  .use(HttpBackend)
  .use(LanguageDetector)
  .use(initReactI18next)
  .init({
    fallbackLng: 'en',
    supportedLngs: ['en', 'ko'],
    ns: ['common', 'home'],
    defaultNS: 'common',
    interpolation: { escapeValue: false }, // React는 XSS 자동 이스케이프
    backend: { loadPath: '/locales/{{lng}}/{{ns}}.json' },
    react: { useSuspense: true },
    detection: {
      order: ['querystring', 'localStorage', 'navigator'],
      caches: ['localStorage']
    }
  });

export default i18n;

3. 번역 사용과 언어 전환

useTranslation 훅으로 문자열을 가져오고, changeLanguage로 즉시 전환합니다. 리소스는 필요할 때만 네트워크로 로딩됩니다.

// index.tsx
import './i18n';
import React, { Suspense } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';

createRoot(document.getElementById('root')!).render(
  <Suspense fallback={<div>Loading...</div>}>
    <App />
  </Suspense>
);

// App.tsx
import { useTranslation } from 'react-i18next';

export default function App() {
  const { t, i18n } = useTranslation(['common', 'home']);
  const toggle = () => {
    const next = i18n.language.startsWith('ko') ? 'en' : 'ko';
    i18n.changeLanguage(next);
    document.documentElement.dir = i18n.dir(); // RTL 대응
  };
  return (
    <div>
      <h1>{t('home:title')}</h1>
      <p>{t('common:greeting', { name: 'Kim' })}</p>
      <button onClick={toggle}>{t('common:switchLanguage')}</button>
    </div>
  );
}

4. 번역 파일 구조와 네임스페이스

네임스페이스로 도메인별 파일을 분리하면 충돌을 줄이고 로딩도 작아집니다. public/locales/{lng}/{ns}.json 구조를 기본으로 합니다.

// locales/en/common.json
{
  'greeting': 'Hello, {{name}}',
  'switchLanguage': 'Switch language',
  'items_one': '{{count}} item',
  'items_other': '{{count}} items'
}
// locales/en/home.json
{
  'title': 'Dashboard'
}

// locales/ko/common.json
{
  'greeting': '{{name}}님, 안녕하세요',
  'switchLanguage': '언어 전환',
  'items': '{{count}}개 항목'
}
// locales/ko/home.json
{
  'title': '대시보드'
}

사용 시 t('home:title'), t('common:items', { count })처럼 네임스페이스를 명시합니다. 한국어는 단수/복수 구분이 없어 items 키 하나면 충분하지만, 영어는 items_one/items_other가 필요합니다.

5. 동적 로딩과 성능

i18next-http-backend는 필요한 언어/네임스페이스만 요청합니다. 초기 화면에 필요한 ns만 useTranslation에 적어주면 번들과 네트워크가 줄어듭니다. 라우트 전환 전에 미리 로딩해 UX를 올릴 수 있습니다.

// 라우트 진입 전에 네임스페이스 프리로딩
import i18n from './i18n';

async function preloadI18n(lng, nsList) {
  if (i18n.language !== lng) await i18n.changeLanguage(lng);
  await i18n.loadNamespaces(nsList);
}
// 예: 제품 상세 페이지 진입 전에
preloadI18n(i18n.language, ['product']);

6. 복수형, 보간, ICU

보간은 {{name}}, 숫자는 count로 처리합니다. 영어처럼 복수형 규칙이 있는 언어는 키 접미사(one, other 등)를 사용합니다. 복잡한 성/서식 규칙이 필요하면 i18next-icu 플러그인을 추가합니다.

// 복수형 사용
t('common:items', { count: 1 });   // en: 1 item, ko: 1개 항목
t('common:items', { count: 3 });   // en: 3 items, ko: 3개 항목

// ICU가 필요할 때
// npm i i18next-icu
// i18n에 .use(new I18NextICU()) 추가
// 'apples': '{count, plural, one {# apple} other {# apples}}'

7. 날짜/숫자/통화는 Intl로

번역 파일에 날짜를 넣지 말고 Intl로 포맷하세요. 사용자 언어에 맞춰 자동 서식을 적용합니다.

import { useTranslation } from 'react-i18next';

function Meta() {
  const { i18n, t } = useTranslation();
  const now = new Date();
  const date = new Intl.DateTimeFormat(i18n.language, { dateStyle: 'medium' }).format(now);
  const price = new Intl.NumberFormat(i18n.language, { style: 'currency', currency: 'USD' }).format(1234.5);

  return (
    <p>{t('common:reportAt', { date })} · {price}</p>
  );
}
// 예) ko: 2026. 4. 21. · US$1,234.50

8. 언어 감지/보존과 RTL

브라우저 언어, 쿼리스트링, 로컬스토리지 순으로 감지하고, 선택한 언어를 로컬스토리지에 보존합니다. RTL 언어(ar, he 등) 지원을 위해 언어 변경 시 document.documentElement.dir = i18n.dir()를 호출해 방향성을 맞춥니다. 스타일은 logical 속성(margin-inline 등)을 쓰면 대응이 쉽습니다.

9. 라우팅과 SEO

CSR에서는 언어 전환만으로 충분하지만, SEO가 중요하면 언어별 경로를 사용하세요. 예: /en, /ko. Next.js라면 next-i18next로 언어 프리픽스, 서버 번역 주입, hreflang 태그를 자동화할 수 있습니다. SSR에서는 서버와 클라이언트 언어가 반드시 일치해야 수화 불일치가 발생하지 않습니다.

10. 테스트와 운영 체크리스트

테스트에서는 간단한 테스트 전용 i18n 인스턴스를 만들어 I18nextProvider로 감싸고 렌더합니다. 누락 키는 디폴트 언어로 폴백하도록 설정하고, 빌드 시 미사용/누락 키 검사를 걸면 품질이 올라갑니다. 긴 문자열은 줄바꿈, 자리표시자 보간 결과, RTL 스크린샷을 함께 확인하세요.