# Panduan Translation Content

## Ringkasan

Sistem guna dua pattern translation:

| Pattern | Module | Cara Kerja |
|---------|--------|------------|
| **Separate table** | ContentArticle, ContentApplication, ContentSlider, ContentVideo | Data translatable disimpan dalam table berasingan, di-link via `*_translation_parent_id` FK |
| **Flat sibling** | ContentDownload, ContentImage, ContentPhotoGallery, ContentPhotoList | Translation adalah row lain dalam table yang sama, di-link via `*_parent_id`, dengan `*_main = 1` untuk parent |

Kedua-dua pattern menggunakan konsep **parent + siblings**, bezanya cuma lokasi simpanan data.

---

## 1. Setup Middleware (dah siap)

`app/Http/Middleware/SetLocale.php` — auto set `app()->getLocale()` dari session.

Didaftarkan di `bootstrap/app.php` sebagai alias `'locale'` dan guna di routes public.

---

## 2. Tukar Bahasa

```
GET /lang/{locale}
```

Contoh: `/lang/en` atau `/lang/ms`

Simpan locale dalam session, redirect balik.

---

## 3. Display Content Ikut Bahasa

### Pattern: Separate Translation Table

Sesuai untuk: **ContentArticle**, **ContentApplication**, **ContentSlider**, **ContentVideo**

```php
use App\Models\Backend\ContentArticle;

$locale = app()->getLocale();

// Single item
$article = ContentArticle::with(['translations' => function ($q) use ($locale) {
    $q->where('article_translation_language', $locale);
}])->findOrFail($id);

// List
$articles = ContentArticle::with(['translations' => function ($q) use ($locale) {
    $q->where('article_translation_language', $locale);
}])->get();
```

View:
```blade
@php
    $translation = $article->translations->first();
@endphp
<h1>{{ $translation->article_translation_title ?? '-' }}</h1>
<div>{!! $translation->article_translation_content ?? '' !!}</div>
```

#### ContentArticle

| Table | Translation FK Field | Translation Language Field |
|-------|---------------------|--------------------------|
| `content_article` | `article_translation_parent_id` → `article_id` | `article_translation_language` |

| Translatable Column | Type |
|--------------------|------|
| `article_translation_title` | `text` |
| `article_translation_content` | `longtext` (Summernote) |

#### ContentApplication

| Table | Translation FK Field | Translation Language Field |
|-------|---------------------|--------------------------|
| `content_applications` | `application_translation_parent_id` → `application_id` | `application_translation_language` |

| Translatable Column | Type |
|--------------------|------|
| `application_translation_title` | `varchar(255)` |

```php
use App\Models\Backend\ContentApplication;

$locale = app()->getLocale();

$applications = ContentApplication::with(['translations' => function ($q) use ($locale) {
    $q->where('application_translation_language', $locale);
}])->get();
```

View:
```blade
@php
    $translation = $application->translations->first();
@endphp
<h3>{{ $translation->application_translation_title ?? '-' }}</h3>
```

#### ContentSlider

| Table | Translation FK Field | Translation Language Field |
|-------|---------------------|--------------------------|
| `content_slider` | `slider_translation_parent_id` → `slider_id` | `slider_translation_language` |

| Translatable Column | Type | Notes |
|--------------------|------|-------|
| `slider_translation_title` | `text` | |
| `slider_translation_img` | `text` | File path — berbeza mengikut bahasa |

```php
use App\Models\Backend\ContentSlider;

$locale = app()->getLocale();

$sliders = ContentSlider::with(['translations' => function ($q) use ($locale) {
    $q->where('slider_translation_language', $locale);
}])->where('slider_status', 'ACTIVE')->orderBy('slider_sort')->get();
```

View:
```blade
@foreach ($sliders as $slider)
    @php
        $trans = $slider->translations->first();
    @endphp
    @if ($trans)
        <img src="{{ asset('storage/' . $trans->slider_translation_img) }}"
             alt="{{ $trans->slider_translation_title }}">
    @endif
@endforeach
```

#### ContentVideo

| Table | Translation FK Field | Translation Language Field |
|-------|---------------------|--------------------------|
| `content_video` | `video_translation_parent_id` → `video_id` | `video_translation_language` |

| Translatable Column | Type |
|--------------------|------|
| `video_translation_title` | `text` |
| `video_translation_content` | `text` |
| `video_translation_location` | `text` |
| `video_translation_url` | `varchar(255)` |

```php
use App\Models\Backend\ContentVideo;

$locale = app()->getLocale();

$videos = ContentVideo::with(['translations' => function ($q) use ($locale) {
    $q->where('video_translation_language', $locale);
}])->orderBy('video_sorting')->get();
```

View:
```blade
@foreach ($videos as $video)
    @php
        $trans = $video->translations->first();
    @endphp
    <h3>{{ $trans->video_translation_title ?? '-' }}</h3>
    @if ($trans && $trans->video_translation_url)
        <a href="{{ $trans->video_translation_url }}">Link</a>
    @endif
@endforeach
```

---

### Pattern: Flat Sibling (Self-Referential)

Sesuai untuk: **ContentDownload**, **ContentImage**, **ContentPhotoGallery**, **ContentPhotoList**

Dalam pattern ini, parent row dan sibling translation **duduk dalam table yang sama**. Parent ditanda dengan `*_main = 1`, manakala sibling ada `*_main = 0` dan `*_parent_id` pointing ke parent.

Frontend query guna `WHERE *_main = 1 AND *_language = ?` — parent row yang bahasanya sepadan dengan locale semasa.

```php
use App\Models\Backend\ContentDownload;

$locale = app()->getLocale();

// Parent rows dalam locale semasa
$downloads = ContentDownload::where('download_main', 1)
    ->where('download_language', $locale)
    ->orderBy('created_at', 'desc')
    ->get();
```

View — parent row sudah ada data untuk locale tersebut:
```blade
@foreach ($downloads as $download)
    <h3>{{ $download->download_title }}</h3>
    @if ($download->download_file)
        <a href="{{ asset('storage/' . $download->download_file) }}">Download</a>
    @endif
@endforeach
```

#### ContentDownload

| Column Parent | Column Sibling |
|--------------|---------------|
| `download_main = 1` | `download_main = 0` |
| `download_parent_id = null` | `download_parent_id` → parent `download_id` |
| `download_language` = locale | `download_language` = locale |

| Translatable Column | Type |
|--------------------|------|
| `download_title` | `varchar(255)` |
| `download_img` | `varchar(255)` — file path, berbeza per language |
| `download_file` | `varchar(255)` — file path, berbeza per language |

```php
$downloads = ContentDownload::where('download_main', 1)
    ->where('download_language', app()->getLocale())
    ->orderBy('created_at', 'desc')
    ->get();
```

Fallback ke parent kalau field tertentu kosong (guna `translations()` relationship):
```blade
@php
    // Kalau nak guna sibling dengan fallback:
    $sibling = $download->translations->first(); // translation dalam locale lain
    $title = $download->download_title;           // parent title (locale semasa)
    $file  = $sibling->download_file ?? $download->download_file; // fallback
@endphp
```

#### ContentImage

| Column Parent | Column Sibling |
|--------------|---------------|
| `image_main = 1` | `image_main = 0` |
| `image_parent_id = null` | `image_parent_id` → parent `image_id` |
| `image_language` = locale | `image_language` = locale |

| Translatable Column | Type |
|--------------------|------|
| `image_title` | `varchar(255)` |
| `image_file` | `varchar(255)` — file path, berbeza per language |

```php
use App\Models\Backend\ContentImage;

$locale = app()->getLocale();

$images = ContentImage::where('image_main', 1)
    ->where('image_language', $locale)
    ->orderBy('image_sort')
    ->get();
```

View:
```blade
@foreach ($images as $image)
    <figure>
        <img src="{{ asset('storage/' . $image->image_file) }}"
             alt="{{ $image->image_title }}">
        <figcaption>{{ $image->image_title }}</figcaption>
    </figure>
@endforeach
```

#### ContentPhotoGallery

| Table | Translation FK Field | Translation Language Field |
|-------|---------------------|--------------------------|
| `content_photo_gallery` | `gallery_translation_parent_id` → `gallery_id` | `gallery_translation_language` |

| Translatable Column | Type |
|--------------------|------|
| `gallery_translation_title` | `varchar(255)` |
| `gallery_translation_descr` | `text` (Summernote) |

```php
use App\Models\Backend\ContentPhotoGallery;

$locale = app()->getLocale();

$galleries = ContentPhotoGallery::with(['translations' => function ($q) use ($locale) {
    $q->where('gallery_translation_language', $locale);
}])->orderBy('gallery_sort')->get();
```

View:
```blade
@foreach ($galleries as $gallery)
    @php $trans = $gallery->translations->first(); @endphp
    <h2>{{ $trans->gallery_translation_title ?? '-' }}</h2>
    @if ($gallery->gallery_thumbnail)
        <img src="{{ asset('storage/' . $gallery->gallery_thumbnail) }}">
    @endif
    <div>{!! $trans->gallery_translation_descr ?? '' !!}</div>

    {{-- Senarai photo dalam gallery ni --}}
    @php
        $photos = App\Models\Backend\ContentPhotoList::where('photo_main', 1)
            ->where('photo_language', $locale)
            ->where('photo_gallery_id', $gallery->gallery_id)
            ->orderBy('photo_sort')
            ->get();
    @endphp
    @foreach ($photos as $photo)
        <img src="{{ asset('storage/' . $photo->photo_url) }}">
        <p>{{ $photo->photo_descr }}</p>
    @endforeach
@endforeach
```

#### ContentPhotoList

| Column Parent | Column Sibling |
|--------------|---------------|
| `photo_main = 1` | `photo_main = 0` |
| `photo_parent_id = null` | `photo_parent_id` → parent `photo_id` |
| `photo_language` = locale | `photo_language` = locale |

| Translatable Column | Type |
|--------------------|------|
| `photo_descr` | `text` |

```php
$photos = ContentPhotoList::where('photo_main', 1)
    ->where('photo_language', app()->getLocale())
    ->when($galleryId, fn($q) => $q->where('photo_gallery_id', $galleryId))
    ->orderBy('photo_sort')
    ->get();
```

View:
```blade
@foreach ($photos as $photo)
    <img src="{{ asset('storage/' . $photo->photo_url) }}" alt="{{ $photo->photo_descr }}">
    <p>{{ $photo->photo_descr }}</p>
@endforeach
```

---

## 4. Model Scopes

| Module | Scope Available | Description |
|--------|---------------|-------------|
| ContentDownload | `main()` | Hanya parent rows (`download_main = 1`) |
| ContentDownload | `forLocale($locale)` | Eager load translations ikut locale |

Guna:
```php
ContentDownload::main()->forLocale('en')->get();
// atau guna locale semasa
ContentDownload::main()->forLocale()->get();
```

> **Nota:** Module flat sibling lain (ContentImage, ContentPhotoGallery, ContentPhotoList) **tiada scope khusus**. Guna query terus:
> ```php
> Module::where('*_main', 1)->where('*_language', $locale)->...
> ```

---

## 5. Language Switcher Dropdown

```blade
@php $currentLocale = app()->getLocale(); @endphp

<select onchange="window.location='{{ url('lang') }}/'+this.value">
    @foreach ($languages as $code => $descr)
        <option value="{{ $code }}" {{ $currentLocale == $code ? 'selected' : '' }}>
            {{ $descr }}
        </option>
    @endforeach
</select>
```

`$languages` datang dari Ref table:
```php
$languages = Ref::where('cat', 'LANGUAGE')->orderBy('sort')->pluck('descr', 'code');
```

---

## 6. Admin: Cara Masukkan Translation

### ContentArticle, ContentApplication, ContentSlider, ContentVideo (Separate Table + Inline Alpine Tabs)

- Buka create/edit → ada card **Translations**
- Pill buttons / Dropdown untuk pilih bahasa
- Isi field translatable (title, content/description, untuk Slider: image upload per language)
- Tandakan **Set as Main Translation** untuk satu bahasa
- Submit → controller akan delete semua translation lama dan re-insert

### ContentPhotoGallery (Separate Table + Inline Alpine Tabs)

- Buka create/edit → ada card **Translations** dengan pill tabs / dropdown
- Isi field translatable (title/description) untuk setiap bahasa
- Tandakan **Set as Main Translation** untuk satu bahasa
- Submit → controller akan delete semua translation lama dan re-insert

### ContentPhotoList (Flat Sibling + Inline Alpine Tabs)

- Buka create/edit → ada card **Translations** dengan pill tabs
- Isi description untuk setiap bahasa
- Tandakan **Set as Main Translation** untuk satu bahasa
- Submit → controller akan delete semua sibling lama dan re-insert
- Parent row akan reflect data dari tab yang ditanda main

### ContentDownload, ContentImage (Flat Sibling — Separate Add/Delete Page)

- **Langkah 1:** Create parent entry dulu dengan 1 bahasa
- **Langkah 2:** Pada edit page, ada **Translations table** — senarai sibling sedia ada
- **Langkah 3:** Klik **Add Translation** → page berasingan untuk tambah sibling
- **Langkah 4:** Isi title, upload file/image untuk translation tu
- **Langkah 5:** Setiap translation boleh ada file dan image berbeza

---

## 7. Fallback Logic

### Separate Table (Article, Application, Slider, Video)

Bila display, eager load translation ikut locale. Kalau tiada translation untuk locale tersebut, fallback ke main translation (`*_translation_main = 1`):

```php
$article = ContentArticle::with(['translations' => function ($q) use ($locale) {
    $q->where('article_translation_language', $locale)
      ->orWhere('article_translation_main', 1); // + main sebagai fallback
}])->findOrFail($id);

$translation = $article->translations->firstWhere('article_translation_language', $locale)
    ?? $article->translations->firstWhere('article_translation_main', 1);
```

### Flat Sibling (Download, Image, PhotoGallery, PhotoList)

Parent row sudah dalam locale semasa — data terus dari parent. Tapi kalau nak fallback untuk field tertentu dari sibling:

```blade
@php
    $sibling = $download->translations->first();
@endphp
{{-- Parent punya field --}}
{{ $download->download_title }}
{{-- Fallback ke sibling kalau parent takde --}}
{{ $sibling->download_file ?? $download->download_file }}
```

---

## 8. Quick Reference: Field Names by Module

### Separate Translation Table

| Module | Parent PK | Translation FK | Translation Table | Lang Field | Main Field |
|--------|-----------|---------------|-------------------|------------|------------|
| ContentArticle | `article_id` | `article_translation_parent_id` | `content_article_translations` | `article_translation_language` | `article_translation_main` |
| ContentApplication | `application_id` | `application_translation_parent_id` | `content_application_translations` | `application_translation_language` | `application_translation_main` |
| ContentSlider | `slider_id` | `slider_translation_parent_id` | `content_slider_translations` | `slider_translation_language` | `slider_translation_main` |
| ContentVideo | `video_id` | `video_translation_parent_id` | `content_video_translations` | `video_translation_language` | `video_translation_main` |
| ContentPhotoGallery | `gallery_id` | `gallery_translation_parent_id` | `content_photo_gallery_translations` | `gallery_translation_language` | `gallery_translation_main` |

### Flat Sibling

| 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` |
| ContentPhotoGallery | `content_photo_gallery_translations` | `gallery_translation_main` | `gallery_translation_parent_id` | `gallery_translation_language` |
| ContentPhotoList | `content_photo_list` | `photo_main` | `photo_parent_id` | `photo_language` |

---

## 9. Files Reference

| File | Function |
|------|----------|
| `app/Http/Middleware/SetLocale.php` | Set locale dari session |
| `app/Http/Controllers/PublicController.php` | Contoh public controller |
| `app/Models/backendUser/ContentDownload.php` | Model Download + scopes |
| `docs/MODULE-REFERENCE.md` | Dokumentasi penuh setiap modul |
| `routes/web.php` | Routes public & admin |
