Skip to main content

react-intl 指南:設定 React 國際化

在 React 應用程式中設定 FormatJS react-intl,包括 IntlProvider、FormattedMessage、useIntl、ICU 訊息格式和自動翻譯。

改用 react-i18next? 查看 react-i18next 指南

1

安裝 react-intl

react-intl 是 FormatJS 項目的一部分,提供 React 元件和 hook,並按 ICU MessageFormat 標準格式化字串、數字、日期和複數。

除 React 外,react-intl 沒有執行階段依賴。它使用瀏覽器內置 Intl API 格式化數字和日期,並自帶 ICU MessageFormat 解析器以處理複數、select 和富文字。
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" }。傳給 IntlProvider 前必須壓平嵌套 JSON,也可使用 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-intl 與 react-i18next 不同,它要求扁平 messages 物件,因此需要壓平鍵,而不是結構。
3

在元件中使用譯文

react-intl 提供兩個主要 API:用於渲染翻譯 JSX 的 FormattedMessage 元件,以及用於命令式訪問的 useIntl hook,例如佔位文字、aria 標籤和編程式格式化。

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 Hook

需要將譯文作為普通值使用時,請呼叫 useIntl(),例如輸入框佔位文字、aria-label、document.title,或向非 React API 傳遞字串。它還提供 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 屬性傳入標籤處理程式,即可在翻譯訊息中渲染連結、粗體文字或任意 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 屬性,或用自己的元素包裝 FormattedMessage。

使用 @formatjs/cli 提取訊息

FormatJS 提供 CLI,可自動從源程式碼提取訊息 ID 到 JSON 檔案。這樣無需手動記錄,也能讓訊息檔案與元件保持同步。

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 導致重新渲染

如果在 render 函數中以內嵌方式建立 messages 物件,IntlProvider 每次渲染都會收到新的物件引用,導致所有使用方重新渲染。請使用 useMemo 快取 messages,或在元件外定義。

測試中缺少 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 →

常見問題