RTL Layout Support — i18n

RTL Layout Support RTL (Right-to-Left) layout diperlukan untuk bahasa seperti Arab, Ibrani, Farsi, dan Urdu. Mendukung RTL bukan hanya membalik teks — seluruh…

RTL (right-to-left) adalah tulisan yang dibaca dari kanan ke kiri: Arab, Ibrani, Persia, dan Urdu. Mendukung RTL artinya seluruh halaman ikut dicerminkan, bukan cuma hurufnya. Bayangin Bu Sri mencetak brosur Homestay Kabut versi Arab untuk tamu dari Mesir. Brosur Arab dibuka dari sisi yang buat kita adalah sampul belakang, fotonya pindah ke kanan, dan tiap baris mulai dari kanan. Tapi di dalamnya, harga "Rp 700.000" dan tanggal "2026-12-12" tetap ditulis dari kiri ke kanan. Dua aturan itu yang dipelajari di sini: tata letaknya dicerminkan, angkanya enggak.

🧪 Tebakan cepat: CSS kartu Bu Sri berisi .card { padding-left: 20px; border-left: 4px solid #15803d; }, dan tiga tombol lama menginap disusun dengan display: flex. Untuk ar-EG, Bu Sri menambah dir="rtl" di <html>. Mana yang pindah ke kanan dengan sendirinya: teksnya, urutan tombolnya, atau garis hijaunya? Jawabannya ada di bagian "CSS logical properties: awal dan akhir, bukan kiri dan kanan".

dir="rtl" di tag html

Arah halaman diatur atribut HTML dir. Nilainya ltr (kiri ke kanan, bawaan), rtl (kanan ke kiri), atau auto (browser menebak dari isinya). Taruh di <html>, bersama lang:

<html lang="ar-EG" dir="rtl">

Di aplikasi yang bisa ganti bahasa, set keduanya dari locale:

const RTL = ['ar', 'he', 'fa', 'ur'];

function setDirection(locale) {
  const language = new Intl.Locale(locale).language; // 'ar-EG' jadi 'ar'
  document.documentElement.lang = locale;
  document.documentElement.dir = RTL.includes(language) ? 'rtl' : 'ltr';
}

setDirection('ar-EG'); // <html lang="ar-EG" dir="rtl">

Flex dan grid ikut arah sendiri

Kabar baiknya: layout modern sudah membaca arah. display: flex dengan flex-direction: row menaruh anak pertama di awal baris, dan di dir="rtl" awal baris adalah kanan. Grid sama: kolom pertama dari grid-template-columns muncul di kanan.

Tombol lama menginap Bu Sri (.nights { display: flex; gap: 8px; }) di ar-EG tersusun ليلة واحدة, ليلتان, ٣ ليالٍ dari kanan ke kiri. Kami mengukur posisi tombolnya di Chromium 147, Firefox 146, dan WebKit 26.4: di ketiganya tombol pertama paling kanan, tanpa CSS tambahan. Yang justru merusak adalah menulis flex-direction: row-reverse untuk "membalik" layout Arab: flex sudah membaliknya, jadi hasilnya terbalik dua kali.

CSS logical properties: awal dan akhir, bukan kiri dan kanan

Masalahnya ada di properti yang menyebut sisi fisik: left, right, margin-left, padding-right, border-left, text-align: left. Kiri tetap kiri, apa pun arah tulisannya. Logical properties menyebut sisi menurut arah tulisan. Inline adalah arah baris (kiri ke kanan, atau kanan ke kiri), block adalah arah tumpukan baris (atas ke bawah). start adalah awal, end adalah akhir.

Ini CSS kartu Bu Sri, sebelum dan sesudah. Kami membaca hasilnya dengan getComputedStyle di Chromium 147, Firefox 146, dan WebKit 26.4, dan ketiganya sama:

Bu Sri menulis Di ar-EG Versi logical Di ar-EG
padding-left: 20px padding 20px tetap di kiri padding-inline-start: 20px padding 20px di kanan
border-left: 4px solid garis hijau tetap di kiri border-inline-start: 4px solid garis hijau di kanan
right: 12px (badge Favorit) badge tetap di kanan, menimpa awal judul inset-inline-end: 12px badge di kiri
margin-right: 6px (jarak ikon) jaraknya di sisi yang salah margin-inline-end: 6px jarak di kiri ikon

Jadi teksnya dan urutan tombol flex pindah sendiri, tapi garis hijaunya enggak, karena border-left menyebut kiri. Itu jawaban tebakannya.

/* Sebelum: kiri dan kanan fisik */
.card  { position: relative; padding: 16px 16px 16px 20px; border-left: 4px solid #15803d; }
.badge { position: absolute; top: 12px; right: 12px; }
.icon  { margin-right: 6px; }

/* Sesudah: awal dan akhir, satu CSS untuk kedua arah */
.card  { position: relative; padding-block: 16px; padding-inline: 20px 16px; border-inline-start: 4px solid #15803d; }
.badge { position: absolute; top: 12px; inset-inline-end: 12px; }
.icon  { margin-inline-end: 6px; }

Ikon: panah dicerminkan, jam dan logo jangan

Ikon yang menunjuk arah harus dicerminkan, karena "kembali" di RTL artinya ke kanan. Ikon yang menggambar benda jangan.

Ikon Di RTL Kenapa
Panah "Semua kamar" dicerminkan, menunjuk ke kanan Kembali artinya ke awal, dan awal ada di kanan
Jam di baris check-in tetap Jarum jam berputar searah di seluruh dunia. Kalau dicerminkan, jam 2 jadi jam 10.
Logo Homestay Kabut tetap Logo adalah merek; tulisannya jadi terbalik
Centang, tombol play tetap Bentuknya gak menunjuk arah baca
.back .icon:dir(rtl) { transform: scaleX(-1); }

Angka tetap kiri ke kanan: algoritma bidi

Teks yang mencampur arah kanan ke kiri dan kiri ke kanan disebut bidi (bidirectional). Browser mengurutkannya dengan Unicode Bidirectional Algorithm (UAX #9). Tiap karakter punya tipe arah: huruf Arab kanan ke kiri, huruf Latin kiri ke kanan, sedangkan angka dan tanda baca ikut tetangganya. Satu deret angka selalu ditulis dari kiri ke kanan, tapi letak deret itu di kalimat ditentukan tetangganya.

Kami menaruh tiga kalimat di halaman dir="rtl", lalu mengurutkan karakternya menurut posisinya di layar, dari kiri ke kanan (huruf Arab diganti _). Hasilnya sama di Chromium 147, Firefox 146, dan WebKit 26.4:

Ditulis Yang tampil, dibaca dari kiri Kenapa
الدفع عند الوصول: Rp 700.000 Rp 700.000 :_ _ _ "Rp" huruf Latin, jadi "Rp 700.000" satu potongan kiri ke kanan. Aman.
تسجيل الوصول: 2026-12-12 12-12-2026 :_ _ Angka sesudah huruf Arab diperlakukan sebagai angka Arab. Tiap kelompok tetap kiri ke kanan, tapi kelompoknya diurutkan kanan ke kiri, jadi tahunnya pindah ke ujung kanan.
Catatan tamu: Arriving around 22.00, thanks! !Arriving around 22.00, thanks Paragrafnya mewarisi rtl, jadi tanda seru di akhir kalimat Inggris pindah ke ujung kiri.

Hasil Intl untuk ar-EG juga aman: "‏٧٠٠٬٠٠٠ IDR" dan "١٢‏/١٢‏/٢٠٢٦" membawa tanda tak terlihat U+200F (RLM, right-to-left mark) yang menjaga urutannya. Yang rusak adalah tanggal ISO polos dan teks Latin dari tamu yang diletakkan langsung di halaman RTL.

bdi dan dir="auto" untuk teks dari tamu

<bdi> (bidi isolate) mengurung isinya: arahnya ditentukan isinya sendiri, dan isinya gak menarik tetangganya. dir="auto" membuat sebuah elemen memilih arah dari huruf kuat pertamanya: huruf Latin berarti ltr, huruf Arab berarti rtl.

<p>تسجيل الوصول: <bdi>2026-12-12</bdi></p>
<p dir="auto">Arriving around 22.00, thanks!</p>
<p dir="auto">سأصل حوالي الساعة 22.00، شكرًا!</p>

Cek di tiga browser

Algoritma bidi di tiga mesin browser memberi urutan yang sama untuk semua contoh di atas, tapi font Arab, sambungan huruf, dan pemotongan baris bisa beda. Jadi lihat hasilnya di Chromium, Firefox, dan WebKit (mesin Safari). Cara cepat: di DevTools, tambah dir="rtl" di <html>. Cara yang bisa diulang: satu script Playwright, tiga screenshot.

// cek-rtl.mjs, jalankan dengan: node cek-rtl.mjs
import { chromium, firefox, webkit } from 'playwright';

for (const engine of [chromium, firefox, webkit]) {
  const browser = await engine.launch();
  const page = await browser.newPage({ locale: 'ar-EG' });
  await page.goto('http://localhost:5173/ar/kamar-teras');
  await page.screenshot({ path: `kartu-ar-${engine.name()}.png`, fullPage: true });
  await browser.close();
}

engine.name() memberi chromium, firefox, atau webkit, jadi tiap browser punya file sendiri. Yang dicek di tiap screenshot: garis hijau di kanan, badge di kiri, panah kembali menunjuk ke kanan, jam tetap menunjuk jam 2, harga dan tanggal terbaca utuh, gak ada teks yang saling menimpa.

⚠️ Jebakan yang sering ketemu

TL;DR: Pasang lang dan dir="rtl" di <html> dari locale (ar, he, fa, ur berarti RTL), lewat atribut, bukan CSS direction. Flex dan grid ikut arah sendiri, jadi jangan pakai row-reverse. Properti fisik (padding-left, border-left, right, margin-right, text-align: left) diganti logical properties (padding-inline-start, border-inline-start, inset-inline-end, margin-inline-end, text-align: start): satu CSS untuk kedua arah. Cerminkan ikon arah dengan :dir(rtl), jangan jam dan logo. Angka selalu kiri ke kanan, tapi tanggal ISO polos sesudah huruf Arab tampil 12-12-2026; bungkus dengan <bdi>, dan pakai dir="auto" untuk teks dari tamu. Cek hasilnya dengan screenshot di Chromium, Firefox, dan WebKit.

Yang akan kamu pelajari