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}}diisin = 0dengan localeen-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}!}}"
}
}
{name}adalah slot biasa: nilainya ditulis apa adanya.{n, plural, ...}memilih cabang menurut kategori jamak dariIntl.PluralRules.#di dalamnya adalah angkan, diformat menurut locale.{gender, select, ...}memilih cabang menurut nilai teks. Bahasa Indonesia butuh sapaan Pak, Bu, atau Kak; bahasa Arab butuh "kamu" laki-laki (لكَ) atau perempuan (لكِ); bahasa Inggris dan Jepang gak butuh keduanya, jadi pesannya gak punyaselect.- Tiap
pluraldanselectwajib punya cabangother. Kalau cabang untuk kategorinya gak ada (misalnyazerodiar-EG),otheryang dipakai.
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:
#diformat locale,{n}gak.{n} nightsdengann = 1000memberi "1000 nights", sedangkan#memberi "1,000 nights". Diar-EG,{n}memberi angka 3 biasa,#memberi ٣.- Angka dan tanggal juga bisa diformat di dalam pesan dengan skeleton:
{price, number, ::currency/IDR precision-integer}memberi "Rp 700.000" diid-ID, dan{d, date, ::EEEEdMMMM}memberi "Sabtu, 12 Desember".precision-integersama denganmaximumFractionDigits: 0dari pelajaran Intl API.
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
IntlProviderdipasang sekali di atas:locale, kamus untuk locale itu, dantimeZoneuntuk tanggal di dalam pesan.<FormattedMessage id="total" values={...} />adalah komponen yang menampilkan pesan terjemahan. Boleh diberidefaultMessagesebagai cadangan.useIntl()memberiintl.formatMessage,intl.formatNumber, danintl.formatDateuntuk tempat yang gak bisa diisi komponen:aria-label,placeholder,title.- Kalau kuncinya gak ada di kamus, react-intl memakai
defaultMessagedan melapor lewatonErrordengan kodeMISSING_TRANSLATION: "Missing message: "total" for locale "ja-JP", using default message (...) as fallback." - Cocok untuk aplikasi React tanpa Next.js: SPA dengan Vite, React Router, atau React Native.
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>;
}
next.config.tsmembungkus config dengancreateNextIntlPlugin(), danapp/layout.tsxmembungkuschildrendengan<NextIntlClientProvider>supaya Client Component ikut dapat kamusnya.useTranslations('Booking')dipakai di komponen biasa (Server atau Client);getTranslationsdi Server Component yangasync.- Locale di URL (
/ja-JP/kamar-teras) diatur dengandefineRoutingplus middleware. Di Next.js 16 file middleware itu bernamaproxy.ts; versi sebelumnyamiddleware.ts. - Jadi keunggulan utama next-intl dibanding react-intl biasa di Next.js: dia terintegrasi dengan routing Next.js dan mendukung Server Components, jadi teks di Server Component sudah jadi HTML waktu sampai di browser.
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
- Menyambung potongan kalimat.
'Total for ' + n + ' nights'gak bisa jadi bahasa Arab atau Jepang. Simpan kalimat utuh dengan slot. - Lupa cabang
other.{n, plural, one {# night}}melemparSyntaxError: MISSING_OTHER_CLAUSE, begitu jugaselecttanpaother. - Memakai
{n}di dalamplural. Angkanya gak diformat locale: "1000 nights", bukan "1,000 nights". Pakai#. - Menyalin aturan bahasa Inggris ke semua bahasa.
onedanothercukup untuk bahasa Inggris, tapiar-EGbutuhtwo,few, danmanyjuga. - Slot tanpa nilai.
{name}tanpanamedivaluesmelempar error: "The intl string context variable "name" was not provided to the string "{name}"".
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.