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",
|
"name": "twitch-vod-manager",
|
||||||
"version": "4.0.2",
|
"version": "4.0.3",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "twitch-vod-manager",
|
"name": "twitch-vod-manager",
|
||||||
"version": "4.0.2",
|
"version": "4.0.3",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.6.0",
|
"axios": "^1.6.0",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "twitch-vod-manager",
|
"name": "twitch-vod-manager",
|
||||||
"version": "4.0.2",
|
"version": "4.0.3",
|
||||||
"description": "Twitch VOD Manager - Download Twitch VODs easily",
|
"description": "Twitch VOD Manager - Download Twitch VODs easily",
|
||||||
"main": "dist/main.js",
|
"main": "dist/main.js",
|
||||||
"author": "xRangerDE",
|
"author": "xRangerDE",
|
||||||
|
|||||||
@ -201,7 +201,9 @@ async function run() {
|
|||||||
const deState = {
|
const deState = {
|
||||||
nav: (document.getElementById('navSettingsText')?.textContent || '').trim(),
|
nav: (document.getElementById('navSettingsText')?.textContent || '').trim(),
|
||||||
retry: (document.getElementById('btnRetryFailed')?.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';
|
lang.value = 'en';
|
||||||
@ -210,14 +212,18 @@ async function run() {
|
|||||||
const enState = {
|
const enState = {
|
||||||
nav: (document.getElementById('navSettingsText')?.textContent || '').trim(),
|
nav: (document.getElementById('navSettingsText')?.textContent || '').trim(),
|
||||||
retry: (document.getElementById('btnRetryFailed')?.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 };
|
checks.language = { deState, enState };
|
||||||
assert(deState.nav.includes('Einstellungen'), 'German language switch failed');
|
assert(deState.nav.includes('Einstellungen'), 'German language switch failed');
|
||||||
assert(enState.nav.includes('Settings'), 'English language switch failed');
|
assert(enState.nav.includes('Settings'), 'English language switch failed');
|
||||||
assert(deState.deFlag.includes('🇩🇪'), 'German flag missing');
|
assert(deState.deIcon, 'German flag icon missing');
|
||||||
assert(enState.enFlag.includes('🇺🇸'), 'English flag 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 });
|
await window.api.saveConfig({ client_id: '', client_secret: '', download_path: tmpDir });
|
||||||
window.showTab('vods');
|
window.showTab('vods');
|
||||||
|
|||||||
@ -300,9 +300,19 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label id="languageLabel">Sprache</label>
|
<label id="languageLabel">Sprache</label>
|
||||||
<select id="languageSelect" onchange="changeLanguage(this.value)">
|
<div class="language-picker" id="languagePicker">
|
||||||
<option value="de" id="languageDeText">🇩🇪 Deutsch</option>
|
<button type="button" class="lang-option" id="langOptionDe" onclick="selectLanguageOption('de')" aria-pressed="false">
|
||||||
<option value="en" id="languageEnText">🇺🇸 English</option>
|
<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>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -345,7 +355,7 @@
|
|||||||
|
|
||||||
<div class="settings-card">
|
<div class="settings-card">
|
||||||
<h3 id="updateTitle">Updates</h3>
|
<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>
|
<button class="btn-secondary" id="checkUpdateBtn" onclick="checkUpdate()">Nach Updates suchen</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -377,7 +387,7 @@
|
|||||||
<div class="status-dot" id="statusDot"></div>
|
<div class="status-dot" id="statusDot"></div>
|
||||||
<span id="statusText">Nicht verbunden</span>
|
<span id="statusText">Nicht verbunden</span>
|
||||||
</div>
|
</div>
|
||||||
<span id="versionText">v4.0.2</span>
|
<span id="versionText">v4.0.3</span>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import { autoUpdater } from 'electron-updater';
|
|||||||
// ==========================================
|
// ==========================================
|
||||||
// CONFIG & CONSTANTS
|
// CONFIG & CONSTANTS
|
||||||
// ==========================================
|
// ==========================================
|
||||||
const APP_VERSION = '4.0.2';
|
const APP_VERSION = '4.0.3';
|
||||||
const UPDATE_CHECK_URL = 'http://24-music.de/version.json';
|
const UPDATE_CHECK_URL = 'http://24-music.de/version.json';
|
||||||
|
|
||||||
// Paths
|
// Paths
|
||||||
|
|||||||
@ -27,8 +27,8 @@ const UI_TEXT_DE = {
|
|||||||
designTitle: 'Design',
|
designTitle: 'Design',
|
||||||
themeLabel: 'Theme',
|
themeLabel: 'Theme',
|
||||||
languageLabel: 'Sprache',
|
languageLabel: 'Sprache',
|
||||||
languageDe: '🇩🇪 Deutsch',
|
languageDe: 'Deutsch',
|
||||||
languageEn: '🇺🇸 Englisch',
|
languageEn: 'Englisch',
|
||||||
apiTitle: 'Twitch API',
|
apiTitle: 'Twitch API',
|
||||||
clientIdLabel: 'Client ID',
|
clientIdLabel: 'Client ID',
|
||||||
clientSecretLabel: 'Client Secret',
|
clientSecretLabel: 'Client Secret',
|
||||||
|
|||||||
@ -27,8 +27,8 @@ const UI_TEXT_EN = {
|
|||||||
designTitle: 'Design',
|
designTitle: 'Design',
|
||||||
themeLabel: 'Theme',
|
themeLabel: 'Theme',
|
||||||
languageLabel: 'Language',
|
languageLabel: 'Language',
|
||||||
languageDe: '🇩🇪 German',
|
languageDe: 'German',
|
||||||
languageEn: '🇺🇸 English',
|
languageEn: 'English',
|
||||||
apiTitle: 'Twitch API',
|
apiTitle: 'Twitch API',
|
||||||
clientIdLabel: 'Client ID',
|
clientIdLabel: 'Client ID',
|
||||||
clientSecretLabel: 'Client Secret',
|
clientSecretLabel: 'Client Secret',
|
||||||
|
|||||||
@ -22,6 +22,7 @@ function updateStatus(text: string, connected: boolean): void {
|
|||||||
function changeLanguage(lang: string): void {
|
function changeLanguage(lang: string): void {
|
||||||
const normalized = setLanguage(lang);
|
const normalized = setLanguage(lang);
|
||||||
byId<HTMLSelectElement>('languageSelect').value = normalized;
|
byId<HTMLSelectElement>('languageSelect').value = normalized;
|
||||||
|
updateLanguagePicker(normalized);
|
||||||
config.language = normalized;
|
config.language = normalized;
|
||||||
void window.api.saveConfig({ 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 {
|
function renderPreflightResult(result: PreflightResult): void {
|
||||||
const entries = [
|
const entries = [
|
||||||
[UI_TEXT.static.preflightInternet, result.checks.internet],
|
[UI_TEXT.static.preflightInternet, result.checks.internet],
|
||||||
|
|||||||
@ -15,6 +15,7 @@ async function init(): Promise<void> {
|
|||||||
byId<HTMLInputElement>('downloadPath').value = config.download_path ?? '';
|
byId<HTMLInputElement>('downloadPath').value = config.download_path ?? '';
|
||||||
byId<HTMLSelectElement>('themeSelect').value = config.theme ?? 'twitch';
|
byId<HTMLSelectElement>('themeSelect').value = config.theme ?? 'twitch';
|
||||||
byId<HTMLSelectElement>('languageSelect').value = config.language ?? 'en';
|
byId<HTMLSelectElement>('languageSelect').value = config.language ?? 'en';
|
||||||
|
updateLanguagePicker(config.language ?? 'en');
|
||||||
byId<HTMLSelectElement>('downloadMode').value = config.download_mode ?? 'full';
|
byId<HTMLSelectElement>('downloadMode').value = config.download_mode ?? 'full';
|
||||||
byId<HTMLInputElement>('partMinutes').value = String(config.part_minutes ?? 120);
|
byId<HTMLInputElement>('partMinutes').value = String(config.part_minutes ?? 120);
|
||||||
|
|
||||||
|
|||||||
@ -606,6 +606,62 @@ body {
|
|||||||
border-color: var(--accent);
|
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 {
|
.form-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user