| Current Path : /var/www/html/reddsis/docs/ |
| Current File : /var/www/html/reddsis/docs/TRANSLATION-GUIDE.md |
# 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 |