/** * Shared apply logic for manual Vietnamese translation batches. */ function applyTranslation(q, t, now) { if (t.stem) q.stem.vi = t.stem; if (t.explanation) q.explanation.vi = t.explanation; if (t.options && q.options) { for (const opt of q.options) { if (t.options[opt.key] !== undefined) { opt.text.vi = t.options[opt.key]; } } } if (t.boxes && q.boxes) { for (const box of q.boxes) { const b = t.boxes[box.id]; if (!b) continue; // Keep boxes.label in English — do not overwrite with translated labels. if (b.answer) box.answer.vi = b.answer; } } if (t.answerText && q.answer?.text) { q.answer.text.vi = t.answerText; } if (t.statements && q.interaction?.statements) { for (const st of q.interaction.statements) { if (t.statements[st.id]) st.vi = t.statements[st.id]; } } if (q.meta) q.meta.translatedAt = now; } /** * @param {object[]} data * @param {Record} translations * @param {{ minId?: number, maxId?: number, dryRun?: boolean }} opts */ function applyBatch(data, translations, opts = {}) { const now = new Date().toISOString(); const { minId, maxId, dryRun = false } = opts; let updated = 0; const missing = []; for (const [idStr, t] of Object.entries(translations)) { const id = Number(idStr); if (Number.isNaN(id)) continue; if (minId != null && id < minId) continue; if (maxId != null && id > maxId) continue; const q = data.find((x) => x.id === id); if (!q) { missing.push(id); continue; } if (!dryRun) applyTranslation(q, t, now); updated++; } return { updated, missing, now }; } module.exports = { applyTranslation, applyBatch };