feat: tambah terjemahan bilingual untuk data SOP, FAQ, dan Staf

This commit is contained in:
cygouw
2026-06-05 12:55:11 +07:00
parent 6d4f5be825
commit 754c73caf5
4 changed files with 389 additions and 160 deletions
+52 -18
View File
@@ -12,6 +12,29 @@ function t(key, fallback) {
return fallback;
}
/**
* Mendapatkan bahasa aktif dari localStorage atau default 'id'.
* @returns {'id'|'en'} Kode bahasa aktif
*/
function getLang() {
return localStorage.getItem('if_untan_lang') || 'id';
}
/**
* Mengambil teks dari field bilingual JSON { id: '...', en: '...' }.
* Jika field bukan objek (string lama / belum diupdate), kembalikan apa adanya.
* @param {string|Object} field - Field data dari JSON
* @returns {string} Teks sesuai bahasa aktif
*/
function loc(field) {
if (!field) return '';
if (typeof field === 'object' && field !== null) {
const lang = getLang();
return field[lang] || field['id'] || '';
}
return field;
}
/**
* Normalisasi nomor telepon ke format standard e.g. 628...
* @param {string} phone - Nomor telepon mentah
@@ -98,14 +121,17 @@ export function renderStaffCards(staffs) {
tasks
} = staff;
const roleText = loc(role);
const locationText = loc(location);
const scheduleHTML = schedule.map(sched =>
`<div class="schedule-item">` +
`<span class="s-day">${sched.day}</span>` +
`<span class="s-time">${sched.time} <span class="s-break">| ${sched.break}</span></span>` +
`<span class="s-day">${loc(sched.day)}</span>` +
`<span class="s-time">${sched.time} <span class="s-break">| ${loc(sched.break)}</span></span>` +
`</div>`
).join('');
const tasksHTML = tasks.map(task => `<li>${task}</li>`).join('');
const tasksHTML = tasks.map(task => `<li>${loc(task)}</li>`).join('');
const isPhoneValid = isValidWhatsAppNumber(phone);
const encodedPhone = isPhoneValid ? encodePhone(phone) : '';
const displayPhone = isPhoneValid ? formatPhoneForDisplay(phone) : '-';
@@ -124,12 +150,12 @@ export function renderStaffCards(staffs) {
</div>
<div>
<div class="staff-name">${name}</div>
<div class="staff-role">${role}</div>
<div class="staff-role">${roleText}</div>
</div>
</div>
<div class="staff-card-body">
<div class="staff-info-grid">
<div class="staff-info-item"><span class="icon">📍</span><div><span class="label">${t('grp_c4a_label_location', 'Lokasi')}</span><span class="val">${location}</span></div></div>
<div class="staff-info-item"><span class="icon">📍</span><div><span class="label">${t('grp_c4a_label_location', 'Lokasi')}</span><span class="val">${locationText}</span></div></div>
<div class="staff-info-item">
<span class="icon">🕒</span>
<div style="flex:1">
@@ -200,29 +226,32 @@ function buildSopCard(sop) {
procedures
} = sop;
const titleText = loc(title);
const descText = loc(description);
const catLabel = getCatLabel(category);
const catClass = CATEGORY_COLORS[category] || '';
return `
<article class="sop-grid-card reveal" data-tab-id="${tabId}" data-category="${category}" data-title="${title.toLowerCase()}">
<article class="sop-grid-card reveal" data-tab-id="${tabId}" data-category="${category}" data-title="${titleText.toLowerCase()}">
<div class="sgc-header">
<div class="sgc-icon-wrap">
<i class="fas ${icon}"></i>
</div>
<div class="sgc-meta">
<h3 class="sgc-title">${title}</h3>
<h3 class="sgc-title">${titleText}</h3>
</div>
</div>
<div class="sgc-category-row">
<span class="sgc-category ${catClass}">${catLabel}</span>
</div>
<p class="sgc-desc">${description}</p>
<p class="sgc-desc">${descText}</p>
<div class="sgc-footer">
<div class="sgc-steps">
<i class="fas fa-list-ol"></i>
<span>${procedures.length} ${t('grp_c4a_label_steps', 'Langkah')}</span>
</div>
<button class="sgc-detail-btn" data-tab-id="${tabId}" aria-label="${t('grp_c4a_label_view_detail', 'Lihat Detail')} ${title}">
<button class="sgc-detail-btn" data-tab-id="${tabId}" aria-label="${t('grp_c4a_label_view_detail', 'Lihat Detail')} ${titleText}">
${t('grp_c4a_label_view_detail', 'Lihat Detail')} <i class="fas fa-arrow-right"></i>
</button>
</div>
@@ -247,11 +276,16 @@ function buildSopModalContent(sop) {
additional_info: additionalInfo
} = sop;
const titleText = loc(title);
const descText = loc(description);
const purposeText = loc(purpose);
const additionalInfoText = loc(additionalInfo);
const catLabel = getCatLabel(category);
const catClass = CATEGORY_COLORS[category] || '';
const reqsHTML = requirements.map(req => `<li>${req}</li>`).join('');
const procsHTML = procedures.map(proc => `<li>${proc}</li>`).join('');
const reqsHTML = requirements.map(req => `<li>${loc(req)}</li>`).join('');
const procsHTML = procedures.map(proc => `<li>${loc(proc)}</li>`).join('');
// staffResponsible bersifat opsional — sembunyikan blok jika tidak ada
const hasStaff = Array.isArray(staffResponsible) && staffResponsible.length > 0;
@@ -266,10 +300,10 @@ function buildSopModalContent(sop) {
: '';
// additionalInfo bersifat opsional — sembunyikan blok jika tidak ada
const additionalInfoHTML = additionalInfo
const additionalInfoHTML = additionalInfoText
? `<div class="sop-info-box">
<span class="iico">️</span>
<p class="sop-info-box-text"><strong>${t('grp_c4a_label_additional_info', 'Informasi Tambahan (Bersifat Opsional):')}</strong> ${additionalInfo}</p>
<p class="sop-info-box-text"><strong>${t('grp_c4a_label_additional_info', 'Informasi Tambahan (Bersifat Opsional):')}</strong> ${additionalInfoText}</p>
</div>`
: '';
@@ -283,15 +317,15 @@ function buildSopModalContent(sop) {
<div class="sop-modal-badges">
<span class="sgc-category ${catClass}">${catLabel}</span>
</div>
<h2 class="sop-modal-title" id="sop-modal-title">${title}</h2>
<p class="sop-modal-desc">${description}</p>
<h2 class="sop-modal-title" id="sop-modal-title">${titleText}</h2>
<p class="sop-modal-desc">${descText}</p>
</div>
</div>
</div>
<div class="sop-modal-body">
<div class="sop-block">
<div class="sop-block-label">${t('grp_c4a_label_purpose', 'Tujuan')}</div>
<p class="sop-block-text">${purpose}</p>
<p class="sop-block-text">${purposeText}</p>
</div>
<div class="sop-two-col">
<div>
@@ -626,11 +660,11 @@ export function renderFaqSection(faqs) {
return `
<div class="faq-item reveal ${delayClass}">
<div class="faq-question">
<span class="faq-question-text">${faq.question}</span>
<span class="faq-question-text">${loc(faq.question)}</span>
<div class="faq-icon">+</div>
</div>
<div class="faq-answer">
<div class="faq-answer-inner">${faq.answer}</div>
<div class="faq-answer-inner">${loc(faq.answer)}</div>
</div>
</div>`;
}).join('');