Compare commits

...

2 Commits

Author SHA1 Message Date
xRangerDE
65c9d06dfa release: 4.6.148 localize 2 hardcoded English img alt texts
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 11:33:45 +02:00
xRangerDE
e8404b8802 i18n: localize 2 hardcoded English alt texts on dynamic <img> elements
Two <img> elements rendered by renderer code had hardcoded English alt text that never localized:

- renderer.ts cutter preview frame: alt="Preview"
- renderer-profile.ts live-thumb: alt="Live preview"

Added two new locale keys (DE+EN):
- cutter.previewAlt — "Vorschau" / "Preview"
- profile.liveThumbAlt — "Live-Vorschau" / "Live preview"

renderer.ts updates: the three preview.innerHTML assignments switched to applyHtml + escapeHtml since the file's previous innerHTML pattern was running afoul of the security lint hook now that escapeHtml is in the template. Same shape as the other consolidated renderers (stats, archive, profile).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 11:33:25 +02:00
6 changed files with 11 additions and 7 deletions

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "twitch-vod-manager", "name": "twitch-vod-manager",
"version": "4.6.147", "version": "4.6.148",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "twitch-vod-manager", "name": "twitch-vod-manager",
"version": "4.6.147", "version": "4.6.148",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"axios": "^1.6.0", "axios": "^1.6.0",

View File

@ -1,6 +1,6 @@
{ {
"name": "twitch-vod-manager", "name": "twitch-vod-manager",
"version": "4.6.147", "version": "4.6.148",
"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",

View File

@ -332,6 +332,7 @@ const UI_TEXT_DE = {
openTwitch: 'Auf Twitch oeffnen', openTwitch: 'Auf Twitch oeffnen',
openTwitchTooltip: 'Diesen Kanal auf twitch.tv oeffnen', openTwitchTooltip: 'Diesen Kanal auf twitch.tv oeffnen',
liveCardTooltip: 'Klick um sofort eine Live-Aufnahme zu starten', liveCardTooltip: 'Klick um sofort eine Live-Aufnahme zu starten',
liveThumbAlt: 'Live-Vorschau',
recordNow: 'Jetzt aufnehmen', recordNow: 'Jetzt aufnehmen',
refresh: 'Aktualisieren', refresh: 'Aktualisieren',
agoMinutes: 'vor {n} Min', agoMinutes: 'vor {n} Min',
@ -443,6 +444,7 @@ const UI_TEXT_DE = {
videoInfoFailed: 'Konnte Video-Informationen nicht lesen. FFprobe installiert?', videoInfoFailed: 'Konnte Video-Informationen nicht lesen. FFprobe installiert?',
previewLoading: 'Lade Vorschau...', previewLoading: 'Lade Vorschau...',
previewUnavailable: 'Vorschau nicht verfugbar', previewUnavailable: 'Vorschau nicht verfugbar',
previewAlt: 'Vorschau',
cutting: 'Schneidet...', cutting: 'Schneidet...',
cut: 'Schneiden', cut: 'Schneiden',
cutSuccess: 'Video erfolgreich geschnitten!', cutSuccess: 'Video erfolgreich geschnitten!',

View File

@ -332,6 +332,7 @@ const UI_TEXT_EN = {
openTwitch: 'Open on Twitch', openTwitch: 'Open on Twitch',
openTwitchTooltip: 'Open this channel on twitch.tv', openTwitchTooltip: 'Open this channel on twitch.tv',
liveCardTooltip: 'Click to start a live recording right now', liveCardTooltip: 'Click to start a live recording right now',
liveThumbAlt: 'Live preview',
recordNow: 'Record now', recordNow: 'Record now',
refresh: 'Refresh', refresh: 'Refresh',
agoMinutes: '{n} min ago', agoMinutes: '{n} min ago',
@ -443,6 +444,7 @@ const UI_TEXT_EN = {
videoInfoFailed: 'Could not read video info. Is FFprobe installed?', videoInfoFailed: 'Could not read video info. Is FFprobe installed?',
previewLoading: 'Loading preview...', previewLoading: 'Loading preview...',
previewUnavailable: 'Preview unavailable', previewUnavailable: 'Preview unavailable',
previewAlt: 'Preview',
cutting: 'Cutting...', cutting: 'Cutting...',
cut: 'Cut', cut: 'Cut',
cutSuccess: 'Video cut successfully!', cutSuccess: 'Video cut successfully!',

View File

@ -106,7 +106,7 @@ function renderStreamerProfileCard(p: StreamerProfile): void {
? ` ? `
<div class="streamer-profile-live-card" role="button" tabindex="0" aria-label="${escapeHtml(UI_TEXT.profile.liveCardTooltip)}" onclick="triggerLiveRecordingFromProfile('${safeLogin}')" onkeydown="if((event.key==='Enter'||event.key===' ')&&event.target===event.currentTarget){event.preventDefault();triggerLiveRecordingFromProfile('${safeLogin}');}" title="${escapeHtml(UI_TEXT.profile.liveCardTooltip)}"> <div class="streamer-profile-live-card" role="button" tabindex="0" aria-label="${escapeHtml(UI_TEXT.profile.liveCardTooltip)}" onclick="triggerLiveRecordingFromProfile('${safeLogin}')" onkeydown="if((event.key==='Enter'||event.key===' ')&&event.target===event.currentTarget){event.preventDefault();triggerLiveRecordingFromProfile('${safeLogin}');}" title="${escapeHtml(UI_TEXT.profile.liveCardTooltip)}">
${p.currentStreamPreviewUrl ${p.currentStreamPreviewUrl
? `<img class="streamer-profile-live-thumb" src="${escapeHtml(p.currentStreamPreviewUrl)}" alt="Live preview" onerror="onProfileLivePreviewError(this)">` ? `<img class="streamer-profile-live-thumb" src="${escapeHtml(p.currentStreamPreviewUrl)}" alt="${escapeHtml(UI_TEXT.profile.liveThumbAlt)}" onerror="onProfileLivePreviewError(this)">`
: `<div class="streamer-profile-live-thumb-fallback"></div>`} : `<div class="streamer-profile-live-thumb-fallback"></div>`}
<div class="streamer-profile-live-body"> <div class="streamer-profile-live-body">
<div class="streamer-profile-live-badge-row"> <div class="streamer-profile-live-badge-row">

View File

@ -1557,15 +1557,15 @@ async function updatePreview(time: number): Promise<void> {
} }
const preview = byId('cutterPreview'); const preview = byId('cutterPreview');
preview.innerHTML = `<div class="placeholder"><p>${UI_TEXT.cutter.previewLoading}</p></div>`; applyHtml(preview, `<div class="placeholder"><p>${escapeHtml(UI_TEXT.cutter.previewLoading)}</p></div>`);
const frame = await window.api.extractFrame(cutterFile, time); const frame = await window.api.extractFrame(cutterFile, time);
if (frame) { if (frame) {
preview.innerHTML = `<img src="${frame}" alt="Preview">`; applyHtml(preview, `<img src="${escapeHtml(frame)}" alt="${escapeHtml(UI_TEXT.cutter.previewAlt)}">`);
return; return;
} }
preview.innerHTML = `<div class="placeholder"><p>${UI_TEXT.cutter.previewUnavailable}</p></div>`; applyHtml(preview, `<div class="placeholder"><p>${escapeHtml(UI_TEXT.cutter.previewUnavailable)}</p></div>`);
} }
async function startCutting(): Promise<void> { async function startCutting(): Promise<void> {