Lokalisasi dan intl

Menyiapkan aplikasi multibahasa dengan berkas ARB, delegate locale, dan string terlokalisasi.

Daftar isi

Aplikasi yang ditujukan kepada pengguna dengan bahasa yang beragam menyesuaikan teksnya dengan pengaturan perangkat. Topik ini membahas alur lokalisasi resmi Flutter, mulai dari package flutter_localizations dan intl, berkas ARB per locale (pengenal bahasa dan wilayah, misalnya id untuk Indonesia), konfigurasi generator, pendaftaran delegate pada MaterialApp, sampai penggunaan string terlokalisasi yang memuat placeholder (bagian yang diisi nilai saat pemanggilan) dan plural (bentuk kata menurut jumlah). Setelah topik ini dibaca, aplikasi catatan dapat menampilkan teksnya dalam bahasa Indonesia dan bahasa Inggris sesuai pengaturan bahasa perangkat yang memasangnya.

Package yang diperlukan#

Flutter mengandalkan dua package untuk lokalisasi. flutter_localizations menyediakan terjemahan bawaan untuk widget Material dan Cupertino, sedangkan intl menyediakan pustaka pemformatan tanggal, angka, dan pesan yang digunakan generator. Keduanya ditambahkan pada pubspec.yaml.

yaml Contoh: dependensi dan penanda generator pada pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  flutter_localizations:
    sdk: flutter
  intl: any

flutter:
  generate: true

Nilai any pada intl disengaja. Versi intl terikat pada flutter_localizations yang dibawa Flutter, sehingga penyelesaian dependensi otomatis menetapkan versi yang cocok, yaitu 0.20.3 pada Flutter 3.47. Menulis nomor versi sendiri justru berisiko berbenturan dengan ikatan tersebut. Kunci generate: true pada bagian flutter mengaktifkan generator lokalisasi, dan tanpa kunci ini berkas ARB tidak pernah diproses.

Dua package itu bekerja pada lapisan yang berbeda. flutter_localizations mengurus teks bawaan widget, misalnya label tombol kembali dan judul dialog, sedangkan intl mengurus pemformatan yang aturannya berbeda menurut wilayah, misalnya pemisah ribuan dan urutan tanggal. String milik aplikasi sendiri berada di luar keduanya, dan string itulah yang dikelola oleh berkas ARB pada bagian berikutnya.

Berkas ARB per locale#

String terlokalisasi ditulis dalam berkas ARB (Application Resource Bundle), yaitu format berbasis JSON yang dibaca generator resmi Flutter. Satu berkas untuk satu locale, dengan locale yang tercantum pada nama berkas. Berkas berbahasa Inggris menjadi template karena memuat metadata untuk setiap kuncinya, sedangkan berkas bahasa lain hanya perlu menerjemahkan nilainya.

json app_en.arb
{
  "@@locale": "en",
  "judulAplikasi": "Notes",
  "tombolSimpan": "Save",
  "pesanKosong": "No notes yet. Write your first note.",
  "jumlahCatatan": "{count, plural, =0{No notes} =1{One note} other{{count} notes}}",
  "@jumlahCatatan": {
    "placeholders": {
      "count": {
        "type": "int"
      }
    }
  }
}
json app_id.arb
{
  "@@locale": "id",
  "judulAplikasi": "Catatan",
  "tombolSimpan": "Simpan",
  "pesanKosong": "Belum ada catatan. Tulis catatan pertama.",
  "jumlahCatatan": "{count, plural, =0{Tidak ada catatan} =1{Satu catatan} other{{count} catatan}}",
  "@jumlahCatatan": {
    "placeholders": {
      "count": {
        "type": "int"
      }
    }
  }
}

Kunci jumlahCatatan memperlihatkan dua kemampuan generator sekaligus. Placeholder {count} diisi nilai saat dipanggil, sedangkan blok plural memilih kata menurut jumlah, yaitu bentuk khusus untuk 0 dan 1 beserta bentuk other untuk sisanya. Aturan plural berbeda antarbahasa, dan generator menerapkannya sesuai locale masing-masing. Kunci yang diawali @ adalah metadata yang menjelaskan tipe placeholder, dan metadata ini hanya perlu ada pada berkas template.

Satu locale tidak wajib lengkap sejak hari pertama. Pesan yang belum diterjemahkan pada berkas non-template mengikuti teks template, sehingga aplikasi tetap berjalan dan terjemahan dapat diisi bertahap. Generator juga melaporkan pesan yang belum diterjemahkan setiap kali dijalankan, sehingga kunci yang tertinggal mudah ditemukan sebelum rilis.

Konfigurasi generator#

Generator lokalisasi diatur melalui berkas l10n.yaml di akar proyek. Berkasnya pendek, dan tiga baris berikut sudah memadai untuk aplikasi catatan.

yaml l10n.yaml
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart

Konfigurasi di atas menunjuk direktori tempat berkas ARB disimpan, yaitu lib/l10n, menetapkan berkas template-nya, dan menamai berkas keluaran. Berkas hasil generator ditulis di direktori yang sama dengan berkas ARB kecuali opsi output-dir diatur lain, sehingga impor pada kode aplikasi menjadi import 'l10n/app_localizations.dart'; untuk konfigurasi di atas. Generator dijalankan dengan perintah berikut.

bash
# tidak-dijalankan
# menghasilkan kelas AppLocalizations dari berkas ARB
flutter gen-l10n

Dalam praktiknya generator juga berjalan otomatis setiap flutter pub get, flutter run, atau flutter build selama kunci generate: true aktif, sehingga kelas hasilnya selalu diperbarui sebelum aplikasi dikompilasi. Keluarannya adalah kelas AppLocalizations yang memuat satu properti untuk setiap kunci ARB, termasuk metode jumlahCatatan yang menerima parameter count.

Mendaftarkan locale pada MaterialApp#

MaterialApp perlu diberi tahu locale yang didukung dan delegate-nya, yaitu objek yang membawa kelas lokalisasi ke dalam pohon widget. Generator telah menyediakan keduanya sebagai properti statis pada AppLocalizations, sehingga pendaftarannya hanya berupa dua baris.

dart Contoh: pendaftaran locale pada MaterialApp
// tidak-dijalankan
MaterialApp(
  localizationsDelegates: AppLocalizations.localizationsDelegates,
  supportedLocales: AppLocalizations.supportedLocales,
  home: const LayarCatatan(),
)

Dengan pendaftaran tersebut, Flutter mencocokkan locale perangkat pengguna dengan daftar supportedLocales untuk memilih yang terbaik. Perangkat berbahasa Indonesia menerima isi app_id.arb, sedangkan perangkat dengan bahasa yang tidak didukung jatuh ke locale pertama pada daftar, yang pada contoh ini adalah bahasa Inggris. Urutan pada supportedLocales juga menentukan locale yang terpilih ketika lebih dari satu locale cocok, sehingga locale utama aplikasi diletakkan lebih dahulu. Widget Material juga ikut berbahasa Indonesia karena flutter_localizations terdaftar dalam delegate yang sama.

Locale yang sedang berlaku juga dapat dibaca ketika keputusan lain membutuhkannya, misalnya memilih format tanggal melalui package intl. Pemanggilan Localizations.localeOf(context) mengembalikan locale aktif, dan nilainya berganti bersama pembaruan bahasa sistem karena pendaftarannya berada di atas pohon widget.

Menggunakan AppLocalizations.of(context)#

Pembacaan string terlokalisasi dilakukan melalui context pada widget yang berada di bawah MaterialApp.

dart Contoh: membaca string terlokalisasi
// tidak-dijalankan
Text(AppLocalizations.of(context)!.judulAplikasi),
Text(AppLocalizations.of(context)!.jumlahCatatan(jumlah)),

AppLocalizations.of(context) mencari kelas tersebut ke atas pohon widget dan mengembalikan null apabila tidak ditemukan, sehingga pemanggilannya diberi tanda ! atau diperiksa terlebih dahulu. Pemanggilan hanya sah pada widget di bawah MaterialApp karena delegate didaftarkan di sana. Ketika pengguna mengganti bahasa sistem, MaterialApp membangun ulang pohon widget dan seluruh teks berganti tanpa kode tambahan apa pun.

Apabila satu metode build menggunakan beberapa teks sekaligus, hasil pencariannya disimpan dalam satu variabel lokal terlebih dahulu. Pola final l10n = AppLocalizations.of(context)!; di awal metode build menghindari pengulangan pemanggilan panjang pada setiap baris, dan pemanggilan yang tetap berada di dalam metode build menjamin nilai yang digunakan selalu mengikuti locale yang aktif.

Kesalahan umum#

Lima kesalahan yang sering ditemui saat lokalisasi disiapkan:

  • Melupakan package flutter_localizations. Aplikasi yang menggunakan widget Material melempar error No MaterialLocalizations found saat berjalan karena terjemahan bawaan tidak terdaftar.
  • Menulis kunci yang berbeda antarberkas ARB. Generator membandingkan seluruh kunci dengan template, sehingga kunci yang hilang menghentikan build dengan pesan error yang menunjuk kuncinya.
  • Memanggil AppLocalizations.of(context) dengan context yang berada di atas MaterialApp. Hasilnya selalu null, dan tanda ! kemudian melempar error.
  • Menyisakan teks yang ditulis langsung di widget. Satu string yang lolos membuat pengguna berbahasa lain menerima teks yang salah bahasa, sehingga pemeriksaan berkala terhadap string literal perlu dilakukan.
  • Menambahkan kunci ARB tanpa menjalankan generator. Kelas AppLocalizations hanya diperbarui saat generator berjalan, sehingga kunci baru tidak dikenal sampai flutter gen-l10n atau flutter run dijalankan kembali.

Ringkasan#

  • Package flutter_localizations menyediakan terjemahan widget bawaan, sedangkan intl memformat tanggal, angka, dan pesan.
  • String terlokalisasi ditulis dalam berkas ARB per locale, dengan placeholder dan plural yang dideklarasikan pada metadata kunci di berkas template.
  • l10n.yaml mengatur generator, dan flutter gen-l10n menghasilkan kelas AppLocalizations yang juga diperbarui otomatis saat build.
  • MaterialApp mendaftarkan AppLocalizations.localizationsDelegates beserta supportedLocales, lalu widget membaca teksnya melalui AppLocalizations.of(context).

Referensi#