/** German (de) is the canonical source language for questionnaire content. */ export const SOURCE_LANG = 'de'; export function normalizeTranslations(raw) { if (!raw) return {}; if (Array.isArray(raw)) { const map = {}; raw.forEach(row => { if (row && row.languageCode) map[row.languageCode] = row.text ?? ''; }); return map; } return typeof raw === 'object' ? { ...raw } : {}; } export function normalizeEntry(entry) { const translations = normalizeTranslations(entry.translations); const germanText = (entry.germanText || translations[SOURCE_LANG] || entry.key || '').trim() || entry.key || ''; return { ...entry, translations, germanText }; } export function germanFor(entry) { return entry.germanText || normalizeTranslations(entry.translations)[SOURCE_LANG] || entry.key || ''; } export function translationFor(entry, lang) { return normalizeTranslations(entry.translations)[lang] || ''; } export function targetLanguages(languages) { return (languages || []).filter(l => l.languageCode !== SOURCE_LANG); } /** Sort rows within a group: missing translations first, then questionnaire order */ export function sortGroupItems(items, targetLang) { return [...items].sort((a, b) => { const aMissing = !translationFor(a.entry, targetLang).trim(); const bMissing = !translationFor(b.entry, targetLang).trim(); if (aMissing !== bMissing) return aMissing ? -1 : 1; const orderA = a.entry.sortOrder ?? 0; const orderB = b.entry.sortOrder ?? 0; if (orderA !== orderB) return orderA - orderB; const typeCmp = (a.entry.type === 'question' ? 0 : 1) - (b.entry.type === 'question' ? 0 : 1); if (typeCmp !== 0) return typeCmp; return germanFor(a.entry).localeCompare(germanFor(b.entry), 'de'); }); } /** * Groups for one questionnaire: UI strings, then questions + options (interleaved). * @param {number} indexOffset - global index for save handlers */ export function buildTranslationGroups(entries, targetLang, indexOffset = 0) { const items = entries.map((entry, i) => ({ entry, idx: indexOffset + i })); const strings = items.filter(({ entry }) => entry.type === 'string'); const content = items.filter(({ entry }) => entry.type !== 'string'); const groups = []; if (strings.length) { groups.push({ id: 'strings', title: 'UI strings', items: sortGroupItems(strings, targetLang), }); } if (content.length) { groups.push({ id: 'content', title: 'Questions', items: sortGroupItems(content, targetLang), }); } return groups; } /** Render grouped sections (optional questionnaire wrapper title). */ export function renderGroupedTranslationsHTML(groups, targetLang, sourceLabel, editable, options = {}) { const { showColumnHeader = true, questionnaireTitle = null } = options; if (!groups.length) return ''; const header = showColumnHeader ? translationListHeaderHTML( targetLang, sourceLabel ) : ''; const qnHeader = questionnaireTitle ? `