Intl dan Unicode

Format dan perbandingan yang mengikuti locale untuk aplikasi global.

Daftar isi

Aplikasi yang dipakai lintas negara tidak bisa menganggap format angka, tanggal, dan urutan abjad sama di mana pun. Objek Intl milik JavaScript menyerahkan penyesuaian itu ke data locale yang tersedia pada runtime, mulai dari NumberFormat, DateTimeFormat, sampai Collator. Topik ini juga menyentuh Unicode di balik string, karena panjang string dan perbandingannya ternyata tidak sesederhana kelihatannya.

Intl.NumberFormat mengikuti locale#

Intl.NumberFormat memformat angka mengikuti aturan locale, sehingga angka yang sama tampil berbeda bagi pembaca yang berbeda. Tag locale ditulis sebagai kode bahasa dan negara, misalnya id-ID untuk Indonesia dan en-US untuk Inggris Amerika, dan formatter juga menerima opsi gaya seperti percent.

js Contoh: satu angka, tiga locale
const angka = 1234567.89;
for (const locale of ["id-ID", "en-US", "de-DE"]) {
  console.log(locale, new Intl.NumberFormat(locale).format(angka));
}

const persen = 0.245;
console.log(new Intl.NumberFormat("id-ID", { style: "percent" }).format(persen));
text Keluaran
id-ID 1.234.567,89
en-US 1,234,567.89
de-DE 1.234.567,89
25%

Angka yang sama menjadi 1.234.567,89 pada id-ID dengan titik sebagai pemisah ribuan dan koma sebagai pemisah desimal, sedangkan en-US menukarkan keduanya. de-DE kebetulan memakai bentuk yang sama dengan id-ID untuk angka polos. Pemilihan pemisah ini bukan soal estetika, melainkan kebiasaan baca, sehingga memaksakan satu bentuk untuk semua pembaca berisiko menimbulkan salah tafsir. Formatter sebaiknya dibuat sekali lalu dipakai ulang, karena pembuatannya mengolah data locale yang memakan waktu bila diulang di dalam perulangan.

Gaya currency melengkapi gaya persen untuk kebutuhan uang, dengan opsi currency berisi kode mata uang tiga huruf seperti IDR atau USD. Formatter menambahkan simbolnya di tempat yang lazim bagi locale terkait, sehingga mata uang yang sama bisa tampil di depan atau di belakang angkanya tergantung kebiasaan setempat. Karena kode mata uangnya ditulis lewat opsi, satu formatter tidak terikat satu negara. Kombinasi locale en-US dengan kode IDR pun sah bila konteksnya memang menampilkan rupiah pada antarmuka berbahasa Inggris.

Intl.DateTimeFormat untuk tanggal#

Intl.DateTimeFormat memformat object Date menjadi teks yang lazim bagi locale-nya, dengan opsi dateStyle untuk format siap pakai dan opsi komponen untuk susunan sendiri. Pemformatan mengikuti locale pada bahasa nama bulan dan hari, urutan komponen, serta pemisahnya.

js Contoh: satu tanggal, tiga locale, dan susunan komponen
const tanggal = new Date(2026, 2, 15);
for (const locale of ["id-ID", "en-US", "de-DE"]) {
  console.log(locale, new Intl.DateTimeFormat(locale, { dateStyle: "long" }).format(tanggal));
}
console.log(new Intl.DateTimeFormat("id-ID", { weekday: "long", day: "numeric", month: "long", year: "numeric" }).format(tanggal));
text Keluaran
id-ID 15 Maret 2026
en-US March 15, 2026
de-DE 15. März 2026
Minggu, 15 Maret 2026

Tiga baris pertama memperlihatkan kebiasaan yang berbeda untuk tanggal yang sama, yaitu urutan hari-bulan-tahun pada id-ID dan de-DE serta urutan bulan lebih dulu pada en-US, dengan nama bulan dalam bahasa masing-masing. Baris terakhir menyusun komponen sendiri lewat opsi, dan hari Minggu pun muncul tanpa menghitung manual. Satu hal yang sering menjebak adalah bulan pada konstruktor Date yang berbasis nol, sehingga new Date(2026, 2, 15) adalah 15 Maret, bukan 15 Februari. Untuk penanda waktu yang dibaca lintas zona, opsi timeZone tersedia agar tanggal dicetak mengikuti zona tertentu, bukan zona mesin pembacanya.

Intl.Collator untuk urutan yang benar#

Intl.Collator membandingkan string menurut aturan abjad locale, bukan menurut urutan kode karakternya. Perbedaannya baru terlihat ketika daftar memuat huruf beraksen, karena pengurutan bawaan sort membandingkan unit kode UTF-16 satu per satu.

js Contoh: sort bawaan dibandingkan Collator
const nama = ["Citra", "Clara", "Céline", "Cahya"];
console.log([...nama].sort().join(", "));

const collator = new Intl.Collator("id");
console.log([...nama].sort(collator.compare).join(", "));
text Keluaran
Cahya, Citra, Clara, Céline
Cahya, Céline, Citra, Clara

Pengurutan bawaan menempatkan Céline paling akhir karena kode karakter é berada di atas semua huruf tanpa aksen, sedangkan Collator menempatkannya tepat setelah Cahya dengan memperlakukan é sebagai variasi huruf e. Untuk daftar nama pengguna atau kata dalam kamus, urutan hasil Collator yang terasa benar bagi pembaca. Metode compare milik collator dipakai langsung sebagai fungsi pembanding sort karena parameternya sama dengan fungsi comparator biasa. Kode wilayah pada tag locale juga boleh dilepas bila perbedaannya tidak relevan, dan Intl.Collator("id") tanpa kode negara tetap bekerja dengan aturan yang sama.

Unicode di balik string#

String JavaScript disimpan sebagai urutan unit kode UTF-16, dan karakter dengan titik kode di luar rentang dasar diwakili dua unit sekaligus yang disebut surrogate pair. Properti length menghitung unit, bukan karakter yang tampak, sehingga angkanya bisa mengejutkan pada emoji.

js Contoh: panjang string dan titik kode emoji
const emoji = "🐍";
console.log(emoji.length);
console.log(emoji.codePointAt(0));
console.log(emoji.codePointAt(0).toString(16));
console.log(String.fromCodePoint(0x1f40d));
text Keluaran
2
128013
1f40d
🐍

Emoji ular itu hanya satu karakter bagi pembaca, tetapi length melaporkan 2 karena satu emoji diwakili dua unit kode. Sementara itu, codePointAt membaca kedua unit sekaligus menjadi satu titik kode penuh, yang dalam contoh ini bernilai heksadesimal 1f40d. String.fromCodePoint menjadi kebalikannya, menyusun karakter kembali dari titik kodenya. Permasalahan serupa muncul pada huruf beraksen yang bisa ditulis dua cara berbeda, satu titik kode tunggal atau huruf dasar diikuti tanda aksen, dan dua bentuk itu dihitung berbeda padahal tampak sama.

js Contoh: menyamakan dua bentuk penulisan café dengan NFC
const teks1 = "café";
const teks2 = "cafe\u0301";
console.log(teks1.length, teks2.length);
console.log(teks1 === teks2);
console.log(teks1.normalize("NFC") === teks2.normalize("NFC"));
text Keluaran
4 5
false
true

Kedua string itu tampak identik bagi pembaca, tetapi length keduanya 4 dan 5, dan perbandingan === menyatakan keduanya berbeda. Metode normalize("NFC") menyatukan kedua bentuk ke bentuk komposisinya, dan perbandingan menjadi true. Normalisasi ini penting sebelum membandingkan masukan pengguna, misalnya mencocokkan nama yang diketik dengan data yang tersimpan, karena kedua bentuk penulisan sama-sama sah pada data nyata.

Perulangan dan Array.from bekerja pada tingkat titik kode, bukan unit, sehingga keduanya menjadi cara menelusuri string tanpa membelah surrogate pair. Array.from("🐍") menghasilkan array berisi satu elemen, kebalikan dari length yang melaporkan dua, dan for...of memberikan satu karakter per langkahnya. Kombinasi keduanya cukup untuk penghitungan karakter yang tampak, sedangkan length tetap tepat untuk kebutuhan penyimpanan karena unit itulah yang benar-benar disimpan.

Ringkasan#

  • Intl.NumberFormat, Intl.DateTimeFormat, dan Intl.Collator memformat serta membandingkan mengikuti locale, dengan hasil yang bergantung data ICU pada runtime.
  • Formatter sebaiknya dibuat sekali lalu dipakai ulang, dan bulan pada konstruktor Date berbasis nol.
  • length menghitung unit kode UTF-16, sehingga emoji dihitung dua, sedangkan codePointAt dan fromCodePoint bekerja pada titik kode penuh.
  • normalize("NFC") menyatukan dua bentuk penulisan huruf beraksen sehingga perbandingannya menyatakan sama.

Referensi#