@sonenta/react-i18next 2.1.0 → 2.2.0

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/dist/index.d.cts CHANGED
@@ -13,6 +13,24 @@ import { i18n } from 'i18next';
13
13
  * the reactive viewport listener (web) — see `provider.tsx`.
14
14
  */
15
15
  type Surface = "desktop" | "mobile" | "tablet";
16
+ /**
17
+ * A11y surfaces (#989 / task 994) — SEMANTIC accessibility variants of a key,
18
+ * delivered through the SAME sparse-overlay engine as device surfaces but
19
+ * applied ORTHOGONALLY to the visible text:
20
+ * - `aria_label` → an element's `aria-label` attribute
21
+ * - `alt_text` → an `<img alt>` (+ optional localized image via `$asset`)
22
+ * - `screen_reader` → verbose screen-reader-only (sr-only) text
23
+ * - `plain_language`→ simplified/clear-language (FALC) alternative rendering
24
+ *
25
+ * Unlike a device {@link Surface} (which replaces the visible `t()` value),
26
+ * a11y surfaces are read per-key via `i18n.aria()` / `alt()` / `a11y()` —
27
+ * EXCEPT `plain_language`, which the cognitive `plainLanguage` toggle layers
28
+ * over the visible `t()` output when enabled.
29
+ */
30
+ type A11ySurface = "aria_label" | "alt_text" | "screen_reader" | "plain_language";
31
+ /** The fixed V1 a11y surface taxonomy (matches the backend `surfaceKinds`
32
+ * `a11y` set). Used to validate config + drive eager overlay loading. */
33
+ declare const A11Y_SURFACES: readonly A11ySurface[];
16
34
  /** Min-width (px) thresholds that map a viewport width to a surface. A width
17
35
  * `< mobile` → `mobile`; `< tablet` → `tablet`; otherwise `desktop`. Mirrors
18
36
  * the common mobile-first breakpoint ladder. */
@@ -227,6 +245,23 @@ interface SonentaConfig {
227
245
  * own `useWindowDimensions`. Ignored when `surface` is unset.
228
246
  */
229
247
  surfaceBreakpoints?: SurfaceBreakpoints | boolean;
248
+ /**
249
+ * A11y surfaces (#989 / task 994) to eagerly load alongside the base
250
+ * bundles, so `i18n.aria()` / `alt()` / `a11y()` resolve synchronously in
251
+ * render. Each is a sparse `{ns}.{surface}.json` overlay applied
252
+ * ORTHOGONALLY to the visible text (NOT a `t()` replacement). Omit to
253
+ * disable a11y resolution. Include `"plain_language"` to power the
254
+ * {@link SonentaConfig.plainLanguage} toggle.
255
+ */
256
+ a11ySurfaces?: A11ySurface[];
257
+ /**
258
+ * Start with the cognitive simplified-language ("plain language" / FALC)
259
+ * mode ON — when enabled, `t()` returns the `plain_language` overlay value
260
+ * for keys that have one (else the base text). Toggle at runtime via
261
+ * `i18n.setPlainLanguage(on)`. No effect unless `plain_language` is loaded
262
+ * (it is auto-included when this is `true`, or list it in `a11ySurfaces`).
263
+ */
264
+ plainLanguage?: boolean;
230
265
  /**
231
266
  * Deployment environment. Drives where the SDK fetches translations from:
232
267
  *
@@ -342,6 +377,43 @@ interface I18nInstance {
342
377
  * asset (e.g. an icon/image ref) for the host to render.
343
378
  */
344
379
  asset: (key: string, namespace?: Namespace) => AssetRef | undefined;
380
+ /**
381
+ * A11y (#989 / task 994) — the accessible NAME for a key (`aria_label`
382
+ * overlay), falling back to the visible `t(key)` text when no a11y override
383
+ * exists. Spread onto an element as `aria-label`. Requires `aria_label` in
384
+ * {@link SonentaConfig.a11ySurfaces}.
385
+ */
386
+ aria: (key: string, namespace?: Namespace) => string;
387
+ /**
388
+ * A11y — the image alternative text for a key (`alt_text` overlay), falling
389
+ * back to the visible `t(key)` text. Pair with {@link I18nInstance.a11yAsset}
390
+ * for a localized image. Requires `alt_text` in `a11ySurfaces`.
391
+ */
392
+ alt: (key: string, namespace?: Namespace) => string;
393
+ /**
394
+ * A11y — resolve an arbitrary a11y `surface` overlay for a key, or
395
+ * `undefined` when no override exists (use for `screen_reader` /
396
+ * `plain_language`, which should be OMITTED rather than fall back to the
397
+ * visible text). `aria` / `alt` are convenience wrappers that fall back.
398
+ */
399
+ a11y: (key: string, surface: A11ySurface, namespace?: Namespace) => string | undefined;
400
+ /**
401
+ * A11y — the localized-image `$asset` ref carried by a key's `alt_text`
402
+ * overlay, or `undefined`. Lets the host swap the IMAGE per locale, not just
403
+ * its alt text.
404
+ */
405
+ a11yAsset: (key: string, namespace?: Namespace) => AssetRef | undefined;
406
+ /**
407
+ * A11y — whether the cognitive simplified-language ("plain language") mode is
408
+ * ON. When `true`, `t()` returns the `plain_language` overlay value for keys
409
+ * that have one (else the base text).
410
+ */
411
+ plainLanguage: boolean;
412
+ /**
413
+ * A11y — toggle simplified-language mode and re-render. Loads the
414
+ * `plain_language` overlays on first enable if not already configured.
415
+ */
416
+ setPlainLanguage: (on: boolean) => Promise<void>;
345
417
  /**
346
418
  * Text direction of a locale (defaults to the active locale) — i18next
347
419
  * parity. Sourced from the language catalog's `rtl` (variant → base
@@ -383,6 +455,22 @@ interface TranslationFunction {
383
455
  /** Native form: `t('key', { defaultValue, ...interpolation })`. */
384
456
  (key: string, options?: TranslationOptions): string;
385
457
  }
458
+ /**
459
+ * The `t` returned by {@link useTranslation} — a {@link TranslationFunction}
460
+ * augmented with a11y accessors (#989 / task 994) so a host can write
461
+ * `t.aria(key)` / `t.alt(key)` / `t.a11y(key, surface)` right next to `t(key)`.
462
+ * They honor the hook's `defaultNamespace` and delegate to the engine.
463
+ */
464
+ interface A11yTranslationFunction extends TranslationFunction {
465
+ /** Accessible name for `key` (`aria_label`), falling back to the visible
466
+ * text. Spread onto an element as `aria-label`. */
467
+ aria: (key: string, namespace?: Namespace) => string;
468
+ /** Image alt text for `key` (`alt_text`), falling back to the visible text. */
469
+ alt: (key: string, namespace?: Namespace) => string;
470
+ /** Resolve an arbitrary a11y `surface` overlay for `key`, or `undefined`
471
+ * when no override exists (e.g. `screen_reader` / `plain_language`). */
472
+ a11y: (key: string, surface: A11ySurface, namespace?: Namespace) => string | undefined;
473
+ }
386
474
 
387
475
  interface SonentaProviderProps extends SonentaConfig {
388
476
  children: ReactNode;
@@ -390,7 +478,7 @@ interface SonentaProviderProps extends SonentaConfig {
390
478
  declare function SonentaProvider({ children, ...config }: SonentaProviderProps): react_jsx_runtime.JSX.Element;
391
479
 
392
480
  interface UseTranslationResult {
393
- t: TranslationFunction;
481
+ t: A11yTranslationFunction;
394
482
  i18n: I18nInstance;
395
483
  }
396
484
  /** React hook — returns `{ t, i18n }`. Optional `defaultNamespace` lets you
@@ -537,4 +625,4 @@ declare class KeyRegistry {
537
625
  /** Process-wide singleton — there is exactly one on-screen registry. */
538
626
  declare const keyRegistry: KeyRegistry;
539
627
 
540
- export { type AssetRef, DEFAULT_SURFACE_BREAKPOINTS, type DeclaredKey, type I18nInstance, type LanguageMeta, type Locale, type MissingHandlerMode, type MissingKeyEvent, type Namespace, type SonentaConfig, type SonentaPlugin, type SonentaPluginContext, SonentaProvider, type Surface, type SurfaceBreakpoints, Trans, type TranslationFunction, type TranslationOptions, type Transport, type SonentaConfig as VerbumiaConfig, type SonentaPlugin as VerbumiaPlugin, type SonentaPluginContext as VerbumiaPluginContext, SonentaProvider as VerbumiaProvider, defaultTransport, getI18n, getI18nSafe, keyRegistry, logTransport, surfaceForWidth, t, useTranslation };
628
+ export { A11Y_SURFACES, type A11ySurface, type AssetRef, DEFAULT_SURFACE_BREAKPOINTS, type DeclaredKey, type I18nInstance, type LanguageMeta, type Locale, type MissingHandlerMode, type MissingKeyEvent, type Namespace, type SonentaConfig, type SonentaPlugin, type SonentaPluginContext, SonentaProvider, type Surface, type SurfaceBreakpoints, Trans, type TranslationFunction, type TranslationOptions, type Transport, type SonentaConfig as VerbumiaConfig, type SonentaPlugin as VerbumiaPlugin, type SonentaPluginContext as VerbumiaPluginContext, SonentaProvider as VerbumiaProvider, defaultTransport, getI18n, getI18nSafe, keyRegistry, logTransport, surfaceForWidth, t, useTranslation };
package/dist/index.d.ts CHANGED
@@ -13,6 +13,24 @@ import { i18n } from 'i18next';
13
13
  * the reactive viewport listener (web) — see `provider.tsx`.
14
14
  */
15
15
  type Surface = "desktop" | "mobile" | "tablet";
16
+ /**
17
+ * A11y surfaces (#989 / task 994) — SEMANTIC accessibility variants of a key,
18
+ * delivered through the SAME sparse-overlay engine as device surfaces but
19
+ * applied ORTHOGONALLY to the visible text:
20
+ * - `aria_label` → an element's `aria-label` attribute
21
+ * - `alt_text` → an `<img alt>` (+ optional localized image via `$asset`)
22
+ * - `screen_reader` → verbose screen-reader-only (sr-only) text
23
+ * - `plain_language`→ simplified/clear-language (FALC) alternative rendering
24
+ *
25
+ * Unlike a device {@link Surface} (which replaces the visible `t()` value),
26
+ * a11y surfaces are read per-key via `i18n.aria()` / `alt()` / `a11y()` —
27
+ * EXCEPT `plain_language`, which the cognitive `plainLanguage` toggle layers
28
+ * over the visible `t()` output when enabled.
29
+ */
30
+ type A11ySurface = "aria_label" | "alt_text" | "screen_reader" | "plain_language";
31
+ /** The fixed V1 a11y surface taxonomy (matches the backend `surfaceKinds`
32
+ * `a11y` set). Used to validate config + drive eager overlay loading. */
33
+ declare const A11Y_SURFACES: readonly A11ySurface[];
16
34
  /** Min-width (px) thresholds that map a viewport width to a surface. A width
17
35
  * `< mobile` → `mobile`; `< tablet` → `tablet`; otherwise `desktop`. Mirrors
18
36
  * the common mobile-first breakpoint ladder. */
@@ -227,6 +245,23 @@ interface SonentaConfig {
227
245
  * own `useWindowDimensions`. Ignored when `surface` is unset.
228
246
  */
229
247
  surfaceBreakpoints?: SurfaceBreakpoints | boolean;
248
+ /**
249
+ * A11y surfaces (#989 / task 994) to eagerly load alongside the base
250
+ * bundles, so `i18n.aria()` / `alt()` / `a11y()` resolve synchronously in
251
+ * render. Each is a sparse `{ns}.{surface}.json` overlay applied
252
+ * ORTHOGONALLY to the visible text (NOT a `t()` replacement). Omit to
253
+ * disable a11y resolution. Include `"plain_language"` to power the
254
+ * {@link SonentaConfig.plainLanguage} toggle.
255
+ */
256
+ a11ySurfaces?: A11ySurface[];
257
+ /**
258
+ * Start with the cognitive simplified-language ("plain language" / FALC)
259
+ * mode ON — when enabled, `t()` returns the `plain_language` overlay value
260
+ * for keys that have one (else the base text). Toggle at runtime via
261
+ * `i18n.setPlainLanguage(on)`. No effect unless `plain_language` is loaded
262
+ * (it is auto-included when this is `true`, or list it in `a11ySurfaces`).
263
+ */
264
+ plainLanguage?: boolean;
230
265
  /**
231
266
  * Deployment environment. Drives where the SDK fetches translations from:
232
267
  *
@@ -342,6 +377,43 @@ interface I18nInstance {
342
377
  * asset (e.g. an icon/image ref) for the host to render.
343
378
  */
344
379
  asset: (key: string, namespace?: Namespace) => AssetRef | undefined;
380
+ /**
381
+ * A11y (#989 / task 994) — the accessible NAME for a key (`aria_label`
382
+ * overlay), falling back to the visible `t(key)` text when no a11y override
383
+ * exists. Spread onto an element as `aria-label`. Requires `aria_label` in
384
+ * {@link SonentaConfig.a11ySurfaces}.
385
+ */
386
+ aria: (key: string, namespace?: Namespace) => string;
387
+ /**
388
+ * A11y — the image alternative text for a key (`alt_text` overlay), falling
389
+ * back to the visible `t(key)` text. Pair with {@link I18nInstance.a11yAsset}
390
+ * for a localized image. Requires `alt_text` in `a11ySurfaces`.
391
+ */
392
+ alt: (key: string, namespace?: Namespace) => string;
393
+ /**
394
+ * A11y — resolve an arbitrary a11y `surface` overlay for a key, or
395
+ * `undefined` when no override exists (use for `screen_reader` /
396
+ * `plain_language`, which should be OMITTED rather than fall back to the
397
+ * visible text). `aria` / `alt` are convenience wrappers that fall back.
398
+ */
399
+ a11y: (key: string, surface: A11ySurface, namespace?: Namespace) => string | undefined;
400
+ /**
401
+ * A11y — the localized-image `$asset` ref carried by a key's `alt_text`
402
+ * overlay, or `undefined`. Lets the host swap the IMAGE per locale, not just
403
+ * its alt text.
404
+ */
405
+ a11yAsset: (key: string, namespace?: Namespace) => AssetRef | undefined;
406
+ /**
407
+ * A11y — whether the cognitive simplified-language ("plain language") mode is
408
+ * ON. When `true`, `t()` returns the `plain_language` overlay value for keys
409
+ * that have one (else the base text).
410
+ */
411
+ plainLanguage: boolean;
412
+ /**
413
+ * A11y — toggle simplified-language mode and re-render. Loads the
414
+ * `plain_language` overlays on first enable if not already configured.
415
+ */
416
+ setPlainLanguage: (on: boolean) => Promise<void>;
345
417
  /**
346
418
  * Text direction of a locale (defaults to the active locale) — i18next
347
419
  * parity. Sourced from the language catalog's `rtl` (variant → base
@@ -383,6 +455,22 @@ interface TranslationFunction {
383
455
  /** Native form: `t('key', { defaultValue, ...interpolation })`. */
384
456
  (key: string, options?: TranslationOptions): string;
385
457
  }
458
+ /**
459
+ * The `t` returned by {@link useTranslation} — a {@link TranslationFunction}
460
+ * augmented with a11y accessors (#989 / task 994) so a host can write
461
+ * `t.aria(key)` / `t.alt(key)` / `t.a11y(key, surface)` right next to `t(key)`.
462
+ * They honor the hook's `defaultNamespace` and delegate to the engine.
463
+ */
464
+ interface A11yTranslationFunction extends TranslationFunction {
465
+ /** Accessible name for `key` (`aria_label`), falling back to the visible
466
+ * text. Spread onto an element as `aria-label`. */
467
+ aria: (key: string, namespace?: Namespace) => string;
468
+ /** Image alt text for `key` (`alt_text`), falling back to the visible text. */
469
+ alt: (key: string, namespace?: Namespace) => string;
470
+ /** Resolve an arbitrary a11y `surface` overlay for `key`, or `undefined`
471
+ * when no override exists (e.g. `screen_reader` / `plain_language`). */
472
+ a11y: (key: string, surface: A11ySurface, namespace?: Namespace) => string | undefined;
473
+ }
386
474
 
387
475
  interface SonentaProviderProps extends SonentaConfig {
388
476
  children: ReactNode;
@@ -390,7 +478,7 @@ interface SonentaProviderProps extends SonentaConfig {
390
478
  declare function SonentaProvider({ children, ...config }: SonentaProviderProps): react_jsx_runtime.JSX.Element;
391
479
 
392
480
  interface UseTranslationResult {
393
- t: TranslationFunction;
481
+ t: A11yTranslationFunction;
394
482
  i18n: I18nInstance;
395
483
  }
396
484
  /** React hook — returns `{ t, i18n }`. Optional `defaultNamespace` lets you
@@ -537,4 +625,4 @@ declare class KeyRegistry {
537
625
  /** Process-wide singleton — there is exactly one on-screen registry. */
538
626
  declare const keyRegistry: KeyRegistry;
539
627
 
540
- export { type AssetRef, DEFAULT_SURFACE_BREAKPOINTS, type DeclaredKey, type I18nInstance, type LanguageMeta, type Locale, type MissingHandlerMode, type MissingKeyEvent, type Namespace, type SonentaConfig, type SonentaPlugin, type SonentaPluginContext, SonentaProvider, type Surface, type SurfaceBreakpoints, Trans, type TranslationFunction, type TranslationOptions, type Transport, type SonentaConfig as VerbumiaConfig, type SonentaPlugin as VerbumiaPlugin, type SonentaPluginContext as VerbumiaPluginContext, SonentaProvider as VerbumiaProvider, defaultTransport, getI18n, getI18nSafe, keyRegistry, logTransport, surfaceForWidth, t, useTranslation };
628
+ export { A11Y_SURFACES, type A11ySurface, type AssetRef, DEFAULT_SURFACE_BREAKPOINTS, type DeclaredKey, type I18nInstance, type LanguageMeta, type Locale, type MissingHandlerMode, type MissingKeyEvent, type Namespace, type SonentaConfig, type SonentaPlugin, type SonentaPluginContext, SonentaProvider, type Surface, type SurfaceBreakpoints, Trans, type TranslationFunction, type TranslationOptions, type Transport, type SonentaConfig as VerbumiaConfig, type SonentaPlugin as VerbumiaPlugin, type SonentaPluginContext as VerbumiaPluginContext, SonentaProvider as VerbumiaProvider, defaultTransport, getI18n, getI18nSafe, keyRegistry, logTransport, surfaceForWidth, t, useTranslation };
package/dist/index.js CHANGED
@@ -210,7 +210,7 @@ function flattenPlurals(tree, locale) {
210
210
 
211
211
  // src/transport.ts
212
212
  var SDK_LIB = "@sonenta/react-i18next";
213
- var SDK_VER = true ? "2.1.0" : "0.0.0-dev";
213
+ var SDK_VER = true ? "2.2.0" : "0.0.0-dev";
214
214
  function defaultTransport(opts) {
215
215
  return async (batch) => {
216
216
  if (!batch.length) return;
@@ -416,6 +416,14 @@ var SonentaI18n = class {
416
416
  // Resolved asset refs (#911 minimal v1), keyed `${locale}/${ns}/${keyPath}`
417
417
  // for the CURRENT composition (base assets, then overlay assets override).
418
418
  _assets = /* @__PURE__ */ new Map();
419
+ // A11y surfaces (#989 / task 994). Each configured a11y surface S is loaded
420
+ // as its OWN i18next namespace `${ns}__${S}` (sparse `{ns}.{S}.json`), so the
421
+ // accessors resolve through i18next's native locale-fallback + nested-key
422
+ // logic without touching the visible-text bundle. `_a11yAssets` carries the
423
+ // `alt_text` `$asset` refs, keyed `${locale}/${ns}/${keyPath}#${surface}`.
424
+ _a11ySurfaces = /* @__PURE__ */ new Set();
425
+ _plainLanguage = false;
426
+ _a11yAssets = /* @__PURE__ */ new Map();
419
427
  _missing;
420
428
  _listeners = /* @__PURE__ */ new Set();
421
429
  // Stable snapshot reference for useSyncExternalStore. Rebuilt ONLY in _notify
@@ -443,6 +451,9 @@ var SonentaI18n = class {
443
451
  this.locale = config.defaultLocale;
444
452
  this.fallbackLng = config.fallbackLng;
445
453
  this._surface = config.surface;
454
+ for (const s of config.a11ySurfaces ?? []) this._a11ySurfaces.add(s);
455
+ this._plainLanguage = config.plainLanguage === true;
456
+ if (this._plainLanguage) this._a11ySurfaces.add("plain_language");
446
457
  let keySeparator = ".";
447
458
  if (config.keySeparator !== void 0) {
448
459
  keySeparator = config.keySeparator;
@@ -627,6 +638,12 @@ var SonentaI18n = class {
627
638
  surface: this._surface,
628
639
  setSurface: this.setSurface,
629
640
  asset: this.asset,
641
+ aria: this.aria,
642
+ alt: this.alt,
643
+ a11y: this.a11y,
644
+ a11yAsset: this.a11yAsset,
645
+ plainLanguage: this._plainLanguage,
646
+ setPlainLanguage: this.setPlainLanguage,
630
647
  dir: this.dir,
631
648
  nativeName: this.nativeName,
632
649
  languageMeta: this.languageMeta,
@@ -868,9 +885,96 @@ var SonentaI18n = class {
868
885
  }
869
886
  return void 0;
870
887
  };
888
+ // ---- A11y (#989 / task 994) ----
889
+ /** Split a possibly-`ns:key` string into `{ ns, bareKey }`, honoring the
890
+ * configured nsSeparator (mirrors {@link asset}). */
891
+ _splitKey(key, namespace) {
892
+ let ns = namespace ?? this.defaultNamespace;
893
+ let bareKey = key;
894
+ if (typeof this._nsSeparator === "string" && this._nsSeparator) {
895
+ const idx = key.indexOf(this._nsSeparator);
896
+ if (idx > 0) {
897
+ ns = key.slice(0, idx);
898
+ bareKey = key.slice(idx + this._nsSeparator.length);
899
+ }
900
+ }
901
+ return { ns, bareKey };
902
+ }
903
+ /** Resolve an a11y `surface` override for `key`, or `undefined` when none
904
+ * exists (the namespace is sparse). Goes through i18next so the locale
905
+ * fallback chain + nested keys + plurals apply. */
906
+ a11y = (key, surface, namespace) => {
907
+ if (!this._a11ySurfaces.has(surface)) return void 0;
908
+ const { ns, bareKey } = this._splitKey(key, namespace);
909
+ const a11yNs = this._a11yNs(ns, surface);
910
+ if (!this._i18next.exists(bareKey, { ns: a11yNs })) return void 0;
911
+ return this._i18next.t(bareKey, { ns: a11yNs });
912
+ };
913
+ /** Accessible name for `key` (`aria_label` overlay) — falls back to the
914
+ * visible `t(key)` text when no override exists. Spread as `aria-label`. */
915
+ aria = (key, namespace) => {
916
+ const v = this.a11y(key, "aria_label", namespace);
917
+ if (v !== void 0) return v;
918
+ const { ns, bareKey } = this._splitKey(key, namespace);
919
+ return this._i18next.t(bareKey, { ns });
920
+ };
921
+ /** Image alt text for `key` (`alt_text` overlay) — falls back to the visible
922
+ * `t(key)` text. Pair with {@link a11yAsset} for a localized image. */
923
+ alt = (key, namespace) => {
924
+ const v = this.a11y(key, "alt_text", namespace);
925
+ if (v !== void 0) return v;
926
+ const { ns, bareKey } = this._splitKey(key, namespace);
927
+ return this._i18next.t(bareKey, { ns });
928
+ };
929
+ /** Localized-image `$asset` ref from a key's `alt_text` overlay, or
930
+ * `undefined`. Walks the locale fallback chain like `t()` would. */
931
+ a11yAsset = (key, namespace) => {
932
+ const { ns, bareKey } = this._splitKey(key, namespace);
933
+ for (const loc of this._resolutionChain(this.locale)) {
934
+ const ref = this._a11yAssets.get(`${loc}/${ns}/${bareKey}#alt_text`);
935
+ if (ref) return ref;
936
+ }
937
+ return void 0;
938
+ };
939
+ get plainLanguage() {
940
+ return this._plainLanguage;
941
+ }
942
+ /** Toggle simplified-language ("plain language" / FALC) mode (#989). When
943
+ * on, `t()` returns the `plain_language` overlay value for keys that have
944
+ * one. Loads the `plain_language` overlays on first enable if they were not
945
+ * configured, then re-renders. No-op when unchanged. */
946
+ setPlainLanguage = async (on) => {
947
+ if (on === this._plainLanguage) return;
948
+ this._plainLanguage = on;
949
+ if (on && !this._a11ySurfaces.has("plain_language")) {
950
+ this._a11ySurfaces.add("plain_language");
951
+ const targets = [];
952
+ for (const k of this._attempted) {
953
+ const parts = k.split("/");
954
+ const locale = parts[1];
955
+ const ns = parts[2];
956
+ if (locale && ns) targets.push({ locale, ns });
957
+ }
958
+ await Promise.all(
959
+ targets.map((t2) => this._loadA11yOverlays(t2.locale, t2.ns))
960
+ );
961
+ }
962
+ this._notify();
963
+ this._signalLoaded();
964
+ };
871
965
  // ---- Translation ----
872
966
  t = (key, optionsOrDefault, maybeOptions) => {
873
967
  const options = typeof optionsOrDefault === "string" ? { ...maybeOptions ?? {}, defaultValue: optionsOrDefault } : optionsOrDefault;
968
+ if (this._plainLanguage && this._a11ySurfaces.has("plain_language")) {
969
+ const { ns, bareKey } = this._splitKey(key);
970
+ const plainNs = this._a11yNs(ns, "plain_language");
971
+ if (this._i18next.exists(bareKey, { ...options ?? {}, ns: plainNs })) {
972
+ return this._i18next.t(bareKey, {
973
+ ...options ?? {},
974
+ ns: plainNs
975
+ });
976
+ }
977
+ }
874
978
  const literal = this._probeLiteral(key);
875
979
  if (literal !== void 0) {
876
980
  const interpolator = this._i18next.services?.interpolator;
@@ -1021,6 +1125,67 @@ var SonentaI18n = class {
1021
1125
  this._attempted.add(cacheKey);
1022
1126
  }
1023
1127
  await this._composeBundle(locale, ns, fetchImpl, opts.bust);
1128
+ await this._loadA11yOverlays(locale, ns, fetchImpl, opts.bust);
1129
+ }
1130
+ /**
1131
+ * Load every configured a11y surface overlay (#989 / task 994) for
1132
+ * (locale, ns) into a DEDICATED i18next namespace `${ns}__${surface}`, so
1133
+ * `aria()` / `alt()` / `a11y()` resolve them through i18next (locale
1134
+ * fallback + nested keys + plurals) WITHOUT polluting the visible-text
1135
+ * bundle. Sparse + best-effort: an absent overlay registers `{}` and the
1136
+ * accessor reports "no override".
1137
+ */
1138
+ async _loadA11yOverlays(locale, ns, fetchImpl = fetch, bust = false) {
1139
+ if (this._a11ySurfaces.size === 0) return;
1140
+ await Promise.all(
1141
+ [...this._a11ySurfaces].map(async (surface) => {
1142
+ const overlay = await this._loadOverlay(
1143
+ locale,
1144
+ ns,
1145
+ surface,
1146
+ fetchImpl,
1147
+ bust
1148
+ );
1149
+ const tree = this._unwrapA11y(overlay, locale, ns, surface);
1150
+ this._i18next.addResourceBundle(
1151
+ locale,
1152
+ this._a11yNs(ns, surface),
1153
+ flattenPlurals(tree, locale),
1154
+ false,
1155
+ true
1156
+ );
1157
+ })
1158
+ );
1159
+ }
1160
+ /** i18next namespace that backs an a11y surface overlay for `ns`. */
1161
+ _a11yNs(ns, surface) {
1162
+ return `${ns}__${surface}`;
1163
+ }
1164
+ /** Like {@link _unwrapAssets} but records `$asset` refs into `_a11yAssets`
1165
+ * (keyed with the `#${surface}` suffix) instead of the visible-text
1166
+ * `_assets`, so `alt_text` images don't collide with device-surface assets. */
1167
+ _unwrapA11y(tree, locale, ns, surface) {
1168
+ const sep = typeof this._i18next.options.keySeparator === "string" ? this._i18next.options.keySeparator : ".";
1169
+ const walk = (node, path) => {
1170
+ if (!node || typeof node !== "object") return node;
1171
+ const obj = node;
1172
+ if (Object.prototype.hasOwnProperty.call(obj, "$value")) {
1173
+ const a = obj.$asset;
1174
+ if (a && typeof a.kind === "string" && typeof a.ref === "string") {
1175
+ this._a11yAssets.set(
1176
+ `${locale}/${ns}/${path.join(sep)}#${surface}`,
1177
+ { kind: a.kind, ref: a.ref }
1178
+ );
1179
+ }
1180
+ return obj.$value;
1181
+ }
1182
+ const out = {};
1183
+ for (const [k, v] of Object.entries(obj)) {
1184
+ out[k] = walk(v, [...path, k]);
1185
+ }
1186
+ return out;
1187
+ };
1188
+ return walk(tree, []);
1024
1189
  }
1025
1190
  /**
1026
1191
  * Compose the i18next bundle for (locale, ns) as base ⊕ surface overlay
@@ -1148,6 +1313,12 @@ function t(key, optionsOrDefault, maybeOptions) {
1148
1313
  }
1149
1314
 
1150
1315
  // src/surface.ts
1316
+ var A11Y_SURFACES = [
1317
+ "aria_label",
1318
+ "alt_text",
1319
+ "screen_reader",
1320
+ "plain_language"
1321
+ ];
1151
1322
  var DEFAULT_SURFACE_BREAKPOINTS = {
1152
1323
  mobile: 640,
1153
1324
  tablet: 1024
@@ -1231,7 +1402,12 @@ function useTranslation(defaultNamespace) {
1231
1402
  );
1232
1403
  return i18n.t(fullKey, optionsOrDefault, maybeOptions);
1233
1404
  };
1234
- return fn;
1405
+ const withNs = (key) => defaultNamespace && !key.includes(":") ? `${defaultNamespace}:${key}` : key;
1406
+ const aug = fn;
1407
+ aug.aria = (key, namespace) => i18n.aria(withNs(key), namespace);
1408
+ aug.alt = (key, namespace) => i18n.alt(withNs(key), namespace);
1409
+ aug.a11y = (key, surface, namespace) => i18n.a11y(withNs(key), surface, namespace);
1410
+ return aug;
1235
1411
  }, [i18n, defaultNamespace]);
1236
1412
  useEffect2(() => {
1237
1413
  keyRegistry._set(tokenRef.current, renderedRef.current);
@@ -1283,6 +1459,7 @@ function splitOnComponents(text, components) {
1283
1459
  return out;
1284
1460
  }
1285
1461
  export {
1462
+ A11Y_SURFACES,
1286
1463
  DEFAULT_SURFACE_BREAKPOINTS,
1287
1464
  SonentaProvider,
1288
1465
  Trans,