Skip to main content

React 국제화 완벽 가이드

기초 설정부터 다국어 앱 완성까지, React 앱에 i18n을 설정하고 AI로 번역을 자동화하는 방법을 알아보세요.

1

패키지 설치

React 바인딩인 react-i18next, 핵심 라이브러리인 i18next, 그리고 로케일 자동 감지가 필요하다면 i18next-browser-languagedetector까지 세 가지 패키지가 필요해요.

react-i18next는 React 훅과 컴포넌트를 제공해요. i18next는 번역 로드, 보간, 복수형 처리를 담당하는 핵심 엔진이에요. 언어 감지 플러그인은 브라우저의 언어 설정을 자동으로 읽어요.
Terminal
npm install react-i18next i18next i18next-browser-languagedetector i18next-http-backend
2

i18n 인스턴스 구성

기본 언어, 번역 리소스, 플러그인 체인으로 i18next를 초기화하는 i18n 구성 파일을 만드세요. 컴포넌트가 렌더링되기 전에 앱 진입점에서 이 파일을 가져와야 해요.

src/i18n.ts
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import Backend from 'i18next-http-backend';

i18n
  .use(Backend)
  .use(LanguageDetector)
  .use(initReactI18next)  // Must come before .init()
  .init({
    fallbackLng: 'en',
    debug: process.env.NODE_ENV === 'development',
    interpolation: {
      escapeValue: false,  // React already escapes
    },
    backend: {
      loadPath: '/locales/{{lng}}/{{ns}}.json',
    },
  });

export default i18n;
"You will need to pass in an i18next instance by using initReactI18next" 오류는 i18n.init()보다 먼저 i18n.use(initReactI18next)를 호출하지 않았다는 뜻이에요. .use() 호출은 반드시 .init()보다 먼저 와야 해요.
3

I18nextProvider로 앱 감싸기

앱 루트에서 i18n 구성 파일을 가져오고 컴포넌트 트리를 I18nextProvider로 감싸세요. 이 설정이 없으면 useTranslation()이 번역된 텍스트 대신 원시 키를 반환해요.

src/main.tsx
import React, { Suspense } from 'react';
import ReactDOM from 'react-dom/client';
import { I18nextProvider } from 'react-i18next';
import i18n from './i18n';  // Import your config
import App from './App';

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <Suspense fallback={<div>Loading...</div>}>
      <I18nextProvider i18n={i18n}>
        <App />
      </I18nextProvider>
    </Suspense>
  </React.StrictMode>
);
번역에 "Welcome to our app" 대신 "welcome" 같은 원시 키가 표시된다면 I18nextProvider가 없거나 i18n 구성 파일을 가져오지 않은 경우가 가장 흔한 원인이에요.
4

번역 파일 생성

언어마다 JSON 파일을 하나씩 만드세요. 중첩 키를 사용해 기능이나 페이지별로 문자열을 구성하세요. 원문 언어(일반적으로 영어)는 하나의 기준점으로 관리하세요.

public/locales/en/translation.json
// public/locales/en/translation.json
{
  "nav": {
    "home": "Home",
    "about": "About",
    "settings": "Settings"
  },
  "greeting": "Hello, {{name}}!",
  "cart": {
    "itemCount_one": "{{count}} item",
    "itemCount_other": "{{count}} items"
  }
}

// public/locales/de/translation.json
{
  "nav": {
    "home": "Startseite",
    "about": "Über uns",
    "settings": "Einstellungen"
  },
  "greeting": "Hallo, {{name}}!",
  "cart": {
    "itemCount_one": "{{count}} Artikel",
    "itemCount_other": "{{count}} Artikel"
  }
}
키가 표시되는 위치가 아니라 설명하는 내용에 따라 이름을 정하세요. "homepageCartLabel"보다 "cart.itemCount"가 더 좋아요. UI를 다시 설계해도 키는 계속 사용할 수 있어야 해요.
5

컴포넌트에서 번역 사용

컴포넌트에서 useTranslation()을 호출해 t() 함수를 가져오세요. 단순 문자열과 변수가 보간된 문자열은 물론, Trans 컴포넌트를 이용해 JSX가 포함된 번역에도 사용할 수 있어요.

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

function Greeting({ userName }: { userName: string }) {
  const { t } = useTranslation();

  return (
    <div>
      <h1>{t('greeting', { name: userName })}</h1>
      <nav>
        <a href="/">{t('nav.home')}</a>
        <a href="/about">{t('nav.about')}</a>
      </nav>
    </div>
  );
}
Trans component for JSX
import { Trans, useTranslation } from 'react-i18next';

// For JSX inside translations:
// "terms": "By signing up, you agree to our <link>Terms</link>."

function SignUp() {
  const { t } = useTranslation();
  return (
    <Trans i18nKey="terms" components={{
      link: <a href="/terms" className="underline" />
    }} />
  );
}
t(`error.$'{code}'`) 같은 동적 키는 런타임에 작동하지만 i18next-scanner 같은 도구로 정적 추출할 수 없어요. 추출 도구를 사용한다면 동적 키를 명시적으로 나열하거나 주석 힌트를 사용하세요.
6

복수형 및 변수 처리

i18next는 단수와 복수만 구분하는 것이 아니라 CLDR 규칙에 따라 복수형을 처리해요. 아랍어에는 6가지 형식 (zero, one, two, few, many, other)이 있고 일본어에는 1가지 형식 (other)이 있어요. 번역 파일에 필요한 모든 형식을 정의하면 i18next가 올바른 형식을 자동으로 선택해요.

Plural forms by language
// English: 2 forms (one, other)
{
  "itemCount_one": "{{count}} item",
  "itemCount_other": "{{count}} items"
}

// Arabic: 6 forms (zero, one, two, few, many, other)
{
  "itemCount_zero": "لا عناصر",
  "itemCount_one": "عنصر واحد",
  "itemCount_two": "عنصران",
  "itemCount_few": "{{count}} عناصر",
  "itemCount_many": "{{count}} عنصرًا",
  "itemCount_other": "{{count}} عنصر"
}

// Japanese: 1 form (other)
{
  "itemCount_other": "{{count}}個のアイテム"
}
단수형을 감지하기 위해 count === 1을 하드코딩하지 마세요. 프랑스어 같은 언어는 0을 단수로 취급해요. 러시아어, 아랍어, 폴란드어에는 영어에 없는 형식이 있어요. 복수형 규칙은 i18next가 처리하게 하세요.
7

언어 전환 및 감지 추가

i18n.changeLanguage()를 호출하는 언어 선택기를 만드세요. 브라우저 언어 감지기와 결합해 첫 방문 시 사용자의 선호 언어를 자동으로 감지하고, 이후 사용자가 명시적으로 선택한 언어를 저장하세요.

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

const LANGUAGES = [
  { code: 'en', label: 'English' },
  { code: 'de', label: 'Deutsch' },
  { code: 'ja', label: '日本語' },
  { code: 'es', label: 'Español' },
];

function LanguageSwitcher() {
  const { i18n } = useTranslation();

  return (
    <select
      value={i18n.language}
      onChange={(e) => i18n.changeLanguage(e.target.value)}
    >
      {LANGUAGES.map(({ code, label }) => (
        <option key={code} value={code}>{label}</option>
      ))}
    </select>
  );
}
SSR (Next.js, Remix)을 사용하면 서버에 브라우저 언어 설정이 없기 때문에 서버가 클라이언트와 다른 언어를 감지할 수 있어요. 그러면 하이드레이션 불일치가 발생해요. 감지한 로케일을 서버에서 클라이언트로 prop이나 쿠키로 전달해 양쪽이 같은 언어를 렌더링하도록 하세요.
8

번역 자동화

i18n 설정을 마쳤다면 AI로 로케일 파일을 번역하세요. IDE에서 AI 어시스턴트에게 원문 파일 번역을 요청하거나 CI/CD 파이프라인에서 i18n Agent CLI를 사용하세요.

Terminal
# In your IDE, ask your AI assistant:
> Translate public/locales/en/translation.json to German, Japanese, and Spanish

✓ de/translation.json created (1.2s)
✓ ja/translation.json created (1.5s)
✓ es/translation.json created (1.1s)

# Or use the CLI in CI/CD:
npx i18n-agent translate public/locales/en/translation.json --lang de,ja,es
번역은 점진적으로 진행하세요. 원문 파일에 새 키를 추가하면 모든 파일을 다시 생성하지 말고 변경된 부분만 번역하세요. 사람이 검토한 기존 번역을 유지할 수 있어요.

번역 품질 검사 자동화

i18n-validate를 사용해 누락된 키와 손상된 플레이스홀더를 배포 전에 찾아내세요. 실제 번역이 준비되기 전에 i18n-pseudo의 의사 번역으로 UI를 테스트하세요.

흔한 실수

번역에 원시 키 표시

I18nextProvider가 없거나, 앱 루트에서 i18n 구성을 가져오지 않았거나, 네임스페이스를 로드하지 않았거나, 번역을 아직 비동기로 로드하는 중일 수 있어요. debug: true로 설정하고 브라우저 콘솔에서 단서를 확인하세요.

폴백 없는 Suspense 오류

"A component suspended while responding to synchronous input" 오류가 발생하면 앱을 '&lt;Suspense&gt;' 경계로 감싸거나 i18next init 구성에서 useSuspense: false로 설정하세요.

SSR 하이드레이션 불일치

서버와 클라이언트가 서로 다른 로케일로 렌더링하고 있어요. 브라우저 감지에만 의존하지 말고 서버에서 prop으로 전달해 양쪽이 같은 로케일 소스를 사용하도록 하세요.

번역 키 자동 완성 누락

리소스 타입으로 i18next 모듈을 확장하세요: declare module 'i18next' '{ interface CustomTypeOptions { resources: typeof resources } }'. 그러면 자동 완성을 지원하면서 타입 안전성이 보장된 t() 호출을 사용할 수 있어요.

권장 파일 구조

Project Structure
my-react-app/
├── public/
│   └── locales/
│       ├── en/
│       │   ├── translation.json    # Default namespace
│       │   ├── common.json         # Shared strings
│       │   └── dashboard.json      # Feature namespace
│       ├── de/
│       │   ├── translation.json
│       │   ├── common.json
│       │   └── dashboard.json
│       └── ja/
│           └── ...
├── src/
│   ├── i18n.ts                     # i18n configuration
│   ├── main.tsx                    # App entry with Provider
│   ├── App.tsx
│   └── components/
│       └── LanguageSwitcher.tsx
└── package.json

지금 i18n Agent 사용해 보기

번역 파일을 여기에 드롭

JSON, YAML, PO, XML, CSV, Markdown, Properties

또는 클릭하여 파일 선택

대상 언어

가입 불필요즉시 견적

자주 묻는 질문