Skip to main content

คู่มือ react-intl : ตั้งค่า React ให้รองรับหลายภาษา

ตั้งค่า FormatJS react-intl ในแอป React ด้วย IntlProvider, FormattedMessage, useIntl รูปแบบข้อความ ICU และการแปลอัตโนมัติ

กำลังใช้ react-i18next อยู่ใช่ไหม? ดูคู่มือ react-i18next ของเรา

1

ติดตั้ง react-intl

react-intl เป็นส่วนหนึ่งของโปรเจกต์ FormatJS มีคอมโพเนนต์และฮุก React สำหรับจัดรูปแบบข้อความ ตัวเลข วันที่ และพหูพจน์ด้วยมาตรฐาน ICU MessageFormat

react-intl ไม่มีการพึ่งพาขณะรันนอกเหนือจาก React ใช้ 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" } ต้องทำ 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"
}
ใช้ ID คั่นด้วยจุดอย่าง “nav.home” เพื่อจัดระเบียบ ต่างจาก react-i18next เพราะ react-intl ต้องการออบเจ็กต์ messages แบบแบน คุณทำคีย์ให้แบน ไม่ใช่โครงสร้าง
3

ใช้คำแปลในคอมโพเนนต์

react-intl มี API หลักสองแบบ ได้แก่ คอมโพเนนต์ FormattedMessage สำหรับเรนเดอร์ JSX ที่แปลแล้ว และฮุก useIntl สำหรับการเข้าถึงแบบสั่งงาน เช่น ตัวยึดตำแหน่ง ป้าย aria และการจัดรูปแบบด้วยโปรแกรม

คอมโพเนนต์ FormattedMessage

ใช้ FormattedMessage สำหรับคำแปลแบบประกาศใน JSX ส่ง 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

ใช้ useIntl() เมื่อต้องการข้อความที่แปลแล้วเป็นค่าธรรมดา เช่น ตัวยึดตำแหน่งอินพุต aria-label, document.title หรือเมื่อส่งข้อความให้ API ที่ไม่ใช่ React นอกจากนี้ยังมี 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 ในคำแปล)

ฝัง JSX ในคำแปลด้วยแท็กคล้าย XML ในข้อความ ส่งตัวจัดการแท็กผ่าน prop 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 เป็นค่าเริ่มต้น หากต้องการองค์ประกอบครอบเฉพาะ ให้ส่ง prop textComponent ให้ IntlProvider หรือครอบ 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 จัดการกฎ เพียงส่งค่า count

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 จับคีย์ที่หายไปและตัวยึดตำแหน่งเสียหายก่อนส่งขึ้นใช้งาน แล้วทดสอบ UI ด้วยคำแปลจำลองผ่าน i18n-pseudo ก่อนคำแปลจริงจะมาถึง

ข้อผิดพลาดที่พบบ่อย

พึ่ง defaultMessage มากเกินไป

defaultMessage เป็นค่าสำรองระหว่างพัฒนา ไม่ใช่กลยุทธ์การแปล หากใช้ defaultMessage กับทุกข้อความ ผลลัพธ์การแยกข้อความจะมีข้อความภาษาอังกฤษ แต่นักแปลอาจพลาดคีย์ใหม่ ให้แยกและดูแลไฟล์ภาษาต้นฉบับที่ครบถ้วนเสมอ

ใช้ออบเจ็กต์ซ้อนแทนคีย์แบบแบน

IntlProvider ต้องการ Record&lt;string, string&gt; แบบแบนสำหรับ messages หากส่ง JSON แบบซ้อนอย่าง { nav: { home: "Home" } } react-intl จะหาคีย์ “nav.home” ไม่พบ ให้ทำ messages ให้แบนก่อนส่ง หรือใช้ไลบรารีอย่าง flat

IntlProvider ทำให้เรนเดอร์ใหม่

หากสร้างออบเจ็กต์ messages ในบรรทัดภายในฟังก์ชัน render IntlProvider จะได้รับการอ้างอิงออบเจ็กต์ใหม่ทุกครั้งที่เรนเดอร์ ทำให้ผู้ใช้ทุกตัวเรนเดอร์ใหม่ ให้จดจำ messages ด้วย useMemo หรือกำหนดไว้นอกคอมโพเนนต์

ไม่มี IntlProvider ในการทดสอบ

คอมโพเนนต์ที่ใช้ FormattedMessage หรือ useIntl จะโยนข้อผิดพลาดหากเรนเดอร์โดยไม่มี IntlProvider เป็นบรรพบุรุษ ในการทดสอบ ให้ครอบคอมโพเนนต์ด้วย IntlProvider ที่มี locale="en" และออบเจ็กต์ messages ว่างหรือขั้นต่ำ

โครงสร้างไฟล์ที่แนะนำ

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 →

คำถามที่พบบ่อย