# Lokalisasi

<div id="introduction"></div>

## Pengantar

Lokalisasi memungkinkan Anda menyediakan aplikasi dalam beberapa bahasa. Nylo v7 memudahkan pelokalan teks menggunakan file bahasa JSON.

Berikut contoh singkatnya:

**lang/en.json**
``` json
{
  "welcome": "Welcome",
  "greeting": "Hello {{name}}"
}
```

**Di widget Anda:**
``` dart
Text("welcome".tr())              // "Welcome"
Text("greeting".tr(arguments: {"name": "Anthony"}))  // "Hello Anthony"
```

<div id="configuration"></div>

## Konfigurasi

Lokalisasi dikonfigurasi di `lib/config/localization.dart`:

``` dart
final class LocalizationConfig {
  // Kode bahasa default (sesuai dengan file JSON Anda, contoh: 'en' untuk lang/en.json)
  static final String languageCode =
      getEnv('DEFAULT_LOCALE', defaultValue: "en");

  // LocaleType.device - Gunakan pengaturan bahasa perangkat
  // LocaleType.asDefined - Gunakan languageCode di atas
  static final LocaleType localeType =
      getEnv('LOCALE_TYPE', defaultValue: 'asDefined') == 'device'
          ? LocaleType.device
          : LocaleType.asDefined;

  // Direktori yang berisi file JSON bahasa
  static const String assetsDirectory = 'lang/';

  // Daftar locale yang didukung
  static const List<Locale> supportedLocales = [
    Locale('en'),
    Locale('es'),
    // Tambahkan lebih banyak locale sesuai kebutuhan
  ];

  // Fallback ketika key tidak ditemukan di locale aktif
  static const String fallbackLanguageCode = 'en';

  // Kode bahasa RTL
  static const List<String> rtlLanguages = ['ar', 'he', 'fa', 'ur'];

  // Log peringatan untuk key terjemahan yang hilang
  static final bool debugMissingKeys =
      getEnv('DEBUG_TRANSLATIONS', defaultValue: 'false') == 'true';
}
```

<div id="adding-localized-files"></div>

## Menambahkan File Lokal

Tambahkan file JSON bahasa Anda ke direktori `lang/`:

```
lang/
├── en.json   # Inggris
├── es.json   # Spanyol
├── fr.json   # Prancis
└── ...
```

**lang/en.json**
``` json
{
  "welcome": "Welcome",
  "settings": "Settings",
  "navigation": {
    "home": "Home",
    "profile": "Profile"
  }
}
```

**lang/es.json**
``` json
{
  "welcome": "Bienvenido",
  "settings": "Configuración",
  "navigation": {
    "home": "Inicio",
    "profile": "Perfil"
  }
}
```

### Daftarkan di pubspec.yaml

Pastikan file bahasa Anda disertakan di `pubspec.yaml` Anda:

``` yaml
flutter:
  assets:
    - lang/
```

<div id="localizing-text"></div>

## Melokalkan Teks

Gunakan extension `.tr()` atau helper `trans()` untuk menerjemahkan string:

``` dart
// Menggunakan extension .tr()
"welcome".tr()

// Menggunakan helper trans()
trans("welcome")
```

### Key Bersarang

Akses key JSON bersarang menggunakan notasi titik:

**lang/en.json**
``` json
{
  "navigation": {
    "home": "Home",
    "profile": "Profile"
  }
}
```

``` dart
"navigation.home".tr()       // "Home"
trans("navigation.profile")  // "Profile"
```

<div id="arguments"></div>

### Argumen

Sisipkan nilai dinamis ke dalam terjemahan Anda menggunakan sintaks `{{key}}`:

**lang/en.json**
``` json
{
  "greeting": "Hello {{name}}",
  "items_count": "You have {{count}} items"
}
```

``` dart
"greeting".tr(arguments: {"name": "Anthony"})
// "Hello Anthony"

trans("items_count", arguments: {"count": "5"})
// "You have 5 items"
```

<div id="styled-text-placeholders"></div>

### Placeholder StyledText

Saat menggunakan `StyledText.template` dengan string yang dilokalkan, Anda dapat menggunakan sintaks `{{key:text}}`. Ini menjaga **key** tetap stabil di semua locale (sehingga style dan tap handler Anda selalu cocok), sementara **text** diterjemahkan per locale.

**lang/id.json**
``` json
{
  "learn_skills": "Pelajari {{lang:Bahasa}}, {{read:Membaca}} dan {{speak:Berbicara}}",
  "already_have_account": "Sudah punya akun? {{login:Masuk}}"
}
```

**lang/es.json**
``` json
{
  "learn_skills": "Aprende {{lang:Idiomas}}, {{read:Lectura}} y {{speak:Habla}}",
  "already_have_account": "¿Ya tienes una cuenta? {{login:Iniciar sesión}}"
}
```

**Di widget Anda:**
``` dart
StyledText.template(
  "learn_skills".tr(),
  styles: {
    "lang|read|speak": TextStyle(fontWeight: FontWeight.bold),
  },
)
```

Key `lang`, `read`, dan `speak` sama di setiap file locale, sehingga style map berfungsi untuk semua bahasa. Teks tampilan setelah `:` adalah yang dilihat pengguna — "Bahasa" dalam bahasa Indonesia, "Idiomas" dalam bahasa Spanyol, dll.

Anda juga dapat menggunakan ini dengan `onTap`:

``` dart
StyledText.template(
  "already_have_account".tr(),
  styles: {
    "login": TextStyle(fontWeight: FontWeight.bold),
  },
  onTap: {
    "login": () => routeTo(LoginPage.path),
  },
)
```

> **Catatan:** Sintaks `{{key}}` (dengan awalan `@`) digunakan untuk argumen yang diganti oleh `.tr(arguments:)` pada waktu terjemahan. Sintaks `{{key:text}}` (tanpa `@`) digunakan untuk placeholder `StyledText` yang diurai pada waktu render. Jangan mencampurnya — gunakan `{{}}` untuk nilai dinamis dan `@{{}}` untuk span bergaya.

<div id="updating-the-locale"></div>

## Memperbarui Locale

Ubah bahasa aplikasi saat runtime:

``` dart
// Menggunakan NyLocalization secara langsung
await NyLocalization.instance.setLanguage(
  context,
  language: 'es'  // Harus sesuai dengan nama file JSON Anda (es.json)
);
```

Jika widget Anda meng-extend `NyPage`, gunakan helper `changeLanguage`:

``` dart
class _SettingsPageState extends NyPage<SettingsPage> {

  @override
  Widget view(BuildContext context) {
    return ListView(
      children: [
        ListTile(
          title: Text("English"),
          onTap: () => changeLanguage('en'),
        ),
        ListTile(
          title: Text("Español"),
          onTap: () => changeLanguage('es'),
        ),
      ],
    );
  }
}
```

<div id="setting-a-default-locale"></div>

## Mengatur Locale Default

Atur bahasa default di file `.env` Anda:

``` bash
DEFAULT_LOCALE="en"
```

Atau gunakan locale perangkat dengan mengatur:

``` bash
LOCALE_TYPE="device"
```

Setelah mengubah `.env`, regenerate konfigurasi environment Anda:

``` bash
metro make:env
```

<div id="supported-locales"></div>

## Locale yang Didukung

Tentukan locale mana yang didukung aplikasi Anda di `LocalizationConfig`:

``` dart
static const List<Locale> supportedLocales = [
  Locale('en'),
  Locale('es'),
  Locale('fr'),
  Locale('de'),
  Locale('ar'),
];
```

Daftar ini digunakan oleh `MaterialApp.supportedLocales` Flutter.

<div id="fallback-language"></div>

## Bahasa Fallback

Ketika key terjemahan tidak ditemukan di locale aktif, Nylo secara otomatis mencarinya di bahasa fallback sebelum mengembalikan key mentah. Bahasa fallback dikonfigurasi di `lib/config/localization.dart`:

``` dart
static const String fallbackLanguageCode = 'en';
```

Resolusi dua langkah ini berlaku untuk key tingkat atas maupun key bersarang dengan notasi titik:

1. Cari key di file locale aktif.
2. Jika tidak ditemukan, cari di file locale fallback.
3. Jika masih tidak ditemukan, kembalikan string key mentah.

Misalnya, jika file locale Prancis tidak memiliki key `settings.privacy`, logika fallback mencari `settings.privacy` di file locale Inggris sebelum mengembalikan `"settings.privacy"` apa adanya.

Ini memastikan aplikasi Anda tidak pernah menampilkan key mentah jika terjemahan hanya sebagian selesai.

<div id="rtl-support"></div>

## Dukungan RTL

Nylo v7 menyertakan dukungan bawaan untuk bahasa kanan-ke-kiri (RTL):

``` dart
static const List<String> rtlLanguages = ['ar', 'he', 'fa', 'ur'];

// Periksa apakah bahasa saat ini adalah RTL
if (LocalizationConfig.isRtl(currentLanguageCode)) {
  // Tangani tata letak RTL
}
```

<div id="debug-missing-keys"></div>

## Debug Key yang Hilang

Aktifkan peringatan untuk key terjemahan yang hilang selama pengembangan:

Di file `.env` Anda:
``` bash
DEBUG_TRANSLATIONS="true"
```

Ini mencatat peringatan saat `.tr()` tidak dapat menemukan key, membantu Anda menemukan string yang belum diterjemahkan.

<div id="nylocalization-api"></div>

## API NyLocalization

`NyLocalization` adalah singleton yang mengelola semua lokalisasi. Selain method dasar `translate()`, ia menyediakan beberapa method tambahan:

### Memeriksa Apakah Terjemahan Ada

``` dart
bool exists = NyLocalization.instance.hasTranslation('welcome');
// true jika key ada di file bahasa saat ini

// Juga bekerja dengan key bersarang
bool nestedExists = NyLocalization.instance.hasTranslation('navigation.home');
```

### Mendapatkan Semua Key Terjemahan

Berguna untuk debugging untuk melihat key mana yang dimuat:

``` dart
List<String> keys = NyLocalization.instance.getAllKeys();
// ['welcome', 'settings', 'navigation', ...]
```

### Mengubah Locale Tanpa Restart

Jika Anda ingin mengubah locale secara diam-diam (tanpa me-restart aplikasi):

``` dart
await NyLocalization.instance.setLocale(locale: Locale('fr'));
```

Ini memuat file bahasa baru tetapi **tidak** me-restart aplikasi. Berguna saat Anda ingin menangani pembaruan UI secara manual.

### Memeriksa Arah RTL

``` dart
bool isRtl = NyLocalization.instance.isDirectionRTL(context);
```

### Mengakses Locale Saat Ini

``` dart
// Mendapatkan kode bahasa saat ini
String code = NyLocalization.instance.languageCode;  // contoh: 'en'

// Mendapatkan objek Locale saat ini
Locale currentLocale = NyLocalization.instance.locale;

// Mendapatkan delegate lokalisasi Flutter (digunakan di MaterialApp)
var delegates = NyLocalization.instance.delegates;
```

### Referensi API Lengkap

| Method / Properti | Mengembalikan | Deskripsi |
|-------------------|---------------|-----------|
| `instance` | `NyLocalization` | Instance singleton |
| `translate(key, [arguments])` | `String` | Menerjemahkan key dengan argumen opsional |
| `hasTranslation(key)` | `bool` | Memeriksa apakah key terjemahan ada |
| `getAllKeys()` | `List<String>` | Mendapatkan semua key terjemahan yang dimuat |
| `setLanguage(context, {language, restart})` | `Future<void>` | Mengubah bahasa, opsional restart |
| `setLocale({locale})` | `Future<void>` | Mengubah locale tanpa restart |
| `setDebugMissingKeys(enabled)` | `void` | Mengaktifkan/menonaktifkan logging key yang hilang |
| `isDirectionRTL(context)` | `bool` | Memeriksa apakah arah saat ini RTL |
| `restart(context)` | `void` | Me-restart aplikasi |
| `languageCode` | `String` | Kode bahasa saat ini |
| `locale` | `Locale` | Objek Locale saat ini |
| `delegates` | `Iterable<LocalizationsDelegate>` | Delegate lokalisasi Flutter |
| `setValuesForTesting({values, fallbackValues})` | `void` | Injeksi map terjemahan langsung untuk unit test |

<div id="nylocalehelper"></div>

## NyLocaleHelper

`NyLocaleHelper` adalah kelas utilitas statis untuk operasi locale. Ia menyediakan method untuk mendeteksi locale saat ini, memeriksa dukungan RTL, dan membuat objek Locale.

``` dart
// Mendapatkan locale sistem saat ini
Locale locale = NyLocaleHelper.getCurrentLocale(context: context);

// Mendapatkan kode bahasa dan negara
String langCode = NyLocaleHelper.getLanguageCode(context: context);  // 'en'
String? countryCode = NyLocaleHelper.getCountryCode(context: context);  // 'US' atau null

// Memeriksa apakah locale saat ini cocok
bool isEnglish = NyLocaleHelper.matchesLocale(context, 'en');
bool isUsEnglish = NyLocaleHelper.matchesLocale(context, 'en', 'US');

// Deteksi RTL
bool isRtl = NyLocaleHelper.isRtlLanguage('ar');  // true
bool currentIsRtl = NyLocaleHelper.isCurrentLocaleRtl(context: context);

// Mendapatkan arah teks
TextDirection direction = NyLocaleHelper.getTextDirection('ar');  // TextDirection.rtl
TextDirection currentDir = NyLocaleHelper.getCurrentTextDirection(context: context);

// Membuat Locale dari string
Locale newLocale = NyLocaleHelper.toLocale('en', 'US');
```

### Referensi API Lengkap

| Method | Mengembalikan | Deskripsi |
|--------|---------------|-----------|
| `getCurrentLocale({context})` | `Locale` | Mendapatkan locale sistem saat ini |
| `getLanguageCode({context})` | `String` | Mendapatkan kode bahasa saat ini |
| `getCountryCode({context})` | `String?` | Mendapatkan kode negara saat ini |
| `matchesLocale(context, languageCode, [countryCode])` | `bool` | Memeriksa apakah locale saat ini cocok |
| `isRtlLanguage(languageCode)` | `bool` | Memeriksa apakah kode bahasa adalah RTL |
| `isCurrentLocaleRtl({context})` | `bool` | Memeriksa apakah locale saat ini RTL |
| `getTextDirection(languageCode)` | `TextDirection` | Mendapatkan TextDirection untuk suatu bahasa |
| `getCurrentTextDirection({context})` | `TextDirection` | Mendapatkan TextDirection untuk locale saat ini |
| `toLocale(languageCode, [countryCode])` | `Locale` | Membuat Locale dari string |

Konstanta `rtlLanguages` berisi: `ar`, `he`, `fa`, `ur`, `yi`, `ps`, `ku`, `sd`, `dv`.

<div id="changing-language-from-controller"></div>

## Mengubah Bahasa dari Controller

Jika Anda menggunakan controller dengan halaman Anda, Anda dapat mengubah bahasa dari `NyController`:

``` dart
class SettingsController extends NyController {

  void switchToSpanish() {
    changeLanguage('es');
  }

  void switchToEnglishNoRestart() {
    changeLanguage('en', restartState: false);
  }
}
```

Parameter `restartState` mengontrol apakah aplikasi akan restart setelah mengubah bahasa. Atur ke `false` jika Anda ingin menangani pembaruan UI sendiri.
