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

@ -57,15 +57,25 @@ export function sortGroupItems(items, targetLang) {
*/
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 appStrings = items.filter(({ entry }) => entry.type === 'app_string');
const qStrings = items.filter(({ entry }) => entry.type === 'string');
const content = items.filter(({ entry }) =>
entry.type !== 'string' && entry.type !== 'app_string'
);
const groups = [];
if (strings.length) {
if (appStrings.length) {
groups.push({
id: 'app_strings',
title: 'App strings',
items: sortGroupItems(appStrings, targetLang),
});
}
if (qStrings.length) {
groups.push({
id: 'strings',
title: 'UI strings',
items: sortGroupItems(strings, targetLang),
items: sortGroupItems(qStrings, targetLang),
});
}
if (content.length) {
@ -107,11 +117,29 @@ export function renderGroupedTranslationsHTML(groups, targetLang, sourceLabel, e
return `<div class="${wrapClass}">${qnHeader}${header}${body}</div>`;
}
/** Flatten questionnaires from ?all=1 into one entries array + section metadata */
export function flattenAllQuestionnaires(questionnaires) {
/** Only global app UI rows (never questionnaire questions/options). */
export function filterGlobalAppEntries(entries) {
return (entries || []).filter(e => e && e.type === 'app_string');
}
/** Flatten app strings + questionnaires from ?all=1 */
export function flattenAllQuestionnaires(questionnaires, appStrings = []) {
const allEntries = [];
const sections = [];
const appNorm = filterGlobalAppEntries((appStrings || []).map(normalizeEntry));
if (appNorm.length) {
const startIdx = 0;
appNorm.forEach(e => allEntries.push(e));
sections.push({
questionnaireID: '__app__',
name: 'App UI',
startIdx,
entryCount: appNorm.length,
isApp: true,
});
}
for (const qn of questionnaires) {
const normalized = (qn.entries || []).map(normalizeEntry);
if (!normalized.length) continue;
@ -122,6 +150,7 @@ export function flattenAllQuestionnaires(questionnaires) {
name: qn.name || 'Questionnaire',
startIdx,
entryCount: normalized.length,
isApp: false,
});
}
@ -129,8 +158,8 @@ export function flattenAllQuestionnaires(questionnaires) {
}
export function typeBadge(type) {
const map = { question: 'Q', answer_option: 'Opt', string: 'Str' };
const cls = { question: 'badge-q', answer_option: 'badge-opt', string: 'badge-str' };
const map = { question: 'Q', answer_option: 'Opt', string: 'Str', app_string: 'App' };
const cls = { question: 'badge-q', answer_option: 'badge-opt', string: 'badge-str', app_string: 'badge-app' };
return `<span class="trans-type-badge ${cls[type] || ''}">${map[type] || type}</span>`;
}
@ -165,7 +194,7 @@ export function translationRowHTML(entry, idx, { targetLang, editable = true })
const missing = !val.trim();
const disabled = editable ? '' : 'disabled';
const sourceCell = entry.type === 'string' && editable
const sourceCell = editable
? `<input type="text" class="trans-cell-input" data-idx="${idx}" data-field="german"
value="${escHtml(german)}" placeholder="German text…" autocomplete="off">`
: `<span class="trans-source-text" title="${escHtml(german)}">${escHtml(german)}</span>`;