Skip to main content

Hướng dẫn đầy đủ về quốc tế hóa React

Từ con số 0 đến ứng dụng đa ngôn ngữ: thiết lập i18n trong ứng dụng React rồi tự động dịch bằng AI.

1

Cài đặt gói

Bạn cần ba gói: react-i18next (các liên kết React), i18next (thư viện lõi) và i18next-browser-languagedetector tùy chọn để tự động phát hiện locale.

react-i18next cung cấp các hook và thành phần React. i18next là công cụ lõi xử lý việc tải bản dịch, nội suy và số nhiều. Plugin phát hiện ngôn ngữ tự động đọc tùy chọn ngôn ngữ của trình duyệt.
Terminal
npm install react-i18next i18next i18next-browser-languagedetector i18next-http-backend
2

Cấu hình phiên bản i18n

Tạo tệp cấu hình i18n để khởi tạo i18next với ngôn ngữ mặc định, tài nguyên bản dịch và chuỗi plugin của bạn. Bạn phải nhập tệp này tại điểm vào của ứng dụng trước khi bất kỳ thành phần nào hiển thị.

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" — lỗi này có nghĩa là bạn quên gọi i18n.use(initReactI18next) trước i18n.init(). Lệnh gọi .use() phải đứng trước .init().
3

Bọc ứng dụng bằng I18nextProvider

Nhập tệp cấu hình i18n tại gốc ứng dụng và bọc cây thành phần bằng I18nextProvider. Nếu thiếu bước này, useTranslation() sẽ trả về khóa thô thay vì văn bản đã dịch.

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>
);
Nếu bản dịch hiển thị khóa thô như "welcome" thay vì "Chào mừng bạn đến với ứng dụng", nguyên nhân phổ biến nhất là thiếu I18nextProvider hoặc chưa nhập tệp cấu hình i18n.
4

Tạo tệp bản dịch

Tạo một tệp JSON cho mỗi ngôn ngữ. Dùng khóa lồng nhau để sắp xếp chuỗi theo tính năng hoặc trang. Duy trì ngôn ngữ nguồn (thường là tiếng Anh) làm nguồn dữ liệu chuẩn duy nhất.

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"
  }
}
Đặt tên khóa theo nội dung mô tả thay vì vị trí xuất hiện: "cart.itemCount" tốt hơn "homepageCartLabel". Khóa nên tiếp tục dùng được sau khi thiết kế lại giao diện.
5

Dùng bản dịch trong thành phần

Gọi useTranslation() trong bất kỳ thành phần nào để lấy hàm t(). Dùng hàm này cho chuỗi đơn giản, biến nội suy và bản dịch nhúng JSX với thành phần Trans.

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" />
    }} />
  );
}
Khóa động như t(`error.$'{code}'`) hoạt động khi chạy nhưng các công cụ như i18next-scanner không thể trích xuất tĩnh. Nếu dùng công cụ trích xuất, hãy liệt kê rõ khóa động hoặc dùng chú thích gợi ý.
6

Xử lý số nhiều và biến

i18next xử lý số nhiều theo quy tắc CLDR chứ không chỉ dạng số ít/số nhiều. Tiếng Ả Rập có 6 dạng (zero, one, two, few, many, other). Tiếng Nhật có 1 dạng (other). Hãy định nghĩa mọi dạng bắt buộc trong tệp bản dịch để i18next tự động chọn đúng dạng.

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}}個のアイテム"
}
Không bao giờ mã hóa cứng count === 1 để phát hiện số ít. Các ngôn ngữ như tiếng Pháp coi 0 là số ít. Tiếng Nga, Ả Rập và Ba Lan có những dạng mà tiếng Anh không có. Hãy để i18next xử lý quy tắc số nhiều.
7

Thêm tính năng chuyển đổi và phát hiện ngôn ngữ

Tạo bộ chọn ngôn ngữ gọi i18n.changeLanguage(). Kết hợp với trình phát hiện ngôn ngữ của trình duyệt để tự động nhận biết ngôn ngữ người dùng ưu tiên trong lần truy cập đầu tiên rồi lưu lựa chọn rõ ràng của họ.

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>
  );
}
Nếu dùng SSR (Next.js, Remix), máy chủ có thể phát hiện ngôn ngữ khác máy khách (máy chủ không có tùy chọn trình duyệt). Điều này gây lỗi không khớp hydration. Cách khắc phục: truyền locale đã phát hiện từ máy chủ sang máy khách dưới dạng prop hoặc cookie để cả hai hiển thị cùng ngôn ngữ.
8

Tự động hóa bản dịch

Sau khi hoàn tất thiết lập i18n, hãy dùng AI để dịch các tệp locale. Trong IDE, yêu cầu trợ lý AI dịch tệp nguồn hoặc dùng CLI i18n Agent trong quy trình CI/CD.

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
Dịch tăng dần — khi thêm khóa mới vào tệp nguồn, chỉ dịch phần khác biệt thay vì tạo lại mọi tệp. Cách này bảo toàn các bản dịch đã được con người rà soát.

Tự động bảo đảm chất lượng bản dịch

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

Lỗi thường gặp

Bản dịch hiển thị khóa thô

Nguyên nhân: thiếu I18nextProvider, chưa nhập cấu hình i18n tại gốc ứng dụng, chưa tải không gian tên hoặc bản dịch vẫn đang tải bất đồng bộ. Kiểm tra bảng điều khiển của trình duyệt với debug: true để tìm manh mối.

Lỗi Suspense khi không có phương án dự phòng

"A component suspended while responding to synchronous input" — thêm ranh giới '&lt;Suspense&gt;' quanh ứng dụng hoặc đặt useSuspense: false trong cấu hình khởi tạo i18next.

SSR hydration không khớp

Máy chủ hiển thị bằng một locale còn máy khách hydrate bằng locale khác. Hãy bảo đảm cả hai dùng cùng nguồn locale — truyền locale dưới dạng prop từ máy chủ, đừng chỉ dựa vào tính năng phát hiện của trình duyệt.

Không tự động hoàn thành khóa bản dịch

Mở rộng mô-đun i18next bằng kiểu tài nguyên của bạn: declare module 'i18next' '{ interface CustomTypeOptions { resources: typeof resources } }'. Nhờ đó, các lệnh gọi t() sẽ an toàn về kiểu và có tính năng tự động hoàn thành.

Cấu trúc tệp đề xuấ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

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ì

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