ngp-accessibility 1.0.5 โ†’ 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,6 +5,7 @@ React accessibility package with translation, text sizing, contrast modes, and v
5
5
  ## Features
6
6
 
7
7
  - ๐ŸŒ Multi-language support (English, Tagalog, Cebuano/Visayan)
8
+ - ๐ŸŒ Google auto-translation for page content with manual UI fallback
8
9
  - ๐Ÿ“ Text size adjustment (increase/decrease)
9
10
  - ๐ŸŽจ High contrast mode
10
11
  - ๐Ÿ”„ Negative contrast mode
@@ -31,9 +32,11 @@ import "ngp-accessibility/dist/styles.css";
31
32
 
32
33
  function App() {
33
34
  return (
34
- <AccessibilityProvider>
35
+ <AccessibilityProvider googleTranslateTargetSelector="main">
35
36
  <AccessibilityToolbar />
36
- <YourContent />
37
+ <main>
38
+ <YourContent />
39
+ </main>
37
40
  </AccessibilityProvider>
38
41
  );
39
42
  }
@@ -50,11 +53,13 @@ import "ngp-accessibility/dist/styles.css";
50
53
 
51
54
  function App() {
52
55
  return (
53
- <AccessibilityProvider>
56
+ <AccessibilityProvider googleTranslateTargetSelector="#page-content">
54
57
  <div style={{ textAlign: "right", padding: "10px" }}>
55
58
  <AccessibilityDropdown />
56
59
  </div>
57
- <YourContent />
60
+ <section id="page-content">
61
+ <YourContent />
62
+ </section>
58
63
  </AccessibilityProvider>
59
64
  );
60
65
  }
@@ -78,11 +83,102 @@ When voice command is enabled, you can use:
78
83
 
79
84
  Wrap your app with this provider.
80
85
 
86
+ Props:
87
+
88
+ - `translations`: Manual translations for your own keyed content through `t()`.
89
+ - `googleTranslateTargetSelector`: CSS selector for the DOM subtree that should be eligible for Google auto-translation. Defaults to `main`.
90
+
91
+ Example:
92
+
93
+ ```tsx
94
+ <AccessibilityProvider googleTranslateTargetSelector="#article-content">
95
+ <Header />
96
+ <AccessibilityDropdown />
97
+ <div id="article-content">
98
+ <ArticlePage />
99
+ </div>
100
+ </AccessibilityProvider>
101
+ ```
102
+
103
+ Only the subtree matched by `googleTranslateTargetSelector` should contain the content you want Google to translate. Elements outside that path are marked as `notranslate`.
104
+
105
+ ### Language Behavior
106
+
107
+ `setLanguage(lang)` updates the current language state once, and the package applies that language in 3 different ways:
108
+
109
+ 1. Toolbar and dropdown UI labels use `translate(key)`.
110
+ 2. Your app's keyed content uses `t(key)`.
111
+ 3. Google Translate auto-translates the subtree matched by `googleTranslateTargetSelector`.
112
+
113
+ This means the package now supports 2 language modes:
114
+
115
+ - Manual UI language: If the selected language exists in the built-in package dictionary, the accessibility controls use those manual labels.
116
+ - Google-only language: If the selected language does not have a built-in package dictionary, the accessibility controls fall back to English while Google still translates the page content.
117
+
118
+ Example:
119
+
120
+ - `tl`: package UI uses manual Tagalog labels, and page content can also be translated.
121
+ - `ja`: package UI falls back to English unless you add manual Japanese labels, while Google translates the selected page subtree to Japanese.
122
+
123
+ ### Manual Translation For Specific Content
124
+
125
+ Yes, this setup is more flexible when you want manual translations only for specific components or content.
126
+
127
+ Use `translations` plus `t(key)` when you want full control over exact wording in your own app content. Use Google Translate for the rest of the page content inside your translation target.
128
+
129
+ Example:
130
+
131
+ ```tsx
132
+ const myTranslations = {
133
+ en: {
134
+ heroTitle: "Welcome",
135
+ heroBody: "This section is manually translated.",
136
+ },
137
+ ja: {
138
+ heroTitle: "ใ‚ˆใ†ใ“ใ",
139
+ heroBody: "ใ“ใฎใ‚ปใ‚ฏใ‚ทใƒงใƒณใฏๆ‰‹ๅ‹•ใง็ฟป่จณใ•ใ‚Œใฆใ„ใพใ™ใ€‚",
140
+ },
141
+ };
142
+
143
+ function Hero() {
144
+ const { t } = useAccessibility();
145
+
146
+ return (
147
+ <section>
148
+ <h1>{t("heroTitle")}</h1>
149
+ <p>{t("heroBody")}</p>
150
+ </section>
151
+ );
152
+ }
153
+
154
+ function App() {
155
+ return (
156
+ <AccessibilityProvider
157
+ translations={myTranslations}
158
+ googleTranslateTargetSelector="main"
159
+ >
160
+ <AccessibilityDropdown />
161
+ <main>
162
+ <Hero />
163
+ <OtherPageContent />
164
+ </main>
165
+ </AccessibilityProvider>
166
+ );
167
+ }
168
+ ```
169
+
170
+ In that setup:
171
+
172
+ - `Hero` can use your manual translations through `t(key)`.
173
+ - other content inside `main` can still be translated by Google.
174
+ - the accessibility toolbar/dropdown uses built-in package labels when available, otherwise English fallback.
175
+
81
176
  ### useAccessibility()
82
177
 
83
178
  Hook that returns:
84
179
 
85
180
  - `language`, `setLanguage(lang)`
181
+ - `hasManualTranslations`
86
182
  - `textSize`, `increaseText()`, `decreaseText()`
87
183
  - `highContrast`, `toggleHighContrast()`
88
184
  - `negativeContrast`, `toggleNegativeContrast()`
@@ -90,7 +186,8 @@ Hook that returns:
90
186
  - `underlineLinks`, `toggleUnderlineLinks()`
91
187
  - `readableFont`, `toggleReadableFont()`
92
188
  - `voiceEnabled`, `toggleVoice()`
93
- - `translate(key)` - Get translated text
189
+ - `translate(key)` - Get translated package UI text with English fallback
190
+ - `t(key)` - Get your app-specific manual text for the current language, then fall back to English, then the raw key
94
191
 
95
192
  ### AccessibilityToolbar
96
193
 
@@ -1,5 +1,6 @@
1
1
  import React, { ReactNode } from "react";
2
2
  import { Language, TranslationKey } from "./translations";
3
+ export type ColorBlindMode = "none" | "protanopia" | "deuteranopia" | "tritanopia";
3
4
  interface AccessibilityState {
4
5
  language: Language;
5
6
  textSize: number;
@@ -13,6 +14,9 @@ interface AccessibilityState {
13
14
  highlightTitles: boolean;
14
15
  readableFont: boolean;
15
16
  voiceEnabled: boolean;
17
+ readAloud: boolean;
18
+ colorBlindMode: ColorBlindMode;
19
+ focusIndicator: boolean;
16
20
  }
17
21
  interface AccessibilityContextType extends AccessibilityState {
18
22
  setLanguage: (lang: Language) => void;
@@ -28,12 +32,19 @@ interface AccessibilityContextType extends AccessibilityState {
28
32
  toggleHighlightTitles: () => void;
29
33
  toggleReadableFont: () => void;
30
34
  toggleVoice: () => void;
35
+ toggleReadAloud: () => void;
36
+ setColorBlindMode: (mode: ColorBlindMode) => void;
37
+ toggleFocusIndicator: () => void;
38
+ hasManualTranslations: boolean;
31
39
  translate: (key: TranslationKey) => string;
32
40
  t: (key: string) => string;
33
41
  }
34
- interface AccessibilityProviderProps {
42
+ export interface AccessibilityProviderProps {
35
43
  children: ReactNode;
36
44
  translations?: Partial<Record<Language, Record<string, string>>>;
45
+ googleTranslateTargetSelector?: string;
46
+ /** @deprecated Use googleTranslateTargetSelector instead. */
47
+ translationTargetSelector?: string;
37
48
  }
38
49
  export declare const AccessibilityProvider: React.FC<AccessibilityProviderProps>;
39
50
  export declare const useAccessibility: () => AccessibilityContextType;
@@ -1,4 +1,28 @@
1
1
  import React from "react";
2
+ export interface AccessibilityDropdownClasses {
3
+ root?: string;
4
+ title?: string;
5
+ trigger?: string;
6
+ panel?: string;
7
+ toggle?: string;
8
+ section?: string;
9
+ sectionHeader?: string;
10
+ button?: string;
11
+ activeButton?: string;
12
+ select?: string;
13
+ menu?: string;
14
+ }
15
+ export interface AccessibilityDropdownProps {
16
+ className?: string;
17
+ style?: React.CSSProperties;
18
+ classes?: AccessibilityDropdownClasses;
19
+ triggerLabel?: string;
20
+ renderTrigger?: (props: {
21
+ isOpen: boolean;
22
+ toggle: () => void;
23
+ className: string;
24
+ }) => React.ReactNode;
25
+ }
2
26
  export interface AccessibilityDropdownClasses {
3
27
  root?: string;
4
28
  trigger?: string;
package/dist/index.d.ts CHANGED
@@ -3,6 +3,7 @@ export { AccessibilityToolbar } from "./AccessibilityToolbar";
3
3
  export { AccessibilityDropdown } from "./AccessibilityDropdown";
4
4
  export { T } from "./T";
5
5
  export { translations, type Language, type TranslationKey, } from "./translations";
6
+ export type { AccessibilityProviderProps } from "./AccessibilityContext";
6
7
  export type { AccessibilityToolbarProps, AccessibilityToolbarClasses, } from "./AccessibilityToolbar";
7
8
  export type { AccessibilityDropdownProps, AccessibilityDropdownClasses, } from "./AccessibilityDropdown";
8
9
  export type { default as React } from "react";
package/dist/index.esm.js CHANGED
@@ -2,6 +2,7 @@ import React, { createContext, useState, useEffect, useContext } from 'react';
2
2
 
3
3
  const translations = {
4
4
  en: {
5
+ language: "Language",
5
6
  increaseText: "Increase Text",
6
7
  decreaseText: "Decrease Text",
7
8
  textMagnifier: "Text Magnifier",
@@ -14,8 +15,12 @@ const translations = {
14
15
  pauseAnimations: "Pause Animations",
15
16
  readingGuide: "Reading Guide",
16
17
  voiceCommand: "Voice Command",
18
+ readAloud: "Read Aloud",
19
+ colorBlindMode: "Color Blind Mode",
20
+ focusIndicator: "Focus Indicator",
17
21
  },
18
22
  tl: {
23
+ language: "Wika",
19
24
  increaseText: "Palakihin ang Teksto",
20
25
  decreaseText: "Paliitin ang Teksto",
21
26
  textMagnifier: "Text Magnifier",
@@ -28,8 +33,12 @@ const translations = {
28
33
  pauseAnimations: "I-pause ang mga Animation",
29
34
  readingGuide: "Reading Guide",
30
35
  voiceCommand: "Voice Command",
36
+ readAloud: "Basahin nang Malakas",
37
+ colorBlindMode: "Color Blind Mode",
38
+ focusIndicator: "Focus Indicator",
31
39
  },
32
40
  ceb: {
41
+ language: "Pinulongan",
33
42
  increaseText: "Padak-on ang Teksto",
34
43
  decreaseText: "Pagamay-on ang Teksto",
35
44
  textMagnifier: "Text Magnifier",
@@ -42,9 +51,38 @@ const translations = {
42
51
  pauseAnimations: "Ihunong ang mga Animation",
43
52
  readingGuide: "Reading Guide",
44
53
  voiceCommand: "Voice Command",
54
+ readAloud: "Basaha og Kusog",
55
+ colorBlindMode: "Color Blind Mode",
56
+ focusIndicator: "Focus Indicator",
45
57
  },
46
58
  };
47
59
 
60
+ const GOOGLE_TRANSLATE_COOKIE_NAME = "googtrans";
61
+ const GOOGLE_TRANSLATE_SOURCE_LANGUAGE = "en";
62
+ const getCookieDomains = () => {
63
+ const domains = new Set([window.location.hostname]);
64
+ const hostnameParts = window.location.hostname.split(".");
65
+ if (hostnameParts.length > 2) {
66
+ domains.add(hostnameParts.slice(-2).join("."));
67
+ }
68
+ return Array.from(domains);
69
+ };
70
+ const setGoogleTranslateCookie = (targetLanguage) => {
71
+ const cookieValue = `/${GOOGLE_TRANSLATE_SOURCE_LANGUAGE}/${targetLanguage}`;
72
+ document.cookie = `${GOOGLE_TRANSLATE_COOKIE_NAME}=${cookieValue}; path=/; SameSite=Lax`;
73
+ for (const domain of getCookieDomains()) {
74
+ document.cookie = `${GOOGLE_TRANSLATE_COOKIE_NAME}=${cookieValue}; path=/; domain=${domain}; SameSite=Lax`;
75
+ }
76
+ };
77
+ const clearGoogleTranslateCookie = () => {
78
+ document.cookie = `${GOOGLE_TRANSLATE_COOKIE_NAME}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; SameSite=Lax`;
79
+ for (const domain of getCookieDomains()) {
80
+ document.cookie = `${GOOGLE_TRANSLATE_COOKIE_NAME}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; domain=${domain}; SameSite=Lax`;
81
+ }
82
+ };
83
+ const dispatchNativeChangeEvent = (element) => {
84
+ element.dispatchEvent(new Event("change", { bubbles: true }));
85
+ };
48
86
  const normalizeMagnifierText = (value) => value.replace(/\s+/g, " ").trim();
49
87
  const magnifierSelector = "p, li, button, h1, h2, h3, h4, h5, h6, [role=heading]";
50
88
  const getMagnifierElement = (target) => {
@@ -65,7 +103,8 @@ const getMagnifierText = (target) => {
65
103
  return normalizeMagnifierText(element.innerText || element.textContent || "");
66
104
  };
67
105
  const AccessibilityContext = createContext(undefined);
68
- const AccessibilityProvider = ({ children, translations: customTranslations, }) => {
106
+ const AccessibilityProvider = ({ children, translations: customTranslations, googleTranslateTargetSelector, translationTargetSelector, }) => {
107
+ var _a;
69
108
  const [state, setState] = useState({
70
109
  language: "en",
71
110
  textSize: 100,
@@ -79,8 +118,49 @@ const AccessibilityProvider = ({ children, translations: customTranslations, })
79
118
  highlightTitles: false,
80
119
  readableFont: false,
81
120
  voiceEnabled: false,
121
+ readAloud: false,
122
+ colorBlindMode: "none",
123
+ focusIndicator: false,
82
124
  });
83
125
  const allTranslations = customTranslations || translations;
126
+ const defaultUiTranslations = translations.en;
127
+ const manualTranslationsForLanguage = translations[state.language];
128
+ const hasManualTranslations = Boolean(manualTranslationsForLanguage);
129
+ const resolvedTranslationTargetSelector = (_a = googleTranslateTargetSelector !== null && googleTranslateTargetSelector !== void 0 ? googleTranslateTargetSelector : translationTargetSelector) !== null && _a !== void 0 ? _a : "main";
130
+ useEffect(() => {
131
+ const target = document.querySelector(resolvedTranslationTargetSelector);
132
+ if (!target) {
133
+ console.warn(`[NGP Accessibility] Google translation target selector "${resolvedTranslationTargetSelector}" did not match any element. Falling back to page-wide translation behavior.`);
134
+ return;
135
+ }
136
+ const markedElements = new Set();
137
+ let current = target;
138
+ while (current && current !== document.body) {
139
+ const parentElement = current.parentElement;
140
+ if (!parentElement)
141
+ break;
142
+ for (const sibling of Array.from(parentElement.children)) {
143
+ if (sibling === current || !(sibling instanceof HTMLElement))
144
+ continue;
145
+ if (sibling.id === "a11y-google-translate-element")
146
+ continue;
147
+ sibling.classList.add("notranslate");
148
+ sibling.setAttribute("translate", "no");
149
+ sibling.dataset.a11yTranslationScoped = "true";
150
+ markedElements.add(sibling);
151
+ }
152
+ current = parentElement;
153
+ }
154
+ return () => {
155
+ for (const element of markedElements) {
156
+ if (element.dataset.a11yTranslationScoped !== "true")
157
+ continue;
158
+ element.classList.remove("notranslate");
159
+ element.removeAttribute("translate");
160
+ delete element.dataset.a11yTranslationScoped;
161
+ }
162
+ };
163
+ }, [resolvedTranslationTargetSelector]);
84
164
  useEffect(() => {
85
165
  const root = document.documentElement;
86
166
  root.style.fontSize = `${state.textSize}%`;
@@ -91,7 +171,103 @@ const AccessibilityProvider = ({ children, translations: customTranslations, })
91
171
  root.classList.toggle("highlight-titles", state.highlightTitles);
92
172
  root.classList.toggle("readable-font", state.readableFont);
93
173
  root.classList.toggle("pause-animations", state.pauseAnimations);
174
+ root.classList.toggle("focus-indicator", state.focusIndicator);
175
+ const body = document.body;
176
+ if (state.colorBlindMode !== "none") {
177
+ body.style.filter = `url(#a11y-${state.colorBlindMode})`;
178
+ }
179
+ else {
180
+ body.style.filter = "";
181
+ }
94
182
  }, [state]);
183
+ useEffect(() => {
184
+ const svgId = "a11y-colorblind-filters";
185
+ if (document.getElementById(svgId))
186
+ return;
187
+ const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
188
+ svg.id = svgId;
189
+ svg.setAttribute("aria-hidden", "true");
190
+ svg.style.cssText = "position:absolute;width:0;height:0;overflow:hidden;";
191
+ svg.innerHTML = `
192
+ <defs>
193
+ <filter id="a11y-protanopia">
194
+ <feColorMatrix type="matrix" values="
195
+ 0.567 0.433 0 0 0
196
+ 0.558 0.442 0 0 0
197
+ 0 0.242 0.758 0 0
198
+ 0 0 0 1 0"/>
199
+ </filter>
200
+ <filter id="a11y-deuteranopia">
201
+ <feColorMatrix type="matrix" values="
202
+ 0.625 0.375 0 0 0
203
+ 0.7 0.3 0 0 0
204
+ 0 0.3 0.7 0 0
205
+ 0 0 0 1 0"/>
206
+ </filter>
207
+ <filter id="a11y-tritanopia">
208
+ <feColorMatrix type="matrix" values="
209
+ 0.95 0.05 0 0 0
210
+ 0 0.433 0.567 0 0
211
+ 0 0.475 0.525 0 0
212
+ 0 0 0 1 0"/>
213
+ </filter>
214
+ </defs>
215
+ `;
216
+ document.body.prepend(svg);
217
+ return () => {
218
+ var _a;
219
+ (_a = document.getElementById(svgId)) === null || _a === void 0 ? void 0 : _a.remove();
220
+ };
221
+ }, []);
222
+ useEffect(() => {
223
+ if (!state.readAloud || !("speechSynthesis" in window))
224
+ return;
225
+ const readableSelector = "p, li, button, a, h1, h2, h3, h4, h5, h6, label, td, th, [role='button'], [role='heading'], [role='link']";
226
+ const speak = (text) => {
227
+ window.speechSynthesis.cancel();
228
+ const utterance = new SpeechSynthesisUtterance(text);
229
+ utterance.lang =
230
+ state.language === "tl"
231
+ ? "tl-PH"
232
+ : state.language === "ceb"
233
+ ? "fil-PH"
234
+ : "en-US";
235
+ utterance.rate = 0.95;
236
+ window.speechSynthesis.speak(utterance);
237
+ };
238
+ const getReadableText = (el) => normalizeMagnifierText(el.getAttribute("aria-label") ||
239
+ el.getAttribute("placeholder") ||
240
+ el.getAttribute("title") ||
241
+ el.innerText ||
242
+ el.textContent ||
243
+ "");
244
+ const handleMouseOver = (event) => {
245
+ const target = event.target;
246
+ if (!target)
247
+ return;
248
+ const el = target.closest(readableSelector);
249
+ if (!el || el.closest(".a11y-root"))
250
+ return;
251
+ const text = getReadableText(el);
252
+ if (text)
253
+ speak(text);
254
+ };
255
+ const handleFocus = (event) => {
256
+ const el = event.target;
257
+ if (!el || el.closest(".a11y-root"))
258
+ return;
259
+ const text = getReadableText(el);
260
+ if (text)
261
+ speak(text);
262
+ };
263
+ document.addEventListener("mouseover", handleMouseOver, true);
264
+ document.addEventListener("focusin", handleFocus, true);
265
+ return () => {
266
+ document.removeEventListener("mouseover", handleMouseOver, true);
267
+ document.removeEventListener("focusin", handleFocus, true);
268
+ window.speechSynthesis.cancel();
269
+ };
270
+ }, [state.readAloud, state.language]);
95
271
  useEffect(() => {
96
272
  if (!state.readingGuide)
97
273
  return;
@@ -323,8 +499,81 @@ const AccessibilityProvider = ({ children, translations: customTranslations, })
323
499
  const toggleHighlightTitles = () => setState((s) => (Object.assign(Object.assign({}, s), { highlightTitles: !s.highlightTitles })));
324
500
  const toggleReadableFont = () => setState((s) => (Object.assign(Object.assign({}, s), { readableFont: !s.readableFont })));
325
501
  const toggleVoice = () => setState((s) => (Object.assign(Object.assign({}, s), { voiceEnabled: !s.voiceEnabled })));
326
- const translate = (key) => translations[state.language][key];
327
- const t = (key) => { var _a; return ((_a = allTranslations[state.language]) === null || _a === void 0 ? void 0 : _a[key]) || key; };
502
+ const toggleReadAloud = () => setState((s) => (Object.assign(Object.assign({}, s), { readAloud: !s.readAloud })));
503
+ const setColorBlindMode = (mode) => setState((s) => (Object.assign(Object.assign({}, s), { colorBlindMode: mode })));
504
+ const toggleFocusIndicator = () => setState((s) => (Object.assign(Object.assign({}, s), { focusIndicator: !s.focusIndicator })));
505
+ // Google Translate: auto-translate page content when language changes
506
+ useEffect(() => {
507
+ const GTRANSLATE_SCRIPT_ID = "a11y-google-translate-script";
508
+ const GTRANSLATE_CONTAINER_ID = "a11y-google-translate-element";
509
+ const loadGoogleTranslateScript = () => {
510
+ if (document.getElementById(GTRANSLATE_SCRIPT_ID))
511
+ return;
512
+ // Create hidden container for the widget
513
+ const container = document.createElement("div");
514
+ container.id = GTRANSLATE_CONTAINER_ID;
515
+ container.style.display = "none";
516
+ document.body.appendChild(container);
517
+ // Define the init callback
518
+ window.googleTranslateElementInit = () => {
519
+ new window.google.translate.TranslateElement({
520
+ pageLanguage: "en",
521
+ autoDisplay: false,
522
+ }, GTRANSLATE_CONTAINER_ID);
523
+ };
524
+ const script = document.createElement("script");
525
+ script.id = GTRANSLATE_SCRIPT_ID;
526
+ script.src =
527
+ "//translate.google.com/translate_a/element.js?cb=googleTranslateElementInit";
528
+ script.async = true;
529
+ document.body.appendChild(script);
530
+ };
531
+ const triggerTranslation = (langCode) => {
532
+ setGoogleTranslateCookie(langCode);
533
+ const select = document.querySelector(".goog-te-combo");
534
+ if (select) {
535
+ select.value = langCode;
536
+ dispatchNativeChangeEvent(select);
537
+ }
538
+ };
539
+ const resetTranslation = () => {
540
+ const select = document.querySelector(".goog-te-combo");
541
+ if (select) {
542
+ select.value = GOOGLE_TRANSLATE_SOURCE_LANGUAGE;
543
+ dispatchNativeChangeEvent(select);
544
+ }
545
+ clearGoogleTranslateCookie();
546
+ };
547
+ const langMap = {
548
+ en: GOOGLE_TRANSLATE_SOURCE_LANGUAGE,
549
+ tl: "tl",
550
+ ceb: "ceb",
551
+ ja: "ja",
552
+ es: "es",
553
+ fr: "fr",
554
+ de: "de",
555
+ zh: "zh-CN",
556
+ ar: "ar",
557
+ };
558
+ const googleLang = langMap[state.language] || state.language;
559
+ if (googleLang === GOOGLE_TRANSLATE_SOURCE_LANGUAGE) {
560
+ resetTranslation();
561
+ return;
562
+ }
563
+ loadGoogleTranslateScript();
564
+ setGoogleTranslateCookie(googleLang);
565
+ // Wait for Google Translate widget to be ready, then trigger
566
+ const interval = window.setInterval(() => {
567
+ const select = document.querySelector(".goog-te-combo");
568
+ if (select) {
569
+ window.clearInterval(interval);
570
+ triggerTranslation(googleLang);
571
+ }
572
+ }, 300);
573
+ return () => window.clearInterval(interval);
574
+ }, [state.language]);
575
+ const translate = (key) => (manualTranslationsForLanguage === null || manualTranslationsForLanguage === void 0 ? void 0 : manualTranslationsForLanguage[key]) || defaultUiTranslations[key];
576
+ const t = (key) => { var _a, _b; return ((_a = allTranslations[state.language]) === null || _a === void 0 ? void 0 : _a[key]) || ((_b = allTranslations.en) === null || _b === void 0 ? void 0 : _b[key]) || key; };
328
577
  return (React.createElement(AccessibilityContext.Provider, { value: Object.assign(Object.assign({}, state), { setLanguage,
329
578
  increaseText,
330
579
  decreaseText,
@@ -338,6 +587,10 @@ const AccessibilityProvider = ({ children, translations: customTranslations, })
338
587
  toggleHighlightTitles,
339
588
  toggleReadableFont,
340
589
  toggleVoice,
590
+ toggleReadAloud,
591
+ setColorBlindMode,
592
+ toggleFocusIndicator,
593
+ hasManualTranslations,
341
594
  translate,
342
595
  t }) }, children));
343
596
  };
@@ -350,8 +603,8 @@ const useAccessibility = () => {
350
603
 
351
604
  const cn$1 = (...parts) => parts.filter(Boolean).join(" ");
352
605
  const AccessibilityToolbar = ({ className, style, classes, }) => {
353
- const { language, setLanguage, increaseText, decreaseText, toggleTextMagnifier, togglePauseAnimations, toggleReadingGuide, toggleHighContrast, toggleNegativeContrast, toggleLightBackground, toggleUnderlineLinks, toggleHighlightTitles, toggleReadableFont, toggleVoice, translate, textMagnifier, pauseAnimations, readingGuide, highContrast, negativeContrast, lightBackground, underlineLinks, highlightTitles, readableFont, voiceEnabled, } = useAccessibility();
354
- return (React.createElement("div", { className: cn$1("accessibility-toolbar", classes === null || classes === void 0 ? void 0 : classes.root, className), style: style },
606
+ const { language, setLanguage, increaseText, decreaseText, toggleTextMagnifier, togglePauseAnimations, toggleReadingGuide, toggleHighContrast, toggleNegativeContrast, toggleLightBackground, toggleUnderlineLinks, toggleHighlightTitles, toggleReadableFont, toggleVoice, toggleReadAloud, setColorBlindMode, toggleFocusIndicator, translate, textMagnifier, pauseAnimations, readingGuide, highContrast, negativeContrast, lightBackground, underlineLinks, highlightTitles, readableFont, voiceEnabled, readAloud, colorBlindMode, focusIndicator, } = useAccessibility();
607
+ return (React.createElement("div", { className: cn$1("accessibility-toolbar notranslate", classes === null || classes === void 0 ? void 0 : classes.root, className), style: style, translate: "no" },
355
608
  React.createElement("select", { className: cn$1(classes === null || classes === void 0 ? void 0 : classes.select), value: language, onChange: (e) => setLanguage(e.target.value) },
356
609
  React.createElement("option", { value: "en" }, "English"),
357
610
  React.createElement("option", { value: "tl" }, "Tagalog"),
@@ -373,7 +626,16 @@ const AccessibilityToolbar = ({ className, style, classes, }) => {
373
626
  React.createElement("button", { className: cn$1(classes === null || classes === void 0 ? void 0 : classes.button, readingGuide && "active", readingGuide && (classes === null || classes === void 0 ? void 0 : classes.activeButton)), "data-active": readingGuide, onClick: toggleReadingGuide }, translate("readingGuide")),
374
627
  React.createElement("button", { className: cn$1(classes === null || classes === void 0 ? void 0 : classes.button, classes === null || classes === void 0 ? void 0 : classes.voiceButton, voiceEnabled && "active", voiceEnabled && (classes === null || classes === void 0 ? void 0 : classes.activeButton)), "data-active": voiceEnabled, onClick: toggleVoice },
375
628
  "\uD83C\uDFA4 ",
376
- translate("voiceCommand"))));
629
+ translate("voiceCommand")),
630
+ React.createElement("button", { className: cn$1(classes === null || classes === void 0 ? void 0 : classes.button, readAloud && "active", readAloud && (classes === null || classes === void 0 ? void 0 : classes.activeButton)), "data-active": readAloud, onClick: toggleReadAloud },
631
+ "\uD83D\uDD0A ",
632
+ translate("readAloud")),
633
+ React.createElement("button", { className: cn$1(classes === null || classes === void 0 ? void 0 : classes.button, focusIndicator && "active", focusIndicator && (classes === null || classes === void 0 ? void 0 : classes.activeButton)), "data-active": focusIndicator, onClick: toggleFocusIndicator }, translate("focusIndicator")),
634
+ React.createElement("select", { className: cn$1(classes === null || classes === void 0 ? void 0 : classes.select), value: colorBlindMode, onChange: (e) => setColorBlindMode(e.target.value), "aria-label": translate("colorBlindMode") },
635
+ React.createElement("option", { value: "none" }, translate("colorBlindMode")),
636
+ React.createElement("option", { value: "protanopia" }, "Protanopia (Red)"),
637
+ React.createElement("option", { value: "deuteranopia" }, "Deuteranopia (Green)"),
638
+ React.createElement("option", { value: "tritanopia" }, "Tritanopia (Blue)"))));
377
639
  };
378
640
 
379
641
  const cn = (...parts) => parts.filter(Boolean).join(" ");
@@ -396,39 +658,45 @@ const AccessibilityDropdown = ({ className, style, classes, triggerLabel = "โ™ฟ
396
658
  React.createElement("div", { className: cn("a11y-panel-header", classes === null || classes === void 0 ? void 0 : classes.panelHeader) },
397
659
  React.createElement("div", { className: "a11y-panel-heading" },
398
660
  React.createElement("span", { className: "a11y-panel-eyebrow" }, "NGP"),
399
- React.createElement("label", { className: "a11y-panel-title" }, "Accessibility Options")),
661
+ React.createElement("label", { className: cn("a11y-panel-title", classes === null || classes === void 0 ? void 0 : classes.title) }, "Accessibility Options")),
400
662
  React.createElement("button", { type: "button", className: cn("a11y-panel-close", classes === null || classes === void 0 ? void 0 : classes.closeButton), onClick: closePanel, "aria-label": "Close accessibility panel" }, "\u00D7")),
401
- React.createElement("div", { className: "a11y-language" },
402
- React.createElement("label", { htmlFor: "a11y-language-select" }, "Language"),
663
+ React.createElement("div", { className: cn("a11y-language", classes === null || classes === void 0 ? void 0 : classes.section) },
664
+ React.createElement("label", { htmlFor: "a11y-language-select", className: cn("a11y-language-label", classes === null || classes === void 0 ? void 0 : classes.title) }, translate("language")),
403
665
  React.createElement("select", { id: "a11y-language-select", value: language, onChange: (e) => setLanguage(e.target.value) },
404
666
  React.createElement("option", { value: "en" }, "English"),
405
667
  React.createElement("option", { value: "tl" }, "Tagalog"),
406
- React.createElement("option", { value: "ceb" }, "Cebuano"))),
407
- React.createElement("section", { className: "a11y-section" },
408
- React.createElement("h3", { className: "a11y-section-title" }, "Content Adjustments"),
668
+ React.createElement("option", { value: "ceb" }, "Cebuano"),
669
+ React.createElement("option", { value: "ja" }, "Japanese"),
670
+ React.createElement("option", { value: "es" }, "Spanish"),
671
+ React.createElement("option", { value: "fr" }, "French"),
672
+ React.createElement("option", { value: "de" }, "German"),
673
+ React.createElement("option", { value: "zh" }, "Chinese"),
674
+ React.createElement("option", { value: "ar" }, "Arabic"))),
675
+ React.createElement("section", { className: cn("a11y-section", classes === null || classes === void 0 ? void 0 : classes.section) },
676
+ React.createElement("h3", { className: cn("a11y-section-title", classes === null || classes === void 0 ? void 0 : classes.title) }, "Content Adjustments"),
409
677
  React.createElement("div", { className: "a11y-card" },
410
- React.createElement("span", { className: "a11y-card-title" }, "Content Scaling"),
678
+ React.createElement("span", { className: cn("a11y-card-title", classes === null || classes === void 0 ? void 0 : classes.title) }, "Content Scaling"),
411
679
  React.createElement("div", { className: "a11y-stepper" },
412
680
  React.createElement("button", { onClick: decreaseText }, "\u2212"),
413
681
  React.createElement("span", { className: "a11y-stepper-value" }, textScaleLabel),
414
682
  React.createElement("button", { onClick: increaseText }, "+"))),
415
- React.createElement("div", { className: "a11y-grid" },
416
- React.createElement("button", { className: cn("a11y-card", textMagnifier && "active"), onClick: toggleTextMagnifier }, translate("textMagnifier")),
417
- React.createElement("button", { className: cn("a11y-card", readableFont && "active"), onClick: toggleReadableFont }, "Readable Font"),
418
- React.createElement("button", { className: cn("a11y-card", underlineLinks && "active"), onClick: toggleUnderlineLinks }, "Highlight Links"),
419
- React.createElement("button", { className: cn("a11y-card", highlightTitles && "active"), onClick: toggleHighlightTitles }, "Highlight Titles"))),
420
- React.createElement("section", { className: "a11y-section" },
421
- React.createElement("h3", { className: "a11y-section-title" }, "Color Adjustments"),
422
- React.createElement("div", { className: "a11y-grid" },
423
- React.createElement("button", { className: cn("a11y-card", highContrast && "active"), onClick: toggleHighContrast }, translate("highContrast")),
424
- React.createElement("button", { className: cn("a11y-card", negativeContrast && "active"), onClick: toggleNegativeContrast }, translate("negativeContrast")),
425
- React.createElement("button", { className: cn("a11y-card", lightBackground && "active"), onClick: toggleLightBackground }, translate("lightBackground")))),
426
- React.createElement("section", { className: "a11y-section" },
427
- React.createElement("h3", { className: "a11y-section-title" }, "Accessibility Tools"),
428
- React.createElement("div", { className: "a11y-grid" },
429
- React.createElement("button", { className: cn("a11y-card", pauseAnimations && "active"), onClick: togglePauseAnimations }, translate("pauseAnimations")),
430
- React.createElement("button", { className: cn("a11y-card", readingGuide && "active"), onClick: toggleReadingGuide }, translate("readingGuide")),
431
- React.createElement("button", { className: cn("a11y-card", voiceEnabled && "active"), onClick: toggleVoice }, translate("voiceCommand"))))))));
683
+ React.createElement("div", { className: cn("a11y-grid", classes === null || classes === void 0 ? void 0 : classes.section) },
684
+ React.createElement("button", { className: cn("a11y-card", textMagnifier && "active", classes === null || classes === void 0 ? void 0 : classes.button), onClick: toggleTextMagnifier }, translate("textMagnifier")),
685
+ React.createElement("button", { className: cn("a11y-card", readableFont && "active", classes === null || classes === void 0 ? void 0 : classes.button), onClick: toggleReadableFont }, "Readable Font"),
686
+ React.createElement("button", { className: cn("a11y-card", underlineLinks && "active", classes === null || classes === void 0 ? void 0 : classes.button), onClick: toggleUnderlineLinks }, "Highlight Links"),
687
+ React.createElement("button", { className: cn("a11y-card", highlightTitles && "active", classes === null || classes === void 0 ? void 0 : classes.button), onClick: toggleHighlightTitles }, "Highlight Titles"))),
688
+ React.createElement("section", { className: cn("a11y-section", classes === null || classes === void 0 ? void 0 : classes.section) },
689
+ React.createElement("h3", { className: cn("a11y-section-title", classes === null || classes === void 0 ? void 0 : classes.title) }, "Color Adjustments"),
690
+ React.createElement("div", { className: cn("a11y-grid", classes === null || classes === void 0 ? void 0 : classes.section) },
691
+ React.createElement("button", { className: cn("a11y-card", highContrast && "active", classes === null || classes === void 0 ? void 0 : classes.button), onClick: toggleHighContrast }, translate("highContrast")),
692
+ React.createElement("button", { className: cn("a11y-card", negativeContrast && "active", classes === null || classes === void 0 ? void 0 : classes.button), onClick: toggleNegativeContrast }, translate("negativeContrast")),
693
+ React.createElement("button", { className: cn("a11y-card", lightBackground && "active", classes === null || classes === void 0 ? void 0 : classes.button), onClick: toggleLightBackground }, translate("lightBackground")))),
694
+ React.createElement("section", { className: cn("a11y-section", classes === null || classes === void 0 ? void 0 : classes.section) },
695
+ React.createElement("h3", { className: cn("a11y-section-title", classes === null || classes === void 0 ? void 0 : classes.title) }, "Accessibility Tools"),
696
+ React.createElement("div", { className: cn("a11y-grid", classes === null || classes === void 0 ? void 0 : classes.section) },
697
+ React.createElement("button", { className: cn("a11y-card", pauseAnimations && "active", classes === null || classes === void 0 ? void 0 : classes.button), onClick: togglePauseAnimations }, translate("pauseAnimations")),
698
+ React.createElement("button", { className: cn("a11y-card", readingGuide && "active", classes === null || classes === void 0 ? void 0 : classes.button), onClick: toggleReadingGuide }, translate("readingGuide")),
699
+ React.createElement("button", { className: cn("a11y-card", voiceEnabled && "active", classes === null || classes === void 0 ? void 0 : classes.button), onClick: toggleVoice }, translate("voiceCommand"))))))));
432
700
  };
433
701
 
434
702
  const T = ({ k, children }) => {