@pie-players/pie-assessment-toolkit 0.3.67 → 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.
Files changed (57) hide show
  1. package/README.md +1 -1
  2. package/dist/attempt/AssessmentSession.d.ts +7 -27
  3. package/dist/components/ItemToolBar.custom-element.js +1 -1
  4. package/dist/components/PieAssessmentToolkit.custom-element.js +13 -13
  5. package/dist/components/SectionToolBar.custom-element.js +1 -1
  6. package/dist/components/chunks/ItemToolBar-38mhtjsq.js +51 -0
  7. package/dist/components/chunks/ItemToolBar-9ymm7pd1.js +46 -0
  8. package/dist/components/item-toolbar-element.js +1 -0
  9. package/dist/components/pie-assessment-toolkit-element.js +1 -0
  10. package/dist/components/section-toolbar-element.js +1 -0
  11. package/dist/context/assessment-toolkit-context.d.ts +28 -0
  12. package/dist/index.d.ts +7 -3
  13. package/dist/index.js +4 -2
  14. package/dist/runtime/SectionRuntimeEngine.d.ts +56 -0
  15. package/dist/runtime/SectionRuntimeEngine.js +66 -1
  16. package/dist/runtime/core/engine-resolver.d.ts +25 -1
  17. package/dist/runtime/registration-events.d.ts +52 -0
  18. package/dist/runtime/registration-events.js +2 -0
  19. package/dist/services/AccessibilityCatalogResolver.js +21 -5
  20. package/dist/services/I18nService.d.ts +28 -100
  21. package/dist/services/I18nService.js +43 -233
  22. package/dist/services/TTSService.d.ts +12 -0
  23. package/dist/services/TTSService.js +18 -17
  24. package/dist/services/ToolRegistry.d.ts +86 -2
  25. package/dist/services/ToolRegistry.js +30 -0
  26. package/dist/services/ToolkitCoordinator.d.ts +39 -37
  27. package/dist/services/ToolkitCoordinator.js +40 -0
  28. package/dist/services/audio-handoff.d.ts +39 -0
  29. package/dist/services/audio-handoff.js +58 -0
  30. package/dist/services/catalog-media.d.ts +35 -4
  31. package/dist/services/catalog-media.js +92 -3
  32. package/dist/services/framework-error.d.ts +15 -1
  33. package/dist/services/interfaces.d.ts +28 -0
  34. package/dist/services/pnp-standard-features.d.ts +1 -1
  35. package/dist/services/section-controller-types.d.ts +218 -7
  36. package/dist/services/selection-action.d.ts +49 -0
  37. package/dist/services/selection-action.js +10 -0
  38. package/dist/services/spoken-audio-cards.js +5 -1
  39. package/dist/services/tool-context.d.ts +6 -5
  40. package/dist/services/tool-context.js +197 -152
  41. package/dist/services/tool-icons.d.ts +18 -0
  42. package/dist/services/tool-icons.js +31 -0
  43. package/dist/services/tool-providers/DesmosToolProvider.d.ts +6 -5
  44. package/dist/services/tool-request.d.ts +106 -0
  45. package/dist/services/tool-request.js +127 -0
  46. package/dist/services/toolbar-items.d.ts +6 -0
  47. package/dist/tools/client.d.ts +0 -1
  48. package/dist/tools/client.js +0 -2
  49. package/dist/tools/internal.d.ts +6 -0
  50. package/dist/tools/internal.js +7 -0
  51. package/dist/tools/tool-surface-host.d.ts +57 -0
  52. package/dist/tools/tool-surface-host.js +610 -0
  53. package/package.json +10 -10
  54. package/dist/components/chunks/ItemToolBar-8jgdz50p.js +0 -51
  55. package/dist/components/chunks/ItemToolBar-cvs646j3.js +0 -36
  56. package/dist/tools/calculators/desmos-provider.d.ts +0 -46
  57. package/dist/tools/calculators/desmos-provider.js +0 -393
@@ -48,6 +48,7 @@ import type { SectionEngineInput } from "./core/engine-input.js";
48
48
  import type { SectionEngineOutput } from "./core/engine-output.js";
49
49
  import { type EffectiveRuntime, type RuntimeInputs } from "./core/engine-resolver.js";
50
50
  import { type SectionEngineState } from "./core/engine-state.js";
51
+ import type { MediaTimeSource } from "@pie-players/pie-players-shared/timed-media";
51
52
  import type { RuntimeRegistrationDetail } from "./registration-events.js";
52
53
  import { RuntimeRegistry } from "./RuntimeRegistry.js";
53
54
  /**
@@ -95,6 +96,27 @@ interface RuntimeController extends SectionControllerHandle {
95
96
  } | null;
96
97
  subscribe?: (listener: (event: SectionControllerEvent) => void) => () => void;
97
98
  navigateToItem?: (index: number) => unknown;
99
+ attachMediaTimeSource?: (source: MediaTimeSource, options?: {
100
+ origin?: "native-adapter" | "host";
101
+ renderableId?: string;
102
+ }) => void;
103
+ detachMediaTimeSource?: (options?: {
104
+ origin?: "native-adapter" | "host";
105
+ }) => void;
106
+ pauseMediaForCompetingAudio?: () => boolean;
107
+ }
108
+ /** What a media-time-source registration reaching the engine has to say. */
109
+ export interface SectionRuntimeMediaTimeSourceAction {
110
+ renderableId: string;
111
+ action: "attach" | "detach";
112
+ source?: MediaTimeSource;
113
+ origin?: "native-adapter" | "host";
114
+ }
115
+ /** What a formative action reaching the engine has to say. */
116
+ export interface SectionRuntimeFormativeAction {
117
+ itemId: string;
118
+ action: "check" | "retry";
119
+ outcomes?: unknown[];
98
120
  }
99
121
  /**
100
122
  * Initialize args used by the toolkit CE to resolve the coordinator-backed
@@ -109,6 +131,14 @@ interface EngineInitArgs {
109
131
  attemptId?: string;
110
132
  createDefaultController: () => Promise<RuntimeController> | RuntimeController;
111
133
  onCompositionChanged?: (composition: unknown) => void;
134
+ /**
135
+ * The controller event that caused a republish, for the few events the toolkit
136
+ * has to act on rather than merely propagate — a timed-media policy that cannot
137
+ * be enforced becomes a framework warning, and malformed cue data becomes a
138
+ * framework error. Every other event reaches hosts through the composition
139
+ * republish and the coordinator's own subscriptions.
140
+ */
141
+ onControllerEvent?: (event: SectionControllerEvent) => void;
112
142
  }
113
143
  /**
114
144
  * Engine-side `attachHost` args. The host element, framework-error bus,
@@ -273,6 +303,32 @@ export declare class SectionRuntimeEngine {
273
303
  }): void;
274
304
  updateItemSession(itemId: string, session: unknown): unknown;
275
305
  navigateToItem(index: number): unknown;
306
+ /**
307
+ * Route a learner's formative action to the controller.
308
+ *
309
+ * Canonicalized here for the same reason `updateItemSession` is: the runtime
310
+ * id a card dispatches with is not necessarily the identifier the controller
311
+ * keys state by.
312
+ */
313
+ handleFormativeAction(action: SectionRuntimeFormativeAction): void;
314
+ /**
315
+ * Bind or release the section's Media Time Source.
316
+ *
317
+ * A pass-through, like `handleFormativeAction`: the engine routes, the
318
+ * controller decides. `renderableId` travels with the attach because whether the
319
+ * registering renderable is the one `stimulusRef` names is the controller's call
320
+ * — it validated `stimulusRef` in the first place.
321
+ */
322
+ handleMediaTimeSource(action: SectionRuntimeMediaTimeSourceAction): void;
323
+ /**
324
+ * Silence media audio because something else is about to speak.
325
+ *
326
+ * A pass-through like `handleMediaTimeSource`, and for the same reason: which
327
+ * source is authoritative and whether it reports `canPause` are the controller's
328
+ * to know. Returns whether media audio is now silent, or `true` where there is
329
+ * no timed-media controller to ask — nothing is playing.
330
+ */
331
+ requestMediaPauseForCompetingAudio(): boolean;
276
332
  persist(): Promise<void>;
277
333
  hydrate(): Promise<void>;
278
334
  getRegistry(): RuntimeRegistry;
@@ -41,8 +41,10 @@
41
41
  import { SectionEngineAdapter, } from "./adapter/SectionEngineAdapter.js";
42
42
  import { resolveSectionEngineRuntimeState, } from "./core/engine-resolver.js";
43
43
  import { createInitialEngineState, } from "./core/engine-state.js";
44
+ import { createPieLogger, isGlobalDebugEnabled, } from "@pie-players/pie-players-shared";
44
45
  import { RuntimeRegistry } from "./RuntimeRegistry.js";
45
46
  import { createRuntimeId } from "./runtime-id.js";
47
+ const logger = createPieLogger("section-runtime-engine", () => isGlobalDebugEnabled());
46
48
  export class SectionRuntimeEngine {
47
49
  registry = new RuntimeRegistry();
48
50
  runtimeId = createRuntimeId("section-engine");
@@ -209,7 +211,17 @@ export class SectionRuntimeEngine {
209
211
  this.replayRegisteredShellsIntoController(resolved);
210
212
  args.onCompositionChanged?.(resolved.getCompositionModel?.());
211
213
  this.unsubscribeController =
212
- resolved.subscribe?.(() => {
214
+ resolved.subscribe?.((event) => {
215
+ // Isolated deliberately: the controller's emit loop catches per
216
+ // listener, so a diagnostic handler that throws would take the
217
+ // composition republish down with it and every cue and Try would stop
218
+ // reaching the cards — a failure with no symptom except a warning.
219
+ try {
220
+ args.onControllerEvent?.(event);
221
+ }
222
+ catch (error) {
223
+ logger.warn("onControllerEvent handler threw", error);
224
+ }
213
225
  args.onCompositionChanged?.(resolved.getCompositionModel?.());
214
226
  }) || null;
215
227
  }
@@ -333,6 +345,59 @@ export class SectionRuntimeEngine {
333
345
  navigateToItem(index) {
334
346
  return this.controller?.navigateToItem?.(index) ?? null;
335
347
  }
348
+ /**
349
+ * Route a learner's formative action to the controller.
350
+ *
351
+ * Canonicalized here for the same reason `updateItemSession` is: the runtime
352
+ * id a card dispatches with is not necessarily the identifier the controller
353
+ * keys state by.
354
+ */
355
+ handleFormativeAction(action) {
356
+ if (!action?.itemId)
357
+ return;
358
+ const canonicalId = this.getCanonicalItemId(action.itemId);
359
+ if (action.action === "retry") {
360
+ this.controller?.retryFormativeItem?.({ itemId: canonicalId });
361
+ return;
362
+ }
363
+ this.controller?.recordFormativeTry?.({
364
+ itemId: canonicalId,
365
+ outcomes: action.outcomes,
366
+ });
367
+ }
368
+ /**
369
+ * Bind or release the section's Media Time Source.
370
+ *
371
+ * A pass-through, like `handleFormativeAction`: the engine routes, the
372
+ * controller decides. `renderableId` travels with the attach because whether the
373
+ * registering renderable is the one `stimulusRef` names is the controller's call
374
+ * — it validated `stimulusRef` in the first place.
375
+ */
376
+ handleMediaTimeSource(action) {
377
+ if (action?.action === "detach") {
378
+ this.controller?.detachMediaTimeSource?.({
379
+ origin: action.origin ?? "host",
380
+ });
381
+ return;
382
+ }
383
+ if (!action?.source)
384
+ return;
385
+ this.controller?.attachMediaTimeSource?.(action.source, {
386
+ origin: action.origin ?? "host",
387
+ renderableId: action.renderableId,
388
+ });
389
+ }
390
+ /**
391
+ * Silence media audio because something else is about to speak.
392
+ *
393
+ * A pass-through like `handleMediaTimeSource`, and for the same reason: which
394
+ * source is authoritative and whether it reports `canPause` are the controller's
395
+ * to know. Returns whether media audio is now silent, or `true` where there is
396
+ * no timed-media controller to ask — nothing is playing.
397
+ */
398
+ requestMediaPauseForCompetingAudio() {
399
+ return this.controller?.pauseMediaForCompetingAudio?.() ?? true;
400
+ }
336
401
  async persist() {
337
402
  await this.controller?.persist?.();
338
403
  }
@@ -68,6 +68,18 @@ export type RuntimeConfig = {
68
68
  * markup (the default). Purely visual — no engine effect.
69
69
  */
70
70
  ndsIcons?: boolean;
71
+ /**
72
+ * Interface locale: a BCP-47 tag naming the language the player renders its own
73
+ * UI in. Purely presentational — no engine effect.
74
+ *
75
+ * Distinct from content language, which describes the authored item and
76
+ * travels on `env`. QTI 3's implementation guide states the independence
77
+ * directly: a candidate may choose an interface language which may or may not
78
+ * also be the language of the content.
79
+ *
80
+ * Unset renders `en-US`. POSIX (`nl_NL`) and bare (`nl`) forms both resolve.
81
+ */
82
+ locale?: string;
71
83
  toolConfigStrictness?: ToolConfigStrictness;
72
84
  onFrameworkError?: FrameworkErrorHandler;
73
85
  onStageChange?: StageChangeHandler;
@@ -120,7 +132,7 @@ export declare function resolveRuntime(args: {
120
132
  createSectionController: unknown;
121
133
  isolation: string;
122
134
  env: Record<string, unknown>;
123
- toolConfigStrictness: "off" | "warn" | "error";
135
+ toolConfigStrictness: "off" | "error" | "warn";
124
136
  onFrameworkError: FrameworkErrorHandler | undefined;
125
137
  onStageChange: StageChangeHandler | undefined;
126
138
  onLoadingComplete: LoadingCompleteHandler | undefined;
@@ -134,6 +146,18 @@ export declare function resolveRuntime(args: {
134
146
  * markup (the default). Purely visual — no engine effect.
135
147
  */
136
148
  ndsIcons?: boolean;
149
+ /**
150
+ * Interface locale: a BCP-47 tag naming the language the player renders its own
151
+ * UI in. Purely presentational — no engine effect.
152
+ *
153
+ * Distinct from content language, which describes the authored item and
154
+ * travels on `env`. QTI 3's implementation guide states the independence
155
+ * directly: a candidate may choose an interface language which may or may not
156
+ * also be the language of the content.
157
+ *
158
+ * Unset renders `en-US`. POSIX (`nl_NL`) and bare (`nl`) forms both resolve.
159
+ */
160
+ locale?: string;
137
161
  };
138
162
  /**
139
163
  * Effective runtime returned by `resolveRuntime`. The shape is exposed
@@ -1,9 +1,12 @@
1
+ import type { MediaTimeSource } from "@pie-players/pie-players-shared/timed-media";
1
2
  export declare const PIE_REGISTER_EVENT = "pie-register";
2
3
  export declare const PIE_UNREGISTER_EVENT = "pie-unregister";
3
4
  export declare const PIE_INTERNAL_ITEM_SESSION_CHANGED_EVENT = "pie-item-session-changed";
4
5
  export declare const PIE_ITEM_SESSION_CHANGED_EVENT = "item-session-changed";
5
6
  export declare const PIE_INTERNAL_CONTENT_LOADED_EVENT = "pie-content-loaded";
6
7
  export declare const PIE_INTERNAL_ITEM_PLAYER_ERROR_EVENT = "pie-item-player-error";
8
+ export declare const PIE_INTERNAL_FORMATIVE_ACTION_EVENT = "pie-formative-action";
9
+ export declare const PIE_INTERNAL_MEDIA_TIME_SOURCE_EVENT = "pie-media-time-source";
7
10
  export type RuntimeRegistrationKind = "item" | "passage";
8
11
  export interface RuntimeRegistrationDetail {
9
12
  kind: RuntimeRegistrationKind;
@@ -35,3 +38,52 @@ export interface InternalItemPlayerErrorDetail {
35
38
  contentKind?: string;
36
39
  error: unknown;
37
40
  }
41
+ /**
42
+ * A learner's formative action, dispatched by the component that owns the
43
+ * control and the item player node — the only place that can call
44
+ * `provideScore()`. It reports outcomes rather than interpreting them; the
45
+ * section controller derives correctness and owns the state.
46
+ *
47
+ * `outcomes` is the array `pie-item-player.provideScore()` returned, verbatim,
48
+ * including the `undefined` slots it leaves for models with no element or
49
+ * controller.
50
+ */
51
+ export interface InternalFormativeActionDetail {
52
+ itemId: string;
53
+ canonicalItemId?: string;
54
+ action: "check" | "retry";
55
+ outcomes?: unknown[];
56
+ }
57
+ /**
58
+ * A Media Time Source becoming available or going away.
59
+ *
60
+ * The one seam through which a timed-media section reaches media, and
61
+ * deliberately the *only* one: the stimulus card dispatches this with a native
62
+ * `<video>` adapter, and a host wrapping a third-party player dispatches the same
63
+ * event with its own port. One code path, two producers — which is what keeps
64
+ * "a host can supply its own media element without shipping a PIE element" true
65
+ * rather than aspirational.
66
+ *
67
+ * `source` carries a live object, not serializable data. That is fine and
68
+ * intended: this event never crosses a realm, exactly like the element reference
69
+ * on `pie-register`.
70
+ */
71
+ export interface InternalMediaTimeSourceDetail {
72
+ /** The renderable that owns the media, for matching against `stimulusRef`. */
73
+ renderableId: string;
74
+ action: "attach" | "detach";
75
+ source?: MediaTimeSource;
76
+ /**
77
+ * Which producer this came from. `"native-adapter"` is the stimulus card
78
+ * wrapping a media element it found in its own subtree; anything else is a host
79
+ * wiring its own player, and omitting the field reads as `"host"` because a
80
+ * caller constructing this event by hand is one.
81
+ *
82
+ * Load-bearing for precedence: the card re-runs its discovery whenever its
83
+ * content changes, so without this a host that supplied a third-party port would
84
+ * have it silently replaced by the native element mid-session — and the
85
+ * capabilities would flip back with it, which is exactly the "appears to enforce"
86
+ * failure this contract exists to prevent.
87
+ */
88
+ origin?: "native-adapter" | "host";
89
+ }
@@ -4,3 +4,5 @@ export const PIE_INTERNAL_ITEM_SESSION_CHANGED_EVENT = "pie-item-session-changed
4
4
  export const PIE_ITEM_SESSION_CHANGED_EVENT = "item-session-changed";
5
5
  export const PIE_INTERNAL_CONTENT_LOADED_EVENT = "pie-content-loaded";
6
6
  export const PIE_INTERNAL_ITEM_PLAYER_ERROR_EVENT = "pie-item-player-error";
7
+ export const PIE_INTERNAL_FORMATIVE_ACTION_EVENT = "pie-formative-action";
8
+ export const PIE_INTERNAL_MEDIA_TIME_SOURCE_EVENT = "pie-media-time-source";
@@ -1,3 +1,4 @@
1
+ import { languageTagLookupSequence, normalizeLanguageTag, } from "@pie-players/pie-players-shared/i18n/language-tags";
1
2
  import { catalogOwnerContextFor, collectOwnerCatalogRegistrations, } from "./catalog-owner.js";
2
3
  import { sanitizeSsmlString } from "./SSMLExtractor.js";
3
4
  /**
@@ -537,14 +538,25 @@ export class AccessibilityCatalogResolver {
537
538
  */
538
539
  findMatchingCard(catalog, options) {
539
540
  const { type, language, useFallback = true, form } = options;
540
- // Language rungs, most specific first: requested language, then the default
541
- // language, then any. Unchanged — only what happens *within* a rung is new.
541
+ // Language rungs, most specific first: the requested language, then the
542
+ // default language, then any.
543
+ //
544
+ // Each requested tag expands into its RFC 4647 lookup sequence, so `es-MX`
545
+ // tries `es-mx` and then `es` before falling through to the default. Matching
546
+ // was `===`, which made a POSIX `es_ES` card — what the Learnosity transform
547
+ // emits — unreachable for an `es-ES` request except through the final
548
+ // no-constraint rung, i.e. by accident.
542
549
  const languageRungs = [];
550
+ const pushLookupRungs = (tag) => {
551
+ for (const step of languageTagLookupSequence(tag)) {
552
+ languageRungs.push((card) => normalizeLanguageTag(card.language) === step);
553
+ }
554
+ };
543
555
  if (language) {
544
- languageRungs.push((card) => card.language === language);
556
+ pushLookupRungs(language);
545
557
  }
546
558
  if (useFallback) {
547
- languageRungs.push((card) => card.language === this.defaultLanguage);
559
+ pushLookupRungs(this.defaultLanguage);
548
560
  languageRungs.push(() => true);
549
561
  }
550
562
  for (const matchesLanguage of languageRungs) {
@@ -586,7 +598,11 @@ export class AccessibilityCatalogResolver {
586
598
  // asking what alternates exist under-reported them.
587
599
  const claimed = new Set();
588
600
  const add = (card, source) => {
589
- const key = `${card.catalog}|${card.language ?? ""}|${catalogCardForm(card)}`;
601
+ // The language part is normalized, so a POSIX `es_ES` card and a BCP-47
602
+ // `es-ES` card collapse to one entry here exactly as they now collapse on
603
+ // the resolution path. Keying on the raw string would report two
604
+ // alternates where resolution can only ever return one.
605
+ const key = `${card.catalog}|${normalizeLanguageTag(card.language)}|${catalogCardForm(card)}`;
590
606
  if (claimed.has(key))
591
607
  return;
592
608
  claimed.add(key);
@@ -1,119 +1,47 @@
1
1
  /**
2
2
  * I18nService
3
3
  *
4
- * Internationalization service with hybrid loading strategy.
5
- * Manages translations, locale switching, and RTL/LTR direction.
4
+ * The toolkit's view of the shared i18n provider: a delegating wrapper over
5
+ * `SimpleI18n` from `players-shared` that adds toolkit-scoped logging and wires
6
+ * the lazy catalog loaders.
6
7
  *
7
- * Features:
8
- * - Subscriber/listener pattern for reactive updates
9
- * - Hybrid loading: Bundle English, lazy-load other locales
10
- * - Interpolation: {variable} syntax
11
- * - Pluralization: ICU-style plural rules
12
- * - RTL detection: Automatic based on locale
13
- * - Fallback to English for missing keys
8
+ * It was once a near-verbatim second copy of that class — same fields, same
9
+ * lookup, same plural selection. Duplicate implementations of one contract drift
10
+ * silently, because nothing fails when only one of them is fixed. Keep this a
11
+ * wrapper.
14
12
  *
15
13
  * Part of PIE Assessment Toolkit.
16
14
  */
17
- import type { I18nConfig, I18nServiceApi, PluralTranslation, TranslationBundle } from "@pie-players/pie-players-shared/i18n";
18
- export type { I18nConfig, I18nServiceApi, PluralTranslation, TranslationBundle, };
15
+ import type { I18nConfig, I18nProvider, I18nServiceApi, InterpolationValues, LocaleCode, MessageCatalog, MessageKeyInput, PluralOptions, TextDirection } from "@pie-players/pie-players-shared/i18n";
16
+ export type { I18nConfig, I18nProvider, I18nServiceApi, InterpolationValues, LocaleCode, MessageCatalog, MessageKeyInput, PluralOptions, TextDirection, };
19
17
  /**
20
- * I18nService
18
+ * Toolkit-scoped i18n service.
21
19
  *
22
- * Manages internationalization with reactive state updates.
20
+ * Serves every locale this repository ships, English resident and the rest
21
+ * lazily loaded.
23
22
  */
24
23
  export declare class I18nService implements I18nServiceApi {
25
- private locale;
26
- private fallbackLocale;
27
- private direction;
28
- private translations;
29
- private listeners;
30
- private loadingPromises;
31
- private config;
24
+ private readonly i18n;
32
25
  constructor(config?: I18nConfig);
33
26
  /**
34
- * Initialize i18n with locale and loading strategy
35
- */
36
- initialize(config: I18nConfig): Promise<void>;
37
- /**
38
- * Translate a key with optional interpolation
39
- *
40
- * @param key Translation key (e.g., 'common.save')
41
- * @param params Optional parameters for interpolation
42
- * @returns Translated string
43
- */
44
- t(key: string, params?: Record<string, any>): string;
45
- /**
46
- * Translate with pluralization
27
+ * Set the locale, loading its catalog first.
47
28
  *
48
- * @param key Translation key
49
- * @param count Count for pluralization
50
- * @param params Optional parameters for interpolation
51
- * @returns Translated string with plural form
52
- */
53
- tn(key: string, count: number, params?: Record<string, any>): string;
54
- /**
55
- * Get current locale
29
+ * Never falls back to the browser locale: a rendered-string change reaches a
30
+ * host's live delivery on their next install with no build signal on their
31
+ * side, so the locale has to be something the host asked for.
56
32
  */
33
+ initialize(config: I18nConfig): Promise<void>;
34
+ t(key: MessageKeyInput, values?: InterpolationValues): string;
35
+ plural(key: MessageKeyInput, options: PluralOptions): string;
57
36
  getLocale(): string;
58
- /**
59
- * Change locale (triggers async loading if needed)
60
- *
61
- * @param locale Locale code (e.g., 'en', 'es', 'zh', 'ar')
62
- */
63
- setLocale(locale: string): Promise<void>;
64
- /**
65
- * Get current text direction
66
- */
67
- getDirection(): "ltr" | "rtl";
68
- /**
69
- * Get available locales
70
- */
37
+ setLocale(locale: LocaleCode): Promise<void>;
38
+ getDirection(): TextDirection;
71
39
  getAvailableLocales(): string[];
72
- /**
73
- * Check if locale is loaded
74
- */
75
- isLocaleLoaded(locale: string): boolean;
76
- /**
77
- * Subscribe to locale/translation changes
78
- * Returns unsubscribe function
79
- */
40
+ isLocaleLoaded(locale: LocaleCode): boolean;
80
41
  subscribe(listener: () => void): () => void;
81
- /**
82
- * Check if a translation key exists
83
- */
84
- hasKey(key: string): boolean;
85
- /**
86
- * Notify all listeners of state change
87
- */
88
- private notifyListeners;
89
- /**
90
- * Apply locale and notify listeners
91
- */
92
- private applyLocale;
93
- /**
94
- * Apply direction to DOM
95
- */
96
- private applyDOMDirection;
97
- /**
98
- * Load translations for a locale
99
- */
100
- private loadTranslationsForLocale;
101
- /**
102
- * Get translation for a key
103
- */
104
- private getTranslation;
105
- /**
106
- * Interpolate variables in translation string
107
- * Replaces {variable} with params.variable
108
- */
109
- private interpolate;
110
- /**
111
- * Select plural form based on count and locale
112
- * Simplified implementation - real implementation would use Intl.PluralRules
113
- */
114
- private selectPluralForm;
115
- /**
116
- * Detect browser locale
117
- */
118
- private detectBrowserLocale;
42
+ hasKey(key: MessageKeyInput): boolean;
43
+ addCustomMessages(locale: string, messages: MessageCatalog): void;
44
+ withLocale(locale: LocaleCode): I18nProvider;
45
+ formatNumber(value: number, options?: Intl.NumberFormatOptions): string;
46
+ formatDate(date: Date, options?: Intl.DateTimeFormatOptions): string;
119
47
  }