Internationalization (i18n) — UI/UX

Internationalization (i18n) — Desain untuk Dunia, Bukan Hanya Jakarta i18n (i-18 huruf-n, singkatan internationalization) adalah proses mempersiapkan UI agar…

Internationalization, disingkat i18n (huruf i, 18 huruf di tengah, huruf n), adalah menyiapkan halaman supaya bisa tampil dalam bahasa dan kebiasaan daerah lain tanpa menulis ulang kodenya. Mengisi terjemahan untuk satu bahasa disebut localization (l10n). Bayangin papan harga di depan Homestay Kabut. Kalau Bu Sri melukis seluruh kalimatnya langsung di papan, tiap tamu asing butuh papan baru. Kalau papannya punya slot (nama kamar, harga, tanggal) dengan kartu yang bisa diganti, cukup ganti kartunya. i18n membuat slotnya; l10n mengisi kartunya.

🧪 Tebakan cepat: Halaman Bu Sri menulis harga dengan 'Rp ' + total.toLocaleString(). Pak Hadi membukanya di laptop yang bahasa browser-nya English (US). Total 2 malam tampil sebagai apa? Jawabannya ada di bagian "Angka dan uang: Intl.NumberFormat".

Teks keluar dari kode

Langkah pertama: semua teks halaman dipindah ke satu kamus per bahasa, dan kode cuma menyebut kuncinya.

const messages = {
  id: { book: 'Pesan kamar, bayar di tempat', cancel: 'Gratis batal sampai 3 hari sebelum check-in.' },
  en: { book: 'Book now, pay at the homestay', cancel: 'Free cancellation up to 3 days before check-in.' },
};

const locale = 'en-US';
const lang = locale.slice(0, 2);                       // 'en'
document.querySelector('.book').textContent = messages[lang].book;

Angka dan uang: Intl.NumberFormat

Intl.NumberFormat adalah pemformat bawaan browser yang tahu aturan tiap locale: pemisah ribuan, desimal, dan simbol mata uang. Kami menjalankannya di Chromium 147, WebKit 26.4 (mesin Safari), dan Firefox 146:

Kode Chrome, Firefox Safari
new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR' }).format(700000) Rp 700.000,00 Rp700.000,00
sama, plus minimumFractionDigits: 0, maximumFractionDigits: 0 Rp 700.000 Rp700.000
new Intl.NumberFormat('en-US', { style: 'currency', currency: 'IDR', maximumFractionDigits: 0 }).format(700000) IDR 700,000 IDR 700,000
new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(1234.5) $1,234.50 $1,234.50
(700000).toLocaleString() di browser English (US) 700,000 700,000

Jadi jawaban tebakannya: Rp 700,000, dengan koma. toLocaleString() tanpa locale memakai bahasa browser, jadi di laptop English (US) pemisahnya koma, dan tamu Indonesia membacanya sebagai tujuh ratus rupiah koma nol nol nol. Itu jawaban tebakannya.

Tanggal: Intl.DateTimeFormat

const checkIn = new Date(2026, 11, 12);   // bulan dihitung dari 0: 11 = Desember
new Intl.DateTimeFormat('id-ID', { day: 'numeric', month: 'short' }).format(checkIn); // "12 Des"
new Intl.DateTimeFormat('en-US', { day: 'numeric', month: 'short' }).format(checkIn); // "Dec 12"
new Intl.DateTimeFormat('en-GB', { day: 'numeric', month: 'short' }).format(checkIn); // "12 Dec"

Hasilnya sama di ketiga browser. Dua hal yang sering salah:

Plural: Intl.PluralRules

Bahasa Indonesia gak mengubah kata benda untuk jumlah: 1 malam, 2 malam. Bahasa Inggris punya dua bentuk: 1 night, 2 nights. Bahasa Arab punya enam. Intl.PluralRules memberi tahu kategori mana yang dipakai:

new Intl.PluralRules('id-ID').select(2);  // "other": kategori satu-satunya di bahasa Indonesia
new Intl.PluralRules('en-US').select(1);  // "one"
new Intl.PluralRules('en-US').select(2);  // "other"

const nightWord = { id: { other: 'malam' }, en: { one: 'night', other: 'nights' } };
const rule = new Intl.PluralRules(locale).select(nights);
const label = `${nights} ${nightWord[lang][rule]}`;      // "1 night", "2 nights"

Kami mengecek resolvedOptions().pluralCategories di ketiga browser: id cuma other, en punya one dan other, ar punya zero, one, two, few, many, other. Jangan menulis n === 1 ? 'night' : 'nights' sendiri; aturan itu salah untuk banyak bahasa lain. Di react-i18next dan FormatJS, bentuk jamaknya ditulis di kamus dengan aturan yang sama.

Teks yang memanjang

Terjemahan jarang sama panjang. Di kamus BelajarWeb.dev sendiri (21.318 teks yang ada di id.json dan en.json), teks Indonesia secara total 2,1% lebih pendek, tapi untuk label pendek (sampai 10 huruf dalam bahasa Inggris) teks Indonesia rata-rata 15,4% lebih panjang: "Search" jadi "Cari", tapi "Save" jadi "Simpan" dan "Settings" jadi "Pengaturan". Panduan IBM yang dikutip artikel W3C "Text size in translation" mencatat pola yang sama dari bahasa Inggris ke bahasa lain: teks sampai 10 huruf bisa memanjang 200 sampai 300%, teks di atas 70 huruf sekitar 130%.

Tombol Pesan Bu Sri dulu dibuat pas untuk teks Indonesia. Kami mengukur lebarnya di Chromium 147 (font sistem, 16px, tebal 600):

Teks tombol Lebar teks Di tombol width: 256px; padding: 0 16px (ruang 224px)
Pesan kamar, bayar di tempat 223,4px muat
Book now, pay at the homestay 235,2px terpotong 11,2px
.book {
  min-inline-size: 16rem;   /* boleh lebih lebar, gak boleh lebih sempit */
  max-inline-size: 100%;
  padding-inline: 16px;
  white-space: normal;      /* teks yang panjang turun ke baris kedua, bukan terpotong */
}

Font berbeda di tiap sistem, jadi angkanya beda di Windows atau Android, tapi masalahnya sama: ukuran yang dikunci pas untuk satu bahasa.

lang, dir, dan logical properties

<html lang="en"> menyebut bahasa halaman. Screen reader memilih suara dan cara baca dari sini (WCAG 3.1.1 Language of Page), dan hyphens: auto memakai kamus bahasa itu. Satu frasa berbahasa lain di dalam halaman diberi lang sendiri (WCAG 3.1.2 Language of Parts).

dir="rtl" membalik arah tulisan untuk bahasa kanan ke kiri seperti Arab. Supaya tata letaknya ikut terbalik, pakai logical properties: properti yang menyebut awal dan akhir baris, bukan kiri dan kanan.

Fisik (kiri dan kanan) Logical (awal dan akhir)
padding-left padding-inline-start
margin-right margin-inline-end
left inset-inline-start
width inline-size
text-align: left text-align: start

Kami memberi dir="rtl" lalu membaca hasilnya di ketiga browser: padding-inline-start: 16px berpindah ke kanan, padding-left: 16px tetap di kiri (sekarang di sisi yang salah), dan text-align: start ikut ke kanan. Dengan logical properties, browser membalik sendiri; gak perlu stylesheet RTL kedua.

⚠️ Jebakan yang sering ketemu

TL;DR: i18n menyiapkan slot, l10n mengisi terjemahannya. Teks keluar dari kode ke kamus per bahasa, sebagai kalimat utuh. Locale (id-ID, en-US) menentukan format: Intl.NumberFormat("id-ID", { style: "currency", currency: "IDR" }) memberi Rp 1.234.567,89; en-US dengan USD memberi $1,234.50 dan $1,000.00; toLocaleString() tanpa locale ikut browser dan bisa menulis Rp 700,000. Intl.DateTimeFormat memberi "12 Des" atau "Dec 12"; pakai nama bulan dan timeZone: 'UTC' (6/15/2024, bukan 6/14/2024). Intl.PluralRules memilih bentuk jamak: Indonesia cuma other, Inggris one dan other. Label pendek Indonesia rata-rata 15,4% lebih panjang dari Inggris, dan terjemahan bisa melebarkan tombol: pakai min-inline-size, bukan lebar tetap. lang menyebut bahasa halaman (WCAG 3.1.1), dan logical properties seperti padding-inline-start membalik sendiri saat dir="rtl".

Yang akan kamu pelajari