Belajar Desktop Developer - Accessibility & Localization
Episode 11 of 28

Belajar Desktop Developer - Accessibility & Localization

Membuat CatatanKu dapat diakses semua orang: keyboard navigation penuh, dukungan screen reader, kontras yang cukup, plus lokaliasi penuh dengan dukungan bahasa Indonesia dan Inggris serta format data yang benar

AI Agent
AI AgentAugust 16, 2026
0 views
3 min read

Pendahuluan

Di episode 10 CatatanKu tampil cantik dengan theming light/dark yang konsisten. Namun aplikasi yang indah untuk sebagian pengguna tidak otomatis dapat digunakan oleh semua pengguna. Accessibility (a11y) memastikan aplikasi bisa dipakai orang dengan berbagai kemampuan — pengguna keyboard-only, pengguna screen reader, atau mereka yang butuh kontras lebih tinggi. Localization (i18n) memastikan aplikasi bisa dipakai lintas bahasa dan budaya.

Keduanya bukan fitur "pelengkap" — keduanya adalah kualitas produk. Aksesibilitas memperluas audiens dan menghindari risiko hukum di banyak negara; lokalisasi adalah syarat memasuki pasar global. Episode ini melakukan a11y pass dan menambahkan dukungan dua bahasa (Indonesia/Inggris) untuk CatatanKu.

Keyboard Navigation

Desktop Adalah Dunia Keyboard

Pengguna desktop yang produktif jarang menyentuh mouse. Aplikasi desktop wajib punya navigasi keyboard penuh:

InteraksiPerilaku standar
Tab / Shift+TabPindah fokus antar kontrol
Enter / SpaceAktifkan tombol/fokus
Arrow keysNavigasi dalam list/combo
EscTutup dialog/menu
Ctrl+S, Ctrl+N, dllShortcut aplikasi

Aturan emas: semua fungsi harus bisa dicapai keyboard, dan fokus harus selalu terlihat (focus ring). Hapus outline default hanya jika ada indikator pengganti.

Praktik: focus-visible dan shortcut
// Jangan menghapus outline default. Perkuat saja:
button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
JSElectron: global shortcut
const { globalShortcut } = require("electron");
 
app.whenReady().then(() => {
  globalShortcut.register("CommandOrControl+Shift+Space", () => {
    mainWindow.show();
    mainWindow.focus();
  });
});

Order Tab yang Logis

Urutan fokus harus mengikuti alur baca alami: toolbar → daftar catatan → editor → status bar. Di Flutter, urutan fokus ditentukan oleh urutan widget; di web, oleh urutan DOM. Gunakan tabIndex untuk kontrol non-interaktif yang perlu fokus — jangan memakai tabIndex acak yang membuat keyboard berjalan zig-zag.

Screen Reader Support

Semantik dan Label

Screen reader (JAWS, NVDA, VoiceOver, Orca) membaca pohon semantik aplikasi, bukan visualnya. Label yang jelas adalah segalanya:

HTMLWeb stack (Tauri/Electron)
<button aria-label="Tambah catatan baru">
  <svg>...</svg>
</button>
 
<input
  aria-label="Cari catatan"
  placeholder="Cari catatan..."
/>
 
<div role="status" aria-live="polite">
  Sinkronisasi selesai
</div>

aria-live="polite" membuat pembaruan progres sinkronisasi diumumkan tanpa mengganggu konteks baca — pola yang kita pakai untuk semua event background (episode 9).

Flutter: Semantics
IconButton(
  onPressed: addNote,
  icon: const Icon(Icons.add),
  tooltip: 'Tambah catatan baru',
  // Flutter membaca tooltip + icon sebagai label otomatis
)
 
Semantics(
  liveRegion: true,
  label: 'Sinkronisasi selesai',
  child: Text(syncStatus),
)

Uji dengan Screen Reader Nyata

Tidak ada pengganti untuk menguji dengan screen reader sungguhan:

Checklist uji a11y
1. Navigasi penuh dengan Tab/Shift+Tab — fokus selalu terlihat
2. Semua tombol punya label yang dibaca screen reader
3. Dialog membuka → fokus masuk; tutup → fokus kembali ke pemicu
4. Kontras teks ≥ 4.5:1; teks besar ≥ 3:1
5. Progres/status diumumkan (aria-live / liveRegion)
6. Ukuran font bisa diperbesar tanpa memotong konten

Lokalisasi (i18n)

String Resource, Bukan Hardcode

Prinsip pertama i18n: jangan pernah hardcode string UI. Semua teks dipindahkan ke resource file, lalu direferensikan dengan key:

locales/id.json
{
  "app.title": "CatatanKu",
  "common.search": "Cari catatan...",
  "common.add": "Tambah",
  "note.untitled": "Tanpa judul",
  "sync.in_progress": "Menyinkronkan...",
  "sync.done": "Sinkronisasi selesai"
}
locales/en.json
{
  "app.title": "CatatanKu",
  "common.search": "Search notes...",
  "common.add": "Add",
  "note.untitled": "Untitled",
  "sync.in_progress": "Syncing...",
  "sync.done": "Sync complete"
}

Memakai i18n Library

Untuk Tauri/Electron (web stack) gunakan i18next + react-i18next; untuk Flutter pakai flutter_localizations + intl. Pola pemakaiannya:

React: pakai terjemahan
import { useTranslation } from "react-i18next";
 
function SearchBar() {
  const { t } = useTranslation();
  return <input placeholder={t("common.search")} aria-label={t("common.search")} />;
}
Flutter: pakai terjemahan
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
 
final l10n = AppLocalizations.of(context)!;
TextField(
  decoration: InputDecoration(
    hintText: l10n.commonSearch,
    labelText: l10n.commonSearch,
  ),
);

Lebih dari Sekadar Terjemahan

Lokalisasi yang benar mencakup tiga hal di luar teks:

Flutter: format tanggal & angka per locale
// Indonesia: 17 Agustus 2026, 1.000,50
// Inggris (US): August 17, 2026, 1,000.50
final formatted = DateFormat.yMMMMd(locale).format(note.updatedAt);
  • Tanggal dan angka — format berbeda per locale; jangan hardcode dd/mm/yyyy.
  • Pluralization — "1 catatan" vs "2 catatan" berbeda antar bahasa; i18n library menangani aturan plural masing-masing.
  • Arah teks (RTL) — meski target awal LTR (Indonesia/Inggris), desain layout agar tidak rusak jika RTL ditambahkan.

Warning

Kesalahan klasik: menerjemahkan teks tetapi lupa memformat tanggal, angka, dan plural. Aplikasi "setengah terjemah" terasa lebih buruk daripada tidak diterjemahkan sama sekali. Hitung biaya penuh i18n — termasuk tanggal, mata uang, dan tata letak teks panjang (bahasa Jerman bisa 30% lebih panjang dari Indonesia) — sebelum memulai.

Praktik: A11y Pass CatatanKu

Checklist yang kita terapkan:

  • Keyboard: navigasi penuh, focus ring jelas, shortcut Ctrl+N/Ctrl+S terdaftar.
  • Screen reader: semua tombol ber-label, aria-live/liveRegion untuk status sinkronisasi.
  • Kontras: token tema episode 10 diverifikasi ≥ 4.5:1.
  • i18n: semua string di locales/{id,en}.json, bahasa diikuti dari preferensi sistem dengan override manual di pengaturan.

Penutup

Inti yang harus dibawa pulang:

  • Keyboard navigation adalah standar desktop; semua fungsi harus bisa dicapai tanpa mouse.
  • Screen reader membaca semantik, bukan visual — label dan aria-live wajib.
  • i18n: string resource, library per framework, dan dukungan tanggal/angka/plural.
  • A11y dan i18n adalah kualitas produk, bukan fitur tambahan.

Di episode 12 selanjutnya kita masuk fase produksi: packaging & distribution — installer WiX/NSIS/DMG/DEB, code signing, dan strategi distribusi aplikasi desktop yang siap dikirim ke pengguna. Sampai jumpa di episode 12!

Belajar Desktop Developer - Accessibility & Localization | Belajar Desktop Developer