a11y+i18n: localize modal close aria-labels + strip dead modal title fallbacks
Two related artifacts from the 4.6.31 a11y pass.
aria-label="Close" was hardcoded English on all five modal-close X
buttons — anyone running the German locale would still hear "Close
button" from their screen reader. Added a shared
.modal-close-localizable class on each X, plus a streamers.modalCloseAria
locale string ("Close dialog" / "Dialog schliessen"), plus a small
setAriaLabelAll helper in renderer-texts that resolves the class via
querySelectorAll and applies the localized label in one shot. Now all
five modals announce in the active language.
While editing the modal headers, also removed the dead "Stream events"
and "Chat replay" English fallback text from eventsViewerTitle and
chatViewerTitle. Both h2s get their textContent overwritten the
instant openEventsViewer / openChatViewer is called (with the
streamers name or a UI_TEXT fallback), so the inline English text was
never user-visible past first-paint and only mattered to a screen
reader if a user managed to focus an unopened modal. Empty <h2/> is
cheaper and removes the i18n drift.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
4956a68d9b
commit
b37244cccf
@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
<div class="modal-overlay" id="updateModal" role="dialog" aria-modal="true" aria-labelledby="updateModalTitle" onclick="handleUpdateModalOverlayClick(event)">
|
<div class="modal-overlay" id="updateModal" role="dialog" aria-modal="true" aria-labelledby="updateModalTitle" onclick="handleUpdateModalOverlayClick(event)">
|
||||||
<div class="modal update-modal">
|
<div class="modal update-modal">
|
||||||
<button class="modal-close" aria-label="Close" onclick="dismissUpdateModal()">x</button>
|
<button class="modal-close modal-close-localizable" aria-label="Close" onclick="dismissUpdateModal()">x</button>
|
||||||
<div class="update-modal-eyebrow" id="updateModalEyebrow">Updates</div>
|
<div class="update-modal-eyebrow" id="updateModalEyebrow">Updates</div>
|
||||||
<h2 id="updateModalTitle">Update verfugbar</h2>
|
<h2 id="updateModalTitle">Update verfugbar</h2>
|
||||||
<p class="update-modal-message" id="updateModalMessage">Version 0.0.0 ist verfugbar. Jetzt herunterladen?</p>
|
<p class="update-modal-message" id="updateModalMessage">Version 0.0.0 ist verfugbar. Jetzt herunterladen?</p>
|
||||||
@ -48,7 +48,7 @@
|
|||||||
<!-- Clip Dialog Modal -->
|
<!-- Clip Dialog Modal -->
|
||||||
<div class="modal-overlay" id="clipModal" role="dialog" aria-modal="true" aria-labelledby="clipDialogTitle">
|
<div class="modal-overlay" id="clipModal" role="dialog" aria-modal="true" aria-labelledby="clipDialogTitle">
|
||||||
<div class="modal clip-modal">
|
<div class="modal clip-modal">
|
||||||
<button class="modal-close" aria-label="Close" onclick="closeClipDialog()">x</button>
|
<button class="modal-close modal-close-localizable" aria-label="Close" onclick="closeClipDialog()">x</button>
|
||||||
<h2 class="clip-modal-title" id="clipDialogTitle">VOD zuschneiden</h2>
|
<h2 class="clip-modal-title" id="clipDialogTitle">VOD zuschneiden</h2>
|
||||||
|
|
||||||
<div class="clip-modal-field">
|
<div class="clip-modal-field">
|
||||||
@ -116,8 +116,8 @@
|
|||||||
<!-- Events Viewer Modal -->
|
<!-- Events Viewer Modal -->
|
||||||
<div class="modal-overlay" id="eventsViewerModal" role="dialog" aria-modal="true" aria-labelledby="eventsViewerTitle">
|
<div class="modal-overlay" id="eventsViewerModal" role="dialog" aria-modal="true" aria-labelledby="eventsViewerTitle">
|
||||||
<div class="modal" style="max-width: 700px; max-height: 80vh; display:flex; flex-direction:column;">
|
<div class="modal" style="max-width: 700px; max-height: 80vh; display:flex; flex-direction:column;">
|
||||||
<button class="modal-close" aria-label="Close" onclick="closeEventsViewer()">x</button>
|
<button class="modal-close modal-close-localizable" aria-label="Close" onclick="closeEventsViewer()">x</button>
|
||||||
<h2 id="eventsViewerTitle" style="margin-top:0;">Stream events</h2>
|
<h2 id="eventsViewerTitle" style="margin-top:0;"></h2>
|
||||||
<div id="eventsViewerStatus" style="color:var(--text-secondary); font-size:12px; margin-bottom:8px;"></div>
|
<div id="eventsViewerStatus" style="color:var(--text-secondary); font-size:12px; margin-bottom:8px;"></div>
|
||||||
<div id="eventsViewerList" style="flex:1; overflow-y:auto; background: var(--bg-main); border:1px solid var(--border-soft); border-radius:6px; padding:8px;"></div>
|
<div id="eventsViewerList" style="flex:1; overflow-y:auto; background: var(--bg-main); border:1px solid var(--border-soft); border-radius:6px; padding:8px;"></div>
|
||||||
</div>
|
</div>
|
||||||
@ -126,8 +126,8 @@
|
|||||||
<!-- Chat Replay Viewer Modal -->
|
<!-- Chat Replay Viewer Modal -->
|
||||||
<div class="modal-overlay" id="chatViewerModal" role="dialog" aria-modal="true" aria-labelledby="chatViewerTitle">
|
<div class="modal-overlay" id="chatViewerModal" role="dialog" aria-modal="true" aria-labelledby="chatViewerTitle">
|
||||||
<div class="modal" style="max-width: 800px; height: 80vh; display:flex; flex-direction:column;">
|
<div class="modal" style="max-width: 800px; height: 80vh; display:flex; flex-direction:column;">
|
||||||
<button class="modal-close" aria-label="Close" onclick="closeChatViewer()">x</button>
|
<button class="modal-close modal-close-localizable" aria-label="Close" onclick="closeChatViewer()">x</button>
|
||||||
<h2 id="chatViewerTitle" style="margin-top:0;">Chat replay</h2>
|
<h2 id="chatViewerTitle" style="margin-top:0;"></h2>
|
||||||
<div class="form-row" style="margin-bottom:8px; gap:8px; flex-wrap:wrap; align-items:center;">
|
<div class="form-row" style="margin-bottom:8px; gap:8px; flex-wrap:wrap; align-items:center;">
|
||||||
<input type="text" id="chatViewerFilter" placeholder="Filter..." oninput="onChatViewerFilterChange()" style="flex:1; min-width:160px; background: var(--bg-card); border:1px solid var(--border-soft); border-radius:6px; padding:6px 10px; color:var(--text); font-size:13px;">
|
<input type="text" id="chatViewerFilter" placeholder="Filter..." oninput="onChatViewerFilterChange()" style="flex:1; min-width:160px; background: var(--bg-card); border:1px solid var(--border-soft); border-radius:6px; padding:6px 10px; color:var(--text); font-size:13px;">
|
||||||
<span id="chatViewerStatus" style="color:var(--text-secondary); font-size:12px;"></span>
|
<span id="chatViewerStatus" style="color:var(--text-secondary); font-size:12px;"></span>
|
||||||
@ -139,7 +139,7 @@
|
|||||||
<!-- Template Guide Modal -->
|
<!-- Template Guide Modal -->
|
||||||
<div class="modal-overlay" id="templateGuideModal" role="dialog" aria-modal="true" aria-labelledby="templateGuideTitle">
|
<div class="modal-overlay" id="templateGuideModal" role="dialog" aria-modal="true" aria-labelledby="templateGuideTitle">
|
||||||
<div class="modal template-guide-modal">
|
<div class="modal template-guide-modal">
|
||||||
<button class="modal-close" aria-label="Close" onclick="closeTemplateGuide()">x</button>
|
<button class="modal-close modal-close-localizable" aria-label="Close" onclick="closeTemplateGuide()">x</button>
|
||||||
<h2 id="templateGuideTitle">Template Guide</h2>
|
<h2 id="templateGuideTitle">Template Guide</h2>
|
||||||
<p id="templateGuideIntro" class="template-guide-intro">Nutze Variablen fur Dateinamen und prufe das Ergebnis als Live-Vorschau.</p>
|
<p id="templateGuideIntro" class="template-guide-intro">Nutze Variablen fur Dateinamen und prufe das Ergebnis als Live-Vorschau.</p>
|
||||||
|
|
||||||
|
|||||||
@ -351,7 +351,8 @@ const UI_TEXT_DE = {
|
|||||||
autoVodScanEmpty: 'Keine neuen VODs gefunden.',
|
autoVodScanEmpty: 'Keine neuen VODs gefunden.',
|
||||||
autoRecordScanTriggered: 'Manueller Scan: {count} Live-Aufnahme(n) gestartet.',
|
autoRecordScanTriggered: 'Manueller Scan: {count} Live-Aufnahme(n) gestartet.',
|
||||||
autoRecordScanEmpty: 'Manueller Scan: kein Streamer ist gerade live.',
|
autoRecordScanEmpty: 'Manueller Scan: kein Streamer ist gerade live.',
|
||||||
liveNowTooltip: 'Aktuell live auf Twitch'
|
liveNowTooltip: 'Aktuell live auf Twitch',
|
||||||
|
modalCloseAria: 'Dialog schliessen'
|
||||||
},
|
},
|
||||||
vods: {
|
vods: {
|
||||||
noneTitle: 'Keine VODs',
|
noneTitle: 'Keine VODs',
|
||||||
|
|||||||
@ -351,7 +351,8 @@ const UI_TEXT_EN = {
|
|||||||
autoVodScanEmpty: 'No new VODs found.',
|
autoVodScanEmpty: 'No new VODs found.',
|
||||||
autoRecordScanTriggered: 'Manual scan: {count} live recording(s) started.',
|
autoRecordScanTriggered: 'Manual scan: {count} live recording(s) started.',
|
||||||
autoRecordScanEmpty: 'Manual scan: no streamers currently live.',
|
autoRecordScanEmpty: 'Manual scan: no streamers currently live.',
|
||||||
liveNowTooltip: 'Currently live on Twitch'
|
liveNowTooltip: 'Currently live on Twitch',
|
||||||
|
modalCloseAria: 'Close dialog'
|
||||||
},
|
},
|
||||||
vods: {
|
vods: {
|
||||||
noneTitle: 'No VODs',
|
noneTitle: 'No VODs',
|
||||||
|
|||||||
@ -26,6 +26,12 @@ function setText(id: string, value: string): void {
|
|||||||
if (node) node.textContent = value;
|
if (node) node.textContent = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setAriaLabelAll(selector: string, value: string): void {
|
||||||
|
document.querySelectorAll(selector).forEach((el) => {
|
||||||
|
el.setAttribute('aria-label', value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function setPlaceholder(id: string, value: string): void {
|
function setPlaceholder(id: string, value: string): void {
|
||||||
const node = document.getElementById(id) as HTMLInputElement | null;
|
const node = document.getElementById(id) as HTMLInputElement | null;
|
||||||
if (node) node.placeholder = value;
|
if (node) node.placeholder = value;
|
||||||
@ -236,6 +242,10 @@ function applyLanguageToStaticUI(): void {
|
|||||||
setText('autoVodMaxAgeHoursLabel', UI_TEXT.static.autoVodMaxAgeHoursLabel);
|
setText('autoVodMaxAgeHoursLabel', UI_TEXT.static.autoVodMaxAgeHoursLabel);
|
||||||
setText('btnAutoVodScanNow', UI_TEXT.static.autoVodScanNow);
|
setText('btnAutoVodScanNow', UI_TEXT.static.autoVodScanNow);
|
||||||
setText('btnAutoRecordScanNow', UI_TEXT.static.autoRecordScanNow);
|
setText('btnAutoRecordScanNow', UI_TEXT.static.autoRecordScanNow);
|
||||||
|
|
||||||
|
// Localize the modal close-button aria-label. The buttons share a
|
||||||
|
// .modal-close-localizable class so one call updates all five.
|
||||||
|
setAriaLabelAll('.modal-close-localizable', UI_TEXT.streamers.modalCloseAria);
|
||||||
setText('backupCardTitle', UI_TEXT.static.backupCardTitle);
|
setText('backupCardTitle', UI_TEXT.static.backupCardTitle);
|
||||||
setText('backupCardIntro', UI_TEXT.static.backupCardIntro);
|
setText('backupCardIntro', UI_TEXT.static.backupCardIntro);
|
||||||
setText('btnExportConfig', UI_TEXT.static.exportConfig);
|
setText('btnExportConfig', UI_TEXT.static.exportConfig);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user