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 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>
|
||||
<h2 id="updateModalTitle">Update verfugbar</h2>
|
||||
<p class="update-modal-message" id="updateModalMessage">Version 0.0.0 ist verfugbar. Jetzt herunterladen?</p>
|
||||
@ -48,7 +48,7 @@
|
||||
<!-- Clip Dialog Modal -->
|
||||
<div class="modal-overlay" id="clipModal" role="dialog" aria-modal="true" aria-labelledby="clipDialogTitle">
|
||||
<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>
|
||||
|
||||
<div class="clip-modal-field">
|
||||
@ -116,8 +116,8 @@
|
||||
<!-- Events Viewer Modal -->
|
||||
<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;">
|
||||
<button class="modal-close" aria-label="Close" onclick="closeEventsViewer()">x</button>
|
||||
<h2 id="eventsViewerTitle" style="margin-top:0;">Stream events</h2>
|
||||
<button class="modal-close modal-close-localizable" aria-label="Close" onclick="closeEventsViewer()">x</button>
|
||||
<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="eventsViewerList" style="flex:1; overflow-y:auto; background: var(--bg-main); border:1px solid var(--border-soft); border-radius:6px; padding:8px;"></div>
|
||||
</div>
|
||||
@ -126,8 +126,8 @@
|
||||
<!-- Chat Replay Viewer Modal -->
|
||||
<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;">
|
||||
<button class="modal-close" aria-label="Close" onclick="closeChatViewer()">x</button>
|
||||
<h2 id="chatViewerTitle" style="margin-top:0;">Chat replay</h2>
|
||||
<button class="modal-close modal-close-localizable" aria-label="Close" onclick="closeChatViewer()">x</button>
|
||||
<h2 id="chatViewerTitle" style="margin-top:0;"></h2>
|
||||
<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;">
|
||||
<span id="chatViewerStatus" style="color:var(--text-secondary); font-size:12px;"></span>
|
||||
@ -139,7 +139,7 @@
|
||||
<!-- Template Guide Modal -->
|
||||
<div class="modal-overlay" id="templateGuideModal" role="dialog" aria-modal="true" aria-labelledby="templateGuideTitle">
|
||||
<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>
|
||||
<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.',
|
||||
autoRecordScanTriggered: 'Manueller Scan: {count} Live-Aufnahme(n) gestartet.',
|
||||
autoRecordScanEmpty: 'Manueller Scan: kein Streamer ist gerade live.',
|
||||
liveNowTooltip: 'Aktuell live auf Twitch'
|
||||
liveNowTooltip: 'Aktuell live auf Twitch',
|
||||
modalCloseAria: 'Dialog schliessen'
|
||||
},
|
||||
vods: {
|
||||
noneTitle: 'Keine VODs',
|
||||
|
||||
@ -351,7 +351,8 @@ const UI_TEXT_EN = {
|
||||
autoVodScanEmpty: 'No new VODs found.',
|
||||
autoRecordScanTriggered: 'Manual scan: {count} live recording(s) started.',
|
||||
autoRecordScanEmpty: 'Manual scan: no streamers currently live.',
|
||||
liveNowTooltip: 'Currently live on Twitch'
|
||||
liveNowTooltip: 'Currently live on Twitch',
|
||||
modalCloseAria: 'Close dialog'
|
||||
},
|
||||
vods: {
|
||||
noneTitle: 'No VODs',
|
||||
|
||||
@ -26,6 +26,12 @@ function setText(id: string, value: string): void {
|
||||
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 {
|
||||
const node = document.getElementById(id) as HTMLInputElement | null;
|
||||
if (node) node.placeholder = value;
|
||||
@ -236,6 +242,10 @@ function applyLanguageToStaticUI(): void {
|
||||
setText('autoVodMaxAgeHoursLabel', UI_TEXT.static.autoVodMaxAgeHoursLabel);
|
||||
setText('btnAutoVodScanNow', UI_TEXT.static.autoVodScanNow);
|
||||
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('backupCardIntro', UI_TEXT.static.backupCardIntro);
|
||||
setText('btnExportConfig', UI_TEXT.static.exportConfig);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user