@pie-players/pie-players-shared 1.0.0-beta.7 → 1.0.0-beta.8
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 +3 -1
- package/dist/i18n/catalogs.d.ts +20 -13
- package/dist/i18n/catalogs.js +22 -23
- package/dist/i18n/language-tags.d.ts +23 -6
- package/dist/i18n/language-tags.js +74 -9
- package/dist/i18n/messages/en-US.d.ts +17 -0
- package/dist/i18n/messages/en-US.js +19 -0
- package/dist/i18n/types.d.ts +6 -1
- package/dist/security/private-hosts.d.ts +27 -0
- package/dist/security/private-hosts.js +85 -0
- package/dist/tools/term-lookup.d.ts +15 -0
- package/dist/tools/term-lookup.js +16 -0
- package/dist/types/index.d.ts +16 -2
- package/dist/types/interchange.d.ts +66 -4
- package/dist/ui/fontawesome.d.ts +51 -0
- package/dist/ui/fontawesome.js +122 -0
- package/package.json +12 -3
- package/dist/i18n/messages/nl-NL.d.ts +0 -639
- package/dist/i18n/messages/nl-NL.js +0 -639
package/README.md
CHANGED
|
@@ -20,6 +20,7 @@ The subpaths below are the package's `exports`; nothing else is importable.
|
|
|
20
20
|
| `@pie-players/pie-players-shared` | The root: the loader, PIE, security and object helpers, loader config, instrumentation providers, the item-player strategy helpers, the default bundle host URL (`BUILDER_BUNDLE_URL`), a subset of the shared types, and the UI helpers (attribute coercion, content styles, pointer gestures, overlay placement, focus trap, safe storage, scoped CSS) |
|
|
21
21
|
| `/loader-config` | `LoaderConfig` and its defaults: instrumentation (`trackPageActions`, `instrumentationProvider`), resource retry, and IIFE bundle retry |
|
|
22
22
|
| `/security` | The item-markup sanitizer (`sanitizeItemMarkup`, `createDefaultItemMarkupSanitizer`, `buildAuthoringAllowList`), external-stylesheet URL validation, the SVG icon and `style` attribute sanitizers, and the wrappers that put overwide images and tables in a scrollable region |
|
|
23
|
+
| `/security/private-hosts` | `isPrivateHostname` and `isCloudMetadataHostname`, the host classification for a URL a server fetches on someone else's behalf. Node-safe: it loads none of the markup sanitizers |
|
|
23
24
|
| `/object` | `mergeObjectsIgnoringNullUndefined`, `cloneDeep`, `isPlainRecord` |
|
|
24
25
|
| `/types` | The shared types: item, passage, section and session entities, PIE models and controllers, accessibility catalogs, and the [media vocabulary](#media-validation) |
|
|
25
26
|
| `/formative` | [Formative delivery](#formative-delivery) |
|
|
@@ -33,9 +34,10 @@ The subpaths below are the package's `exports`; nothing else is importable.
|
|
|
33
34
|
| `/i18n/provider` | `SimpleI18n`, `getDefaultI18n` and `resolveInterfaceI18n`, with the English catalog only |
|
|
34
35
|
| `/i18n/catalogs` | `BUNDLED_LOCALES` and lazy loaders for the bundled locales other than English |
|
|
35
36
|
| `/i18n/language-tags` | BCP 47 language tag normalization and matching |
|
|
36
|
-
| `/nds-icon-button` | `<nds-icon-button>`, an icon button vendored as a prebuilt bundle with Lit inlined; importing it registers the tag. The assessment toolkit and `@pie-players/pie-tool-tts-inline` render it |
|
|
37
|
+
| `/nds-icon-button` | `<nds-icon-button>`, an icon button vendored as a prebuilt bundle with Lit inlined; importing it registers the tag. The assessment toolkit, the section player and `@pie-players/pie-tool-tts-inline` render it |
|
|
37
38
|
| `/ui/attribute-coercion` | Boolean attribute coercion for custom elements |
|
|
38
39
|
| `/ui/content-styles` | `installContentStyles`, `auditContentStyles` and the opt-out checks behind the item player's content styles |
|
|
40
|
+
| `/ui/fontawesome` | The FontAwesome `<nds-icon-button>` glyphs need: the one FA Free pin, Pro detection, the `<head>` and shadow-root installs, and the glyph weight. The assessment toolkit, the section player and `@pie-players/pie-tool-tts-inline` use it |
|
|
39
41
|
| `/tools/term-lookup` | Term normalization and the lookup session the dictionary tools share; the host supplies the endpoint or a resolver |
|
|
40
42
|
|
|
41
43
|
## Formative Delivery
|
package/dist/i18n/catalogs.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* The bundled catalog loader: `en-US`, the one statically shipped catalog.
|
|
3
3
|
*
|
|
4
4
|
* Imported by players, never by tools. A tool resolves its provider from the
|
|
5
5
|
* toolkit runtime context and needs only the interface plus the English
|
|
@@ -7,25 +7,32 @@
|
|
|
7
7
|
* into a bundle that never calls `setLocale`, because every tool
|
|
8
8
|
* `vite.config.ts` sets `external: []`.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* alongside the file.
|
|
10
|
+
* `en-US` is the source of truth — the fallback for every missing key in every
|
|
11
|
+
* other locale — so the provider imports it statically and it is always
|
|
12
|
+
* resident. It is the only catalog this package ships: every other locale this
|
|
13
|
+
* repository ships lives in the optional generated tier
|
|
14
|
+
* (`@pie-players/pie-i18n-generated`), whose loaders a host composes after
|
|
15
|
+
* this one. The composed shape is that package's README's wiring example:
|
|
17
16
|
*
|
|
18
|
-
*
|
|
19
|
-
* locale
|
|
17
|
+
* ```ts
|
|
18
|
+
* loadCatalog: (locale) =>
|
|
19
|
+
* loadBundledCatalog(locale).catch(() => loadGeneratedCatalog(locale));
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* A request this function cannot serve rejects, and the provider treats that
|
|
23
|
+
* as "resolve through the fallback chain" rather than an error, so a host
|
|
24
|
+
* locale nothing ships still renders English.
|
|
20
25
|
*
|
|
21
26
|
* @module @pie-players/pie-players-shared/i18n/catalogs
|
|
22
27
|
*/
|
|
23
28
|
import type { BundledLocaleCode, MessageCatalog } from "./types.js";
|
|
24
29
|
/**
|
|
25
|
-
* Every locale tag this repository
|
|
30
|
+
* Every locale tag this repository bundles a catalog for. `en-US` alone: the
|
|
31
|
+
* statically resident source of truth.
|
|
26
32
|
*
|
|
27
33
|
* The provider resolves a request against this list through RFC 4647 lookup and
|
|
28
|
-
* primary-subtag widening, so `
|
|
34
|
+
* primary-subtag widening, so `en`, `en_US` and `en-GB`-without-a-catalog all
|
|
35
|
+
* land on `en-US` unless a composed loader serves the region.
|
|
29
36
|
*/
|
|
30
37
|
export declare const BUNDLED_LOCALES: readonly BundledLocaleCode[];
|
|
31
38
|
/**
|
|
@@ -36,7 +43,7 @@ export declare const BUNDLED_LOCALES: readonly BundledLocaleCode[];
|
|
|
36
43
|
* or a pseudo-localization of the English catalog — asks for it by name like
|
|
37
44
|
* any other locale.
|
|
38
45
|
*
|
|
39
|
-
*
|
|
46
|
+
* Rejects for a locale with no bundled catalog; the provider treats that as
|
|
40
47
|
* "resolve through the fallback chain" rather than an error, so a host locale we
|
|
41
48
|
* do not ship still renders.
|
|
42
49
|
*/
|
package/dist/i18n/catalogs.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* The bundled catalog loader: `en-US`, the one statically shipped catalog.
|
|
3
3
|
*
|
|
4
4
|
* Imported by players, never by tools. A tool resolves its provider from the
|
|
5
5
|
* toolkit runtime context and needs only the interface plus the English
|
|
@@ -7,30 +7,34 @@
|
|
|
7
7
|
* into a bundle that never calls `setLocale`, because every tool
|
|
8
8
|
* `vite.config.ts` sets `external: []`.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* alongside the file.
|
|
10
|
+
* `en-US` is the source of truth — the fallback for every missing key in every
|
|
11
|
+
* other locale — so the provider imports it statically and it is always
|
|
12
|
+
* resident. It is the only catalog this package ships: every other locale this
|
|
13
|
+
* repository ships lives in the optional generated tier
|
|
14
|
+
* (`@pie-players/pie-i18n-generated`), whose loaders a host composes after
|
|
15
|
+
* this one. The composed shape is that package's README's wiring example:
|
|
17
16
|
*
|
|
18
|
-
*
|
|
19
|
-
* locale
|
|
17
|
+
* ```ts
|
|
18
|
+
* loadCatalog: (locale) =>
|
|
19
|
+
* loadBundledCatalog(locale).catch(() => loadGeneratedCatalog(locale));
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* A request this function cannot serve rejects, and the provider treats that
|
|
23
|
+
* as "resolve through the fallback chain" rather than an error, so a host
|
|
24
|
+
* locale nothing ships still renders English.
|
|
20
25
|
*
|
|
21
26
|
* @module @pie-players/pie-players-shared/i18n/catalogs
|
|
22
27
|
*/
|
|
23
28
|
import enUS from "./messages/en-US.js";
|
|
24
|
-
const CATALOG_LOADERS = {
|
|
25
|
-
"nl-NL": () => import("./messages/nl-NL.js"),
|
|
26
|
-
};
|
|
27
29
|
/**
|
|
28
|
-
* Every locale tag this repository
|
|
30
|
+
* Every locale tag this repository bundles a catalog for. `en-US` alone: the
|
|
31
|
+
* statically resident source of truth.
|
|
29
32
|
*
|
|
30
33
|
* The provider resolves a request against this list through RFC 4647 lookup and
|
|
31
|
-
* primary-subtag widening, so `
|
|
34
|
+
* primary-subtag widening, so `en`, `en_US` and `en-GB`-without-a-catalog all
|
|
35
|
+
* land on `en-US` unless a composed loader serves the region.
|
|
32
36
|
*/
|
|
33
|
-
export const BUNDLED_LOCALES = ["en-US"
|
|
37
|
+
export const BUNDLED_LOCALES = ["en-US"];
|
|
34
38
|
/**
|
|
35
39
|
* Load one shipped catalog.
|
|
36
40
|
*
|
|
@@ -39,7 +43,7 @@ export const BUNDLED_LOCALES = ["en-US", "nl-NL"];
|
|
|
39
43
|
* or a pseudo-localization of the English catalog — asks for it by name like
|
|
40
44
|
* any other locale.
|
|
41
45
|
*
|
|
42
|
-
*
|
|
46
|
+
* Rejects for a locale with no bundled catalog; the provider treats that as
|
|
43
47
|
* "resolve through the fallback chain" rather than an error, so a host locale we
|
|
44
48
|
* do not ship still renders.
|
|
45
49
|
*/
|
|
@@ -47,10 +51,5 @@ export async function loadBundledCatalog(locale) {
|
|
|
47
51
|
if (locale === "en-US") {
|
|
48
52
|
return enUS;
|
|
49
53
|
}
|
|
50
|
-
|
|
51
|
-
if (!loader) {
|
|
52
|
-
throw new Error(`No bundled i18n catalog for locale: ${locale}`);
|
|
53
|
-
}
|
|
54
|
-
const module = await loader();
|
|
55
|
-
return module.default;
|
|
54
|
+
throw new Error(`No bundled i18n catalog for locale: ${locale}`);
|
|
56
55
|
}
|
|
@@ -78,19 +78,31 @@ export declare function languageTagScriptLookupSequence(tag: string | null | und
|
|
|
78
78
|
* `zh-TW` passes it by.
|
|
79
79
|
*/
|
|
80
80
|
export declare function findBestLanguageMatch(requested: string | null | undefined, available: readonly (string | null | undefined)[]): string | undefined;
|
|
81
|
+
/**
|
|
82
|
+
* A tag without its variant, extension and private-use subtags, casing kept:
|
|
83
|
+
* `en-GB-x-gulf` yields `en-GB`. The form for a speech engine, whose voices
|
|
84
|
+
* are keyed by language and region.
|
|
85
|
+
*/
|
|
86
|
+
export declare function languageTagWithoutVariants(tag: string | null | undefined): string;
|
|
81
87
|
/**
|
|
82
88
|
* `findBestLanguageMatch`, then primary-subtag widening, so `nl-BE` and a bare
|
|
83
|
-
* `nl` reach a regional `nl-NL` and `es-MX` reaches `es-ES`.
|
|
84
|
-
* the request's script
|
|
85
|
-
* `sr-
|
|
89
|
+
* `nl` reach a regional `nl-NL` and `es-MX` reaches `es-ES`. Widening stays
|
|
90
|
+
* within the request's script and keeps its variant subtags: `zh` never
|
|
91
|
+
* reaches `zh-TW`, `sr-Latn` may reach `sr-ME`, `en-GB` never reaches
|
|
92
|
+
* `en-GB-x-gulf`, and `en-US-x-gulf` may reach `en-GB-x-gulf`. Among several
|
|
93
|
+
* siblings the first in `available` wins. The rule that picks an interface
|
|
94
|
+
* catalog and a content variant alike.
|
|
86
95
|
*/
|
|
87
96
|
export declare function findClosestLanguageMatch(requested: string | null | undefined, available: readonly (string | null | undefined)[]): string | undefined;
|
|
88
97
|
/** The content language a backend serves, and whether it is a substitute. */
|
|
89
98
|
export interface ContentLanguageMatch {
|
|
90
99
|
/** The `available` entry served, as authored; `null` for content that declares no language. */
|
|
91
100
|
language: string | null;
|
|
92
|
-
/**
|
|
93
|
-
|
|
101
|
+
/**
|
|
102
|
+
* `"fallback"` when the served content stands in for the request;
|
|
103
|
+
* `"variety"` when it is another variety of the requested language.
|
|
104
|
+
*/
|
|
105
|
+
substitution?: "fallback" | "variety";
|
|
94
106
|
}
|
|
95
107
|
/**
|
|
96
108
|
* The language to serve content in: the closest `available` match for the
|
|
@@ -100,7 +112,12 @@ export interface ContentLanguageMatch {
|
|
|
100
112
|
* chain serves the closest match for `defaultLanguage` (`en-US` reaches `en`),
|
|
101
113
|
* then content that declares no language (a `null` entry), then `source`;
|
|
102
114
|
* `matching` does not narrow it. `undefined` means nothing can be served. No
|
|
103
|
-
* requested languages run the chain without a substitution.
|
|
115
|
+
* requested languages run the chain without a substitution. A closest match
|
|
116
|
+
* in another variety, `es-MX` served `es-ES`, records `"variety"`; a host
|
|
117
|
+
* that must not serve one sets `matching: "exact"` and lists the varieties it
|
|
118
|
+
* accepts. Candidates rank
|
|
119
|
+
* `source` first, then by tag, so the order of `available` never decides
|
|
120
|
+
* between two siblings.
|
|
104
121
|
*/
|
|
105
122
|
export declare function negotiateContentLanguage(requested: readonly string[], available: readonly (string | null)[], options?: {
|
|
106
123
|
defaultLanguage?: string;
|
|
@@ -149,11 +149,59 @@ export function findBestLanguageMatch(requested, available) {
|
|
|
149
149
|
}
|
|
150
150
|
return undefined;
|
|
151
151
|
}
|
|
152
|
+
/**
|
|
153
|
+
* A tag's subtags split at the end of its language, script and region:
|
|
154
|
+
* `en-GB-x-gulf` yields `[["en", "GB"], ["x", "gulf"]]`. The second part holds
|
|
155
|
+
* variants, extensions and private use, as authored.
|
|
156
|
+
*/
|
|
157
|
+
function splitVariantSubtags(tag) {
|
|
158
|
+
const subtags = toLanguageTag(tag).split(/[.@]/)[0].split("-");
|
|
159
|
+
let length = 1;
|
|
160
|
+
if (/^[a-z]{4}$/i.test(subtags[length] ?? ""))
|
|
161
|
+
length++;
|
|
162
|
+
if (/^([a-z]{2}|\d{3})$/i.test(subtags[length] ?? ""))
|
|
163
|
+
length++;
|
|
164
|
+
return [subtags.slice(0, length), subtags.slice(length)];
|
|
165
|
+
}
|
|
166
|
+
/** The variant, extension and private-use subtags as a comparison key. */
|
|
167
|
+
function variantSubtagsKey(tag) {
|
|
168
|
+
return splitVariantSubtags(tag)[1].join("-").toLowerCase();
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Whether `served` is another variety of `requested`: it names a different
|
|
172
|
+
* region, or different variant or private-use subtags. `es-MX` served `es-ES`
|
|
173
|
+
* or `en-GB-x-gulf` served `en-GB` is; `es-MX` served `es`, or `zh-TW` served
|
|
174
|
+
* `zh-Hant`, is not.
|
|
175
|
+
*/
|
|
176
|
+
function isOtherLanguageVariety(requested, served) {
|
|
177
|
+
const region = (tag) => {
|
|
178
|
+
const [base] = splitVariantSubtags(tag);
|
|
179
|
+
const last = base.length > 1 ? base[base.length - 1] : "";
|
|
180
|
+
return /^([a-z]{2}|\d{3})$/i.test(last) ? last.toLowerCase() : undefined;
|
|
181
|
+
};
|
|
182
|
+
const requestedRegion = region(requested);
|
|
183
|
+
const servedRegion = region(served);
|
|
184
|
+
return ((requestedRegion !== undefined &&
|
|
185
|
+
servedRegion !== undefined &&
|
|
186
|
+
requestedRegion !== servedRegion) ||
|
|
187
|
+
variantSubtagsKey(requested) !== variantSubtagsKey(served));
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* A tag without its variant, extension and private-use subtags, casing kept:
|
|
191
|
+
* `en-GB-x-gulf` yields `en-GB`. The form for a speech engine, whose voices
|
|
192
|
+
* are keyed by language and region.
|
|
193
|
+
*/
|
|
194
|
+
export function languageTagWithoutVariants(tag) {
|
|
195
|
+
return splitVariantSubtags(tag)[0].join("-");
|
|
196
|
+
}
|
|
152
197
|
/**
|
|
153
198
|
* `findBestLanguageMatch`, then primary-subtag widening, so `nl-BE` and a bare
|
|
154
|
-
* `nl` reach a regional `nl-NL` and `es-MX` reaches `es-ES`.
|
|
155
|
-
* the request's script
|
|
156
|
-
* `sr-
|
|
199
|
+
* `nl` reach a regional `nl-NL` and `es-MX` reaches `es-ES`. Widening stays
|
|
200
|
+
* within the request's script and keeps its variant subtags: `zh` never
|
|
201
|
+
* reaches `zh-TW`, `sr-Latn` may reach `sr-ME`, `en-GB` never reaches
|
|
202
|
+
* `en-GB-x-gulf`, and `en-US-x-gulf` may reach `en-GB-x-gulf`. Among several
|
|
203
|
+
* siblings the first in `available` wins. The rule that picks an interface
|
|
204
|
+
* catalog and a content variant alike.
|
|
157
205
|
*/
|
|
158
206
|
export function findClosestLanguageMatch(requested, available) {
|
|
159
207
|
const best = findBestLanguageMatch(requested, available);
|
|
@@ -162,7 +210,9 @@ export function findClosestLanguageMatch(requested, available) {
|
|
|
162
210
|
const language = normalizeLanguageTag(requested).split("-")[0];
|
|
163
211
|
if (!language)
|
|
164
212
|
return undefined;
|
|
213
|
+
const variants = variantSubtagsKey(requested);
|
|
165
214
|
const sibling = available.find((candidate) => normalizeLanguageTag(candidate).split("-")[0] === language &&
|
|
215
|
+
variantSubtagsKey(candidate) === variants &&
|
|
166
216
|
languageTagScriptsMatch(requested, candidate));
|
|
167
217
|
return sibling ?? undefined;
|
|
168
218
|
}
|
|
@@ -174,20 +224,35 @@ export function findClosestLanguageMatch(requested, available) {
|
|
|
174
224
|
* chain serves the closest match for `defaultLanguage` (`en-US` reaches `en`),
|
|
175
225
|
* then content that declares no language (a `null` entry), then `source`;
|
|
176
226
|
* `matching` does not narrow it. `undefined` means nothing can be served. No
|
|
177
|
-
* requested languages run the chain without a substitution.
|
|
227
|
+
* requested languages run the chain without a substitution. A closest match
|
|
228
|
+
* in another variety, `es-MX` served `es-ES`, records `"variety"`; a host
|
|
229
|
+
* that must not serve one sets `matching: "exact"` and lists the varieties it
|
|
230
|
+
* accepts. Candidates rank
|
|
231
|
+
* `source` first, then by tag, so the order of `available` never decides
|
|
232
|
+
* between two siblings.
|
|
178
233
|
*/
|
|
179
234
|
export function negotiateContentLanguage(requested, available, options = {}) {
|
|
180
235
|
const { defaultLanguage, fallback = "default", matching = "closest", source } = options;
|
|
236
|
+
const candidates = [...available].sort((a, b) => {
|
|
237
|
+
if ((a === source) !== (b === source))
|
|
238
|
+
return a === source ? -1 : 1;
|
|
239
|
+
const left = normalizeLanguageTag(a);
|
|
240
|
+
const right = normalizeLanguageTag(b);
|
|
241
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
242
|
+
});
|
|
181
243
|
for (const tag of requested) {
|
|
182
244
|
const language = matching === "exact"
|
|
183
|
-
?
|
|
184
|
-
: findClosestLanguageMatch(tag,
|
|
185
|
-
if (language != null)
|
|
186
|
-
return
|
|
245
|
+
? candidates.find((candidate) => languageTagsEqual(candidate, tag))
|
|
246
|
+
: findClosestLanguageMatch(tag, candidates);
|
|
247
|
+
if (language != null) {
|
|
248
|
+
return isOtherLanguageVariety(tag, language)
|
|
249
|
+
? { language, substitution: "variety" }
|
|
250
|
+
: { language };
|
|
251
|
+
}
|
|
187
252
|
}
|
|
188
253
|
if (requested.length > 0 && fallback === "none")
|
|
189
254
|
return undefined;
|
|
190
|
-
let language = findClosestLanguageMatch(defaultLanguage,
|
|
255
|
+
let language = findClosestLanguageMatch(defaultLanguage, candidates);
|
|
191
256
|
if (language === undefined && available.includes(null))
|
|
192
257
|
language = null;
|
|
193
258
|
if (language === undefined && source !== undefined && available.includes(source)) {
|
|
@@ -301,6 +301,12 @@ declare const enUS: {
|
|
|
301
301
|
lanthanide: string;
|
|
302
302
|
actinide: string;
|
|
303
303
|
};
|
|
304
|
+
/** Standard-state phase, keyed by the data file's lowercased `phase` value. */
|
|
305
|
+
phaseValue: {
|
|
306
|
+
solid: string;
|
|
307
|
+
liquid: string;
|
|
308
|
+
gas: string;
|
|
309
|
+
};
|
|
304
310
|
};
|
|
305
311
|
ruler: {
|
|
306
312
|
name: string;
|
|
@@ -462,6 +468,17 @@ declare const enUS: {
|
|
|
462
468
|
highlightPinkA11y: string;
|
|
463
469
|
highlightBlueA11y: string;
|
|
464
470
|
highlightGreenA11y: string;
|
|
471
|
+
selectionAvailableA11y: string;
|
|
472
|
+
highlightedYellowA11y: string;
|
|
473
|
+
highlightedPinkA11y: string;
|
|
474
|
+
highlightedBlueA11y: string;
|
|
475
|
+
highlightedGreenA11y: string;
|
|
476
|
+
underlinedA11y: string;
|
|
477
|
+
removedA11y: string;
|
|
478
|
+
clearedA11y: {
|
|
479
|
+
one: string;
|
|
480
|
+
other: string;
|
|
481
|
+
};
|
|
465
482
|
};
|
|
466
483
|
theme: {
|
|
467
484
|
name: string;
|
|
@@ -306,6 +306,12 @@ const enUS = {
|
|
|
306
306
|
lanthanide: "Lanthanide",
|
|
307
307
|
actinide: "Actinide",
|
|
308
308
|
},
|
|
309
|
+
/** Standard-state phase, keyed by the data file's lowercased `phase` value. */
|
|
310
|
+
phaseValue: {
|
|
311
|
+
solid: "Solid",
|
|
312
|
+
liquid: "Liquid",
|
|
313
|
+
gas: "Gas",
|
|
314
|
+
},
|
|
309
315
|
},
|
|
310
316
|
ruler: {
|
|
311
317
|
name: "Ruler",
|
|
@@ -482,6 +488,19 @@ const enUS = {
|
|
|
482
488
|
highlightPinkA11y: "Pink highlight",
|
|
483
489
|
highlightBlueA11y: "Blue highlight",
|
|
484
490
|
highlightGreenA11y: "Green highlight",
|
|
491
|
+
// Announced in the strip's live region. A colour is authored per phrase:
|
|
492
|
+
// a colour adjective agrees with its noun in some languages.
|
|
493
|
+
selectionAvailableA11y: "Annotation toolbar available for “{text}”. Press Shift+F10 for annotation tools.",
|
|
494
|
+
highlightedYellowA11y: "“{text}” highlighted in yellow",
|
|
495
|
+
highlightedPinkA11y: "“{text}” highlighted in pink",
|
|
496
|
+
highlightedBlueA11y: "“{text}” highlighted in blue",
|
|
497
|
+
highlightedGreenA11y: "“{text}” highlighted in green",
|
|
498
|
+
underlinedA11y: "“{text}” underlined",
|
|
499
|
+
removedA11y: "Removed annotation from “{text}”",
|
|
500
|
+
clearedA11y: {
|
|
501
|
+
one: "{count} annotation cleared",
|
|
502
|
+
other: "{count} annotations cleared",
|
|
503
|
+
},
|
|
485
504
|
},
|
|
486
505
|
theme: {
|
|
487
506
|
name: "Theme",
|
package/dist/i18n/types.d.ts
CHANGED
|
@@ -16,8 +16,13 @@ import type enUS from "./messages/en-US.js";
|
|
|
16
16
|
* syntax — the Learnosity transform emits POSIX `nl_NL`, QTI catalog cards carry
|
|
17
17
|
* `xml:lang` — and the resolver normalizes both onto these, so this list is the
|
|
18
18
|
* set of catalogs, not the set of accepted inputs.
|
|
19
|
+
*
|
|
20
|
+
* `en-US` is the only bundled catalog: the statically resident source of truth
|
|
21
|
+
* every other locale falls back through. Every other locale this repository
|
|
22
|
+
* ships lives in the optional generated tier
|
|
23
|
+
* (`@pie-players/pie-i18n-generated`), which a host composes into its loader.
|
|
19
24
|
*/
|
|
20
|
-
export type BundledLocaleCode = "en-US"
|
|
25
|
+
export type BundledLocaleCode = "en-US";
|
|
21
26
|
/**
|
|
22
27
|
* Any locale tag. A host may supply a locale we ship no catalog for and provide
|
|
23
28
|
* its own messages through {@link I18nConfig.customMessages}, so this is
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hostname classification for a URL a server fetches on someone else's behalf,
|
|
3
|
+
* such as an SSML `<audio src>` a TTS provider will request or an asset URL an
|
|
4
|
+
* upstream service returns. Every such check reads this module, so no two
|
|
5
|
+
* copies of it can drift apart.
|
|
6
|
+
*
|
|
7
|
+
* IP literals are classified by `ipaddr.js`, whose ranges follow the IANA IPv4
|
|
8
|
+
* and IPv6 Special-Purpose Address Registries (RFC 6890). A host is private
|
|
9
|
+
* unless it is a public unicast address or a name: loopback, private, link-local,
|
|
10
|
+
* unique-local, carrier-grade NAT, multicast, broadcast, documentation and
|
|
11
|
+
* reserved ranges, and the IPv6 forms that embed an IPv4 address (NAT64, 6to4,
|
|
12
|
+
* Teredo), all count. `ipaddr.js` also reads the numeric IPv4 forms (`2130706433`,
|
|
13
|
+
* `0x7f000001`, `0177.0.0.1`) and unmaps IPv4-mapped IPv6.
|
|
14
|
+
*
|
|
15
|
+
* Only the literal is checked. A public name that resolves to a private address
|
|
16
|
+
* passes; guarding that takes a check of the resolved address at connect time.
|
|
17
|
+
*
|
|
18
|
+
* @module
|
|
19
|
+
*/
|
|
20
|
+
/** Whether `hostname` is a cloud instance-metadata endpoint. */
|
|
21
|
+
export declare function isCloudMetadataHostname(hostname: string): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Whether `hostname` is anything other than a public unicast address or a
|
|
24
|
+
* public name: `localhost` and its subdomains (RFC 6761), or an IP literal in
|
|
25
|
+
* any special-purpose range.
|
|
26
|
+
*/
|
|
27
|
+
export declare function isPrivateHostname(hostname: string): boolean;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hostname classification for a URL a server fetches on someone else's behalf,
|
|
3
|
+
* such as an SSML `<audio src>` a TTS provider will request or an asset URL an
|
|
4
|
+
* upstream service returns. Every such check reads this module, so no two
|
|
5
|
+
* copies of it can drift apart.
|
|
6
|
+
*
|
|
7
|
+
* IP literals are classified by `ipaddr.js`, whose ranges follow the IANA IPv4
|
|
8
|
+
* and IPv6 Special-Purpose Address Registries (RFC 6890). A host is private
|
|
9
|
+
* unless it is a public unicast address or a name: loopback, private, link-local,
|
|
10
|
+
* unique-local, carrier-grade NAT, multicast, broadcast, documentation and
|
|
11
|
+
* reserved ranges, and the IPv6 forms that embed an IPv4 address (NAT64, 6to4,
|
|
12
|
+
* Teredo), all count. `ipaddr.js` also reads the numeric IPv4 forms (`2130706433`,
|
|
13
|
+
* `0x7f000001`, `0177.0.0.1`) and unmaps IPv4-mapped IPv6.
|
|
14
|
+
*
|
|
15
|
+
* Only the literal is checked. A public name that resolves to a private address
|
|
16
|
+
* passes; guarding that takes a check of the resolved address at connect time.
|
|
17
|
+
*
|
|
18
|
+
* @module
|
|
19
|
+
*/
|
|
20
|
+
import ipaddr from "ipaddr.js";
|
|
21
|
+
/**
|
|
22
|
+
* Cloud instance-metadata hostnames. They hand out credentials. No registry
|
|
23
|
+
* lists them; each is the provider's documented name.
|
|
24
|
+
*/
|
|
25
|
+
const CLOUD_METADATA_HOSTNAMES = new Set([
|
|
26
|
+
"metadata.google.internal",
|
|
27
|
+
"metadata.azure.internal",
|
|
28
|
+
"metadata.packet.net",
|
|
29
|
+
"metadata.oci.oraclecloud.com",
|
|
30
|
+
]);
|
|
31
|
+
/** Cloud instance-metadata addresses: the link-local one most clouds use, and AWS's IPv6 one. */
|
|
32
|
+
const CLOUD_METADATA_ADDRESSES = ["169.254.169.254", "fd00:ec2::254"].map((address) => ipaddr.parse(address));
|
|
33
|
+
/** Lower-cased, without brackets or a trailing dot. */
|
|
34
|
+
function normalizeHostname(hostname) {
|
|
35
|
+
return hostname
|
|
36
|
+
.replace(/^\[|\]$/g, "")
|
|
37
|
+
.replace(/\.$/, "")
|
|
38
|
+
.toLowerCase();
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The address `host` is a literal for, IPv4-mapped (`::ffff:a.b.c.d`) and
|
|
42
|
+
* IPv4-compatible (`::a.b.c.d`) IPv6 reduced to IPv4, or `null` for a name.
|
|
43
|
+
*/
|
|
44
|
+
function parseAddress(host) {
|
|
45
|
+
if (!ipaddr.isValid(host))
|
|
46
|
+
return null;
|
|
47
|
+
const address = ipaddr.process(host);
|
|
48
|
+
if (address.kind() !== "ipv6")
|
|
49
|
+
return address;
|
|
50
|
+
// `ipaddr.js` classifies deprecated IPv4-compatible addresses as unicast;
|
|
51
|
+
// read the IPv4 address in their low 32 bits instead.
|
|
52
|
+
const { parts } = address;
|
|
53
|
+
const isIpv4Compatible = parts.slice(0, 6).every((part) => part === 0) &&
|
|
54
|
+
(parts[6] !== 0 || parts[7] > 1);
|
|
55
|
+
return isIpv4Compatible
|
|
56
|
+
? new ipaddr.IPv4([
|
|
57
|
+
parts[6] >> 8,
|
|
58
|
+
parts[6] & 0xff,
|
|
59
|
+
parts[7] >> 8,
|
|
60
|
+
parts[7] & 0xff,
|
|
61
|
+
])
|
|
62
|
+
: address;
|
|
63
|
+
}
|
|
64
|
+
/** Whether `hostname` is a cloud instance-metadata endpoint. */
|
|
65
|
+
export function isCloudMetadataHostname(hostname) {
|
|
66
|
+
const host = normalizeHostname(hostname);
|
|
67
|
+
if (CLOUD_METADATA_HOSTNAMES.has(host))
|
|
68
|
+
return true;
|
|
69
|
+
const address = parseAddress(host);
|
|
70
|
+
return (address !== null &&
|
|
71
|
+
CLOUD_METADATA_ADDRESSES.some((metadata) => metadata.kind() === address.kind() &&
|
|
72
|
+
metadata.toNormalizedString() === address.toNormalizedString()));
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Whether `hostname` is anything other than a public unicast address or a
|
|
76
|
+
* public name: `localhost` and its subdomains (RFC 6761), or an IP literal in
|
|
77
|
+
* any special-purpose range.
|
|
78
|
+
*/
|
|
79
|
+
export function isPrivateHostname(hostname) {
|
|
80
|
+
const host = normalizeHostname(hostname);
|
|
81
|
+
if (host === "localhost" || host.endsWith(".localhost"))
|
|
82
|
+
return true;
|
|
83
|
+
const address = parseAddress(host);
|
|
84
|
+
return address !== null && address.range() !== "unicast";
|
|
85
|
+
}
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* corpus behind one is licensed per programme, so a host supplies either an endpoint
|
|
14
14
|
* or a resolver of its own.
|
|
15
15
|
*/
|
|
16
|
+
import type { TextDirection } from "../i18n/types.js";
|
|
16
17
|
/** What a host's term service is asked for. */
|
|
17
18
|
export interface TermLookupRequest {
|
|
18
19
|
/** The term, already normalised by {@link normalizeTerm}. */
|
|
@@ -187,3 +188,17 @@ export declare function termPanelStatusMessage<TItem>(state: TermPanelState<TIte
|
|
|
187
188
|
countLabel: (count: number) => string;
|
|
188
189
|
emptyLabel: (term: string) => string;
|
|
189
190
|
}): string;
|
|
191
|
+
/**
|
|
192
|
+
* `lang` and `dir` for a lookup's results: the language the lookup asked in.
|
|
193
|
+
*
|
|
194
|
+
* Results are in the corpus's language, which is neither the panel's interface
|
|
195
|
+
* locale nor necessarily the content the term came from: a Spanish gloss for an
|
|
196
|
+
* English passage. Stamped on the results, an Arabic definition in an English panel
|
|
197
|
+
* reads and lays out as Arabic, and the panel's language never reaches the content.
|
|
198
|
+
* With no language named the corpus's is unknown, so the stamp is `lang=""` and
|
|
199
|
+
* `dir="auto"`, the one undeclared content gets.
|
|
200
|
+
*/
|
|
201
|
+
export declare function termResultLanguage(language: string | undefined): {
|
|
202
|
+
lang: string;
|
|
203
|
+
dir: TextDirection | "auto";
|
|
204
|
+
};
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
* corpus behind one is licensed per programme, so a host supplies either an endpoint
|
|
14
14
|
* or a resolver of its own.
|
|
15
15
|
*/
|
|
16
|
+
import { toLanguageTag } from "../i18n/language-tags.js";
|
|
17
|
+
import { localeDirection } from "../i18n/provider.js";
|
|
16
18
|
/** Longest term worth sending. Past this it is prose, not a headword. */
|
|
17
19
|
export const MAX_LOOKUP_TERM_LENGTH = 80;
|
|
18
20
|
/** Words a term may span. Generous enough for `carbon dioxide` and `mother in law`. */
|
|
@@ -282,3 +284,17 @@ export function termPanelStatusMessage(state, labels) {
|
|
|
282
284
|
return labels.emptyLabel(state.term);
|
|
283
285
|
return "";
|
|
284
286
|
}
|
|
287
|
+
/**
|
|
288
|
+
* `lang` and `dir` for a lookup's results: the language the lookup asked in.
|
|
289
|
+
*
|
|
290
|
+
* Results are in the corpus's language, which is neither the panel's interface
|
|
291
|
+
* locale nor necessarily the content the term came from: a Spanish gloss for an
|
|
292
|
+
* English passage. Stamped on the results, an Arabic definition in an English panel
|
|
293
|
+
* reads and lays out as Arabic, and the panel's language never reaches the content.
|
|
294
|
+
* With no language named the corpus's is unknown, so the stamp is `lang=""` and
|
|
295
|
+
* `dir="auto"`, the one undeclared content gets.
|
|
296
|
+
*/
|
|
297
|
+
export function termResultLanguage(language) {
|
|
298
|
+
const tag = toLanguageTag(language);
|
|
299
|
+
return tag ? { lang: tag, dir: localeDirection(tag) } : { lang: "", dir: "auto" };
|
|
300
|
+
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -10,10 +10,10 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import type { FormativeDeliveryPolicy, FormativeItemPolicy, FormativeSectionSlice } from "../formative/types.js";
|
|
12
12
|
import type { TimedMediaSectionData, TimedMediaSectionSessionSlice } from "../timed-media/types.js";
|
|
13
|
-
import type { AdaptationLink, LanguagePolicy } from "./interchange.js";
|
|
13
|
+
import type { AdaptationLink, CurriculumStandard, LanguagePolicy, SkillLevel } from "./interchange.js";
|
|
14
14
|
export type { FormativeDeliveryPolicy, FormativeItemPolicy };
|
|
15
15
|
export type { TimedMediaSectionData };
|
|
16
|
-
export type { AdaptationLink, ContentSubstitution, LanguagePolicy, ServedContentIdentity, } from "./interchange.js";
|
|
16
|
+
export type { AdaptationLink, ContentSubstitution, CurriculumStandard, LanguagePolicy, ServedContentIdentity, SkillLevel, } from "./interchange.js";
|
|
17
17
|
export interface BaseEntity {
|
|
18
18
|
id?: string;
|
|
19
19
|
createdAt?: Date;
|
|
@@ -76,6 +76,13 @@ export interface PassageEntity extends VersionEntity, ConfigContainerEntity {
|
|
|
76
76
|
isAdaptationOf?: AdaptationLink;
|
|
77
77
|
/** QTI/APIP-style accessibility catalogs owned by this passage content. */
|
|
78
78
|
accessibilityCatalogs?: AccessibilityCatalog[];
|
|
79
|
+
/**
|
|
80
|
+
* Standards and skills the content is aligned to. Absent means no data; `[]`
|
|
81
|
+
* means the writer states that none applies. No player reads it.
|
|
82
|
+
*/
|
|
83
|
+
curriculumStandards?: CurriculumStandard[];
|
|
84
|
+
/** DOK and Bloom's classifications, on the same terms. No player reads it. */
|
|
85
|
+
skillLevels?: SkillLevel[];
|
|
79
86
|
retired?: boolean;
|
|
80
87
|
published?: boolean;
|
|
81
88
|
}
|
|
@@ -96,6 +103,13 @@ export interface ItemEntity extends VersionEntity, ConfigContainerEntity {
|
|
|
96
103
|
passage?: string | PassageEntity | null;
|
|
97
104
|
/** QTI/APIP-style accessibility catalogs owned by the item root. */
|
|
98
105
|
accessibilityCatalogs?: AccessibilityCatalog[];
|
|
106
|
+
/**
|
|
107
|
+
* Standards and skills the content is aligned to. Absent means no data; `[]`
|
|
108
|
+
* means the writer states that none applies. No player reads it.
|
|
109
|
+
*/
|
|
110
|
+
curriculumStandards?: CurriculumStandard[];
|
|
111
|
+
/** DOK and Bloom's classifications, on the same terms. No player reads it. */
|
|
112
|
+
skillLevels?: SkillLevel[];
|
|
99
113
|
retired?: boolean;
|
|
100
114
|
published?: boolean;
|
|
101
115
|
configuration?: any;
|