Add multilingual preview text picker with resizable textarea
- Preview input changed to a resizable textarea (resize: vertical) - Flag button before textarea opens searchable language picker (23 languages) - Selecting a language fills textarea with a pre-translated benchmark sentence - VL_SAMPLE_TEXTS map covers EN/DE/FR/ES/PT/IT/NL/PL/SV/DA/NB/FI/HU/CS/RO/UK/RU/TR/AR/HI/ZH/JA/KO - setPreviewLang / openPreviewLangPicker / syncPreviewLangBtn wired up globally Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
ecef913837
commit
79aaed4b7d
@ -562,6 +562,66 @@ const ALL_FLAGS = uniqueFlagOptions([ENGLISH_FLAGS, EUROPE_FLAGS, ASIA_FLAGS, LA
|
||||
['EG','EGY - Egyptian'], ['MA','MAR - Moroccan'], ['ZA','ZAF - South African'], ['NG','NGA - Nigerian'], ['KE','KEN - Kenyan'], ['GH','GHA - Ghanaian'],
|
||||
]]);
|
||||
|
||||
// ── Preview sample texts per language ────────────────────────────────────
|
||||
const VL_SAMPLE_TEXTS = {
|
||||
EN: 'Hello, how are you today? Please read this sample clearly for a fair voice benchmark.',
|
||||
DE: 'Hallo, wie geht es Ihnen heute? Bitte lesen Sie diesen Text deutlich vor — für einen fairen Stimmvergleich.',
|
||||
FR: 'Bonjour, comment allez-vous aujourd\'hui ? Veuillez lire ce texte clairement pour un test vocal équitable.',
|
||||
ES: '¡Hola! ¿Cómo estás hoy? Por favor, lee este texto con claridad para una evaluación justa de la voz.',
|
||||
PT: 'Olá, como vai você hoje? Por favor, leia este texto claramente para uma avaliação justa da voz.',
|
||||
IT: 'Ciao, come stai oggi? Per favore leggi questo testo in modo chiaro per una valutazione equa della voce.',
|
||||
NL: 'Hallo, hoe gaat het vandaag? Lees dit voorbeeld alstublieft duidelijk voor een eerlijke stembeoordeling.',
|
||||
PL: 'Cześć, jak się dziś masz? Przeczytaj proszę ten tekst wyraźnie, aby dokonać rzetelnej oceny głosu.',
|
||||
SV: 'Hej, hur mår du idag? Vänligen läs detta exempel tydligt för en rättvis röstbedömning.',
|
||||
DA: 'Hej, hvordan har du det i dag? Læs venligst dette eksempel tydeligt for en retfærdig stemmevurdering.',
|
||||
NB: 'Hei, hvordan har du det i dag? Vennligst les dette eksempelet tydelig for en rettferdig stemmevurdering.',
|
||||
FI: 'Hei, kuinka voit tänään? Lue tämä esimerkki selkeästi reilua ääniarviointia varten.',
|
||||
HU: 'Szia, hogy vagy ma? Kérlek, olvasd fel ezt a szöveget érthetően az igazságos hangértékelés érdekében.',
|
||||
CS: 'Dobrý den, jak se máte? Přečtěte prosím tento text zřetelně pro spravedlivé hodnocení hlasu.',
|
||||
RO: 'Bună ziua, cum vă simțiți astăzi? Vă rugăm citiți acest text clar pentru o evaluare corectă a vocii.',
|
||||
UK: 'Привіт, як ви сьогодні? Будь ласка, прочитайте цей текст чітко для справедливого тестування голосу.',
|
||||
RU: 'Здравствуйте, как вы сегодня? Пожалуйста, прочитайте этот текст чётко для справедливого тестирования голоса.',
|
||||
TR: 'Merhaba, bugün nasılsınız? Adil bir ses değerlendirmesi için lütfen bu örneği açıkça okuyun.',
|
||||
AR: 'مرحباً، كيف حالك اليوم؟ يرجى قراءة هذا النص بوضوح لإجراء اختبار صوت عادل.',
|
||||
HI: 'नमस्ते, आप आज कैसे हैं? कृपया इस नमूने को स्पष्ट रूप से पढ़ें ताकि एक उचित आवाज़ मूल्यांकन हो सके।',
|
||||
ZH: '你好,你今天怎么样?请清晰地朗读这段示例,以便进行公正的语音评测。',
|
||||
JA: 'こんにちは、今日はいかがですか?公平な音声評価のために、このサンプルをはっきりと読んでください。',
|
||||
KO: '안녕하세요, 오늘 어떠세요? 공정한 음성 벤치마크를 위해 이 샘플을 명확하게 읽어주세요.',
|
||||
};
|
||||
|
||||
const _VL_LANG_FLAG = Object.assign({ DA:'DK', NB:'NO', FI:'FI', HU:'HU', CS:'CZ', RO:'RO', UK:'UA' }, LANG_FLAG_DEFAULT);
|
||||
|
||||
function setPreviewLang(lang) {
|
||||
const ta = $('vl-preview-sample');
|
||||
const hidden = $('benchmark-sample-text');
|
||||
if (!ta) return;
|
||||
const text = VL_SAMPLE_TEXTS[lang] || VL_SAMPLE_TEXTS.EN;
|
||||
ta.value = text;
|
||||
ta.dispatchEvent(new Event('input'));
|
||||
if (hidden) { hidden.value = text; hidden.dispatchEvent(new Event('input')); }
|
||||
try { localStorage.setItem(BENCHMARK_SAMPLE_STORAGE_KEY, text); } catch {}
|
||||
const btn = $('vl-preview-lang-btn');
|
||||
if (btn) {
|
||||
const cc = (_VL_LANG_FLAG[lang] || 'gb').toLowerCase();
|
||||
btn.innerHTML = `<span class="fi fi-${cc}"></span>`;
|
||||
btn.title = (LANGUAGE_LABELS[lang] || lang) + ' — click to change';
|
||||
btn.dataset.lang = lang;
|
||||
}
|
||||
}
|
||||
|
||||
function openPreviewLangPicker(anchor) {
|
||||
const items = Object.keys(VL_SAMPLE_TEXTS).map(l => [l, LANGUAGE_LABELS[l] ? `${LANGUAGE_LABELS[l]} (${l})` : l]);
|
||||
createSearchablePicker(anchor, items, setPreviewLang, {
|
||||
placeholder: 'Sample language…',
|
||||
renderItem: (l) => {
|
||||
const cc = (_VL_LANG_FLAG[l] || 'gb').toLowerCase();
|
||||
return `<span class="fi fi-${cc}" style="width:20px;height:14px;background-size:cover;border-radius:2px;flex-shrink:0;display:inline-block"></span>`
|
||||
+ `<span class="ipi-code">${escHtml(l)}</span>`
|
||||
+ `<span>${escHtml(LANGUAGE_LABELS[l] || l)}</span>`;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// ── Searchable picker dropdown ────────────────────────────────────────────
|
||||
function createSearchablePicker(anchor, items, onSelect, { placeholder = 'Search…', renderItem } = {}) {
|
||||
document.querySelector('.insp-picker')?.remove();
|
||||
@ -5815,6 +5875,19 @@ initBenchmarkSampleControls();
|
||||
});
|
||||
})();
|
||||
|
||||
// Sync flag button to current preview text language (best-match)
|
||||
(function syncPreviewLangBtn() {
|
||||
const ta = $('vl-preview-sample');
|
||||
const btn = $('vl-preview-lang-btn');
|
||||
if (!ta || !btn) return;
|
||||
const cur = ta.value.trim();
|
||||
const matched = Object.keys(VL_SAMPLE_TEXTS).find(l => VL_SAMPLE_TEXTS[l] === cur) || 'EN';
|
||||
const cc = (_VL_LANG_FLAG[matched] || 'gb').toLowerCase();
|
||||
btn.innerHTML = `<span class="fi fi-${cc}"></span>`;
|
||||
btn.title = (LANGUAGE_LABELS[matched] || matched) + ' — click to change sample language';
|
||||
btn.dataset.lang = matched;
|
||||
})();
|
||||
|
||||
// Synth mode segmented control (preview text vs. reference transcript)
|
||||
(function initSynthMode() {
|
||||
const seg = $('vl-synth-mode-seg');
|
||||
|
||||
@ -17,14 +17,6 @@
|
||||
<!-- LEFT: compact voice list -->
|
||||
<div class="voices-list-pane">
|
||||
|
||||
<div class="vl-toolbar">
|
||||
<button class="btn-secondary vl-tb-btn" id="refresh-voices-btn" title="Refresh voice list">↻ Refresh</button>
|
||||
<button class="btn-secondary vl-tb-btn" id="sync-voice-folders-btn" title="Sync active/hidden folders">Sync</button>
|
||||
<button class="btn-secondary vl-tb-btn" id="calculate-db-btn" title="Calculate dBFS">Calc dB</button>
|
||||
<button class="btn-secondary vl-tb-btn" id="benchmark-voices-btn" title="Benchmark TTS speed">Benchmark</button>
|
||||
<button class="btn-secondary vl-tb-btn" id="copy-active-voices-btn" title="Copy active voice names">Copy active</button>
|
||||
</div>
|
||||
|
||||
<!-- TTS synth mode + preview sentence -->
|
||||
<div class="vl-synth-panel">
|
||||
<div class="vl-synth-mode-row">
|
||||
@ -37,10 +29,19 @@
|
||||
title="Synthesize this voice’s saved reference transcript — good for quality check vs. original recording">Reference transcript</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="vl-preview-text-wrap" id="vl-preview-text-wrap">
|
||||
<input type="text" class="vl-preview-input" id="vl-preview-sample"
|
||||
<div class="vl-preview-row">
|
||||
<button class="vl-preview-lang-btn" id="vl-preview-lang-btn"
|
||||
title="English — click to change sample language"
|
||||
onclick="openPreviewLangPicker(this)" data-lang="EN">
|
||||
<span class="fi fi-gb"></span>
|
||||
</button>
|
||||
<textarea class="vl-preview-input" id="vl-preview-sample" rows="2"
|
||||
placeholder="Sample sentence for TTS preview…"
|
||||
oninput="var t=document.getElementById('benchmark-sample-text');if(t){t.value=this.value;t.dispatchEvent(new Event('input'));}">
|
||||
oninput="var t=document.getElementById('benchmark-sample-text');if(t){t.value=this.value;t.dispatchEvent(new Event('input'));}"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -59,6 +60,13 @@
|
||||
</div>
|
||||
|
||||
<div id="voice-list"></div>
|
||||
<div class="vl-toolbar">
|
||||
<button class="btn-secondary vl-tb-btn" id="refresh-voices-btn" title="Refresh voice list">↻ Refresh</button>
|
||||
<button class="btn-secondary vl-tb-btn" id="sync-voice-folders-btn" title="Sync active/hidden folders">Sync</button>
|
||||
<button class="btn-secondary vl-tb-btn" id="calculate-db-btn" title="Calculate dBFS">Calc dB</button>
|
||||
<button class="btn-secondary vl-tb-btn" id="benchmark-voices-btn" title="Benchmark TTS speed">Benchmark</button>
|
||||
<button class="btn-secondary vl-tb-btn" id="copy-active-voices-btn" title="Copy active voice names">Copy active</button>
|
||||
</div>
|
||||
|
||||
<div class="vl-footer">
|
||||
<span id="voice-count" class="note"></span>
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
--border: #E5E7EB;
|
||||
--text: #111827;
|
||||
--subtext: #6B7280;
|
||||
--placehld: #6B7280;
|
||||
--accent: #2563EB;
|
||||
--green: #16A34A;
|
||||
--red: #DC2626;
|
||||
@ -135,7 +136,9 @@ body {
|
||||
}
|
||||
.section-search:focus, .section-search:focus-visible { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px rgba(37,99,235,.12); }
|
||||
.section-new-voice-btn { padding: 6px 14px; font-size: 13px; white-space: nowrap; }
|
||||
|
||||
.section-search::placeholder {
|
||||
color: var(--placehld);
|
||||
}
|
||||
/* ── Force all tab-content visible ──────────────────────────────────────── */
|
||||
.tab-content {
|
||||
display: flex !important; flex-direction: column; gap: 18px; padding: 0;
|
||||
@ -203,7 +206,7 @@ button:not(:disabled):hover { filter: brightness(0.95); }
|
||||
flex: 1; background: var(--panel); border: 1px solid var(--border);
|
||||
color: var(--text); border-radius: var(--radius); padding: 9px 14px; font-size: 14px;
|
||||
}
|
||||
input::placeholder, textarea::placeholder { color: var(--border); }
|
||||
input::placeholder, textarea::placeholder { color: var(--placehld); }
|
||||
#yt-progress { font-size: 13px; color: var(--subtext); min-height: 1.4em; font-family: monospace; }
|
||||
|
||||
/* ── Recording ──────────────────────────────────────────────────────────── */
|
||||
@ -500,12 +503,12 @@ code { background: var(--panel); border-radius: 4px; padding: 1px 5px; font-fami
|
||||
.vr-ref .vr-inline, .vr-note .vr-inline { display: flex; gap: 6px; align-items: center; min-width: 0; }
|
||||
.vr-ref input { width: 100%; min-width: 0; background: transparent; border: none; border-bottom: 1px solid transparent; color: var(--subtext); font-size: 13px; padding: 3px 5px; font-family: inherit; outline: none; }
|
||||
.vr-ref input:focus { border-bottom-color: var(--accent); color: var(--text); }
|
||||
.vr-ref input::placeholder { color: var(--border); font-style: italic; }
|
||||
.vr-ref input::placeholder { color: var(--placehld); font-style: italic; }
|
||||
.ref-transcribe-btn { width: 28px; height: 28px; padding: 0; border-radius: 6px; background: none; border: 1px solid transparent; color: var(--subtext); flex-shrink: 0; font-size: 14px; }
|
||||
.ref-transcribe-btn:hover { background: var(--border); color: var(--text); filter: none; }
|
||||
.vr-note input { width: 100%; background: transparent; border: none; border-bottom: 1px solid var(--border); color: var(--text); font-size: 13px; padding: 3px 5px; font-family: inherit; outline: none; }
|
||||
.vr-note input:focus { border-bottom-color: var(--accent); }
|
||||
.vr-note input::placeholder { color: var(--border); }
|
||||
.vr-note input::placeholder { color: var(--placehld); }
|
||||
.vr-row-subtle { color: var(--subtext); font-size: 14px; font-family: monospace; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.vr-optimizer { grid-column: auto; display: none; margin: 6px 0 0; width: 100%; box-sizing: border-box; }
|
||||
.vl-row.edit-open .vr-optimizer { display: block; }
|
||||
@ -783,7 +786,7 @@ code { background: var(--panel); border-radius: 4px; padding: 1px 5px; font-fami
|
||||
display: flex; flex-wrap: wrap; gap: 4px;
|
||||
padding: 8px 10px 7px; border-bottom: 1px solid var(--border);
|
||||
}
|
||||
.vl-tb-btn { padding: 3px 8px !important; font-size: 14px !important; flex: 0 0 auto; }
|
||||
.vl-tb-btn { padding: 3px 8px !important; font-size: 12px !important; flex: 0 0 auto; }
|
||||
.vl-filters {
|
||||
display: flex; align-items: center; gap: 5px; flex-wrap: wrap;
|
||||
padding: 7px 10px; border-bottom: 1px solid var(--border);
|
||||
@ -793,9 +796,12 @@ code { background: var(--panel); border-radius: 4px; padding: 1px 5px; font-fami
|
||||
border: 1px solid var(--border); border-radius: var(--radius);
|
||||
background: var(--bg); color: var(--text);
|
||||
}
|
||||
.vl-search input::placeholder {
|
||||
color: var(--placehld);
|
||||
}
|
||||
.vl-search:focus { outline: none; border-color: var(--accent); }
|
||||
.vl-filters select {
|
||||
padding: 4px 5px; font-size: 14.5px; border: 1px solid var(--border);
|
||||
padding: 4px 5px; font-size: 12px; border: 1px solid var(--border);
|
||||
border-radius: var(--radius); background: var(--bg); color: var(--text); max-width: 80px;
|
||||
}
|
||||
.vl-disabled-label { font-size: 14.5px; color: var(--subtext); white-space: nowrap; cursor: pointer; gap: 3px; display: flex; align-items: center; }
|
||||
@ -872,11 +878,11 @@ code { background: var(--panel); border-radius: 4px; padding: 1px 5px; font-fami
|
||||
.vl-synth-panel { background: var(--surface); border-bottom: 1px solid var(--border); }
|
||||
.vl-synth-mode-row { display: flex; align-items: center; gap: 7px; padding: 5px 8px 5px; }
|
||||
.vl-synth-icon { font-size: 9px; color: var(--teal); flex-shrink: 0; }
|
||||
.vl-synth-label { font-size: 10px; font-weight: 700; color: var(--subtext); white-space: nowrap; flex-shrink: 0; }
|
||||
.vl-synth-label { font-size: 14px; font-weight: 700; color: var(--subtext); white-space: nowrap; flex-shrink: 0; }
|
||||
.vl-synth-seg { display: flex; border: 1px solid var(--border); border-radius: 6px; overflow: hidden; flex-shrink: 0; }
|
||||
.vl-synth-seg-btn {
|
||||
background: var(--panel); border: none; padding: 3px 9px;
|
||||
font-size: 10.5px; font-weight: 600; color: var(--subtext);
|
||||
font-size: 14.5px; font-weight: 600; color: var(--subtext);
|
||||
cursor: pointer; transition: background .12s, color .12s; white-space: nowrap;
|
||||
border-right: 1px solid var(--border);
|
||||
}
|
||||
@ -884,7 +890,23 @@ code { background: var(--panel); border-radius: 4px; padding: 1px 5px; font-fami
|
||||
.vl-synth-seg-btn.active { background: var(--teal); color: #fff; }
|
||||
.vl-synth-seg-btn:not(.active):hover { background: var(--border); color: var(--text); }
|
||||
.vl-preview-text-wrap { padding: 0 8px 6px; }
|
||||
.vl-preview-input { width: 100%; font-size: 14px; background: var(--bg); border: 1px solid var(--border); border-radius: 5px; color: var(--text); padding: 3px 7px; font-family: var(--font); }
|
||||
.vl-preview-row { display: flex; align-items: flex-start; gap: 6px; }
|
||||
.vl-preview-lang-btn {
|
||||
flex-shrink: 0; width: 34px; height: 34px; margin-top: 1px;
|
||||
border: 1px solid var(--border); border-radius: 6px;
|
||||
background: var(--surface); padding: 0; cursor: pointer;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
transition: border-color .15s, box-shadow .15s;
|
||||
}
|
||||
.vl-preview-lang-btn:hover { border-color: var(--accent); box-shadow: 0 0 0 2px rgba(37,99,235,.12); filter: none; }
|
||||
.vl-preview-lang-btn .fi { width: 22px; height: 16px; background-size: cover; background-position: center; border-radius: 2px; display: block; }
|
||||
.vl-preview-input {
|
||||
flex: 1; min-width: 0; display: block; box-sizing: border-box;
|
||||
font-size: 13px; line-height: 1.5;
|
||||
background: var(--bg); border: 1px solid var(--border); border-radius: 5px;
|
||||
color: var(--text); padding: 5px 7px; font-family: var(--font);
|
||||
resize: vertical; min-height: 42px;
|
||||
}
|
||||
.vl-preview-input:focus { outline: none; border-color: var(--accent); }
|
||||
.vl-preview-text-wrap.hidden { display: none; }
|
||||
|
||||
@ -897,7 +919,7 @@ code { background: var(--panel); border-radius: 4px; padding: 1px 5px; font-fami
|
||||
.inspector-placeholder {
|
||||
height: 100%; display: flex; flex-direction: column;
|
||||
align-items: center; justify-content: center; gap: 10px;
|
||||
color: var(--subtext); text-align: center; padding: 24px;
|
||||
color: var(--placehld); text-align: center; padding: 24px;
|
||||
}
|
||||
.inspector-placeholder span { font-size: 40px; opacity: .4; }
|
||||
.inspector-placeholder p { font-size: 13px; line-height: 1.6; opacity: .7; }
|
||||
@ -985,7 +1007,7 @@ code { background: var(--panel); border-radius: 4px; padding: 1px 5px; font-fami
|
||||
width: 100%; padding: 2px 0; font-size: 14px; font-style: italic; font-family: inherit;
|
||||
border: none; background: transparent; color: var(--text); outline: none; transition: color .15s;
|
||||
}
|
||||
.insp-note-slot .vr-note .vr-inline input::placeholder { color: var(--subtext); opacity: .6; }
|
||||
.insp-note-slot .vr-note .vr-inline input::placeholder { color: var(--placehld); opacity: .6; }
|
||||
.insp-note-slot .vr-note .vr-inline input:focus { font-style: normal; }
|
||||
.insp-note-slot .ref-transcribe-btn { display: none !important; }
|
||||
.insp-actions-delete { flex-shrink: 0; display: flex; align-items: center; padding-left: 20px; margin-right: 20px }
|
||||
@ -1051,7 +1073,7 @@ code { background: var(--panel); border-radius: 4px; padding: 1px 5px; font-fami
|
||||
padding: 0 6px; min-width: 50px;
|
||||
transition: background .15s, color .15s;
|
||||
}
|
||||
.insp-tag-input::placeholder { color: var(--subtext); opacity: .5; font-style: italic; }
|
||||
.insp-tag-input::placeholder { color: var(--placehld); opacity: .5; font-style: italic; }
|
||||
.insp-tag-input:hover { color: var(--text); }
|
||||
.insp-tag-input:focus { background: var(--panel); color: var(--text); }
|
||||
|
||||
@ -1087,7 +1109,7 @@ code { background: var(--panel); border-radius: 4px; padding: 1px 5px; font-fami
|
||||
font-size: 12px; font-family: inherit; background: var(--panel);
|
||||
color: var(--text); outline: none; flex-shrink: 0;
|
||||
}
|
||||
.insp-picker-search::placeholder { color: var(--subtext); opacity: .65; }
|
||||
.insp-picker-search::placeholder { color: var(--placehld); opacity: .65; }
|
||||
.insp-picker-list { flex: 1; overflow-y: auto; padding: 3px 0; }
|
||||
.insp-picker-item {
|
||||
display: flex; align-items: center; width: 100%; gap: 8px;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user