Intl adalah kumpulan pemformat bawaan JavaScript untuk angka, uang, tanggal, waktu relatif, jamak, daftar, urutan abjad, nama bahasa, dan pemotongan teks, semuanya sesuai locale. Bayangin resepsionis Homestay Kabut yang hafal kebiasaan tiap negara: kamu kasih angka, tanggal, atau daftar kamar plus locale tamunya, dia menuliskannya dengan cara tamu itu. Hafalannya berasal dari satu buku besar, CLDR (Unicode Common Locale Data Repository), yang dibawa tiap browser lewat pustaka ICU. Tiap browser membawa edisi yang berbeda, jadi jawabannya kadang beda sedikit. Pelajaran UI/UX "Internationalization (i18n)" sudah memakai NumberFormat, DateTimeFormat, dan PluralRules untuk kartu Bu Sri; di sini kita bongkar semua pemformatnya, plus bedanya di tiap browser.
🧪 Tebakan cepat:
new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR' }).format(700000)menulis rupiah dengan berapa angka desimal di Chrome 147, dan di Node 22.22.1: sama-sama 2, sama-sama 0, atau beda? Jawabannya ada di bagian "NumberFormat: rupiah punya berapa desimal?".
Satu pola untuk semua pemformat
const money = new Intl.NumberFormat('ja-JP', { style: 'currency', currency: 'IDR', maximumFractionDigits: 0 });
money.format(700000); // "IDR 700,000"
money.formatToParts(700000); // [{ type: "currency", value: "IDR" }, { type: "literal", value: "\u00A0" }, { type: "integer", value: "700" }, ...]
money.resolvedOptions().locale; // "ja-JP"
- Constructor menerima locale (satu tag atau daftar tag) dan opsi, lalu mengembalikan pemformat. Buat sekali, pakai berkali-kali.
format()memberi teks jadi.formatToParts()memberi potongannya, berguna kalau angkanya mau ditebalkan sendiri.resolvedOptions()memberi tahu apa yang benar-benar dipakai: locale, sistem angka, jumlah desimal, zona waktu, kalender.Intl.NumberFormatadalah API untuk memformat angka sesuai locale.(700000).toLocaleString('id-ID', opsi)cuma jalan pintas yang membuat pemformat yang sama tiap kali dipanggil.
Kalau browser gak punya data untuk locale yang diminta, dia diam-diam pindah ke locale lain. Bu Sri ingin versi Basa Jawa (jv): new Intl.NumberFormat('jv').resolvedOptions().locale memberi "jv" di Firefox 146, Safari 26.4, dan Node 22.22.1, tapi "en-US" di Chromium 147, karena Chrome gak membawa data bahasa Jawa dan jatuh ke bahasa default browser. Beri daftar cadangan: dengan ['jv', 'id'], Chrome memilih "id" dan menulis "Sabtu, 12 Desember 2026".
NumberFormat: rupiah punya berapa desimal?
Kami menjalankan kode yang sama di tiga mesin browser (lewat Playwright) dan di Node 22.22.1, yang membawa ICU 78.2 dengan CLDR 48:
| Kode | Chromium 147 | Firefox 146 | WebKit 26.4 (Safari) | Node 22.22.1 |
|---|---|---|---|---|
new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR' }).format(700000) |
Rp 700.000,00 | Rp 700.000,00 | Rp700.000,00 | Rp 700.000 |
.resolvedOptions().maximumFractionDigits untuk IDR |
2 | 2 | 2 | 0 |
sama, plus maximumFractionDigits: 0 |
Rp 700.000 | Rp 700.000 | Rp700.000 | Rp 700.000 |
new Intl.NumberFormat('ja-JP', { style: 'currency', currency: 'JPY' }).format(9800) |
¥9,800 | ¥9,800 | ¥9,800 | ¥9,800 |
new Intl.NumberFormat('ar-EG', { style: 'currency', currency: 'IDR', maximumFractionDigits: 0 }).format(700000) |
٧٠٠٬٠٠٠ IDR | ٧٠٠٬٠٠٠ IDR | ٧٠٠٬٠٠٠ IDR | ٧٠٠٬٠٠٠ IDR |
Jumlah desimal tiap mata uang diambil dari data. Tabel ISO 4217 mencatat rupiah dengan 2 desimal. CLDR juga mencatat 2 sampai CLDR 47, lalu CLDR 48 (Oktober 2025) mengubah IDR jadi 0 desimal. V8, mesin JavaScript Chrome dan Node, mengambil desimal dari CLDR di dalam ICU-nya: Node 22.22.1 sudah membawa CLDR 48, jadi 0; Chromium 147 masih memberi 2. Firefox dan Safari memakai tabel ISO 4217, jadi 2. Jawabannya: beda, padahal kodenya sama persis. Itu jawaban tebakannya.
Aturannya: untuk rupiah, selalu sebut maximumFractionDigits: 0, supaya semua mesin menulis "Rp 700.000". Yang lain dari tabel:
- Safari gak memberi spasi sesudah "Rp"; mesin lain memberi spasi tak terputus U+00A0. Yen di
ja-JPmemakai ¥ lebar (U+FFE5), kecuali Safari yang memakai ¥ biasa (U+00A5). ar-EGmemakai angka ٠ sampai ٩, pemisah ribuan ٬, dan diawali tanda tak terlihat U+200F (right-to-left mark) supaya angkanya gak terbalik di tengah teks Arab.notation: 'compact'menulis 700000 sebagai "700 rb" (id-ID), "700K" (en-US), "70万" (ja-JP, satuan 万 = 10.000), dan "٧٠٠ ألف" (ar-EG).currencyDisplay: 'narrowSymbol'dien-USmemberi "Rp 700,000" alih-alih "IDR 700,000".
DateTimeFormat dan timeZone
Check-in Bu Sri: Sabtu, 12 Desember 2026, pukul 14.00 WIB, yaitu 07.00 UTC.
const checkIn = new Date('2026-12-12T07:00:00Z');
const opts = { dateStyle: 'full', timeStyle: 'short', timeZone: 'Asia/Jakarta' };
new Intl.DateTimeFormat('id-ID', opts).format(checkIn); // "Sabtu, 12 Desember 2026 pukul 14.00"
new Intl.DateTimeFormat('en-US', opts).format(checkIn); // "Saturday, December 12, 2026 at 2:00 PM"
new Intl.DateTimeFormat('ja-JP', opts).format(checkIn); // "2026年12月12日土曜日 14:00"
new Intl.DateTimeFormat('ar-EG', opts).format(checkIn); // "السبت، ١٢ ديسمبر ٢٠٢٦ في ٢:٠٠ م"
new Intl.DateTimeFormat('id-ID', { dateStyle: 'long', timeZone: 'Asia/Jakarta' }).format(checkIn); // "12 Desember 2026"
dateStyledantimeStylemenerima'full','long','medium', atau'short'. Tanggal bahasa Indonesia yang lengkap:new Intl.DateTimeFormat('id-ID', { dateStyle: 'long' }).format(date).- Safari sedikit beda: "2026年12月12日 土曜日 14:00" (ada spasi), dan untuk
ar-EGmemakai koma sesudah bulan. timeZonemenentukan jam berapa yang ditulis.Dateadalah satu titik waktu; tanpatimeZone, jam ditulis menurut zona perangkat tamu. Laptop tamu di Tokyo akan menulis 16.00, padahal check-in jam 14.00 waktu Dieng. Jadi selalutimeZone: 'Asia/Jakarta'. DengantimeZoneName: 'short',id-IDmenulis "14.00 WIB", danja-JPdengantimeZone: 'Asia/Tokyo'menulis "16:00 JST".- Kalender ikut locale. Safari menulis
ar-SAdengan kalender Hijriah: "٣ رجب، ١٤٤٨ هـ", danresolvedOptions().calendarmemberi"islamic-umalqura". Kunci kalendernya dengancalendar: 'gregory'atau tagar-SA-u-ca-gregory.
RelativeTimeFormat, PluralRules, ListFormat
const rtf = new Intl.RelativeTimeFormat('id-ID', { numeric: 'auto' });
rtf.format(3, 'day'); // "dalam 3 hari"
rtf.format(1, 'day'); // "besok"
rtf.format(-1, 'day'); // "kemarin"
RelativeTimeFormatcuma menulis selisihnya; menghitung "3 hari lagi" tetap tugasmu. Batas gratis batal 3 hari jadi "in 3 days" (en-US), "3 日後" (ja-JP), dan "خلال ٣ أيام" (ar-EG).numeric: 'auto'memilih kata seperti "besok" dan "lusa"; tanpanya, hasilnya "dalam 1 hari".PluralRulesmemilih kategori jamak.resolvedOptions().pluralCategories:idcumaother,enone, other,jacumaother,arzero, one, two, few, many, other. Untukar-EG: 0 zero, 1 one, 2 two, 3 sampai 10 few, 11 sampai 99 many, 100 sampai 102 other, 103 few lagi.new Intl.PluralRules('en-US', { type: 'ordinal' })untuk urutan: 1 one (1st), 2 two (2nd), 3 few (3rd), 4 other (4th), 11 other (11th), 21 one (21st).ListFormatmenyambung daftar kamar: "Kamar Teras, Kamar Loteng, dan Kamar Kebun" (id-ID), "Kamar Teras, Kamar Loteng, and Kamar Kebun" (en-US), "Kamar Teras、Kamar Loteng、Kamar Kebun" (ja-JP). Dengantype: 'disjunction'kata sambungnya "atau".
Collator, DisplayNames, Segmenter
const guests = ['Zahra', 'Ömer', 'Oscar', 'andi', 'Bu Sri', 'Émile'];
[...guests].sort(); // ["Bu Sri", "Oscar", "Zahra", "andi", "Émile", "Ömer"]
[...guests].sort(new Intl.Collator('id').compare); // ["andi", "Bu Sri", "Émile", "Ömer", "Oscar", "Zahra"]
[...guests].sort(new Intl.Collator('sv').compare); // ["andi", "Bu Sri", "Émile", "Oscar", "Zahra", "Ömer"]
const rooms = ['Kamar 10', 'Kamar 2', 'Kamar 1'];
rooms.sort(new Intl.Collator('id', { numeric: true }).compare); // ["Kamar 1", "Kamar 2", "Kamar 10"]
sort()tanpa pembanding mengurutkan code unit UTF-16: huruf kapital sebelum huruf kecil, huruf beraksen sesudah z.Intl.Collatormengurutkan seperti kamus bahasa itu; bahasa Swedia memang menaruh Ö sesudah Z.numeric: truemembaca angka sebagai angka.DisplayNamesmemberi nama bahasa, wilayah, dan mata uang. Menu bahasa sebaiknya menulis tiap bahasa dalam bahasanya sendiri:new Intl.DisplayNames(['ja'], { type: 'language' }).of('ja')memberi "日本語", untukar"العربية", untukid"Indonesia". Dalam bahasa Indonesia,of('zh-Hant-TW')memberi "Tionghoa (Tradisional, Taiwan)", dantype: 'currency'untuk IDR memberi "Rupiah Indonesia".Segmentermemotong teks per grapheme, kata, atau kalimat. Bahasa Jepang gak memakai spasi:'チェックインの3日前までキャンセル無料。'.split(' ')cuma memberi 1 potongan.new Intl.Segmenter('ja', { granularity: 'word' })memberi kata-kata チェックイン, の, 3, 日前, まで, キャンセル, 無料 (yangisWordLike).
📌 Beda tiap mesin, diukur
Semua hasil di atas sama di empat mesin, kecuali yang ada di tabel ini:
| Yang diukur | Chromium 147 | Firefox 146 | WebKit 26.4 | Node 22.22.1 |
|---|---|---|---|---|
| Desimal bawaan IDR | 2 | 2 | 2 | 0 |
Sesudah "Rp" di id-ID |
U+00A0 | U+00A0 | gak ada | U+00A0 |
Simbol JPY di ja-JP |
¥ | ¥ | ¥ | ¥ |
Kalender ar-SA |
gregory | gregory | islamic-umalqura | gregory |
Data bahasa Jawa (jv) |
gak ada, jatuh ke en-US | ada | ada | ada |
Angka "3" di Segmenter kata ja |
isWordLike | isWordLike | bukan isWordLike | isWordLike |
ListFormat ar-EG |
polos | polos | tiap item diapit U+2068 dan U+2069 | polos |
Versinya: Chromium 147.0.7727.15, Firefox 146.0.1, dan WebKit 26.4 dari Playwright, serta Node 22.22.1 dengan ICU 78.2, CLDR 48, dan Unicode 17.0. Lab-nya menjalankan kode yang sama di browser-mu dan menaruh tabel ini di sampingnya.
⚠️ Jebakan yang sering ketemu
- Membandingkan hasil Intl dengan teks ketikan. Ada U+00A0, U+202F, dan U+200F yang gak kelihatan. Bandingkan dengan hasil Intl juga, atau pakai
formatToParts(). - Mengandalkan desimal bawaan mata uang. IDR bisa 2 atau 0 tergantung mesinnya. Sebut
maximumFractionDigits. - Tanpa
timeZone. Jam check-in ikut zona perangkat tamu. Sebut zona tempat kejadiannya. - Locale yang gak didukung. Hasilnya diam-diam pakai locale lain. Cek
resolvedOptions().localedan beri daftar cadangan seperti['jv', 'id'].
TL;DR: Intl memformat sesuai locale dengan data CLDR yang dibawa tiap browser lewat ICU. Polanya sama: new Intl.X(locale, opsi), lalu format(), formatToParts(), dan resolvedOptions() untuk melihat yang benar-benar dipakai; toLocaleString cuma jalan pintas ke Intl.NumberFormat. NumberFormat: desimal bawaan IDR adalah 2 di Chrome 147, Firefox 146, dan Safari 26.4, tapi 0 di Node 22.22.1 (CLDR 48), jadi sebut maximumFractionDigits: 0. DateTimeFormat: dateStyle: 'long' untuk "12 Desember 2026", selalu dengan timeZone: 'Asia/Jakarta'; Safari memakai kalender Hijriah untuk ar-SA. RelativeTimeFormat menulis "dalam 3 hari", PluralRules memilih kategori (ar punya enam), ListFormat menyambung daftar, Collator mengurutkan seperti kamus (dan numeric: true untuk "Kamar 2" sebelum "Kamar 10"), DisplayNames menulis "日本語" dan "العربية", Segmenter memotong teks Jepang yang tanpa spasi. Chrome 147 gak punya data bahasa Jawa, jadi cek resolvedOptions().locale.