react-intl / next-intl — i18n

react-intl / next-intl react-intl (FormatJS) dan next-intl adalah library populer untuk i18n di React dan Next.js. Keduanya menggunakan ICU Message Format…

Intl memformat satu nilai. Message format mengurus satu kalimat utuh: teks dengan slot, plus pilihan bentuk menurut jumlah atau jenis. Bayangin formulir surat di kantor kelurahan: "Yth. Bapak/Ibu*) ......, pesanan Anda untuk ...... malam", dengan catatan "*) coret yang tidak perlu". Petugasnya mengisi titik-titik dan mencoret pilihan yang gak cocok. ICU MessageFormat adalah formulir itu dalam bentuk teks: {name} adalah titik-titiknya, select dan plural adalah pilihan yang dicoret otomatis. react-intl dan next-intl adalah petugas yang mengisinya di dalam React.

🧪 Tebakan cepat: Pesan {n, plural, one {# night} other {# nights}} diisi n = 0 dengan locale en-US. Hasilnya "0 night", "0 nights", atau error? Jawabannya ada di bagian "plural dan select".

ICU MessageFormat: kalimat dengan slot

Kamus Homestay Kabut, satu file per locale. Ini tiga pesan dari kartu Bu Sri:

{
  "id-ID": {
    "nights": "{n, plural, other {# malam}}",
    "total": "Total {n, plural, other {# malam}}: {price}",
    "thanks": "Terima kasih, {gender, select, male {Pak} female {Bu} other {Kak}} {name}!"
  },
  "en-US": {
    "nights": "{n, plural, one {# night} other {# nights}}",
    "total": "Total for {n, plural, one {# night} other {# nights}}: {price}",
    "thanks": "Thank you, {name}!"
  },
  "ja-JP": {
    "nights": "{n, plural, other {#泊}}",
    "total": "{n, plural, other {#泊}}の合計:{price}",
    "thanks": "{name}様、ありがとうございます。"
  },
  "ar-EG": {
    "nights": "{n, plural, one {ليلة واحدة} two {ليلتان} few {# ليالٍ} many {# ليلة} other {# ليلة}}",
    "total": "الإجمالي ({n, plural, one {ليلة واحدة} two {ليلتان} few {# ليالٍ} many {# ليلة} other {# ليلة}}): {price}",
    "thanks": "{gender, select, female {شكرًا لكِ يا {name}!} other {شكرًا لكَ يا {name}!}}"
  }
}

plural dan select

Kami menjalankan pesan nights di atas dengan intl-messageformat 12.1.3, mesin yang dipakai react-intl dan next-intl:

Locale n PluralRules memberi Cabang yang dipakai Hasil
en-US 1 one one {# night} 1 night
en-US 0 other other {# nights} 0 nights
id-ID 2 other other {# malam} 2 malam
ja-JP 2 other other {#泊} 2泊
ar-EG 2 two two {ليلتان} ليلتان
ar-EG 3 few few {# ليالٍ} ٣ ليالٍ
ar-EG 11 many many {# ليلة} ١١ ليلة

Bahasa Inggris gak punya kategori zero, jadi 0 masuk other: hasilnya "0 nights". Itu jawaban tebakannya. Kalau kamu mau kalimat khusus untuk nol, pakai pencocokan angka persis =0: {n, plural, =0 {Pilih lama menginap} one {# night} other {# nights}}. Cabang =N dicek lebih dulu daripada kategori.

Pesan thanks dengan select: id-ID memberi "Terima kasih, Pak Hadi!" untuk male, "Terima kasih, Bu Rina!" untuk female, dan "Terima kasih, Kak Alex!" untuk nilai lain.

Dua hal kecil yang sering bikin beda:

react-intl: IntlProvider, FormattedMessage, useIntl

react-intl (bagian dari FormatJS) versi 12.1.4, rilis Oktober 2026, butuh React 18 atau lebih baru dan cuma tersedia sebagai ES module.

import { IntlProvider, FormattedMessage, useIntl } from 'react-intl';
import enUS from './messages/en-US.json';
// en-US.json: { "total": "Total for {n, plural, one {# night} other {# nights}}: {price, number, ::currency/IDR precision-integer}",
//               "group": "Length of stay" }

function Total({ nights }) {
  const intl = useIntl();
  return (
    <div role="group" aria-label={intl.formatMessage({ id: 'group' })}>
      <FormattedMessage id="total" values={{ n: nights, price: 350000 * nights }} />
    </div>
  );
}

export default function App() {
  return (
    <IntlProvider locale="en-US" messages={enUS} timeZone="Asia/Jakarta">
      <Total nights={2} />
    </IntlProvider>
  );
}
// Hasil render: Total for 2 nights: IDR 700,000

next-intl: useTranslations di Next.js

next-intl versi 4.14.9 mendukung Next.js 12 sampai 16, dengan sintaks ICU yang sama (di dalamnya juga memakai intl-messageformat). Kamusnya per locale, dan kunci yang bersarang jadi namespace:

// src/i18n/request.ts: konfigurasi per request
import { getRequestConfig } from 'next-intl/server';

export default getRequestConfig(async () => {
  const locale = 'en-US'; // nanti dari URL atau cookie
  return {
    locale,
    timeZone: 'Asia/Jakarta',
    messages: (await import(`../../messages/${locale}.json`)).default,
  };
});
// app/kamar/page.tsx: Server Component
import { getTranslations } from 'next-intl/server';

export default async function KamarPage() {
  const t = await getTranslations('Booking');
  return <p>{t('total', { n: 2, price: 700000 })}</p>;
}

Lab ini: subset ICU kecil yang jujur

react-intl dan next-intl gak terpasang di BelajarWeb.dev, dan Playground gak bisa memasang paket npm. Jadi kode Edit di Playground di Lab berisi pemformat kecil buatan sendiri (sekitar 60 baris) yang mengerti {name}, plural (dengan =N, kategori, dan #), dan select, termasuk yang bersarang. Yang gak didukung: skeleton angka dan tanggal, selectordinal, offset, tanda kutip untuk menulis { apa adanya, dan tag rich text. Harga di Lab dikirim sebagai teks yang sudah diformat Intl.NumberFormat. Kami membandingkan subset itu dengan intl-messageformat 12.1.3 untuk kamus di atas, 22 nilai n, dan empat nilai gender, plus kasus error: 1.063 dari 1.063 hasil sama persis, termasuk pesan error-nya.

⚠️ Jebakan yang sering ketemu

TL;DR: ICU MessageFormat menulis kalimat utuh dengan slot: {name}, {n, plural, one {# night} other {# nights}}, dan {gender, select, male {Pak} female {Bu} other {Kak}}. Tiap plural dan select wajib punya other; =0 mencocokkan angka persis; # adalah angka yang diformat locale. Bahasa Inggris gak punya zero, jadi n = 0 memberi "0 nights"; ar-EG memberi ليلتان untuk 2, ٣ ليالٍ untuk 3, ١١ ليلة untuk 11. react-intl 12 (untuk React tanpa Next.js) memakai IntlProvider, komponen FormattedMessage untuk menampilkan pesan, dan useIntl().formatMessage untuk atribut. next-intl 4 (untuk Next.js) memakai getRequestConfig, useTranslations, dan getTranslations, terintegrasi dengan routing Next.js dan Server Components. Subset ICU di Lab memberi hasil yang sama dengan intl-messageformat 12.1.3 untuk semua kasus yang kami uji.

Yang akan kamu pelajari