fix(i18n): remove duplicate keys and dead frontend branches
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
668681dd24
commit
2f4c8529e9
@ -13,6 +13,7 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · versioned wi
|
|||||||
- **Voice list rows no longer show the "Source unknown" badge** (it crowded the play buttons). The badge stays in the voice inspector, next to the Source & consent panel.
|
- **Voice list rows no longer show the "Source unknown" badge** (it crowded the play buttons). The badge stays in the voice inspector, next to the Source & consent panel.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
- **Duplicate German i18n keys and dead frontend code.** Removed 14 shadowed duplicate keys in `i18n.js` (effective translations unchanged), the unreachable body of `audiobookShowPreview`, and two identical-branch ternaries.
|
||||||
- **Security hardening (Sonar findings).** The Docker installer downloads `get-docker.sh` into a private `mkdtemp` dir (not a predictable `/tmp` path) and removes it afterwards; the OMDb poster lookup uses HTTPS; the fish.audio cache key marks MD5 as `usedforsecurity=False`.
|
- **Security hardening (Sonar findings).** The Docker installer downloads `get-docker.sh` into a private `mkdtemp` dir (not a predictable `/tmp` path) and removes it afterwards; the OMDb poster lookup uses HTTPS; the fish.audio cache key marks MD5 as `usedforsecurity=False`.
|
||||||
- **Background jobs could be garbage-collected mid-run.** Fire-and-forget `asyncio.create_task` calls (magazine, conversation, tts, stt, library, sources) now go through `core.tasks.spawn`, which keeps a strong reference until the task ends and logs failures by exception class only.
|
- **Background jobs could be garbage-collected mid-run.** Fire-and-forget `asyncio.create_task` calls (magazine, conversation, tts, stt, library, sources) now go through `core.tasks.spawn`, which keeps a strong reference until the task ends and logs failures by exception class only.
|
||||||
- **Voice archetype gallery broke `npm run check:frontend`**: its `say` helper collided with `magazine.js`; renamed to `archSay`.
|
- **Voice archetype gallery broke `npm run check:frontend`**: its `say` helper collided with `magazine.js`; renamed to `archSay`.
|
||||||
|
|||||||
@ -56,36 +56,6 @@ async function audiobookOpenCurrentInRehearser() {
|
|||||||
|
|
||||||
function audiobookShowPreview() {
|
function audiobookShowPreview() {
|
||||||
audiobookOpenCurrentInRehearser();
|
audiobookOpenCurrentInRehearser();
|
||||||
return;
|
|
||||||
const segs = _audiobook.segments || [];
|
|
||||||
const roster = _audiobook.roster || [];
|
|
||||||
document.getElementById('audiobook-preview')?.remove();
|
|
||||||
const ov = document.createElement('div');
|
|
||||||
ov.id = 'audiobook-preview';
|
|
||||||
ov.className = 'audiobook-overlay';
|
|
||||||
// datalist = Narrator + LLM roster + any speakers present in the segments (incl. Unknown)
|
|
||||||
const speakerSet = [...new Set(['Narrator', ...roster, ...segs.filter(s => s.type === 'dialogue' && s.speaker).map(s => s.speaker)])];
|
|
||||||
const charCount = speakerSet.filter(n => !/^Unknown|Unbekannt/i.test(n)).length;
|
|
||||||
const opts = speakerSet.map(n => `<option value="${escHtml(n)}">`).join('');
|
|
||||||
ov.innerHTML = `<div class="audiobook-box audiobook-preview-box">
|
|
||||||
<div class="audiobook-title"><span class="mdi mdi-drama-masks"></span> Review cast & lines
|
|
||||||
<span class="audiobook-count">${segs.length} segments · ${charCount} character${charCount !== 1 ? 's' : ''}</span></div>
|
|
||||||
<div class="audiobook-msg">Fix any wrong speaker or emotion, then open in the Script Rehearser to assign voices.${_audiobook.narratedPassages ? ` <span class="audiobook-narr-note">${_audiobook.narratedPassages} passage${_audiobook.narratedPassages !== 1 ? 's' : ''} had no dialogue (narrator).</span>` : ''}${_audiobook.degraded ? ` <span class="audiobook-narr-note">${_audiobook.degraded} passage${_audiobook.degraded !== 1 ? 's' : ''} used quick detection — set the “Unknown” speakers.</span>` : ''}${_audiobook.recoveredGaps ? ` <span class="audiobook-narr-note">${_audiobook.recoveredGaps} dropped sentence/paragraph gap${_audiobook.recoveredGaps !== 1 ? 's' : ''} recovered as Narrator — review placement.</span>` : ''}</div>
|
|
||||||
<datalist id="audiobook-roster">${opts}</datalist>
|
|
||||||
<div class="audiobook-seglist" id="audiobook-seglist"></div>
|
|
||||||
<div class="audiobook-actions">
|
|
||||||
<button class="btn-secondary btn-sm" id="audiobook-preview-cancel">Cancel</button>
|
|
||||||
<button class="btn-primary btn-sm" id="audiobook-preview-open"><span class="mdi mdi-account-music-outline"></span> Open in Rehearser</button>
|
|
||||||
</div>
|
|
||||||
</div>`;
|
|
||||||
document.body.appendChild(ov);
|
|
||||||
ov.querySelector('#audiobook-seglist').innerHTML = segs.map((s, i) => `<div class="audiobook-seg${s.type === 'dialogue' ? ' is-dialog' : ''}">
|
|
||||||
<input class="audiobook-seg-sp" data-i="${i}" list="audiobook-roster" value="${escHtml(s.speaker || 'Narrator')}" aria-label="Speaker">
|
|
||||||
<input class="audiobook-seg-emo" data-i="${i}" value="${escHtml(s.emotion || '')}" placeholder="emotion" aria-label="Emotion"${s.type === 'dialogue' ? '' : ' disabled'}>
|
|
||||||
<div class="audiobook-seg-text">${_abMarkQuotes(highlightText(_abDisplayText(s)))}</div>
|
|
||||||
</div>`).join('');
|
|
||||||
ov.querySelector('#audiobook-preview-cancel').addEventListener('click', () => ov.remove());
|
|
||||||
ov.querySelector('#audiobook-preview-open').addEventListener('click', () => { audiobookApplyPreviewAndOpen(); ov.remove(); });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function audiobookApplyPreviewAndOpen() {
|
function audiobookApplyPreviewAndOpen() {
|
||||||
|
|||||||
@ -40,16 +40,16 @@ const I18N_DICT = {
|
|||||||
'All categories': 'Alle Kategorien',
|
'All categories': 'Alle Kategorien',
|
||||||
'Category': 'Kategorie',
|
'Category': 'Kategorie',
|
||||||
'Load more': 'Mehr laden',
|
'Load more': 'Mehr laden',
|
||||||
'Age': 'Alter',
|
|
||||||
'Language': 'Sprache',
|
|
||||||
'male': 'männlich',
|
'male': 'männlich',
|
||||||
'female': 'weiblich',
|
'female': 'weiblich',
|
||||||
'neutral': 'neutral',
|
'neutral': 'neutral',
|
||||||
'child': 'Kind',
|
|
||||||
'teenager': 'Jugendlich',
|
|
||||||
'young adult': 'jung',
|
|
||||||
'middle-aged': 'mittleren Alters',
|
|
||||||
'elderly': 'älter',
|
|
||||||
'high': 'hoch',
|
'high': 'hoch',
|
||||||
'mid': 'mittel',
|
'mid': 'mittel',
|
||||||
'low': 'tief',
|
'low': 'tief',
|
||||||
@ -62,13 +62,13 @@ const I18N_DICT = {
|
|||||||
'southern': 'südlich',
|
'southern': 'südlich',
|
||||||
'German': 'Deutsch',
|
'German': 'Deutsch',
|
||||||
'English': 'Englisch',
|
'English': 'Englisch',
|
||||||
'warm': 'warm',
|
|
||||||
'husky': 'rau',
|
|
||||||
'crisp': 'klar',
|
|
||||||
'smooth': 'weich',
|
|
||||||
'rich': 'voll',
|
|
||||||
'magnetic': 'magnetisch',
|
|
||||||
'authoritative': 'autoritär',
|
|
||||||
// Seed Finder auto-pick
|
// Seed Finder auto-pick
|
||||||
'Generates up to 8 candidates, scores them and highlights the best. Nothing is saved until you click Use seed.': 'Erzeugt bis zu 8 Kandidaten, bewertet sie und hebt den besten hervor. Gespeichert wird erst nach Klick auf „Seed verwenden“.',
|
'Generates up to 8 candidates, scores them and highlights the best. Nothing is saved until you click Use seed.': 'Erzeugt bis zu 8 Kandidaten, bewertet sie und hebt den besten hervor. Gespeichert wird erst nach Klick auf „Seed verwenden“.',
|
||||||
'Auto-pick best': 'Besten automatisch wählen',
|
'Auto-pick best': 'Besten automatisch wählen',
|
||||||
|
|||||||
@ -1259,7 +1259,7 @@ function _lcdField(label, value, multiline) {
|
|||||||
if (!v) return '';
|
if (!v) return '';
|
||||||
return '<div class="lcd-field">'
|
return '<div class="lcd-field">'
|
||||||
+ '<div class="lcd-field-label">' + label + '</div>'
|
+ '<div class="lcd-field-label">' + label + '</div>'
|
||||||
+ '<div class="lcd-field-value">' + (multiline ? escHtml(v) : escHtml(v)) + '</div>'
|
+ '<div class="lcd-field-value">' + escHtml(v) + '</div>'
|
||||||
+ '</div>';
|
+ '</div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4852,7 +4852,7 @@ function renderArticle() {
|
|||||||
: '';
|
: '';
|
||||||
return '<span class="mag-w" data-w="' + j + '">' + esc(w) + '</span>' + marker;
|
return '<span class="mag-w" data-w="' + j + '">' + esc(w) + '</span>' + marker;
|
||||||
}).join(' ');
|
}).join(' ');
|
||||||
return '<' + tag + ' class="mag-s' + (open === 'span' ? '' : '') + '" data-i="' + i + '" ' +
|
return '<' + tag + ' class="mag-s" data-i="' + i + '" ' +
|
||||||
'onclick="magSeek(' + i + ')">' + wordsHtml + '</' + open + '> ' + (asidesAfter.get(i) || '');
|
'onclick="magSeek(' + i + ')">' + wordsHtml + '</' + open + '> ' + (asidesAfter.get(i) || '');
|
||||||
}).join('');
|
}).join('');
|
||||||
highlight(state.idx);
|
highlight(state.idx);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user