Sebagian besar aplikasi menampilkan jumlah: barang di keranjang, pesan belum dibaca, hasil pencarian. Begitu angka berubah, kalimat ikut berubah — bahasa Inggris menulis 1 item tetapi 5 items, sedangkan bahasa Indonesia memakai bentuk yang sama untuk keduanya. Pluralization adalah proses memilih bentuk pesan yang tepat untuk sebuah angka, dan aturannya berbeda jauh antar bahasa: dari bahasa dengan satu bentuk seperti Indonesia, hingga bahasa Arab yang memerlukan enam kategori berbeda. Chapter ini membahas kategori plural menurut standar CLDR, membangun fungsi selektor plural dalam PHP murni, lalu memakainya pada pesan berparameter dan katalog terjemahan.
Bug Klasik “1 items” #
Cara paling naif menangani jumlah adalah menempelkan satu kata benda di belakang angka. Pendekatan ini tampak berhasil sampai angka 1 muncul:
<?php
function labelItem(int $n): string {
return $n . " items";
}
echo labelItem(1), "\n";
echo labelItem(5), "\n";Output yang Dihasilkan:
1 items
5 itemsBaris echo labelItem(1) mencetak 1 items — kesalahan tata bahasa yang langsung terlihat oleh setiap pembaca bahasa Inggris. Sebagian aplikasi menyembunyikan masalah dengan tulisan item(s), tetapi hasilnya terasa seperti formulir birokrasi, bukan kalimat. Masalah yang lebih dalam: logika jamak yang ditulis untuk bahasa Inggris akan salah total saat aplikasi menambah bahasa lain, karena setiap bahasa membawa aturan jamaknya sendiri.
Enam Kategori Plural CLDR #
CLDR (Common Locale Data Repository) adalah proyek Unicode yang menerbitkan data locale siap pakai, termasuk aturan plural untuk ratusan bahasa dalam bentuk yang dapat dibaca mesin. Inti idenya: setiap bahasa memetakan angka ke satu dari enam kategori, lalu pesan disediakan satu varian per kategori. Tabel berikut merangkum keenam kategori tersebut:
| Kategori | Kapan dipakai (ringkas) | Contoh bahasa |
|---|---|---|
zero | angka tepat nol | Latvia (lv) |
one | Inggris: tepat 1; Prancis: 0 atau 1 | en, fr |
two | tepat 2, termasuk bentuk pasangan | Arab (ar), Wales (cy) |
few | kelompok kecil, mis. akhiran 2–4 di luar 12–14 | Polandia (pl), Rusia (ru) |
many | kelompok besar, mis. 11–99 | Arab (ar), Polandia (pl) |
other | seluruh sisa kasus | id, ja, en (untuk 0, 2, 3, …) |
Dua fakta pada tabel tersebut paling sering mengejutkan pengembang. Pertama, bahasa Indonesia memetakan semua angka ke satu kategori other — 1 pesan dan 5 pesan memakai bentuk yang sama, sehingga katalog Indonesia tidak pernah butuh varian jamak. Kedua, bahasa Inggris memakai kategori one hanya untuk angka tepat 1; angka 0 jatuh ke other sehingga kalimat yang benar adalah 0 items. Bahasa Arab memakai keenam kategori sekaligus. Kesimpulannya: aturan jamak adalah data per bahasa, bukan aritmetika universal yang bisa di-hardcode di satu tempat untuk semua bahasa.
Fungsi Selektor Plural #
Aturan CLDR untuk satu bahasa bisa kita tulis sebagai fungsi kecil yang mengembalikan nama kategori. Berikut selektor untuk bahasa Inggris:
<?php
function pluralCategoryEn(int $n): string {
return $n === 1 ? "one" : "other";
}
foreach ([0, 1, 2, 5] as $n) {
echo $n, " -> ", pluralCategoryEn($n), "\n";
}Output yang Dihasilkan:
0 -> other
1 -> one
2 -> other
5 -> otherFungsi pluralCategoryEn() mengembalikan one hanya ketika $n tepat 1; seluruh angka lain termasuk 0 jatuh ke other. Bandingkan dengan bahasa Prancis yang aturannya berbeda pada angka nol:
<?php
function pluralCategoryFr(int $n): string {
return $n === 0 || $n === 1 ? "one" : "other";
}
foreach ([0, 1, 2] as $n) {
echo $n, " -> ", pluralCategoryFr($n), "\n";
}Output yang Dihasilkan:
0 -> one
1 -> one
2 -> otherKedua fungsi berbentuk sama persis, tetapi menghasilkan kategori berbeda untuk angka 0 — bukti bahwa aturan plural milik data bahasa, bukan logika universal. CLDR menerbitkan aturan ini sebagai berkas data yang dibaca pustaka internasionalisasi; kita menulisnya manual hanya untuk bahasa yang aplikasi dukung, dan itu sudah cukup untuk mayoritas proyek.
Pesan Plural dengan Parameter #
Selektor kategori belum berguna tanpa pesan yang menyediakan varian per kategori. Pola lengkapnya: tabel pesan berisi varian one dan other, selektor menentukan varian yang dipakai, lalu placeholder seperti {count} diganti nilainya:
<?php
$messagesEn = [
"cart.count" => [
"one" => "Your cart has {count} item",
"other" => "Your cart has {count} items",
],
];
function formatCartMessage(int $count, array $messages): string {
$kategori = $count === 1 ? "one" : "other";
return str_replace("{count}", (string) $count, $messages["cart.count"][$kategori]);
}
echo formatCartMessage(1, $messagesEn), "\n";
echo formatCartMessage(5, $messagesEn), "\n";Output yang Dihasilkan:
Your cart has 1 item
Your cart has 5 itemsPerhatikan bahwa kalimat lengkap — angka, kata benda, dan kata kerja — hidup di dalam satu string per kategori, bukan dirakit dari potongan terpisah. Struktur seperti ini penting untuk penerjemah: penerjemah berhak mengubah urutan kata satu bahasa ke bahasa lain tanpa menyentuh kode. Fungsi str_replace() kemudian mengisi placeholder {count} dengan angka aktual.
Ekosistem internasionalisasi menjinakkan pola ini lebih jauh dengan format pesan ICU MessageFormat, yang menuliskan kategori dan varian dalam satu string:
cart.count = {count, plural,
one {Your cart has {count} item}
other {Your cart has {count} items}}PHP dapat memproses format tersebut melalui kelas MessageFormatter dari ekstensi intl:
<?php
// Membutuhkan ekstensi intl — tidak tersedia di lingkungan buku ini
$formatter = new MessageFormatter(
"en",
"{count, plural, one {Your cart has {count} item} other {Your cart has {count} items}}"
);
echo $formatter->format(["count" => 5]);Mesin ICU membaca parameter count, memilih kategori other untuk angka 5, lalu mengisi placeholder di dalam varian terpilih — menghasilkan kalimat yang setara dengan versi PHP murni kita di atas. Ekstensi intl memang tidak tersedia di lingkungan buku ini, sehingga selektor manual tetap menjadi senjata utama kita; konsepnya identik, hanya lokasi aturannya yang berbeda.
Plural dalam Katalog Terjemahan #
Pada aplikasi nyata, pesan hidup di katalog terjemahan per locale — struktur berkasnya kita bahas pada Chapter Translation Files
. Untuk kunci yang mengandung jumlah, katalog menyimpan varian per kategori plural. Katalog Indonesia cukup satu varian other, sedangkan katalog Inggris menyediakan keduanya:
<?php
$katalog = [
"id" => [
"notif.pesan" => ["other" => "Anda menerima {count} pesan baru"],
],
"en" => [
"notif.pesan" => [
"one" => "You received {count} new message",
"other" => "You received {count} new messages",
],
],
];
function terjemahkanPlural(string $locale, string $key, int $count, array $katalog): string {
$kategori = ($locale === "en" && $count === 1) ? "one" : "other";
$teks = $katalog[$locale][$key][$kategori]
?? $katalog[$locale][$key]["other"];
return str_replace("{count}", (string) $count, $teks);
}
foreach (["id", "en"] as $locale) {
foreach ([1, 5] as $count) {
echo "[$locale] ", terjemahkanPlural($locale, "notif.pesan", $count, $katalog), "\n";
}
}Output yang Dihasilkan:
[id] Anda menerima 1 pesan baru
[id] Anda menerima 5 pesan baru
[en] You received 1 new message
[en] You received 5 new messagesFungsi terjemahkanPlural() memilih kategori one hanya untuk locale Inggris dengan angka tepat satu; locale Indonesia mengabaikan angka sama sekali dan langsung memakai varian other. Operator ?? menjadi jaring pengaman: bila varian kategori ternyata belum diterjemahkan, kode jatuh ke varian other alih-alih menghasilkan error — teks mungkin kurang tepat secara tata bahasa, tetapi aplikasi tetap berjalan. Dengan pola ini, menambah bahasa ketiga berarti menambah entri katalog beserta aturan kategorinya, tanpa menyentuh kode pemanggil.
Ringkasan #
Pluralization mengubah angka menjadi kategori pesan, dan kategori itu milik setiap bahasa: Indonesia hanya mengenal other, Inggris memisahkan one untuk angka tepat satu, Prancis menghitung nol sebagai one, dan bahasa Arab memakai enam kategori. Kita membangun selektor kategori sebagai fungsi kecil, menyediakan varian pesan per kategori, lalu menyatukannya di katalog terjemahan dengan jaring pengaman fallback. Angka bukan satu-satunya hal yang tampil berbeda antar locale — angka desimal, pemisah ribuan, dan mata uang pun berubah bentuk, dan itulah topik Chapter Currency dan Number Formatting
berikutnya.