feat: configurable default language and WAI-ARIA accessible name
Release Accessibility Widget / release (push) Waiting to run
Release Accessibility Widget / release (push) Waiting to run
widget.js: - add defaultLanguage config (honored on first visit over browser detect) - compute screen reader text via ARIA order: aria-labelledby -> aria-label -> contents -> alt -> title - extract getSpeechLang() helper (was duplicated in handleFocus + toggle) - use optional chaining for ACCESSIBILITY_WIDGET_CONFIG lookup package.json (fork metadata): - license: GPL -> GPL-3.0-or-later (valid SPDX) - engines: node >=12 -> >=14 (optional chaining) - add homepage, drop empty dep objects and non-standard cdn field
This commit is contained in:
+10
-14
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "accessibility-widgets",
|
"name": "@invisi/accessibility-widgets",
|
||||||
"version": "2.0.14",
|
"version": "2.0.14",
|
||||||
"description": "A comprehensive, lightweight accessibility widget that enhances web accessibility for all users. Provides multiple accessibility features including screen reader support, voice control, high contrast mode, and more.",
|
"description": "A comprehensive, lightweight accessibility widget that enhances web accessibility for all users. Provides multiple accessibility features including screen reader support, voice control, high contrast mode, and more.",
|
||||||
"main": "widget.js",
|
"main": "widget.js",
|
||||||
@@ -32,26 +32,25 @@
|
|||||||
"assistive-technology"
|
"assistive-technology"
|
||||||
],
|
],
|
||||||
"author": {
|
"author": {
|
||||||
"name": "sinanisler",
|
"name": "Surya Handika Putratama",
|
||||||
"email": "sinan@sinan.im",
|
"email": "ubunteroz@gmail.com"
|
||||||
"url": "https://sinan.im"
|
|
||||||
},
|
},
|
||||||
"license": "GPL",
|
"license": "GPL-3.0-or-later",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/sinanisler/accessibility-widgets.git"
|
"url": "git+https://git.invisi.co.id/invisi/accessibility-widgets"
|
||||||
},
|
},
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/sinanisler/accessibility-widgets/issues"
|
"url": "https://git.invisi.co.id/invisi/accessibility-widgets/issues"
|
||||||
},
|
},
|
||||||
"homepage": "https://sinan.im",
|
"homepage": "https://git.invisi.co.id/invisi/accessibility-widgets",
|
||||||
"files": [
|
"files": [
|
||||||
"widget.js",
|
"widget.js",
|
||||||
"README.md",
|
"README.md",
|
||||||
"LICENSE"
|
"LICENSE"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12.0.0"
|
"node": ">=14.0.0"
|
||||||
},
|
},
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
"> 1%",
|
"> 1%",
|
||||||
@@ -59,9 +58,6 @@
|
|||||||
"not dead",
|
"not dead",
|
||||||
"not ie 11"
|
"not ie 11"
|
||||||
],
|
],
|
||||||
"devDependencies": {},
|
|
||||||
"dependencies": {},
|
|
||||||
"peerDependencies": {},
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "github",
|
"type": "github",
|
||||||
"url": "https://github.com/sponsors/sinanisler"
|
"url": "https://github.com/sponsors/sinanisler"
|
||||||
@@ -69,4 +65,4 @@
|
|||||||
"jsdelivr": "widget.js",
|
"jsdelivr": "widget.js",
|
||||||
"unpkg": "widget.js",
|
"unpkg": "widget.js",
|
||||||
"cdn": "widget.js"
|
"cdn": "widget.js"
|
||||||
}
|
}
|
||||||
@@ -1154,7 +1154,10 @@ const savedLanguage = localStorage.getItem('accessibilityWidgetLanguage');
|
|||||||
if (savedLanguage && TRANSLATIONS[savedLanguage]) {
|
if (savedLanguage && TRANSLATIONS[savedLanguage]) {
|
||||||
currentLanguage = savedLanguage;
|
currentLanguage = savedLanguage;
|
||||||
} else {
|
} else {
|
||||||
currentLanguage = detectBrowserLanguage();
|
// Honor configured default language (window.ACCESSIBILITY_WIDGET_CONFIG.defaultLanguage)
|
||||||
|
// over browser detection when no saved preference exists.
|
||||||
|
var configDefault = window?.ACCESSIBILITY_WIDGET_CONFIG?.defaultLanguage;
|
||||||
|
currentLanguage = (configDefault && TRANSLATIONS[configDefault]) ? configDefault : detectBrowserLanguage();
|
||||||
localStorage.setItem('accessibilityWidgetLanguage', currentLanguage);
|
localStorage.setItem('accessibilityWidgetLanguage', currentLanguage);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1234,6 +1237,9 @@ const DEFAULT_WIDGET_CONFIG = {
|
|||||||
hoverScale: '1.05'
|
hoverScale: '1.05'
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Applied on first visit when there is no saved preference; overrides browser-language detection.
|
||||||
|
defaultLanguage: null,
|
||||||
|
|
||||||
// Language/Text Configuration
|
// Language/Text Configuration
|
||||||
lang: {
|
lang: {
|
||||||
accessibilityMenu: 'Accessibility Menu',
|
accessibilityMenu: 'Accessibility Menu',
|
||||||
@@ -1390,7 +1396,7 @@ const DEFAULT_WIDGET_CONFIG = {
|
|||||||
colorFilter: ['renk filtresi', 'renk körü', 'filtre'],
|
colorFilter: ['renk filtresi', 'renk körü', 'filtre'],
|
||||||
screenReader: ['ekran okuyucu', 'sesli oku', 'ses okuyucu'],
|
screenReader: ['ekran okuyucu', 'sesli oku', 'ses okuyucu'],
|
||||||
voiceControl: ['sesli komut', 'sesli kontrol', 'sesli komutlar'],
|
voiceControl: ['sesli komut', 'sesli kontrol', 'sesli komutlar'],
|
||||||
resetAll: ['hepsini sıfırla', 'tümünü sıfırla', 'hepsini temizle', 'ayarları sıfırla','sıfırla']
|
resetAll: ['hepsini sıfırla', 'tümünü sıfırla', 'hepsini temizle', 'ayarları sıfırla', 'sıfırla']
|
||||||
},
|
},
|
||||||
ar: {
|
ar: {
|
||||||
showMenu: ['إظهار القائمة', 'فتح القائمة', 'قائمة إمكانية الوصول', 'قائمة الوصول'],
|
showMenu: ['إظهار القائمة', 'فتح القائمة', 'قائمة إمكانية الوصول', 'قائمة الوصول'],
|
||||||
@@ -2312,10 +2318,10 @@ const pageStyles = `
|
|||||||
|
|
||||||
/* Dyslexia Font */
|
/* Dyslexia Font */
|
||||||
.snn-dyslexia-font {
|
.snn-dyslexia-font {
|
||||||
font-family: 'Comic Sans MS', 'Chalkboard SE', 'Bradley Hand', 'Brush Script MT', fantasy !important;
|
font-family: 'OpenDyslexic', 'Comic Sans MS', 'Chalkboard SE', 'Bradley Hand', 'Brush Script MT', fantasy !important;
|
||||||
}
|
}
|
||||||
.snn-dyslexia-font * {
|
.snn-dyslexia-font * {
|
||||||
font-family: 'Comic Sans MS', 'Chalkboard SE', 'Bradley Hand', 'Brush Script MT', fantasy !important;
|
font-family: 'OpenDyslexic', 'Comic Sans MS', 'Chalkboard SE', 'Bradley Hand', 'Brush Script MT', fantasy !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Line Height - 3 Options */
|
/* Line Height - 3 Options */
|
||||||
@@ -3282,6 +3288,48 @@ function handleLineHeight() {
|
|||||||
// ACCESSIBILITY FEATURES
|
// ACCESSIBILITY FEATURES
|
||||||
// ===========================================
|
// ===========================================
|
||||||
|
|
||||||
|
// Resolve the BCP-47 speech-synthesis locale for the active UI language.
|
||||||
|
function getSpeechLang() {
|
||||||
|
switch (currentLanguage) {
|
||||||
|
case 'de': return 'de-DE';
|
||||||
|
case 'es': return 'es-ES';
|
||||||
|
case 'it': return 'it-IT';
|
||||||
|
case 'fr': return 'fr-FR';
|
||||||
|
case 'ru': return 'ru-RU';
|
||||||
|
case 'tr': return 'tr-TR';
|
||||||
|
case 'ar': return 'ar-SA';
|
||||||
|
case 'hi': return 'hi-IN';
|
||||||
|
case 'zh-cn': return 'zh-CN';
|
||||||
|
case 'jp': return 'ja-JP';
|
||||||
|
case 'pt': return 'pt-PT';
|
||||||
|
case 'bn': return 'bn-IN';
|
||||||
|
case 'ko': return 'ko-KR';
|
||||||
|
case 'vi': return 'vi-VN';
|
||||||
|
case 'id': return 'id-ID';
|
||||||
|
case 'th': return 'th-TH';
|
||||||
|
case 'pl': return 'pl-PL';
|
||||||
|
case 'nl': return 'nl-NL';
|
||||||
|
case 'el': return 'el-GR';
|
||||||
|
case 'sv': return 'sv-SE';
|
||||||
|
case 'no': return 'no-NO';
|
||||||
|
case 'da': return 'da-DK';
|
||||||
|
case 'fi': return 'fi-FI';
|
||||||
|
case 'cs': return 'cs-CZ';
|
||||||
|
case 'hu': return 'hu-HU';
|
||||||
|
case 'ro': return 'ro-RO';
|
||||||
|
case 'he': return 'he-IL';
|
||||||
|
case 'fa': return 'fa-IR';
|
||||||
|
case 'ur': return 'ur-PK';
|
||||||
|
case 'pa': return 'pa-IN';
|
||||||
|
case 'mr': return 'mr-IN';
|
||||||
|
case 'te': return 'te-IN';
|
||||||
|
case 'ta': return 'ta-IN';
|
||||||
|
case 'ms': return 'ms-MY';
|
||||||
|
case 'tl': return 'fil-PH';
|
||||||
|
default: return 'en-US';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Screen reader functionality
|
// Screen reader functionality
|
||||||
const screenReader = {
|
const screenReader = {
|
||||||
active: localStorage.getItem('screenReader') === 'true',
|
active: localStorage.getItem('screenReader') === 'true',
|
||||||
@@ -3289,52 +3337,27 @@ const screenReader = {
|
|||||||
handleFocus: function (event) {
|
handleFocus: function (event) {
|
||||||
if (screenReader.active && screenReader.isSupported) {
|
if (screenReader.active && screenReader.isSupported) {
|
||||||
try {
|
try {
|
||||||
const content = event.target.innerText || event.target.alt || event.target.title || '';
|
// WAI-ARIA accessible name computation order: aria-labelledby -> aria-label -> contents -> alt -> title
|
||||||
|
var target = event.target;
|
||||||
|
var labelledBy = target.getAttribute && target.getAttribute('aria-labelledby');
|
||||||
|
var labelledByText = '';
|
||||||
|
if (labelledBy) {
|
||||||
|
labelledByText = labelledBy.split(/\s+/).map(function (id) {
|
||||||
|
var el = document.getElementById(id);
|
||||||
|
return el ? (el.innerText || el.textContent) : '';
|
||||||
|
}).join(' ').trim();
|
||||||
|
}
|
||||||
|
const content = labelledByText
|
||||||
|
|| (target.getAttribute && target.getAttribute('aria-label'))
|
||||||
|
|| target.innerText
|
||||||
|
|| target.alt
|
||||||
|
|| target.title
|
||||||
|
|| '';
|
||||||
if (content.trim() !== '') {
|
if (content.trim() !== '') {
|
||||||
window.speechSynthesis.cancel();
|
window.speechSynthesis.cancel();
|
||||||
const speech = new SpeechSynthesisUtterance(content);
|
const speech = new SpeechSynthesisUtterance(content);
|
||||||
|
|
||||||
// Set language based on current interface language
|
speech.lang = getSpeechLang();
|
||||||
let speechLang = 'en-US'; // default
|
|
||||||
switch (currentLanguage) {
|
|
||||||
case 'de': speechLang = 'de-DE'; break;
|
|
||||||
case 'es': speechLang = 'es-ES'; break;
|
|
||||||
case 'it': speechLang = 'it-IT'; break;
|
|
||||||
case 'fr': speechLang = 'fr-FR'; break;
|
|
||||||
case 'ru': speechLang = 'ru-RU'; break;
|
|
||||||
case 'tr': speechLang = 'tr-TR'; break;
|
|
||||||
case 'ar': speechLang = 'ar-SA'; break;
|
|
||||||
case 'hi': speechLang = 'hi-IN'; break;
|
|
||||||
case 'zh-cn': speechLang = 'zh-CN'; break;
|
|
||||||
case 'jp': speechLang = 'ja-JP'; break;
|
|
||||||
case 'pt': speechLang = 'pt-PT'; break;
|
|
||||||
case 'bn': speechLang = 'bn-IN'; break;
|
|
||||||
case 'ko': speechLang = 'ko-KR'; break;
|
|
||||||
case 'vi': speechLang = 'vi-VN'; break;
|
|
||||||
case 'id': speechLang = 'id-ID'; break;
|
|
||||||
case 'th': speechLang = 'th-TH'; break;
|
|
||||||
case 'pl': speechLang = 'pl-PL'; break;
|
|
||||||
case 'nl': speechLang = 'nl-NL'; break;
|
|
||||||
case 'el': speechLang = 'el-GR'; break;
|
|
||||||
case 'sv': speechLang = 'sv-SE'; break;
|
|
||||||
case 'no': speechLang = 'no-NO'; break;
|
|
||||||
case 'da': speechLang = 'da-DK'; break;
|
|
||||||
case 'fi': speechLang = 'fi-FI'; break;
|
|
||||||
case 'cs': speechLang = 'cs-CZ'; break;
|
|
||||||
case 'hu': speechLang = 'hu-HU'; break;
|
|
||||||
case 'ro': speechLang = 'ro-RO'; break;
|
|
||||||
case 'he': speechLang = 'he-IL'; break;
|
|
||||||
case 'fa': speechLang = 'fa-IR'; break;
|
|
||||||
case 'ur': speechLang = 'ur-PK'; break;
|
|
||||||
case 'pa': speechLang = 'pa-IN'; break;
|
|
||||||
case 'mr': speechLang = 'mr-IN'; break;
|
|
||||||
case 'te': speechLang = 'te-IN'; break;
|
|
||||||
case 'ta': speechLang = 'ta-IN'; break;
|
|
||||||
case 'ms': speechLang = 'ms-MY'; break;
|
|
||||||
case 'tl': speechLang = 'fil-PH'; break;
|
|
||||||
default: speechLang = 'en-US';
|
|
||||||
}
|
|
||||||
speech.lang = speechLang;
|
|
||||||
|
|
||||||
speech.onerror = function (event) {
|
speech.onerror = function (event) {
|
||||||
console.warn('Speech synthesis error:', event.error);
|
console.warn('Speech synthesis error:', event.error);
|
||||||
@@ -3356,21 +3379,7 @@ const screenReader = {
|
|||||||
localStorage.setItem('screenReader', isActive);
|
localStorage.setItem('screenReader', isActive);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Set language based on current interface language
|
let speechLang = getSpeechLang();
|
||||||
let speechLang = 'en-US'; // default
|
|
||||||
switch (currentLanguage) {
|
|
||||||
case 'de': speechLang = 'de-DE'; break;
|
|
||||||
case 'es': speechLang = 'es-ES'; break;
|
|
||||||
case 'it': speechLang = 'it-IT'; break;
|
|
||||||
case 'fr': speechLang = 'fr-FR'; break;
|
|
||||||
case 'ru': speechLang = 'ru-RU'; break;
|
|
||||||
case 'tr': speechLang = 'tr-TR'; break;
|
|
||||||
case 'ar': speechLang = 'ar-SA'; break;
|
|
||||||
case 'hi': speechLang = 'hi-IN'; break;
|
|
||||||
case 'zh-cn': speechLang = 'zh-CN'; break;
|
|
||||||
case 'jp': speechLang = 'ja-JP'; break;
|
|
||||||
default: speechLang = 'en-US';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isActive) {
|
if (isActive) {
|
||||||
document.addEventListener('focusin', screenReader.handleFocus);
|
document.addEventListener('focusin', screenReader.handleFocus);
|
||||||
|
|||||||
Reference in New Issue
Block a user