added app translations for better maintainability

This commit is contained in:
2026-05-22 16:48:47 +02:00
parent 0291b8be7a
commit 2ded5b6aaf
11 changed files with 594 additions and 122 deletions

View File

@ -961,10 +961,10 @@ function renderTranslationsTab() {
if (!container || !transData) return;
const editable = canEdit();
const languages = transData.languages || [];
const entries = transData.entries || [];
const qnEntries = transData.entries || [];
const targets = targetLanguages(languages);
if (!entries.length) {
if (!qnEntries.length) {
container.innerHTML = `
${editable ? languageManagerHTML(languages) : ''}
<p style="color:var(--text-secondary);margin-top:12px">No translatable content yet. Add questions with German text first.</p>
@ -992,7 +992,7 @@ function renderTranslationsTab() {
container.innerHTML = `
${editable ? languageManagerHTML(languages) : ''}
${langSelectHtml}
${targets.length && editorTransLang ? renderEditorTransList(entries, editable) : ''}
${targets.length && editorTransLang ? renderEditorTransList(qnEntries, editable) : ''}
`;
if (editable) bindLanguageManager(languages);
@ -1003,18 +1003,25 @@ function renderTranslationsTab() {
});
if (targets.length && editorTransLang) {
bindEditorTransEditing(entries, editable);
bindEditorTransFiltering(entries);
bindEditorTransEditing(qnEntries, editable);
bindEditorTransFiltering(qnEntries);
}
}
function renderEditorTransList(entries, editable) {
function renderEditorTransList(qnEntries, editable) {
const targets = targetLanguages(transData.languages || []);
const langLabel = targets.find(l => l.languageCode === editorTransLang)?.name || editorTransLang.toUpperCase();
const sourceLabel = sourceLanguageLabel(transData.languages || []);
const groups = buildTranslationGroups(entries, editorTransLang, 0);
const missingCount = entries.filter(e => !translationFor(e, editorTransLang).trim()).length;
const pct = entries.length ? Math.round(((entries.length - missingCount) / entries.length) * 100) : 0;
const missingCount = qnEntries.filter(e => !translationFor(e, editorTransLang).trim()).length;
const pct = qnEntries.length ? Math.round(((qnEntries.length - missingCount) / qnEntries.length) * 100) : 0;
const qnGroups = buildTranslationGroups(qnEntries, editorTransLang, 0);
const qnName = transData.questionnaire?.name || questionnaire?.name || 'Questionnaire';
const bodyHtml = `
<div class="trans-qn-block" data-qn="${escHtml(questionnaire.questionnaireID)}">
<h2 class="trans-qn-title">${escHtml(qnName)}</h2>
${renderGroupedTranslationsHTML(qnGroups, editorTransLang, sourceLabel, editable, { showColumnHeader: false })}
</div>`;
return `
<div class="trans-toolbar">
@ -1027,11 +1034,9 @@ function renderEditorTransList(entries, editable) {
</select>
</div>
${translationListHeaderHTML(langLabel, sourceLabel)}
<div id="transGroupedRoot">
${renderGroupedTranslationsHTML(groups, editorTransLang, sourceLabel, editable, { showColumnHeader: false })}
</div>
<div id="transGroupedRoot">${bodyHtml}</div>
<div class="trans-stats">
<span id="transStats">${missingCount ? `${missingCount} missing · ` : ''}${entries.length} keys</span>
<span id="transStats">${missingCount ? `${missingCount} missing · ` : ''}${qnEntries.length} keys</span>
<span class="trans-stats-progress">
<span class="trans-progress-bar"><span class="trans-progress-fill" style="width:${pct}%"></span></span>
<span class="trans-progress-label">${pct}% in ${escHtml(editorTransLang.toUpperCase())}</span>
@ -1114,6 +1119,10 @@ function bindEditorTransFiltering(entries) {
const anyVisible = [...group.querySelectorAll('.trans-list-item')].some(r => !r.classList.contains('trans-row-hidden'));
group.classList.toggle('trans-group-hidden', !anyVisible);
});
root.querySelectorAll('.trans-qn-block').forEach(block => {
const anyVisible = [...block.querySelectorAll('.trans-list-item')].some(r => !r.classList.contains('trans-row-hidden'));
block.classList.toggle('trans-qn-hidden', !anyVisible);
});
const stats = document.getElementById('transStats');
if (stats) {
stats.textContent = text || type

View File

@ -57,7 +57,8 @@ function renderShell() {
<input type="search" id="transFilter" class="trans-search-input" placeholder="Search…" disabled>
<select id="transTypeFilter" class="trans-select trans-type-select" disabled>
<option value="">All types</option>
<option value="string">UI strings</option>
<option value="app_string">App strings</option>
<option value="string">Questionnaire UI strings</option>
<option value="question">Questions</option>
<option value="answer_option">Answer options</option>
</select>
@ -99,7 +100,7 @@ async function loadTranslations() {
const languages = transData.languages || [];
const questionnaires = transData.questionnaires || [];
const flat = flattenAllQuestionnaires(questionnaires);
const flat = flattenAllQuestionnaires(questionnaires, data.appStrings || []);
allEntries = flat.allEntries;
transData.sections = flat.sections;
@ -234,7 +235,10 @@ function renderEntryList() {
const headerOnce = translationListHeaderHTML(langLabel, sourceLabel);
const sectionsHtml = sections.map(sec => {
const entries = allEntries.slice(sec.startIdx, sec.startIdx + sec.entryCount);
let entries = allEntries.slice(sec.startIdx, sec.startIdx + sec.entryCount);
if (sec.isApp) {
entries = entries.filter(e => e.type === 'app_string');
}
const groups = buildTranslationGroups(entries, selectedLang, sec.startIdx);
return `
<div class="trans-qn-block" data-qn="${esc(sec.questionnaireID)}">