Skip to main content

Hướng dẫn react-intl: Thiết lập quốc tế hóa React

Thiết lập FormatJS react-intl trong ứng dụng React bằng IntlProvider, FormattedMessage, useIntl, định dạng thông điệp ICU và dịch thuật tự động.

Bạn đang dùng react-i18next? Xem hướng dẫn react-i18next

1

Cài đặt react-intl

react-intl thuộc dự án FormatJS. Thư viện cung cấp component và hook React để định dạng chuỗi, số, ngày tháng và số nhiều theo tiêu chuẩn ICU MessageFormat.

Ngoài React, react-intl không có dependency nào khi chạy. Thư viện dùng API Intl tích hợp sẵn của trình duyệt để định dạng số và ngày tháng, đồng thời cung cấp trình phân tích ICU MessageFormat riêng cho số nhiều, select và văn bản đa dạng thức.
Terminal
npm install react-intl
2

Cấu hình IntlProvider

Bọc gốc ứng dụng bằng IntlProvider. Truyền ngôn ngữ đang hoạt động và một đối tượng messages phẳng. Sau đó, mọi component con đều có thể truy cập bản dịch qua FormattedMessage hoặc 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 yêu cầu một đối tượng messages khóa-giá trị phẳng (ví dụ: { "app.greeting": "Hello" }). Bạn phải làm phẳng JSON lồng nhau trước khi truyền vào IntlProvider hoặc dùng tiện ích như flat để chuyển đổi cấu trúc lồng nhau.

Tệp thông điệp

Tạo một tệp JSON cho mỗi ngôn ngữ. react-intl dùng trực tiếp cú pháp ICU MessageFormat: số nhiều, select và biến đều nằm ngay trong chuỗi thông điệp.

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"
}
Dùng ID phân tách bằng dấu chấm như "nav.home" để sắp xếp. Khác với react-i18next, react-intl yêu cầu đối tượng messages phẳng: bạn làm phẳng khóa, không làm phẳng cấu trúc.
3

Dùng bản dịch trong component

react-intl cung cấp hai API chính: component FormattedMessage để kết xuất JSX đã dịch và hook useIntl để truy cập theo lối mệnh lệnh (placeholder, nhãn aria, định dạng bằng mã).

Component FormattedMessage

Dùng FormattedMessage để khai báo bản dịch trong JSX. Truyền ID thông điệp và các giá trị nội suy. Component sẽ kết xuất trực tiếp chuỗi đã dịch.

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>
  );
}

Hook useIntl

Dùng useIntl() khi cần chuỗi đã dịch dưới dạng giá trị thuần, chẳng hạn placeholder của ô nhập, aria-label, document.title hoặc khi truyền chuỗi cho API ngoài React. Hook này cũng cung cấp formatNumber, formatDate và 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>
  );
}

Văn bản đa dạng thức (HTML trong bản dịch)

Nhúng JSX vào bản dịch bằng thẻ kiểu XML trong chuỗi thông điệp. Truyền trình xử lý thẻ qua prop values để kết xuất liên kết, chữ đậm hoặc bất kỳ component React nào trong thông điệp đã dịch.

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>,
      }}
    />
  );
}
Theo mặc định, FormattedMessage kết xuất một React Fragment. Nếu cần phần tử bao cụ thể, hãy truyền prop textComponent cho IntlProvider hoặc bọc FormattedMessage trong phần tử riêng.

Trích xuất thông điệp bằng @formatjs/cli

FormatJS cung cấp CLI tự động trích xuất ID thông điệp từ mã nguồn vào tệp JSON. Nhờ đó, tệp thông điệp luôn đồng bộ với component mà không cần theo dõi thủ công.

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

Số nhiều và ICU select

react-intl dùng trực tiếp ICU MessageFormat. Số nhiều, select theo giới tính và định dạng lồng nhau đều nằm ngay trong chuỗi thông điệp, không cần quy ước hậu tố hay khóa riêng.

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}個の商品があります"
}
Không bao giờ mã hóa cứng logic số nhiều trong JavaScript. Tiếng Ả Rập có 6 dạng số nhiều, tiếng Pháp coi 0 là số ít, còn tiếng Nhật không phân biệt số nhiều. Hãy để ICU MessageFormat xử lý quy tắc và chỉ truyền giá trị đếm.

ICU select cho giới tính và vai trò

Dùng cú pháp ICU select cho bản dịch phụ thuộc vào ngữ cảnh như giới tính, vai trò người dùng hoặc giá trị trạng thái. Biểu thức select chọn biến thể phù hợp theo giá trị được cung cấp.

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 }}
/>

Tự động kiểm soát chất lượng bản dịch

Dùng i18n-validate để phát hiện khóa thiếu và placeholder hỏng trước khi phát hành. Dùng i18n-pseudo để kiểm thử UI bằng bản dịch giả trước khi có bản dịch thật.

Lỗi thường gặp

Phụ thuộc quá nhiều vào defaultMessage

defaultMessage là cơ chế dự phòng khi phát triển, không phải chiến lược dịch thuật. Nếu dùng defaultMessage cho mọi chuỗi, kết quả trích xuất thông điệp sẽ chứa văn bản tiếng Anh nhưng biên dịch viên có thể bỏ sót khóa mới. Luôn trích xuất và duy trì đầy đủ tệp ngôn ngữ nguồn.

Đối tượng lồng nhau thay vì khóa phẳng

IntlProvider yêu cầu messages là một Record&lt;string, string&gt; phẳng. Nếu truyền JSON lồng nhau như { nav: { home: "Home" } }, react-intl sẽ không tìm thấy khóa "nav.home". Hãy làm phẳng messages trước khi truyền vào hoặc dùng thư viện như flat.

IntlProvider gây kết xuất lại

Nếu tạo đối tượng messages ngay trong hàm render, IntlProvider sẽ nhận một tham chiếu đối tượng mới ở mỗi lần kết xuất, khiến mọi consumer kết xuất lại. Hãy ghi nhớ messages bằng useMemo hoặc khai báo bên ngoài component.

Thiếu IntlProvider trong kiểm thử

Component dùng FormattedMessage hoặc useIntl sẽ ném lỗi nếu kết xuất mà không có IntlProvider ở cấp cha. Trong kiểm thử, hãy bọc component bằng IntlProvider với locale="en" và một đối tượng messages trống hoặc tối thiểu.

Cấu trúc tệp khuyên dùng

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

Dùng thử i18n Agent ngay

Thả tệp bản dịch của bạn vào đây

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

hoặc nhấp để duyệt

Ngôn ngữ đích

Không cần đăng kýBáo giá tức thì

Dự phòng ngôn ngữ với react-intl-locale-chain

Khi thiếu khóa bản dịch trong một ngôn ngữ vùng như pt-BR, react-intl chuyển thẳng sang ngôn ngữ mặc định thay vì kiểm tra ngôn ngữ cha pt trước.

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>

Xem Hướng dẫn dự phòng ngôn ngữ để biết danh sách đầy đủ các framework được hỗ trợ và 75 chuỗi tích hợp sẵn. Learn more →

Câu hỏi thường gặp