Use real flag icons in language picker and polish locale UX (v4.0.3)
Replace unreliable emoji flags in the native select with a custom language picker that renders real CSS flag icons, improve retry button naming/tooltips, and keep quick/full e2e coverage green after the UI change.
This commit is contained in:
parent
e261ca5281
commit
a29c252606
4
typescript-version/package-lock.json
generated
4
typescript-version/package-lock.json
generated
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "twitch-vod-manager",
|
||||
"version": "4.0.2",
|
||||
"version": "4.0.3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "twitch-vod-manager",
|
||||
"version": "4.0.2",
|
||||
"version": "4.0.3",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"axios": "^1.6.0",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "twitch-vod-manager",
|
||||
"version": "4.0.2",
|
||||
"version": "4.0.3",
|
||||
"description": "Twitch VOD Manager - Download Twitch VODs easily",
|
||||
"main": "dist/main.js",
|
||||
"author": "xRangerDE",
|
||||
|
||||
@ -201,7 +201,9 @@ async function run() {
|
||||
const deState = {
|
||||
nav: (document.getElementById('navSettingsText')?.textContent || '').trim(),
|
||||
retry: (document.getElementById('btnRetryFailed')?.textContent || '').trim(),
|
||||
deFlag: (document.getElementById('languageDeText')?.textContent || '').trim()
|
||||
deText: (document.getElementById('languageDeText')?.textContent || '').trim(),
|
||||
deIcon: !!document.querySelector('#langOptionDe .flag-icon.flag-de'),
|
||||
deActive: !!document.getElementById('langOptionDe')?.classList.contains('active')
|
||||
};
|
||||
|
||||
lang.value = 'en';
|
||||
@ -210,14 +212,18 @@ async function run() {
|
||||
const enState = {
|
||||
nav: (document.getElementById('navSettingsText')?.textContent || '').trim(),
|
||||
retry: (document.getElementById('btnRetryFailed')?.textContent || '').trim(),
|
||||
enFlag: (document.getElementById('languageEnText')?.textContent || '').trim()
|
||||
enText: (document.getElementById('languageEnText')?.textContent || '').trim(),
|
||||
enIcon: !!document.querySelector('#langOptionEn .flag-icon.flag-en'),
|
||||
enActive: !!document.getElementById('langOptionEn')?.classList.contains('active')
|
||||
};
|
||||
|
||||
checks.language = { deState, enState };
|
||||
assert(deState.nav.includes('Einstellungen'), 'German language switch failed');
|
||||
assert(enState.nav.includes('Settings'), 'English language switch failed');
|
||||
assert(deState.deFlag.includes('🇩🇪'), 'German flag missing');
|
||||
assert(enState.enFlag.includes('🇺🇸'), 'English flag missing');
|
||||
assert(deState.deIcon, 'German flag icon missing');
|
||||
assert(enState.enIcon, 'English flag icon missing');
|
||||
assert(deState.deActive, 'German language button did not activate');
|
||||
assert(enState.enActive, 'English language button did not activate');
|
||||
|
||||
await window.api.saveConfig({ client_id: '', client_secret: '', download_path: tmpDir });
|
||||
window.showTab('vods');
|
||||
|
||||
@ -300,9 +300,19 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label id="languageLabel">Sprache</label>
|
||||
<select id="languageSelect" onchange="changeLanguage(this.value)">
|
||||
<option value="de" id="languageDeText">🇩🇪 Deutsch</option>
|
||||
<option value="en" id="languageEnText">🇺🇸 English</option>
|
||||
<div class="language-picker" id="languagePicker">
|
||||
<button type="button" class="lang-option" id="langOptionDe" onclick="selectLanguageOption('de')" aria-pressed="false">
|
||||
<span class="flag-icon flag-de" aria-hidden="true"></span>
|
||||
<span id="languageDeText">Deutsch</span>
|
||||
</button>
|
||||
<button type="button" class="lang-option" id="langOptionEn" onclick="selectLanguageOption('en')" aria-pressed="false">
|
||||
<span class="flag-icon flag-en" aria-hidden="true"></span>
|
||||
<span id="languageEnText">English</span>
|
||||
</button>
|
||||
</div>
|
||||
<select id="languageSelect" onchange="changeLanguage(this.value)" style="display:none">
|
||||
<option value="de">de</option>
|
||||
<option value="en">en</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@ -345,7 +355,7 @@
|
||||
|
||||
<div class="settings-card">
|
||||
<h3 id="updateTitle">Updates</h3>
|
||||
<p id="versionInfo" style="margin-bottom: 10px; color: var(--text-secondary);">Version: v4.0.2</p>
|
||||
<p id="versionInfo" style="margin-bottom: 10px; color: var(--text-secondary);">Version: v4.0.3</p>
|
||||
<button class="btn-secondary" id="checkUpdateBtn" onclick="checkUpdate()">Nach Updates suchen</button>
|
||||
</div>
|
||||
|
||||
@ -377,7 +387,7 @@
|
||||
<div class="status-dot" id="statusDot"></div>
|
||||
<span id="statusText">Nicht verbunden</span>
|
||||
</div>
|
||||
<span id="versionText">v4.0.2</span>
|
||||
<span id="versionText">v4.0.3</span>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@ -8,7 +8,7 @@ import { autoUpdater } from 'electron-updater';
|
||||
// ==========================================
|
||||
// CONFIG & CONSTANTS
|
||||
// ==========================================
|
||||
const APP_VERSION = '4.0.2';
|
||||
const APP_VERSION = '4.0.3';
|
||||
const UPDATE_CHECK_URL = 'http://24-music.de/version.json';
|
||||
|
||||
// Paths
|
||||
|
||||
@ -27,8 +27,8 @@ const UI_TEXT_DE = {
|
||||
designTitle: 'Design',
|
||||
themeLabel: 'Theme',
|
||||
languageLabel: 'Sprache',
|
||||
languageDe: '🇩🇪 Deutsch',
|
||||
languageEn: '🇺🇸 Englisch',
|
||||
languageDe: 'Deutsch',
|
||||
languageEn: 'Englisch',
|
||||
apiTitle: 'Twitch API',
|
||||
clientIdLabel: 'Client ID',
|
||||
clientSecretLabel: 'Client Secret',
|
||||
|
||||
@ -27,8 +27,8 @@ const UI_TEXT_EN = {
|
||||
designTitle: 'Design',
|
||||
themeLabel: 'Theme',
|
||||
languageLabel: 'Language',
|
||||
languageDe: '🇩🇪 German',
|
||||
languageEn: '🇺🇸 English',
|
||||
languageDe: 'German',
|
||||
languageEn: 'English',
|
||||
apiTitle: 'Twitch API',
|
||||
clientIdLabel: 'Client ID',
|
||||
clientSecretLabel: 'Client Secret',
|
||||
|
||||
@ -22,6 +22,7 @@ function updateStatus(text: string, connected: boolean): void {
|
||||
function changeLanguage(lang: string): void {
|
||||
const normalized = setLanguage(lang);
|
||||
byId<HTMLSelectElement>('languageSelect').value = normalized;
|
||||
updateLanguagePicker(normalized);
|
||||
config.language = normalized;
|
||||
void window.api.saveConfig({ language: normalized });
|
||||
|
||||
@ -40,6 +41,21 @@ function changeLanguage(lang: string): void {
|
||||
}
|
||||
}
|
||||
|
||||
function updateLanguagePicker(lang: string): void {
|
||||
const de = byId<HTMLButtonElement>('langOptionDe');
|
||||
const en = byId<HTMLButtonElement>('langOptionEn');
|
||||
|
||||
const isDe = lang === 'de';
|
||||
de.classList.toggle('active', isDe);
|
||||
en.classList.toggle('active', !isDe);
|
||||
de.setAttribute('aria-pressed', String(isDe));
|
||||
en.setAttribute('aria-pressed', String(!isDe));
|
||||
}
|
||||
|
||||
function selectLanguageOption(lang: string): void {
|
||||
changeLanguage(lang);
|
||||
}
|
||||
|
||||
function renderPreflightResult(result: PreflightResult): void {
|
||||
const entries = [
|
||||
[UI_TEXT.static.preflightInternet, result.checks.internet],
|
||||
|
||||
@ -15,6 +15,7 @@ async function init(): Promise<void> {
|
||||
byId<HTMLInputElement>('downloadPath').value = config.download_path ?? '';
|
||||
byId<HTMLSelectElement>('themeSelect').value = config.theme ?? 'twitch';
|
||||
byId<HTMLSelectElement>('languageSelect').value = config.language ?? 'en';
|
||||
updateLanguagePicker(config.language ?? 'en');
|
||||
byId<HTMLSelectElement>('downloadMode').value = config.download_mode ?? 'full';
|
||||
byId<HTMLInputElement>('partMinutes').value = String(config.part_minutes ?? 120);
|
||||
|
||||
|
||||
@ -606,6 +606,62 @@ body {
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.language-picker {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.lang-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
border: 1px solid rgba(255,255,255,0.14);
|
||||
border-radius: 6px;
|
||||
background: var(--bg-main);
|
||||
color: var(--text);
|
||||
padding: 9px 10px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.lang-option:hover {
|
||||
border-color: rgba(255,255,255,0.26);
|
||||
}
|
||||
|
||||
.lang-option.active {
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 0 1px rgba(145, 70, 255, 0.2);
|
||||
}
|
||||
|
||||
.flag-icon {
|
||||
width: 16px;
|
||||
height: 12px;
|
||||
border-radius: 2px;
|
||||
border: 1px solid rgba(0,0,0,0.35);
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.flag-de {
|
||||
background: linear-gradient(to bottom, #111 0 33.33%, #dd0000 33.33% 66.66%, #ffce00 66.66% 100%);
|
||||
}
|
||||
|
||||
.flag-en {
|
||||
background: repeating-linear-gradient(to bottom, #b22234 0 7.7%, #ffffff 7.7% 15.4%);
|
||||
}
|
||||
|
||||
.flag-en::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 45%;
|
||||
height: 56%;
|
||||
background: #3c3b6e;
|
||||
}
|
||||
|
||||
.form-row {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user