# Panduan Sistem Multi-Bahasa (Translation)

## Ringkasan

Sistem multi-bahasa mempunyai **2 lapisan**:

| Lapisan | Fungsi | Storage |
|---------|--------|---------|
| **App Translation (UI Strings)** | Label nav, button, footer, text statik | Table `app_translations` + helper `t()` |
| **Content Translation** | Data content (artikel, slider, video, dll) | Table translation berasingan / self-referencing |

---

## 1. App Translation — UI Strings

### Cara Guna

Dalam mana-mana Blade template (termasuk component dalam DB):

```blade
{{ t('login') }}        <!-- Output: Log Masuk / Login / 登录 -->
{{ t('register') }}     <!-- Output: Daftar / Register / 注册 -->
{{ t('nonexistent') }}  <!-- Output: nonexistent (fallback ke key) -->
{{ t('key', 'Default') }} <!-- Output: 'Default' kalau key tak wujud -->
```

### Admin Panel

`/admin/app-translation` → CRUD untuk semua keys.

| key | ms | en | zh |
|-----|----|----|-----|
| login | Log Masuk | Login | 登录 |
| register | Daftar | Register | 注册 |
| dynaweb4_desc | DYNAWEB4 adalah... | DYNAWEB4 is... | DYNAWEB4 是一个... |
| Utama | Utama | Home | 首页 |
| Hubungi Kami | Hubungi Kami | Contact Us | 联系我们 |

- **Index**:
  - **Default view**: Accordion group by language (collapse/expand), paginate language groups 5/page
  - **Filtered view** (`?lang=ms`): Flat list filtered by language, paginate 10/page
  - "Showing" displays language count (not key count)
  - Dropdown: "All Languages" (no dashes)
- **Create**: Tambah key + value untuk 1 bahasa
- **Edit**: Edit semua bahasa untuk 1 key sekali gus

### Helper `t()` Function

**File:** `app/Helpers/Translation.php`

```php
function t(string $key, ?string $default = null): string
{
    static $data = [];
    $locale = session('locale', 'ms');
    
    if (!isset($data[$locale])) {
        $data[$locale] = AppTranslation::where('lang', $locale)
            ->pluck('value', 'key')->all();
    }
    
    return $data[$locale][$key] ?? $default ?? $key;
}
```

- Auto-detect locale dari session (default `'ms'`)
- Cache guna `static` variable dalam 1 request
- Fallback: key → default → key asal

---

## 2. Content Translation

Sistem guna **dua pattern** untuk content translation:

| Pattern | Modules | Cara Kerja |
|---------|---------|------------|
| **Separate Table** | ContentArticle, ContentSlider, ContentVideo, ContentCalendar, ContentPhotoGallery, ContentApplication | Data translatable dalam table berasingan, di-link via `*_parent_id` |
| **Self-Referencing (Sibling)** | ContentDownload, ContentImage, ContentPhotoList | Translation adalah row lain dalam table yang sama, `*_main = 1` untuk parent |

### Language Switching Flow

```
User klik "ENGLISH"
    ↓
GET /lang/en
    ↓
Route set session('locale', 'en')
    ↓
PortalHandler inject $lang = session('locale', 'ms')
    ↓
Components query content WHERE *_language = $lang
```

### Language Switching Route

**File:** `routes/web.php`

```php
Route::get('/lang/{code}', function ($code) {
    $valid = DB::table('ref')->where('cat', 'LANGUAGE')
        ->where('code', $code)->exists();
    if ($valid) session(['locale' => $code]);
    return redirect()->back();
})->name('lang.switch');
```

Navbar links:
```blade
<a href="{{ url('lang/ms') }}">MALAY</a>
<a href="{{ url('lang/en') }}">ENGLISH</a>
<a href="{{ url('lang/zh') }}">中文</a>
```

### PortalHandler — Inject $lang

**File:** `app/Services/PortalHandler.php`

```php
$lang = session('locale', 'ms');

$vars = [
    'lang' => $lang,
    // ... variables lain
];
```

Semua component, page content, dan layout dapat akses `$lang`.

---

## 3. Pattern: Separate Translation Table

Digunakan oleh: **ContentArticle**, **ContentSlider**, **ContentVideo**, **ContentCalendar**, **ContentPhotoGallery**, **ContentApplication**

### Table Structure

```
content_article              content_article_translation
┌──────────────────┐        ┌─────────────────────────────────┐
│ article_id (PK)  │◄──┐   │ article_translation_id (PK)     │
│ article_code     │   └───│ article_translation_parent_id   │
│ article_status   │        │ article_translation_title      │
│ ...              │        │ article_translation_content    │
└──────────────────┘        │ article_translation_language   │
                            │ article_translation_main       │
                            └─────────────────────────────────┘
```

### Query Pattern

```php
// PortalHandler components guna $lang dari session
$news = ContentArticle::with(['translations' => function($q) use ($lang) {
    $q->where('article_translation_language', $lang);
}])->where('article_category', 'NEWS')->get();
```

### Modules

| Module | Parent Table | Translation Table | FK | Language Field |
|--------|-------------|-------------------|----|---------------|
| ContentArticle | `content_article` | `content_article_translation` | `article_translation_parent_id` | `article_translation_language` |
| ContentSlider | `content_slider` | `content_slider_translation` | `slider_translation_parent_id` | `slider_translation_language` |
| ContentVideo | `content_video` | `content_video_translation` | `video_translation_parent_id` | `video_translation_language` |
| ContentCalendar | `content_calendar` | `content_calendar_translation` | `calendar_translation_parent_id` | `calendar_translation_language` |
| ContentPhotoGallery | `content_photo_gallery` | `content_photo_gallery_translations` | `gallery_translation_parent_id` | `gallery_translation_language` |
| ContentApplication | `content_applications` | `content_application_translations` | `application_translation_parent_id` | `application_translation_language` |

---

## 4. Pattern: Self-Referencing (Sibling)

Digunakan oleh: **ContentDownload**, **ContentImage**, **ContentPhotoList**

### Table Structure

```
content_downloads
┌──────────────────────────────┐
│ download_id (PK)            │
│ download_main = 1/0         │ ← 1 = parent, 0 = sibling
│ download_parent_id (FK)     │ ← null utk parent, pointing ke parent utk sibling
│ download_language = 'ms'    │
│ download_title              │
│ download_file               │
│ ...                         │
└──────────────────────────────┘
```

### Query Pattern

```php
// Parent row dalam locale semasa
$downloads = ContentDownload::where('download_language', $lang)
    ->where('download_status', 'ACTIVE')
    ->orderBy('download_category')->get();
```

### Modules

| Module | Table | Main Flag | Parent FK | Language Field |
|--------|-------|-----------|-----------|---------------|
| ContentDownload | `content_downloads` | `download_main` | `download_parent_id` | `download_language` |
| ContentImage | `content_images` | `image_main` | `image_parent_id` | `image_language` |
| ContentPhotoList | `content_photo_list` | `photo_main` | `photo_parent_id` | `photo_language` |

---

## 5. Frontend Menu Translation

Menu names disimpan dalam table `frontend_menu` dalam BM. PortalHandler akan auto-translate menggunakan `t()`.

**File:** `app/Services/PortalHandler.php`

```php
$translateMenuName = function ($name) {
    return t($name);  // lookup dalam app_translations
};

// Build menu tree
$menuTree = $buildTree($publicMappings);

// Translate all menu names
array_walk_recursive($menuTree, function (&$v, $k) use ($translateMenuName) {
    if ($k === 'menu_name') $v = $translateMenuName($v);
});
```

Keys yang perlu ada dalam `app_translations`:
- `Utama`, `Mengenai Kami`, `Sejarah`, `Visi Misi`, `Carta Organisasi`, `Piagam Pelanggan`
- `Perkhidmatan`, `Dalam Talian`, `e-Permohonan`, `FAQ`, `Muat Turun`
- `Media`, `Berita`, `Galeri Foto`, `Video`, `Penerbitan`, `Hubungi Kami`

---

## 6. Seeder

### ContentTranslationSeeder

**File:** `database/seeders/portal/ContentTranslationSeeder.php`

Seed data awal untuk `app_translations` table. Guard: `count() > 0`.

```php
$data = [
    ['key' => 'login', 'ms' => 'Log Masuk', 'en' => 'Login', 'zh' => '登录'],
    ['key' => 'Utama', 'ms' => 'Utama', 'en' => 'Home', 'zh' => '首页'],
    // ... lebih banyak keys
];
```

### Content Seeders

Semua portal content seeders (ContentSliderSeeder, ContentVideoSeeder, dll) sekarang insert **ms, en, zh** untuk setiap content. Guard guna `->where('column', 'CATEGORY')->count()` spesifik mengikut kategori.

---

## 7. Admin Module: App Translation

**Routes:** `/admin/app-translation`

| Item | Nama |
|------|------|
| Table | `app_translations` |
| Model | `AppTranslation` |
| Controller | `AppTranslationController` |
| View folder | `appTranslation/` |
| Menu | Translations (dalam group Content) |
| Permission | `app-translation.view`, `.create`, `.update`, `.delete` |

### Features

- **Index**: Accordion group by language (collapse/expand), paginate by language 5/page, flat filter by language 10/page, search, dropdown "All Languages"
- **Create**: Add key + value for 1 language
- **Edit**: Edit all languages for a key simultaneously
- **Delete**: Delete entire key (all languages)

---

## 8. Language Codes (ISO 639-1)

| Code | Language (Ref) |
|------|---------------|
| `ms` | Bahasa Melayu |
| `en` | English |
| `zh` | 中文 (Mandarin) |

Defined in `ref` table with `cat = 'LANGUAGE'`. Admin boleh tambah bahasa baru di menu Ref.

---

## 9. Files Reference

| File | Function |
|------|----------|
| `app/Helpers/Translation.php` | Helper `t()` function |
| `app/Models/Backend/AppTranslation.php` | Model untuk `app_translations` |
| `app/Http/Controllers/Backend/AppTranslationController.php` | CRUD controller + paginate by language |
| `app/Http/Requests/Backend/AppTranslationRequest.php` | Validation |
| `app/Services/PortalHandler.php` | Inject `$lang`, translate menu names |
| `resources/views/backend/module/appTranslation/*.blade.php` | Admin views |
| `resources/views/vendor/pagination/custom.blade.php` | Pagination template (showing language count) |
| `database/seeders/portal/ContentTranslationSeeder.php` | Seeder app_translations |
| `database/migrations/2026_07_05_000001_create_app_translations_table.php` | Migration |
| `routes/web.php` | Route `/lang/{code}` + admin routes |
| `database/seeders/portal/ContentSliderSeeder.php` | Slider seeder (ms, en, zh) |
| `database/seeders/portal/ContentVideoSeeder.php` | Video seeder (ms, en, zh) |
| `database/seeders/portal/ContentCalendarSeeder.php` | Calendar seeder (ms, en, zh) |
| `database/seeders/portal/ContentGallerySeeder.php` | Gallery seeder (ms, en, zh) |
| `database/seeders/portal/ContentApplicationSeeder.php` | Application seeder (ms, en, zh) |
| `database/seeders/portal/ContentDownloadSeeder.php` | Download seeder (ms, en, zh) |
| `database/seeders/portal/ContentImageSeeder.php` | Image seeder (ms, en, zh) |
| `database/seeders/portal/ContentArticleAboutUsSeeder.php` | Article PRESS (ms, en, zh) |
| `database/seeders/portal/ContentArticleFaqSeeder.php` | Article FAQ (ms, en, zh) |
| `database/seeders/portal/ContentNewsSeeder.php` | Article NEWS (ms, en, zh) |
| `database/seeders/portal/WebSiteBackupSeeder.php` | Portal components with `$lang` queries |
