Skip to main content

react-intl 가이드: React 국제화 설정

IntlProvider, FormattedMessage, useIntl, ICU 메시지 형식, 자동 번역으로 React 앱에 FormatJS react-intl을 설정하세요.

react-i18next를 사용하고 있나요? react-i18next 가이드 보기

1

react-intl 설치

react-intl은 FormatJS 프로젝트의 일부예요. ICU MessageFormat 표준을 사용해 문자열, 숫자, 날짜, 복수형의 서식을 지정하는 React 컴포넌트와 훅을 제공해요.

react-intl은 React 외에 런타임 종속성이 없어요. 숫자와 날짜 서식에는 브라우저 내장 Intl API를 사용하고, 복수형, select, 리치 텍스트에는 자체 ICU MessageFormat 파서를 제공해요.
Terminal
npm install react-intl
2

IntlProvider 구성

앱 루트에서 IntlProvider로 앱을 감싸세요. 활성 로케일과 플랫 messages 객체를 전달하세요. 그러면 아래의 모든 컴포넌트가 FormattedMessage나 useIntl로 번역에 접근할 수 있어요.

src/main.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { IntlProvider } from 'react-intl';
import App from './App';
import enMessages from './messages/en.json';
import deMessages from './messages/de.json';

const messages: Record<string, Record<string, string>> = {
  en: enMessages,
  de: deMessages,
};

// Detect locale from browser or your routing layer
const locale = navigator.language.split('-')[0] || 'en';

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <IntlProvider locale={locale} messages={messages[locale] || messages.en}>
      <App />
    </IntlProvider>
  </React.StrictMode>
);
IntlProvider에는 플랫 키-값 messages 객체가 필요해요(예: { "app.greeting": "Hello" }). 중첩 JSON은 IntlProvider에 전달하기 전에 평탄화하거나 flat 같은 유틸리티로 중첩 구조를 변환해야 해요.

메시지 파일

로케일마다 JSON 파일을 하나씩 만드세요. react-intl은 ICU MessageFormat 구문을 기본으로 사용하므로 복수형, select, 변수를 모두 메시지 문자열 안에서 표현해요.

messages/en.json & messages/de.json
// messages/en.json
{
  "app.greeting": "Hello, {name}!",
  "nav.home": "Home",
  "nav.about": "About",
  "nav.settings": "Settings",
  "cart.itemCount": "{count, plural, one {# item} other {# items}} in your cart"
}

// messages/de.json
{
  "app.greeting": "Hallo, {name}!",
  "nav.home": "Startseite",
  "nav.about": "Über uns",
  "nav.settings": "Einstellungen",
  "cart.itemCount": "{count, plural, one {# Artikel} other {# Artikel}} in Ihrem Warenkorb"
}
구성에는 "nav.home"처럼 점으로 구분한 ID를 사용하세요. react-i18next와 달리 react-intl은 플랫 messages 객체를 요구하므로 구조가 아니라 키를 평탄화해요.
3

컴포넌트에서 번역 사용

react-intl은 두 가지 주요 API를 제공해요. 번역된 JSX를 렌더링하는 FormattedMessage 컴포넌트와 플레이스홀더, aria 레이블, 프로그래밍 방식의 서식에 직접 접근하는 useIntl 훅이에요.

FormattedMessage 컴포넌트

JSX의 선언적 번역에는 FormattedMessage를 사용하세요. 메시지 ID와 보간 값을 전달하면 번역된 문자열을 바로 렌더링해요.

Greeting.tsx
import { FormattedMessage } from 'react-intl';

function Greeting({ userName }: { userName: string }) {
  return (
    <div>
      <h1>
        <FormattedMessage
          id="app.greeting"
          values={{ name: userName }}
        />
      </h1>
      <nav>
        <a href="/"><FormattedMessage id="nav.home" /></a>
        <a href="/about"><FormattedMessage id="nav.about" /></a>
      </nav>
    </div>
  );
}

useIntl 훅

입력 플레이스홀더, aria-label, document.title, React 외 API에 전달할 문자열처럼 번역된 문자열을 일반 값으로 사용해야 할 때 useIntl()을 사용하세요. formatNumber, formatDate, formatRelativeTime도 제공해요.

SearchBar.tsx
import { useIntl } from 'react-intl';

function SearchBar() {
  const intl = useIntl();

  return (
    <input
      type="search"
      placeholder={intl.formatMessage({ id: 'search.placeholder' })}
      aria-label={intl.formatMessage({ id: 'search.ariaLabel' })}
    />
  );
}

// useIntl also gives you formatNumber, formatDate, formatRelativeTime:
function PriceTag({ amount, currency }: { amount: number; currency: string }) {
  const intl = useIntl();
  return (
    <span>{intl.formatNumber(amount, { style: 'currency', currency })}</span>
  );
}

리치 텍스트(번역 내 HTML)

메시지 문자열의 XML 유사 태그로 번역 안에 JSX를 삽입하세요. values prop으로 태그 처리기를 전달해 번역된 메시지 안에 링크, 굵은 텍스트 또는 React 컴포넌트를 렌더링할 수 있어요.

SignUp.tsx
import { FormattedMessage } from 'react-intl';

// Message: "By signing up, you agree to our <link>Terms</link>."
// Key: "signup.terms"
// Value: "By signing up, you agree to our <link>Terms</link>."

function SignUp() {
  return (
    <FormattedMessage
      id="signup.terms"
      values={{
        link: (chunks) => <a href="/terms" className="underline">{chunks}</a>,
      }}
    />
  );
}
FormattedMessage는 기본적으로 React Fragment를 렌더링해요. 특정 래퍼 요소가 필요하면 IntlProvider에 textComponent prop을 전달하거나 자체 요소로 FormattedMessage를 감싸세요.

@formatjs/cli를 활용한 메시지 추출

FormatJS는 소스 코드에서 메시지 ID를 JSON 파일로 자동 추출하는 CLI를 제공해요. 직접 관리하지 않아도 메시지 파일을 컴포넌트와 동기화할 수 있어요.

Terminal
# Install the CLI
npm install -g @formatjs/cli

# Extract messages from source code into a JSON file
formatjs extract 'src/**/*.tsx' --out-file messages/en.json --id-interpolation-pattern '[sha512:contenthash:base64:6]'

# Or use explicit IDs (recommended):
formatjs extract 'src/**/*.tsx' --out-file messages/en.json

# Compile messages for production (optional, improves perf)
formatjs compile messages/en.json --out-file compiled/en.json
formatjs compile messages/de.json --out-file compiled/de.json
4

복수형 및 ICU Select

react-intl은 ICU MessageFormat을 기본으로 사용해요. 복수형, 성별 기반 select, 중첩 서식을 모두 메시지 문자열에 직접 표현하므로 접미사 규칙이나 별도 키가 필요 없어요.

ICU plural syntax by language
// ICU MessageFormat syntax — react-intl uses this natively
// English
{
  "cart.itemCount": "{count, plural, one {# item} other {# items}} in your cart",
  "inbox.unread": "You have {count, plural, =0 {no unread messages} one {# unread message} other {# unread messages}}"
}

// Arabic — 6 plural forms
{
  "cart.itemCount": "{count, plural, zero {لا عناصر} one {عنصر واحد} two {عنصران} few {# عناصر} many {# عنصرًا} other {# عنصر}} في سلتك"
}

// Japanese — 1 form (other)
{
  "cart.itemCount": "カートに{count}個の商品があります"
}
JavaScript에 복수형 로직을 하드코딩하지 마세요. 아랍어에는 복수형이 6가지이고, 프랑스어는 0을 단수로 취급하며, 일본어는 복수형을 구분하지 않아요. 개수 값만 전달하고 규칙은 ICU MessageFormat에 맡기세요.

성별 및 역할용 ICU Select

성별, 사용자 역할, 상태 값처럼 문맥에 따른 번역에는 ICU select 구문을 사용하세요. select 표현식이 제공된 값에 따라 올바른 변형을 선택해요.

ICU select syntax
// Gender-dependent messages using ICU select
{
  "user.greeting": "{gender, select, male {He} female {She} other {They}} liked your post.",
  "user.invitation": "{role, select, admin {You can manage all settings.} editor {You can edit content.} other {You can view content.}}"
}

// Usage:
<FormattedMessage
  id="user.greeting"
  values={{ gender: user.gender }}
/>

번역 품질 자동화

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

흔한 실수

defaultMessage에 과도하게 의존

defaultMessage는 개발용 폴백이지 번역 전략이 아니에요. 모든 문자열에 defaultMessage를 사용하면 메시지 추출 결과에 영어 텍스트가 포함되지만 번역가가 새 키를 놓칠 수 있어요. 항상 완전한 원문 로케일 파일을 추출하고 관리하세요.

플랫 키 대신 중첩 객체 사용

IntlProvider는 messages에 플랫 Record&lt;string, string&gt;을 요구해요. { nav: { home: "Home" } } 같은 중첩 JSON을 전달하면 react-intl이 "nav.home" 키를 찾지 못해요. 메시지를 전달하기 전에 평탄화하거나 flat 같은 라이브러리를 사용하세요.

IntlProvider로 인한 재렌더링

렌더링 함수 안에서 messages 객체를 인라인으로 만들면 IntlProvider가 렌더링할 때마다 새 객체 참조를 받아 모든 소비자가 다시 렌더링돼요. useMemo로 메시지를 메모이제이션하거나 컴포넌트 밖에서 정의하세요.

테스트에서 IntlProvider 누락

FormattedMessage나 useIntl을 사용하는 컴포넌트는 상위 IntlProvider 없이 렌더링하면 오류를 던져요. 테스트에서는 locale="en"과 비어 있거나 최소한의 messages 객체를 전달한 IntlProvider로 컴포넌트를 감싸세요.

권장 파일 구조

Project Structure
my-react-app/
├── messages/
│   ├── en.json              # Source of truth (English)
│   ├── de.json              # German
│   ├── ja.json              # Japanese
│   └── es.json              # Spanish
├── compiled/                # Optional: compiled messages for prod
│   ├── en.json
│   └── ...
├── src/
│   ├── main.tsx             # App entry with IntlProvider
│   ├── App.tsx
│   └── components/
│       ├── Greeting.tsx      # Uses FormattedMessage
│       └── SearchBar.tsx     # Uses useIntl
└── package.json

지금 i18n Agent 사용해 보기

번역 파일을 여기에 드롭

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

또는 클릭하여 파일 선택

대상 언어

가입 불필요즉시 견적

react-intl-locale-chain을 활용한 로케일 폴백

pt-BR 같은 지역 로케일에 번역 키가 없으면 react-intl은 상위 로케일 pt를 먼저 확인하지 않고 기본 로케일로 바로 이동해요.

Terminal
npm install react-intl-locale-chain
Configuration
<LocaleChainProvider
  fallbacks={{
    'pt-BR': ['pt', 'en'],
    'zh-Hant-HK': ['zh-Hant', 'zh', 'en'],
  }}
  defaultLocale="en"
>
  <App />
</LocaleChainProvider>

지원 프레임워크와 내장 체인 75개의 전체 목록은 로케일 폴백 가이드에서 확인하세요. Learn more →

자주 묻는 질문