@readium/shared 2.0.0 → 2.1.1

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.
@@ -0,0 +1,203 @@
1
+ # Accessibility Metadata Localization
2
+
3
+ This document explains how to use and customize the localization system for accessibility metadata in the `@readium/shared` package.
4
+
5
+ ## Overview
6
+
7
+ The localization system allows consumers to provide their own translations for accessibility metadata strings while falling back to English if none is provided. The system uses a singleton pattern for easy access throughout the application and supports multiple registered locales.
8
+
9
+ ## Default Behavior
10
+
11
+ By default, the system uses English (`en`) locale strings that are bundled with the package. These strings are included in the package and cover all accessibility metadata display needs.
12
+
13
+ ## Using the Localization System
14
+
15
+ ### Registering Custom Locales
16
+
17
+ You can register new locales or override existing ones using the `registerLocale` method:
18
+
19
+ ```typescript
20
+ // Register a new locale
21
+ Localization.registerLocale('es', {
22
+ conformance: {
23
+ aaa: {
24
+ compact: "WCAG 2.1 Nivel AAA",
25
+ descriptive: "Esta publicación cumple con WCAG 2.1 Nivel AAA."
26
+ }
27
+ }
28
+ });
29
+
30
+ // Or override an existing locale
31
+ Localization.registerLocale('en', {
32
+ conformance: {
33
+ aaa: {
34
+ compact: "WCAG 2.1 Level AAA (Custom)",
35
+ descriptive: "This publication conforms to WCAG 2.1 Level AAA (Custom)."
36
+ }
37
+ }
38
+ });
39
+ ```
40
+
41
+ ### Setting the Current Locale
42
+
43
+ ```typescript
44
+ // Set the current locale by language code
45
+ Localization.setLocale('es');
46
+
47
+ // Check if the locale was set successfully
48
+ if (Localization.setLocale('es')) {
49
+ console.log('Locale set successfully');
50
+ } else {
51
+ console.log('Locale not available');
52
+ }
53
+ ```
54
+
55
+ ### Getting Localized Strings
56
+
57
+ ```typescript
58
+ // Get a localized string
59
+ const text = Localization.getString('conformance.aaa');
60
+ // Returns: { compact: "WCAG 2.1 Nivel AAA", descriptive: "..." }
61
+ ```
62
+
63
+ ### Getting Available Locales
64
+
65
+ ```typescript
66
+ // Get list of available locale codes
67
+ const availableLocales = Localization.getAvailableLocales();
68
+ // Returns: ['en', 'fr', 'es', ...]
69
+
70
+ // Get current locale code
71
+ const currentLocale = Localization.getCurrentLocale();
72
+ // Returns: 'es'
73
+ ```
74
+
75
+ ## Locale Object Structure
76
+
77
+ The locale object is a nested structure where:
78
+ - Keys are nested objects representing the path to the value
79
+ - Values can be either:
80
+ - A string, which will be used for both `compact` and `descriptive` values
81
+ - An object with `compact` and `descriptive` string properties
82
+
83
+ Example of valid locale values:
84
+
85
+ ```typescript
86
+ const locale = {
87
+ conformance: {
88
+ aaa: "This publication conforms to WCAG 2.1 Level AAA",
89
+ hazards: {
90
+ none: {
91
+ compact: "No hazards",
92
+ descriptive: "This content is known to be free of hazards."
93
+ }
94
+ }
95
+ }
96
+ };
97
+ ```
98
+
99
+ ## Fallback Behavior
100
+
101
+ If a key is not found in the custom locale, the system will fall back to the English locale. If the key is not found in either locale, a warning will be logged to the console and an empty string will be returned.
102
+
103
+ ## Available Locale Keys
104
+
105
+ The following keys are used throughout the accessibility metadata system:
106
+
107
+ ### Conformance
108
+ - `conformance.no`
109
+ - `conformance.a`
110
+ - `conformance.aa`
111
+ - `conformance.aaa`
112
+ - `conformance.unknown-standard`
113
+
114
+ ### Hazards
115
+ - `hazards.none`
116
+ - `hazards.unknown`
117
+ - `hazards.no-metadata`
118
+ - `hazards.flashing`
119
+ - `hazards.flashing-unknown`
120
+ - `hazards.flashing-none`
121
+ - `hazards.motion`
122
+ - `hazards.motion-unknown`
123
+ - `hazards.motion-none`
124
+ - `hazards.sound`
125
+ - `hazards.sound-unknown`
126
+ - `hazards.sound-none`
127
+
128
+ ### Navigation
129
+ - `navigation.toc`
130
+ - `navigation.index`
131
+ - `navigation.structural`
132
+ - `navigation.page-navigation`
133
+ - `navigation.no-metadata`
134
+
135
+ ### Rich Content
136
+ - `rich-content.extended-descriptions`
137
+ - `rich-content.accessible-math-described`
138
+ - `rich-content.accessible-math-as-mathml`
139
+ - `rich-content.accessible-math-as-latex`
140
+ - `rich-content.accessible-chemistry-as-mathml`
141
+ - `rich-content.accessible-chemistry-as-latex`
142
+ - `rich-content.closed-captions`
143
+ - `rich-content.open-captions`
144
+ - `rich-content.transcript`
145
+ - `rich-content.unknown`
146
+
147
+ ### Additional Accessibility Information
148
+ - `additional-accessibility-information.page-breaks`
149
+ - `additional-accessibility-information.aria`
150
+ - `additional-accessibility-information.audio-descriptions`
151
+ - `additional-accessibility-information.braille`
152
+ - `additional-accessibility-information.ruby-annotations`
153
+ - `additional-accessibility-information.full-ruby-annotations`
154
+ - `additional-accessibility-information.high-contrast-between-foreground-and-background-audio`
155
+ - `additional-accessibility-information.high-contrast-between-text-and-background`
156
+ - `additional-accessibility-information.large-print`
157
+ - `additional-accessibility-information.sign-language`
158
+ - `additional-accessibility-information.tactile-graphics`
159
+ - `additional-accessibility-information.tactile-objects`
160
+ - `additional-accessibility-information.text-to-speech-hinting`
161
+
162
+ ### Legal Considerations
163
+ - `legal-considerations.exempt`
164
+ - `legal-considerations.no-metadata`
165
+
166
+ ### Accessibility Summary
167
+ - `accessibility-summary.no-metadata`
168
+
169
+ ## Best Practices
170
+
171
+ 1. **Provide both compact and descriptive versions** when possible by using an object with both properties. This allows for more precise control over the display text.
172
+ 2. **Test your custom locales** to ensure all necessary keys are provided.
173
+ 3. **Handle missing translations gracefully** - the system will log warnings for missing keys.
174
+
175
+ ## TypeScript Support
176
+
177
+ When using TypeScript, the locale object should match the following structure:
178
+
179
+ ```typescript
180
+ type LocalizedValue = string | {
181
+ compact: string;
182
+ descriptive: string;
183
+ };
184
+
185
+ type LocaleObject = {
186
+ [key: string]: LocalizedValue;
187
+ };
188
+
189
+ // Example usage:
190
+ const customLocale = {
191
+ conformance: {
192
+ aaa: "This publication conforms to WCAG 2.1 Level AAA",
193
+ ...
194
+ },
195
+ hazards: {
196
+ none: {
197
+ compact: "No hazards",
198
+ descriptive: "This content is known to be free of hazards."
199
+ },
200
+ ...
201
+ }
202
+ };
203
+ ```
@@ -0,0 +1,115 @@
1
+ // Localization.ts
2
+ import enUS from './locales/en.json';
3
+ import frFR from './locales/fr.json';
4
+
5
+ export interface L10nString {
6
+ compact: string;
7
+ descriptive: string;
8
+ }
9
+
10
+ type LocaleData = Record<string, any>;
11
+
12
+ class LocalizationImpl {
13
+ private static instance: LocalizationImpl;
14
+ private currentLocaleCode: string = 'en';
15
+ private locale: LocaleData = enUS;
16
+ private availableLocales: Record<string, LocaleData> = {
17
+ 'en': enUS,
18
+ 'fr': frFR
19
+ };
20
+
21
+ private constructor() {}
22
+
23
+ public static getInstance(): LocalizationImpl {
24
+ if (!LocalizationImpl.instance) {
25
+ LocalizationImpl.instance = new LocalizationImpl();
26
+ }
27
+ return LocalizationImpl.instance;
28
+ }
29
+
30
+ /**
31
+ * Registers a new locale or updates an existing one
32
+ * @param localeCode BCP 47 language code (e.g., 'en', 'fr-FR')
33
+ * @param localeData The locale data to register
34
+ */
35
+ public registerLocale(localeCode: string, localeData: LocaleData): void {
36
+ if (!localeCode || typeof localeCode !== 'string') {
37
+ throw new Error('Locale code must be a non-empty string');
38
+ }
39
+ this.availableLocales[localeCode] = localeData;
40
+ }
41
+
42
+ /**
43
+ * Sets the current locale by language code
44
+ * @param localeCode BCP 47 language code (e.g., 'en', 'fr-FR')
45
+ * @returns boolean indicating if the locale was set successfully
46
+ */
47
+ public setLocale(localeCode: string): boolean {
48
+ if (!(localeCode in this.availableLocales)) {
49
+ console.warn(`Locale '${localeCode}' is not available`);
50
+ return false;
51
+ }
52
+ this.locale = this.availableLocales[localeCode];
53
+ this.currentLocaleCode = localeCode;
54
+ return true;
55
+ }
56
+
57
+ /**
58
+ * Gets the current locale code (BCP 47)
59
+ */
60
+ public getCurrentLocale(): string {
61
+ return this.currentLocaleCode;
62
+ }
63
+
64
+ /**
65
+ * Gets a list of available locale codes
66
+ */
67
+ public getAvailableLocales(): string[] {
68
+ return Object.keys(this.availableLocales);
69
+ }
70
+
71
+ private getNestedValue(obj: any, path: string): string | L10nString | undefined {
72
+ const parts = path.split('.');
73
+ let current = obj;
74
+
75
+ for (const part of parts) {
76
+ if (current === null || current === undefined) {
77
+ return undefined;
78
+ }
79
+ current = current[part];
80
+ }
81
+
82
+ return current;
83
+ }
84
+
85
+ /**
86
+ * Gets a localized string by key
87
+ * @param key The key for the string to retrieve
88
+ * @returns The localized string as a [L10nString], or an empty string if not found
89
+ */
90
+ public getString(key: string): L10nString {
91
+ // First try the current locale
92
+ let value = this.getNestedValue(this.locale, key);
93
+
94
+ // If not found and current locale is not English, try falling back to English
95
+ if (value === undefined && this.currentLocaleCode !== 'en') {
96
+ value = this.getNestedValue(this.availableLocales['en'], key);
97
+ }
98
+
99
+ // If we have a value, return it with proper formatting
100
+ if (value !== undefined) {
101
+ return typeof value === 'string'
102
+ ? { compact: value, descriptive: value }
103
+ : value;
104
+ }
105
+
106
+ // If we get here, the key wasn't found in either locale
107
+ console.warn(`Missing localization for key: ${key}`);
108
+ return { compact: '', descriptive: '' };
109
+ }
110
+ }
111
+
112
+ /**
113
+ * The singleton instance of the [Localization] class.
114
+ */
115
+ export const Localization = LocalizationImpl.getInstance();
@@ -0,0 +1,3 @@
1
+ export * from './Accessibility';
2
+ export * from './AccessibilityMetadataDisplayGuide';
3
+ export * from './Localization';
@@ -0,0 +1,231 @@
1
+ {
2
+ "ways-of-reading": {
3
+ "title": "Ways of reading",
4
+ "nonvisual-reading": {
5
+ "alt-text": {
6
+ "compact": "Has alternative text",
7
+ "descriptive": "Has alternative text descriptions for images"
8
+ },
9
+ "no-metadata": "No information about nonvisual reading is available",
10
+ "none": {
11
+ "compact": "Not readable in read aloud or dynamic braille",
12
+ "descriptive": "The content is not readable as read aloud speech or dynamic braille"
13
+ },
14
+ "not-fully": {
15
+ "compact": "Not fully readable in read aloud or dynamic braille",
16
+ "descriptive": "Not all of the content will be readable as read aloud speech or dynamic braille"
17
+ },
18
+ "readable": {
19
+ "compact": "Readable in read aloud or dynamic braille",
20
+ "descriptive": "All content can be read as read aloud speech or dynamic braille"
21
+ }
22
+ },
23
+ "prerecorded-audio": {
24
+ "complementary": {
25
+ "compact": "Prerecorded audio clips",
26
+ "descriptive": "Prerecorded audio clips are embedded in the content"
27
+ },
28
+ "no-metadata": "No information about prerecorded audio is available",
29
+ "only": {
30
+ "compact": "Prerecorded audio only",
31
+ "descriptive": "Audiobook with no text alternative"
32
+ },
33
+ "synchronized": {
34
+ "compact": "Prerecorded audio synchronized with text",
35
+ "descriptive": "All the content is available as prerecorded audio synchronized with text"
36
+ }
37
+ },
38
+ "visual-adjustments": {
39
+ "modifiable": {
40
+ "compact": "Appearance can be modified",
41
+ "descriptive": "Appearance of the text and page layout can be modified according to the capabilities of the reading system (font family and font size, spaces between paragraphs, sentences, words, and letters, as well as color of background and text)"
42
+ },
43
+ "unknown": "No information about appearance modifiability is available",
44
+ "unmodifiable": {
45
+ "compact": "Appearance cannot be modified",
46
+ "descriptive": "Text and page layout cannot be modified as the reading experience is close to a print version, but reading systems can still provide zooming options"
47
+ }
48
+ }
49
+ },
50
+ "conformance": {
51
+ "title": "Conformance",
52
+ "details-title": "Detailed conformance information",
53
+ "a": {
54
+ "compact": "This publication meets minimum accessibility standards",
55
+ "descriptive": "The publication contains a conformance statement that it meets the EPUB Accessibility and WCAG 2 Level A standard"
56
+ },
57
+ "aa": {
58
+ "compact": "This publication meets accepted accessibility standards",
59
+ "descriptive": "The publication contains a conformance statement that it meets the EPUB Accessibility and WCAG 2 Level AA standard"
60
+ },
61
+ "aaa": {
62
+ "compact": "This publication exceeds accepted accessibility standards",
63
+ "descriptive": "The publication contains a conformance statement that it meets the EPUB Accessibility and WCAG 2 Level AAA standard"
64
+ },
65
+ "no": "No information is available",
66
+ "unknown-standard": "Conformance to accepted standards for accessibility of this publication cannot be determined",
67
+ "certifier": "The publication was certified by ",
68
+ "certifier-credentials": "The certifier's credential is ",
69
+ "details": {
70
+ "certification-info": "The publication was certified on ",
71
+ "certifier-report": "For more information refer to the certifier's report",
72
+ "claim": "This publication claims to meet",
73
+ "epub-accessibility-1-0": "EPUB Accessibility 1.0",
74
+ "epub-accessibility-1-1": "EPUB Accessibility 1.1",
75
+ "level-a": "Level A",
76
+ "level-aa": "Level AA",
77
+ "level-aaa": "Level AAA",
78
+ "wcag-2-0": {
79
+ "compact": "WCAG 2.0",
80
+ "descriptive": "Web Content Accessibility Guidelines (WCAG) 2.0"
81
+ },
82
+ "wcag-2-1": {
83
+ "compact": "WCAG 2.1",
84
+ "descriptive": "Web Content Accessibility Guidelines (WCAG) 2.1"
85
+ },
86
+ "wcag-2-2": {
87
+ "compact": "WCAG 2.2",
88
+ "descriptive": "Web Content Accessibility Guidelines (WCAG) 2.2"
89
+ }
90
+ }
91
+ },
92
+ "navigation": {
93
+ "title": "Navigation",
94
+ "index": {
95
+ "compact": "Index",
96
+ "descriptive": "Index with links to referenced entries"
97
+ },
98
+ "no-metadata": "No information is available",
99
+ "page-navigation": {
100
+ "compact": "Go to page",
101
+ "descriptive": "Page list to go to pages from the print source version"
102
+ },
103
+ "structural": {
104
+ "compact": "Headings",
105
+ "descriptive": "Elements such as headings, tables, etc for structured navigation"
106
+ },
107
+ "toc": {
108
+ "compact": "Table of contents",
109
+ "descriptive": "Table of contents to all chapters of the text via links"
110
+ }
111
+ },
112
+ "rich-content": {
113
+ "title": "Rich content",
114
+ "accessible-chemistry-as-latex": {
115
+ "compact": "Chemical formulas in LaTeX",
116
+ "descriptive": "Chemical formulas in accessible format (LaTeX)"
117
+ },
118
+ "accessible-chemistry-as-mathml": {
119
+ "compact": "Chemical formulas in MathML",
120
+ "descriptive": "Chemical formulas in accessible format (MathML)"
121
+ },
122
+ "accessible-math-as-latex": {
123
+ "compact": "Math as LaTeX",
124
+ "descriptive": "Math formulas in accessible format (LaTeX)"
125
+ },
126
+ "math-as-mathml": {
127
+ "compact": "Math as MathML",
128
+ "descriptive": "Math formulas in accessible format (MathML)"
129
+ },
130
+ "accessible-math-described": "Text descriptions of math are provided",
131
+ "closed-captions": {
132
+ "compact": "Videos have closed captions",
133
+ "descriptive": "Videos included in publications have closed captions"
134
+ },
135
+ "extended-descriptions": "Information-rich images are described by extended descriptions",
136
+ "open-captions": {
137
+ "compact": "Videos have open captions",
138
+ "descriptive": "Videos included in publications have open captions"
139
+ },
140
+ "transcript": "Transcript(s) provided",
141
+ "unknown": "No information is available"
142
+ },
143
+ "hazards": {
144
+ "title": "Hazards",
145
+ "flashing": {
146
+ "compact": "Flashing content",
147
+ "descriptive": "The publication contains flashing content that can cause photosensitive seizures"
148
+ },
149
+ "flashing-none": {
150
+ "compact": "No flashing hazards",
151
+ "descriptive": "The publication does not contain flashing content that can cause photosensitive seizures"
152
+ },
153
+ "flashing-unknown": {
154
+ "compact": "Flashing hazards not known",
155
+ "descriptive": "The presence of flashing content that can cause photosensitive seizures could not be determined"
156
+ },
157
+ "motion": {
158
+ "compact": "Motion simulation",
159
+ "descriptive": "The publication contains motion simulations that can cause motion sickness"
160
+ },
161
+ "motion-none": {
162
+ "compact": "No motion simulation hazards",
163
+ "descriptive": "The publication does not contain motion simulations that can cause motion sickness"
164
+ },
165
+ "motion-unknown": {
166
+ "compact": "Motion simulation hazards not known",
167
+ "descriptive": "The presence of motion simulations that can cause motion sickness could not be determined"
168
+ },
169
+ "no-metadata": "No information is available",
170
+ "none": {
171
+ "compact": "No hazards",
172
+ "descriptive": "The publication contains no hazards"
173
+ },
174
+ "sound": {
175
+ "compact": "Sounds",
176
+ "descriptive": "The publication contains sounds that can cause sensitivity issues"
177
+ },
178
+ "sound-none": {
179
+ "compact": "No sound hazards",
180
+ "descriptive": "The publication does not contain sounds that can cause sensitivity issues"
181
+ },
182
+ "sound-unknown": {
183
+ "compact": "Sound hazards not known",
184
+ "descriptive": "The presence of sounds that can cause sensitivity issues could not be determined"
185
+ },
186
+ "unknown": "The presence of hazards is unknown"
187
+ },
188
+ "accessibility-summary": {
189
+ "title": "Accessibility summary",
190
+ "no-metadata": "No information is available",
191
+ "publisher-contact": "For more information about the accessibility of this product, please contact the publisher: "
192
+ },
193
+ "legal-considerations": {
194
+ "title": "Legal considerations",
195
+ "exempt": {
196
+ "compact": "Claims an accessibility exemption in some jurisdictions",
197
+ "descriptive": "This publication claims an accessibility exemption in some jurisdictions"
198
+ },
199
+ "no-metadata": "No information is available"
200
+ },
201
+ "additional-accessibility-information": {
202
+ "title": "Additional accessibility information",
203
+ "aria": {
204
+ "compact": "ARIA roles included",
205
+ "descriptive": "Content is enhanced with ARIA roles to optimize organization and facilitate navigation"
206
+ },
207
+ "audio-descriptions": "Audio descriptions",
208
+ "braille": "Braille",
209
+ "color-not-sole-means-of-conveying-information": "Color is not the sole means of conveying information",
210
+ "dyslexia-readability": "Dyslexia readability",
211
+ "full-ruby-annotations": "Full ruby annotations",
212
+ "high-contrast-between-foreground-and-background-audio": "High contrast between foreground and background audio",
213
+ "high-contrast-between-text-and-background": "High contrast between foreground text and background",
214
+ "large-print": "Large print",
215
+ "page-breaks": {
216
+ "compact": "Page breaks included",
217
+ "descriptive": "Page breaks included from the original print source"
218
+ },
219
+ "ruby-annotations": "Some Ruby annotations",
220
+ "sign-language": "Sign language",
221
+ "tactile-graphics": {
222
+ "compact": "Tactile graphics included",
223
+ "descriptive": "Tactile graphics have been integrated to facilitate access to visual elements for blind people"
224
+ },
225
+ "tactile-objects": "Tactile 3D objects",
226
+ "text-to-speech-hinting": "Text-to-speech hinting provided",
227
+ "ultra-high-contrast-between-text-and-background": "Ultra high contrast between text and background",
228
+ "visible-page-numbering": "Visible page numbering",
229
+ "without-background-sounds": "Without background sounds"
230
+ }
231
+ }