improved translation page UX
Some checks failed
PHPUnit / test (push) Has been cancelled

This commit is contained in:
2026-06-12 20:33:52 +02:00
parent d807f753b1
commit cceec525c1
4 changed files with 488 additions and 76 deletions

View File

@ -49,6 +49,22 @@ export function translationFor(entry, lang) {
return normalizeTranslations(entry.translations)[lang] || '';
}
export function isEntryMissing(entry, lang) {
return !translationFor(entry, lang).trim();
}
export function countMissing(entries, lang) {
return entries.filter(e => isEntryMissing(e, lang)).length;
}
export function sectionEntries(allEntries, section) {
let entries = allEntries.slice(section.startIdx, section.startIdx + section.entryCount);
if (section.isApp) {
entries = entries.filter(e => e.type === 'app_string');
}
return entries;
}
export function targetLanguages(languages) {
return (languages || []).filter(l => l.languageCode !== SOURCE_LANG);
}
@ -197,11 +213,11 @@ export function sourceLanguageLabel(languages) {
return row?.name ? `${row.name} (${SOURCE_LANG})` : 'German';
}
/** Column headers: Key | main language (German) | target language */
/** Column headers: label | main language (German) | target language */
export function translationListHeaderHTML(targetLangLabel, sourceLabel = 'German') {
return `
<div class="trans-list-header">
<span class="trans-col-key">Key</span>
<div class="trans-list-header trans-list-header-sticky">
<span class="trans-col-key">Label</span>
<span class="trans-col-source">${escHtml(sourceLabel)}</span>
<span class="trans-col-target">${escHtml(targetLangLabel)}</span>
</div>`;
@ -220,12 +236,17 @@ export function translationRowHTML(entry, idx, { targetLang, editable = true })
value="${escHtml(german)}" placeholder="German text…" autocomplete="off">`
: `<span class="trans-source-text" title="${escHtml(german)}">${escHtml(german)}</span>`;
const germanPreview = german.length > 72 ? `${german.slice(0, 72)}` : german;
return `
<div class="trans-list-item ${missing ? 'trans-list-item-missing' : ''}"
data-idx="${idx}" data-type="${entry.type}" data-key="${escHtml(lookupKey)}" data-label="${escHtml(label)}" data-missing="${missing ? '1' : '0'}">
<div class="trans-list-key">
${typeBadge(entry.type)}
<span class="trans-key-text" title="${escHtml(lookupKey)}">${escHtml(label)}</span>
<div class="trans-key-labels">
<span class="trans-key-primary" title="${escHtml(german)}">${escHtml(germanPreview || label)}</span>
<span class="trans-key-secondary" title="${escHtml(lookupKey)}">${escHtml(lookupKey)}</span>
</div>
</div>
<div class="trans-list-source">${sourceCell}</div>
<input type="text" class="trans-cell-input ${missing ? 'trans-input-missing' : ''}"
@ -233,3 +254,32 @@ export function translationRowHTML(entry, idx, { targetLang, editable = true })
placeholder="Translation…" ${disabled} autocomplete="off">
</div>`;
}
/**
* Grouped rows with collapsible sections and missing counts in summaries.
*/
export function renderCollapsibleGroupedTranslationsHTML(groups, targetLang, sourceLabel, editable, options = {}) {
const { openGroupsWithMissing = false } = options;
if (!groups.length) return '';
const body = groups.map(g => {
const missing = g.items.filter(({ entry }) => isEntryMissing(entry, targetLang)).length;
const total = g.items.length;
const open = openGroupsWithMissing && missing > 0;
const summary = missing
? `${g.title} · ${missing} missing`
: `${g.title} · complete (${total})`;
return `
<details class="trans-group-details"${open ? ' open' : ''}>
<summary class="trans-group-summary">${escHtml(summary)}</summary>
<div class="trans-group-rows">
${g.items.map(({ entry, idx }) =>
translationRowHTML(entry, idx, { targetLang, editable })
).join('')}
</div>
</details>`;
}).join('');
return `<div class="trans-groups-flat">${body}</div>`;
}