@pie-players/pie-players-shared 0.3.66 → 0.3.68
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 +59 -0
- package/dist/formative/index.d.ts +16 -0
- package/dist/formative/index.js +5 -0
- package/dist/formative/mastery.d.ts +16 -0
- package/dist/formative/mastery.js +43 -0
- package/dist/formative/outcome.d.ts +19 -0
- package/dist/formative/outcome.js +90 -0
- package/dist/formative/policy.d.ts +37 -0
- package/dist/formative/policy.js +86 -0
- package/dist/formative/session.d.ts +19 -0
- package/dist/formative/session.js +113 -0
- package/dist/formative/state.d.ts +60 -0
- package/dist/formative/state.js +140 -0
- package/dist/formative/types.d.ts +164 -0
- package/dist/formative/types.js +13 -0
- package/dist/i18n/catalogs.d.ts +38 -0
- package/dist/i18n/catalogs.js +47 -0
- package/dist/i18n/index.d.ts +28 -4
- package/dist/i18n/index.js +34 -3
- package/dist/i18n/language-tags.d.ts +51 -0
- package/dist/i18n/language-tags.js +84 -0
- package/dist/i18n/messages/en-US.d.ts +601 -0
- package/dist/i18n/messages/en-US.js +617 -0
- package/dist/i18n/messages/nl-NL.d.ts +516 -0
- package/dist/i18n/messages/nl-NL.js +516 -0
- package/dist/i18n/provider.d.ts +168 -0
- package/dist/i18n/provider.js +472 -0
- package/dist/i18n/types.d.ts +177 -28
- package/dist/i18n/types.js +6 -2
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -0
- package/dist/pie/custom-element-define.d.ts +24 -0
- package/dist/pie/custom-element-define.js +36 -6
- package/dist/pie/index.d.ts +1 -1
- package/dist/pie/index.js +1 -1
- package/dist/pie/versioned-tag.js +3 -2
- package/dist/security/sanitize-forbidden-lists.d.ts +8 -0
- package/dist/security/sanitize-forbidden-lists.js +41 -0
- package/dist/security/sanitize-item-markup.js +3 -36
- package/dist/security/sanitize-svg-icon.js +3 -25
- package/dist/security/wrap-overwide-images.d.ts +5 -10
- package/dist/security/wrap-overwide-images.js +13 -62
- package/dist/security/wrap-overwide-tables.d.ts +5 -10
- package/dist/security/wrap-overwide-tables.js +13 -62
- package/dist/security/wrap-overwide.d.ts +51 -0
- package/dist/security/wrap-overwide.js +91 -0
- package/dist/server/npm-auth-env.d.ts +13 -0
- package/dist/server/npm-auth-env.js +64 -0
- package/dist/timed-media/index.d.ts +17 -0
- package/dist/timed-media/index.js +16 -0
- package/dist/timed-media/media-element-source.d.ts +36 -0
- package/dist/timed-media/media-element-source.js +121 -0
- package/dist/timed-media/policy.d.ts +36 -0
- package/dist/timed-media/policy.js +297 -0
- package/dist/timed-media/session.d.ts +21 -0
- package/dist/timed-media/session.js +85 -0
- package/dist/timed-media/state.d.ts +66 -0
- package/dist/timed-media/state.js +357 -0
- package/dist/timed-media/types.d.ts +343 -0
- package/dist/timed-media/types.js +12 -0
- package/dist/tools/term-lookup.d.ts +189 -0
- package/dist/tools/term-lookup.js +284 -0
- package/dist/types/index.d.ts +105 -1
- package/dist/ui/attribute-coercion.d.ts +1 -0
- package/dist/ui/attribute-coercion.js +19 -0
- package/dist/ui/content-styles.d.ts +11 -3
- package/dist/ui/content-styles.js +42 -13
- package/dist/ui/first-focusable.d.ts +22 -0
- package/dist/ui/first-focusable.js +47 -0
- package/dist/ui/focus-trap.js +7 -2
- package/dist/ui/pointer-drag.d.ts +30 -0
- package/dist/ui/pointer-drag.js +34 -0
- package/package.json +35 -3
- package/dist/i18n/loader.d.ts +0 -35
- package/dist/i18n/loader.js +0 -132
- package/dist/i18n/simple-i18n.d.ts +0 -68
- package/dist/i18n/simple-i18n.js +0 -198
- package/dist/i18n/translations/ar/common.json +0 -36
- package/dist/i18n/translations/ar/toolkit.json +0 -48
- package/dist/i18n/translations/ar/tools.json +0 -103
- package/dist/i18n/translations/en/common.json +0 -36
- package/dist/i18n/translations/en/toolkit.json +0 -48
- package/dist/i18n/translations/en/tools.json +0 -103
- package/dist/i18n/translations/es/common.json +0 -36
- package/dist/i18n/translations/es/toolkit.json +0 -48
- package/dist/i18n/translations/es/tools.json +0 -103
- package/dist/i18n/translations/zh/common.json +0 -36
- package/dist/i18n/translations/zh/toolkit.json +0 -48
- package/dist/i18n/translations/zh/tools.json +0 -103
|
@@ -17,14 +17,15 @@
|
|
|
17
17
|
*
|
|
18
18
|
* The CSS text is passed in rather than imported here: this package builds with
|
|
19
19
|
* plain `tsc`, so it cannot inline a stylesheet. Bundler-built player packages
|
|
20
|
-
* import it with Vite's `?
|
|
20
|
+
* import it with Vite's `?raw` and hand the text over.
|
|
21
21
|
*/
|
|
22
22
|
/** Marks a `<style>` element this module owns, and keeps installs idempotent. */
|
|
23
23
|
const MARKER_ATTRIBUTE = "data-pie-content-styles";
|
|
24
24
|
/**
|
|
25
25
|
* Declared by `components.css` itself, so it is observable no matter how the
|
|
26
|
-
* stylesheet arrived — our injection or a host import.
|
|
27
|
-
* that opted out
|
|
26
|
+
* stylesheet arrived — our injection or a host import. Diagnostics only: it
|
|
27
|
+
* tells a host that opted out and then shipped nothing, and it spots a host copy
|
|
28
|
+
* sitting alongside ours.
|
|
28
29
|
*/
|
|
29
30
|
const SENTINEL_PROPERTY = "--pie-content-styles";
|
|
30
31
|
// Svelte's dev-mode custom-element reset expands `all: unset` into individual
|
|
@@ -43,7 +44,22 @@ const declaresContentStylesSentinel = (rule) => {
|
|
|
43
44
|
?.getPropertyValue(SENTINEL_PROPERTY)
|
|
44
45
|
.trim()
|
|
45
46
|
.toLowerCase();
|
|
46
|
-
|
|
47
|
+
if (value)
|
|
48
|
+
return !CSS_WIDE_RESET_VALUES.has(value);
|
|
49
|
+
// Grouping rules hold no declarations of their own, so the sentinel sits one
|
|
50
|
+
// or more levels down. A host that confines its copy — `@scope
|
|
51
|
+
// (.item-content) { … }`, `@layer pie-content { … }` — presents exactly one
|
|
52
|
+
// top-level rule with an empty `.style`, and a top-level-only scan reads that
|
|
53
|
+
// as "no copy here". That made both detection paths blind to the one host
|
|
54
|
+
// configuration this module most needs to recognise.
|
|
55
|
+
const nested = rule.cssRules;
|
|
56
|
+
if (!nested)
|
|
57
|
+
return false;
|
|
58
|
+
for (const child of Array.from(nested)) {
|
|
59
|
+
if (declaresContentStylesSentinel(child))
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
return false;
|
|
47
63
|
};
|
|
48
64
|
/** `<html data-pie-content-styles="host">` opts a host out of installation. */
|
|
49
65
|
const OPT_OUT_ATTRIBUTE = "data-pie-content-styles";
|
|
@@ -59,8 +75,16 @@ export function contentStylesOptedOut() {
|
|
|
59
75
|
return (document.documentElement.getAttribute(OPT_OUT_ATTRIBUTE) === OPT_OUT_VALUE);
|
|
60
76
|
}
|
|
61
77
|
/**
|
|
62
|
-
* True when `components.css` is applied to the document, by any route.
|
|
63
|
-
*
|
|
78
|
+
* True when `components.css` is applied to the document, by any route.
|
|
79
|
+
*
|
|
80
|
+
* Two probes, because neither alone covers both deliveries. The computed
|
|
81
|
+
* sentinel on `<html>` is authoritative for a stylesheet applying to the whole
|
|
82
|
+
* document. A host that confines its copy to its player subtree — `@scope
|
|
83
|
+
* (.item-content) { … }`, the documented remedy for these rules reaching host
|
|
84
|
+
* chrome — puts the stylesheet's `:root` rule somewhere it can never match,
|
|
85
|
+
* since `<html>` is not a descendant of the scoping root; the property then
|
|
86
|
+
* reads empty while the stylesheet is present and working. So an empty read
|
|
87
|
+
* falls back to scanning the document's sheets for the sentinel.
|
|
64
88
|
*
|
|
65
89
|
* Only meaningful once the document's stylesheets have been applied — a host
|
|
66
90
|
* that loads CSS via an async `<link>` reads as missing until it lands.
|
|
@@ -71,7 +95,9 @@ export function contentStylesPresent() {
|
|
|
71
95
|
const value = getComputedStyle(document.documentElement)
|
|
72
96
|
.getPropertyValue(SENTINEL_PROPERTY)
|
|
73
97
|
.trim();
|
|
74
|
-
|
|
98
|
+
if (value !== "")
|
|
99
|
+
return true;
|
|
100
|
+
return countContentStyleSheets({ excludeInstalled: false }) > 0;
|
|
75
101
|
}
|
|
76
102
|
/**
|
|
77
103
|
* Installs `cssText` as a document-level stylesheet, once per document.
|
|
@@ -109,21 +135,22 @@ export function installContentStyles(cssText, source) {
|
|
|
109
135
|
return "installed";
|
|
110
136
|
}
|
|
111
137
|
/**
|
|
112
|
-
* Counts content stylesheets in the document
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
138
|
+
* Counts content stylesheets in the document, detected by the sentinel property
|
|
139
|
+
* rather than by URL, so a copy arriving as a `<link>`, a bundler-injected
|
|
140
|
+
* `<style>`, or anything else all count the same. `excludeInstalled` narrows the
|
|
141
|
+
* count to copies the host loaded itself.
|
|
116
142
|
*
|
|
117
143
|
* Cross-origin sheets throw on `cssRules` access and are skipped; a host copy
|
|
118
144
|
* served from another origin therefore reads as absent. That only costs a
|
|
119
145
|
* diagnostic, never correctness.
|
|
120
146
|
*/
|
|
121
|
-
const
|
|
147
|
+
const countContentStyleSheets = ({ excludeInstalled, }) => {
|
|
122
148
|
// Walks the owning elements rather than document.styleSheets: the marker
|
|
123
149
|
// attribute lives on the element, and CSSStyleSheet.ownerNode is not
|
|
124
150
|
// universally implemented (happy-dom omits it), which would make our own
|
|
125
151
|
// installed copy look like a host copy.
|
|
126
|
-
const
|
|
152
|
+
const exclusion = excludeInstalled ? `:not([${MARKER_ATTRIBUTE}])` : "";
|
|
153
|
+
const nodes = document.querySelectorAll(`style${exclusion}, link[rel~="stylesheet"]${exclusion}`);
|
|
127
154
|
let count = 0;
|
|
128
155
|
for (const node of Array.from(nodes)) {
|
|
129
156
|
let rules;
|
|
@@ -144,6 +171,8 @@ const countHostContentStyleSheets = () => {
|
|
|
144
171
|
}
|
|
145
172
|
return count;
|
|
146
173
|
};
|
|
174
|
+
/** Copies the host loaded itself, i.e. not the one this module installed. */
|
|
175
|
+
const countHostContentStyleSheets = () => countContentStyleSheets({ excludeInstalled: true });
|
|
147
176
|
let auditWarningIssued = false;
|
|
148
177
|
const pendingChecks = [];
|
|
149
178
|
/**
|
|
@@ -8,3 +8,25 @@ export declare const FOCUSABLE_SELECTOR: string;
|
|
|
8
8
|
* not disabled, not inside `inert`, and has a layout box).
|
|
9
9
|
*/
|
|
10
10
|
export declare function isProgrammaticFocusTarget(el: HTMLElement): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Whether `el` is in the sequential tab order.
|
|
13
|
+
*
|
|
14
|
+
* Stricter than {@link isProgrammaticFocusTarget}, which deliberately accepts
|
|
15
|
+
* `tabindex="-1"`: such an element is a valid `focus()` target — a roving-tabindex
|
|
16
|
+
* control or a landmark focused after a view change — but is explicitly not tabbable.
|
|
17
|
+
* A tab order that included it would stop on elements the browser skips.
|
|
18
|
+
*/
|
|
19
|
+
export declare function isTabbable(el: HTMLElement): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Focusable descendants of `root` in tab order, descending into open shadow roots.
|
|
22
|
+
*
|
|
23
|
+
* `querySelectorAll` stops at a shadow boundary, which makes it the wrong tool for
|
|
24
|
+
* collecting a tab order in this codebase: every tool renders into `shadow: "open"`,
|
|
25
|
+
* so a container holding one has focusable content the selector cannot see. A focus
|
|
26
|
+
* trap built on the flat query traps Tab in the host's own chrome and never reaches
|
|
27
|
+
* the tool — the tool's controls are then unreachable by keyboard entirely.
|
|
28
|
+
*
|
|
29
|
+
* Closed shadow roots are invisible to script and are skipped; nothing can be done
|
|
30
|
+
* for them from outside.
|
|
31
|
+
*/
|
|
32
|
+
export declare function collectFocusable(root: Element | ShadowRoot): HTMLElement[];
|
|
@@ -24,3 +24,50 @@ export function isProgrammaticFocusTarget(el) {
|
|
|
24
24
|
return false;
|
|
25
25
|
return el.offsetParent !== null || el.getClientRects().length > 0;
|
|
26
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Whether `el` is in the sequential tab order.
|
|
29
|
+
*
|
|
30
|
+
* Stricter than {@link isProgrammaticFocusTarget}, which deliberately accepts
|
|
31
|
+
* `tabindex="-1"`: such an element is a valid `focus()` target — a roving-tabindex
|
|
32
|
+
* control or a landmark focused after a view change — but is explicitly not tabbable.
|
|
33
|
+
* A tab order that included it would stop on elements the browser skips.
|
|
34
|
+
*/
|
|
35
|
+
export function isTabbable(el) {
|
|
36
|
+
if (el.getAttribute("tabindex") === "-1")
|
|
37
|
+
return false;
|
|
38
|
+
return isProgrammaticFocusTarget(el);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Focusable descendants of `root` in tab order, descending into open shadow roots.
|
|
42
|
+
*
|
|
43
|
+
* `querySelectorAll` stops at a shadow boundary, which makes it the wrong tool for
|
|
44
|
+
* collecting a tab order in this codebase: every tool renders into `shadow: "open"`,
|
|
45
|
+
* so a container holding one has focusable content the selector cannot see. A focus
|
|
46
|
+
* trap built on the flat query traps Tab in the host's own chrome and never reaches
|
|
47
|
+
* the tool — the tool's controls are then unreachable by keyboard entirely.
|
|
48
|
+
*
|
|
49
|
+
* Closed shadow roots are invisible to script and are skipped; nothing can be done
|
|
50
|
+
* for them from outside.
|
|
51
|
+
*/
|
|
52
|
+
export function collectFocusable(root) {
|
|
53
|
+
const found = [];
|
|
54
|
+
const visit = (node) => {
|
|
55
|
+
for (const child of Array.from(node.children)) {
|
|
56
|
+
if (!(child instanceof HTMLElement))
|
|
57
|
+
continue;
|
|
58
|
+
// `inert` and its subtree take no focus, so there is nothing below to collect.
|
|
59
|
+
if (child.hasAttribute("inert"))
|
|
60
|
+
continue;
|
|
61
|
+
if (isTabbable(child))
|
|
62
|
+
found.push(child);
|
|
63
|
+
// A shadow host's own light children are slotted into its shadow tree, so the
|
|
64
|
+
// shadow root is where tab order continues.
|
|
65
|
+
if (child.shadowRoot)
|
|
66
|
+
visit(child.shadowRoot);
|
|
67
|
+
else
|
|
68
|
+
visit(child);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
visit(root);
|
|
72
|
+
return found;
|
|
73
|
+
}
|
package/dist/ui/focus-trap.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { collectFocusable } from "./first-focusable.js";
|
|
2
|
+
/**
|
|
3
|
+
* Shadow-aware: a trapped panel almost always hosts a custom element rendering into
|
|
4
|
+
* `shadow: "open"`, and a flat `querySelectorAll` would collect only the panel's own
|
|
5
|
+
* chrome — trapping Tab there and leaving the hosted tool's controls unreachable.
|
|
6
|
+
*/
|
|
2
7
|
function getFocusableElements(container) {
|
|
3
|
-
return
|
|
8
|
+
return collectFocusable(container);
|
|
4
9
|
}
|
|
5
10
|
function getDeepActiveElement(root) {
|
|
6
11
|
let active = root.activeElement;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export type PointerDragPosition = {
|
|
2
|
+
x: number;
|
|
3
|
+
y: number;
|
|
4
|
+
};
|
|
5
|
+
export interface PointerDragControllerArgs {
|
|
6
|
+
getPosition: () => PointerDragPosition;
|
|
7
|
+
setPosition: (next: PointerDragPosition) => void;
|
|
8
|
+
/** Called once a drag starts, before the first move — e.g. to bring the element to front. */
|
|
9
|
+
onDragStart?: (container: Element) => void;
|
|
10
|
+
}
|
|
11
|
+
export interface PointerDragController {
|
|
12
|
+
isDragging: () => boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Begin tracking a drag from a pointerdown. Callers own pointer-capture
|
|
15
|
+
* release and event-listener attach/detach — this only tracks the
|
|
16
|
+
* position math, since a caller may also need the same pointer-capture
|
|
17
|
+
* session for an unrelated interaction (e.g. a resize handle).
|
|
18
|
+
*/
|
|
19
|
+
startDragging: (event: PointerEvent, container: Element) => void;
|
|
20
|
+
/** No-ops when a drag isn't in progress, so callers can route every pointermove through this unconditionally. */
|
|
21
|
+
handlePointerMove: (event: PointerEvent) => void;
|
|
22
|
+
endDragging: () => void;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Tracks a pointer-capture-based drag: the position delta between where the
|
|
26
|
+
* pointer went down (relative to the element's current position) and where
|
|
27
|
+
* it currently is. Shared by every floating tool overlay that repositions
|
|
28
|
+
* itself by dragging its own chrome.
|
|
29
|
+
*/
|
|
30
|
+
export declare function createPointerDragController(args: PointerDragControllerArgs): PointerDragController;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tracks a pointer-capture-based drag: the position delta between where the
|
|
3
|
+
* pointer went down (relative to the element's current position) and where
|
|
4
|
+
* it currently is. Shared by every floating tool overlay that repositions
|
|
5
|
+
* itself by dragging its own chrome.
|
|
6
|
+
*/
|
|
7
|
+
export function createPointerDragController(args) {
|
|
8
|
+
let dragging = false;
|
|
9
|
+
let dragStart = { x: 0, y: 0 };
|
|
10
|
+
return {
|
|
11
|
+
isDragging: () => dragging,
|
|
12
|
+
startDragging(event, container) {
|
|
13
|
+
container.setPointerCapture(event.pointerId);
|
|
14
|
+
dragging = true;
|
|
15
|
+
const position = args.getPosition();
|
|
16
|
+
dragStart = {
|
|
17
|
+
x: event.clientX - position.x,
|
|
18
|
+
y: event.clientY - position.y,
|
|
19
|
+
};
|
|
20
|
+
args.onDragStart?.(container);
|
|
21
|
+
},
|
|
22
|
+
handlePointerMove(event) {
|
|
23
|
+
if (!dragging)
|
|
24
|
+
return;
|
|
25
|
+
args.setPosition({
|
|
26
|
+
x: event.clientX - dragStart.x,
|
|
27
|
+
y: event.clientY - dragStart.y,
|
|
28
|
+
});
|
|
29
|
+
},
|
|
30
|
+
endDragging() {
|
|
31
|
+
dragging = false;
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pie-players/pie-players-shared",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.68",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Shared runtime + UI utilities for PIE players",
|
|
6
6
|
"license": "MIT",
|
|
@@ -37,6 +37,14 @@
|
|
|
37
37
|
"types": "./dist/types/index.d.ts",
|
|
38
38
|
"import": "./dist/types/index.js"
|
|
39
39
|
},
|
|
40
|
+
"./formative": {
|
|
41
|
+
"types": "./dist/formative/index.d.ts",
|
|
42
|
+
"import": "./dist/formative/index.js"
|
|
43
|
+
},
|
|
44
|
+
"./timed-media": {
|
|
45
|
+
"types": "./dist/timed-media/index.d.ts",
|
|
46
|
+
"import": "./dist/timed-media/index.js"
|
|
47
|
+
},
|
|
40
48
|
"./pie": {
|
|
41
49
|
"types": "./dist/pie/index.d.ts",
|
|
42
50
|
"import": "./dist/pie/index.js"
|
|
@@ -53,10 +61,30 @@
|
|
|
53
61
|
"types": "./dist/server/npm-registry.d.ts",
|
|
54
62
|
"import": "./dist/server/npm-registry.js"
|
|
55
63
|
},
|
|
64
|
+
"./server/npm-auth-env": {
|
|
65
|
+
"types": "./dist/server/npm-auth-env.d.ts",
|
|
66
|
+
"import": "./dist/server/npm-auth-env.js"
|
|
67
|
+
},
|
|
56
68
|
"./i18n": {
|
|
57
69
|
"types": "./dist/i18n/index.d.ts",
|
|
58
70
|
"import": "./dist/i18n/index.js"
|
|
59
71
|
},
|
|
72
|
+
"./i18n/types": {
|
|
73
|
+
"types": "./dist/i18n/types.d.ts",
|
|
74
|
+
"import": "./dist/i18n/types.js"
|
|
75
|
+
},
|
|
76
|
+
"./i18n/provider": {
|
|
77
|
+
"types": "./dist/i18n/provider.d.ts",
|
|
78
|
+
"import": "./dist/i18n/provider.js"
|
|
79
|
+
},
|
|
80
|
+
"./i18n/catalogs": {
|
|
81
|
+
"types": "./dist/i18n/catalogs.d.ts",
|
|
82
|
+
"import": "./dist/i18n/catalogs.js"
|
|
83
|
+
},
|
|
84
|
+
"./i18n/language-tags": {
|
|
85
|
+
"types": "./dist/i18n/language-tags.d.ts",
|
|
86
|
+
"import": "./dist/i18n/language-tags.js"
|
|
87
|
+
},
|
|
60
88
|
"./nds-icon-button": {
|
|
61
89
|
"types": "./dist/components/vendor/nds/nds-icon-button.d.ts",
|
|
62
90
|
"import": "./dist/components/vendor/nds/nds-icon-button.js"
|
|
@@ -68,6 +96,10 @@
|
|
|
68
96
|
"./ui/content-styles": {
|
|
69
97
|
"types": "./dist/ui/content-styles.d.ts",
|
|
70
98
|
"import": "./dist/ui/content-styles.js"
|
|
99
|
+
},
|
|
100
|
+
"./tools/term-lookup": {
|
|
101
|
+
"types": "./dist/tools/term-lookup.d.ts",
|
|
102
|
+
"import": "./dist/tools/term-lookup.js"
|
|
71
103
|
}
|
|
72
104
|
},
|
|
73
105
|
"files": [
|
|
@@ -89,11 +121,11 @@
|
|
|
89
121
|
"scan-hardcoded": "bun run src/i18n/scripts/scan-hardcoded.ts"
|
|
90
122
|
},
|
|
91
123
|
"devDependencies": {
|
|
92
|
-
"@biomejs/biome": "^2.5.
|
|
124
|
+
"@biomejs/biome": "^2.5.8",
|
|
93
125
|
"@happy-dom/global-registrator": "^20.11.1",
|
|
94
126
|
"@playwright/test": "^1.62.1",
|
|
95
127
|
"@types/semver": "^7.8.0",
|
|
96
|
-
"esbuild": "^0.28.
|
|
128
|
+
"esbuild": "^0.28.2",
|
|
97
129
|
"glob": "^13.0.0",
|
|
98
130
|
"svelte": "^5.56.8",
|
|
99
131
|
"typescript": "^5.9.3"
|
package/dist/i18n/loader.d.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Translation Loader
|
|
3
|
-
*
|
|
4
|
-
* Implements hybrid loading strategy:
|
|
5
|
-
* - English bundled with application
|
|
6
|
-
* - Other languages loaded on demand
|
|
7
|
-
*
|
|
8
|
-
* Part of PIE Players i18n system.
|
|
9
|
-
*/
|
|
10
|
-
import type { TranslationBundle } from "./types.js";
|
|
11
|
-
/**
|
|
12
|
-
* Bundled English translations
|
|
13
|
-
* Available immediately without network request
|
|
14
|
-
*/
|
|
15
|
-
export declare const BUNDLED_TRANSLATIONS: Record<string, TranslationBundle>;
|
|
16
|
-
/**
|
|
17
|
-
* Load translations for a locale
|
|
18
|
-
*
|
|
19
|
-
* @param locale Locale code (e.g., 'en', 'es', 'zh', 'ar')
|
|
20
|
-
* @returns Translation bundle with flattened keys
|
|
21
|
-
*/
|
|
22
|
-
export declare function loadTranslations(locale: string): Promise<TranslationBundle>;
|
|
23
|
-
/**
|
|
24
|
-
* Get available locales
|
|
25
|
-
*
|
|
26
|
-
* @returns Array of locale codes
|
|
27
|
-
*/
|
|
28
|
-
export declare function getAvailableLocales(): string[];
|
|
29
|
-
/**
|
|
30
|
-
* Check if locale is RTL
|
|
31
|
-
*
|
|
32
|
-
* @param locale Locale code
|
|
33
|
-
* @returns True if locale is right-to-left
|
|
34
|
-
*/
|
|
35
|
-
export declare function isRTLLocale(locale: string): boolean;
|
package/dist/i18n/loader.js
DELETED
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Translation Loader
|
|
3
|
-
*
|
|
4
|
-
* Implements hybrid loading strategy:
|
|
5
|
-
* - English bundled with application
|
|
6
|
-
* - Other languages loaded on demand
|
|
7
|
-
*
|
|
8
|
-
* Part of PIE Players i18n system.
|
|
9
|
-
*/
|
|
10
|
-
// Import English translations (bundled)
|
|
11
|
-
import enCommon from "./translations/en/common.json" with { type: "json" };
|
|
12
|
-
import enToolkit from "./translations/en/toolkit.json" with { type: "json" };
|
|
13
|
-
import enTools from "./translations/en/tools.json" with { type: "json" };
|
|
14
|
-
// RTL locales
|
|
15
|
-
const RTL_LOCALES = ["ar", "he", "fa", "ur"];
|
|
16
|
-
/**
|
|
17
|
-
* Bundled English translations
|
|
18
|
-
* Available immediately without network request
|
|
19
|
-
*/
|
|
20
|
-
export const BUNDLED_TRANSLATIONS = {
|
|
21
|
-
en: {
|
|
22
|
-
locale: "en",
|
|
23
|
-
direction: "ltr",
|
|
24
|
-
translations: {
|
|
25
|
-
...flattenTranslations(enCommon),
|
|
26
|
-
...flattenTranslations(enToolkit),
|
|
27
|
-
...flattenTranslations(enTools),
|
|
28
|
-
},
|
|
29
|
-
},
|
|
30
|
-
};
|
|
31
|
-
const LOCALE_IMPORTS = {
|
|
32
|
-
es: () => Promise.all([
|
|
33
|
-
import("./translations/es/common.json"),
|
|
34
|
-
import("./translations/es/toolkit.json"),
|
|
35
|
-
import("./translations/es/tools.json"),
|
|
36
|
-
]),
|
|
37
|
-
zh: () => Promise.all([
|
|
38
|
-
import("./translations/zh/common.json"),
|
|
39
|
-
import("./translations/zh/toolkit.json"),
|
|
40
|
-
import("./translations/zh/tools.json"),
|
|
41
|
-
]),
|
|
42
|
-
ar: () => Promise.all([
|
|
43
|
-
import("./translations/ar/common.json"),
|
|
44
|
-
import("./translations/ar/toolkit.json"),
|
|
45
|
-
import("./translations/ar/tools.json"),
|
|
46
|
-
]),
|
|
47
|
-
};
|
|
48
|
-
/**
|
|
49
|
-
* Load translations for a locale
|
|
50
|
-
*
|
|
51
|
-
* @param locale Locale code (e.g., 'en', 'es', 'zh', 'ar')
|
|
52
|
-
* @returns Translation bundle with flattened keys
|
|
53
|
-
*/
|
|
54
|
-
export async function loadTranslations(locale) {
|
|
55
|
-
// Return bundled English immediately
|
|
56
|
-
if (locale === "en") {
|
|
57
|
-
return BUNDLED_TRANSLATIONS.en;
|
|
58
|
-
}
|
|
59
|
-
const importer = LOCALE_IMPORTS[locale];
|
|
60
|
-
if (!importer) {
|
|
61
|
-
throw new Error(`Translation files not found for locale: ${locale}. Ensure translation files exist in packages/players-shared/src/i18n/translations/${locale}/`);
|
|
62
|
-
}
|
|
63
|
-
// Lazy load other locales
|
|
64
|
-
try {
|
|
65
|
-
const [common, toolkit, tools] = await importer();
|
|
66
|
-
return {
|
|
67
|
-
locale,
|
|
68
|
-
direction: RTL_LOCALES.includes(locale) ? "rtl" : "ltr",
|
|
69
|
-
translations: {
|
|
70
|
-
...flattenTranslations(common.default || common),
|
|
71
|
-
...flattenTranslations(toolkit.default || toolkit),
|
|
72
|
-
...flattenTranslations(tools.default || tools),
|
|
73
|
-
},
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
catch (error) {
|
|
77
|
-
console.error(`Failed to load translations for locale: ${locale}`, error);
|
|
78
|
-
throw new Error(`Translation files not found for locale: ${locale}. Ensure translation files exist in packages/players-shared/src/i18n/translations/${locale}/`);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
/**
|
|
82
|
-
* Flatten nested JSON to dot notation
|
|
83
|
-
*
|
|
84
|
-
* Converts:
|
|
85
|
-
* { "common": { "save": "Save" } }
|
|
86
|
-
*
|
|
87
|
-
* To:
|
|
88
|
-
* { "common.save": "Save" }
|
|
89
|
-
*
|
|
90
|
-
* @param obj Nested translation object
|
|
91
|
-
* @param prefix Current key prefix
|
|
92
|
-
* @param result Accumulated result
|
|
93
|
-
* @returns Flattened translation object
|
|
94
|
-
*/
|
|
95
|
-
function flattenTranslations(obj, prefix = "", result = {}) {
|
|
96
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
97
|
-
const newKey = prefix ? `${prefix}.${key}` : key;
|
|
98
|
-
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
99
|
-
// Check if it's a plural form (has 'one' or 'other' keys)
|
|
100
|
-
if ("one" in value || "other" in value) {
|
|
101
|
-
// This is a plural translation, keep it as an object
|
|
102
|
-
result[newKey] = value;
|
|
103
|
-
}
|
|
104
|
-
else {
|
|
105
|
-
// This is a nested object, flatten recursively
|
|
106
|
-
flattenTranslations(value, newKey, result);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
else {
|
|
110
|
-
// This is a leaf value
|
|
111
|
-
result[newKey] = value;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
return result;
|
|
115
|
-
}
|
|
116
|
-
/**
|
|
117
|
-
* Get available locales
|
|
118
|
-
*
|
|
119
|
-
* @returns Array of locale codes
|
|
120
|
-
*/
|
|
121
|
-
export function getAvailableLocales() {
|
|
122
|
-
return ["en", "es", "zh", "ar"];
|
|
123
|
-
}
|
|
124
|
-
/**
|
|
125
|
-
* Check if locale is RTL
|
|
126
|
-
*
|
|
127
|
-
* @param locale Locale code
|
|
128
|
-
* @returns True if locale is right-to-left
|
|
129
|
-
*/
|
|
130
|
-
export function isRTLLocale(locale) {
|
|
131
|
-
return RTL_LOCALES.includes(locale);
|
|
132
|
-
}
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Simple I18n Implementation
|
|
3
|
-
*
|
|
4
|
-
* Lightweight i18n without external dependencies.
|
|
5
|
-
* Used for standalone components that don't need the full service architecture.
|
|
6
|
-
*/
|
|
7
|
-
import type { I18nConfig } from "./types.js";
|
|
8
|
-
/**
|
|
9
|
-
* Simple I18n class for standalone use
|
|
10
|
-
*/
|
|
11
|
-
export declare class SimpleI18n {
|
|
12
|
-
private locale;
|
|
13
|
-
private fallbackLocale;
|
|
14
|
-
private direction;
|
|
15
|
-
private translations;
|
|
16
|
-
private listeners;
|
|
17
|
-
private loadingPromises;
|
|
18
|
-
private config;
|
|
19
|
-
constructor(config?: I18nConfig);
|
|
20
|
-
/**
|
|
21
|
-
* Initialize with locale
|
|
22
|
-
*/
|
|
23
|
-
initialize(config: I18nConfig): Promise<void>;
|
|
24
|
-
/**
|
|
25
|
-
* Translate a key
|
|
26
|
-
*/
|
|
27
|
-
t(key: string, params?: Record<string, any>): string;
|
|
28
|
-
/**
|
|
29
|
-
* Translate with pluralization
|
|
30
|
-
*/
|
|
31
|
-
tn(key: string, count: number, params?: Record<string, any>): string;
|
|
32
|
-
/**
|
|
33
|
-
* Get current locale
|
|
34
|
-
*/
|
|
35
|
-
getLocale(): string;
|
|
36
|
-
/**
|
|
37
|
-
* Change locale
|
|
38
|
-
*/
|
|
39
|
-
setLocale(locale: string): Promise<void>;
|
|
40
|
-
/**
|
|
41
|
-
* Get current direction
|
|
42
|
-
*/
|
|
43
|
-
getDirection(): "ltr" | "rtl";
|
|
44
|
-
/**
|
|
45
|
-
* Get available locales
|
|
46
|
-
*/
|
|
47
|
-
getAvailableLocales(): string[];
|
|
48
|
-
/**
|
|
49
|
-
* Check if locale is loaded
|
|
50
|
-
*/
|
|
51
|
-
isLocaleLoaded(locale: string): boolean;
|
|
52
|
-
/**
|
|
53
|
-
* Subscribe to changes
|
|
54
|
-
*/
|
|
55
|
-
subscribe(listener: () => void): () => void;
|
|
56
|
-
/**
|
|
57
|
-
* Check if key exists
|
|
58
|
-
*/
|
|
59
|
-
hasKey(key: string): boolean;
|
|
60
|
-
private notifyListeners;
|
|
61
|
-
private applyLocale;
|
|
62
|
-
private applyDOMDirection;
|
|
63
|
-
private loadTranslationsForLocale;
|
|
64
|
-
private getTranslation;
|
|
65
|
-
private interpolate;
|
|
66
|
-
private selectPluralForm;
|
|
67
|
-
private detectBrowserLocale;
|
|
68
|
-
}
|