@sonenta/react-i18next 2.0.1 → 2.1.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/README.md +5 -5
- package/dist/index.cjs +3 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/provider.tsx","../src/i18n.ts","../src/catalog.ts","../src/engine.ts","../src/missing.ts","../src/plurals.ts","../src/transport.ts","../src/key-registry.ts","../src/singleton.ts","../src/surface.ts","../src/hooks.ts","../src/trans.tsx"],"sourcesContent":["import {\n createContext,\n Fragment,\n useContext,\n useEffect,\n useMemo,\n useSyncExternalStore,\n type ReactNode,\n} from \"react\";\nimport { SonentaI18n } from \"./i18n\";\nimport { _clearActiveInstance, _setActiveInstance } from \"./singleton\";\nimport type { I18nInstance, SonentaConfig } from \"./types\";\nimport {\n DEFAULT_SURFACE_BREAKPOINTS,\n surfaceForWidth,\n type SurfaceBreakpoints,\n} from \"./surface\";\n\ninterface SonentaContextValue {\n i18n: SonentaI18n;\n}\n\nconst SonentaContext = createContext<SonentaContextValue | null>(null);\n\nexport interface SonentaProviderProps extends SonentaConfig {\n children: ReactNode;\n}\n\nexport function SonentaProvider({\n children,\n ...config\n}: SonentaProviderProps) {\n // Stable instance for the lifetime of the provider mount.\n const i18n = useMemo(() => new SonentaI18n(config), []); // eslint-disable-line react-hooks/exhaustive-deps\n\n useEffect(() => {\n // Register as the active instance so `getI18n()` works outside React.\n _setActiveInstance(i18n);\n void i18n.start();\n // Plugins (e.g. @sonenta/feedback) hook the SAME i18n instance —\n // no second context. setup() runs once; optional teardown on unmount.\n // 1.0.5 (#806 SeedSower lang-change propagation): the plugin context\n // now carries `onLanguageChange` — a stable subscribe→unsubscribe\n // hook backed by SonentaI18n's own languageChanged emitter so\n // plugins (feedback ≥0.2.6) can re-sync per-language state without\n // poking at the private _i18next. Additive — older plugins ignore.\n const teardowns = (config.plugins ?? [])\n .map((p) =>\n p.setup?.({\n i18n,\n config,\n onLanguageChange: i18n.onLanguageChange,\n }),\n )\n .filter((t): t is () => void => typeof t === \"function\");\n return () => {\n teardowns.forEach((t) => t());\n i18n.stop();\n _clearActiveInstance(i18n);\n };\n }, [i18n]); // eslint-disable-line react-hooks/exhaustive-deps\n\n // #911 — reactive surface detection from the viewport (web). Enabled only\n // when surface resolution is on (`surface` set) AND `surfaceBreakpoints` is\n // truthy, and a `matchMedia`/`window` exists (so SSR + React Native, which\n // have no `window`, simply keep the initial `surface` — drive RN changes via\n // `i18n.setSurface(surfaceForWidth(width))` from `useWindowDimensions`).\n useEffect(() => {\n if (!config.surface || !config.surfaceBreakpoints) return;\n if (typeof window === \"undefined\" || typeof window.matchMedia !== \"function\")\n return;\n const bp: SurfaceBreakpoints =\n config.surfaceBreakpoints === true\n ? DEFAULT_SURFACE_BREAKPOINTS\n : config.surfaceBreakpoints;\n const sync = () => void i18n.setSurface(surfaceForWidth(window.innerWidth, bp));\n sync(); // align to the actual viewport on mount (initial `surface` is the SSR floor)\n // matchMedia listeners at each threshold — fire only on a surface boundary\n // crossing (cheaper + steadier than a raw resize listener).\n const mqls = [bp.mobile, bp.tablet].map((px) =>\n window.matchMedia(`(min-width: ${px}px)`),\n );\n mqls.forEach((mql) => mql.addEventListener(\"change\", sync));\n return () => mqls.forEach((mql) => mql.removeEventListener(\"change\", sync));\n }, [i18n]); // eslint-disable-line react-hooks/exhaustive-deps\n\n const value = useMemo<SonentaContextValue>(() => ({ i18n }), [i18n]);\n return (\n <SonentaContext.Provider value={value}>\n {children}\n {/* Plugin outlets: isolated sibling leaves AFTER children. Their\n internal state never propagates to the host app subtree. */}\n {(config.plugins ?? []).map((p) =>\n p.render ? <Fragment key={p.name}>{p.render()}</Fragment> : null,\n )}\n </SonentaContext.Provider>\n );\n}\n\n/** Internal — used by useTranslation + Trans. */\nexport function useI18n(): SonentaI18n {\n const ctx = useContext(SonentaContext);\n if (!ctx) {\n throw new Error(\"useTranslation/Trans must be used inside <SonentaProvider>\");\n }\n return ctx.i18n;\n}\n\n/** Subscribes to the i18n store and returns a snapshot the React tree can render.\n *\n * `getSnapshot` MUST return a stable reference between notifications,\n * otherwise React loops forever (Maximum update depth exceeded). The\n * SonentaI18n instance caches its snapshot internally — see\n * `_notify` / `_buildSnapshot`. */\nexport function useI18nSnapshot(): I18nInstance {\n const i18n = useI18n();\n return useSyncExternalStore(i18n.subscribe, i18n.getSnapshot, i18n.getSnapshot);\n}\n","import {\n createInstance,\n type FormatFunction,\n type i18n as I18nextInstance,\n type InterpolationOptions,\n} from \"i18next\";\nimport { initReactI18next } from \"react-i18next\";\nimport { LanguageCatalog, loadCatalog, localeChain } from \"./catalog\";\nimport { detectKeySeparator } from \"./engine\";\nimport { MissingKeyManager } from \"./missing\";\nimport { flattenPlurals } from \"./plurals\";\nimport { defaultTransport, logTransport } from \"./transport\";\nimport type {\n AssetRef,\n I18nInstance,\n LanguageMeta,\n Locale,\n MissingKeyEvent,\n Namespace,\n Transport,\n SonentaConfig,\n} from \"./types\";\nimport type { Surface } from \"./surface\";\nimport { keyRegistry } from \"./key-registry\";\n\nconst DEFAULT_API_BASE = \"https://api.verbumia.dev\";\nconst DEFAULT_CDN_BASE = \"https://cdn.verbumia.ca\";\nconst DEFAULT_FLUSH_MS = 5_000;\nconst DEFAULT_BATCH = 50;\nconst DEFAULT_BUFFER = 200;\nconst DEFAULT_VERSION_SLUG = \"main\";\n\ntype Bundle = Record<string, unknown>;\ntype Listener = () => void;\n\n/** Normalize `fallbackLng` (single | ordered chain | unset) to an array. */\nfunction asArray(fb: Locale | Locale[] | undefined): Locale[] {\n if (fb == null) return [];\n return (Array.isArray(fb) ? fb : [fb]).filter(Boolean);\n}\n\n/**\n * `@sonenta/react-i18next`'s public store — now a thin adapter over a real\n * `i18next` engine (#805, Option A). The class keeps its exact legacy surface\n * and behavior (the test suite is the contract), but delegates key resolution,\n * plurals, interpolation, context, and the variant/fallback chain to i18next.\n *\n * Architecture (why it's wired this way):\n * - The i18next instance is `init()`-ed SYNCHRONOUSLY in the constructor with\n * `initImmediate: false` and the (plural-flattened) `initialBundles` as\n * `resources`, and NO backend module. So `t()` + `ready` work offline,\n * pre-`start()` — matching the legacy sync constructor — and i18next never\n * fires a network load on its own (the env-routing tests pin that bundle\n * fetches happen in `start(fetchImpl)`, with the passed fetch, at the exact\n * legacy URLs/auth/cache semantics).\n * - `start()` / `setLocale()` / `reload()` perform the bundle fetches\n * themselves (ported `_loadBundle`: prod CDN, dev runtime, 404→{} sentinel,\n * cache-bust, offline last-known-good) and `addResourceBundle()` the result\n * into i18next, then `changeLanguage()` so i18next's language chain\n * (variant→base→fallbackLng) stays correct.\n * - The adapter owns its own `ready` / `locale` / listeners / snapshot so\n * `getSnapshot()` returns a STABLE reference between state changes (the P0\n * useSyncExternalStore contract) instead of riding i18next events.\n */\nexport class SonentaI18n implements I18nInstance {\n ready = false;\n locale: Locale;\n fallbackLng: Locale | Locale[] | undefined;\n missingEvents: MissingKeyEvent[] = [];\n\n // The underlying real i18next instance (engine). Resolution/plurals/\n // interpolation/context all run through this; the adapter only orchestrates\n // loading + the missing-key gate + the React subscription.\n private _i18next: I18nextInstance;\n // Original (native) i18next.changeLanguage, captured before we override the\n // instance's changeLanguage to route through setLocale (#806 bug3). Used by\n // _syncLanguage to switch the language WITHOUT re-entering our loader.\n private _origChangeLanguage!: I18nextInstance[\"changeLanguage\"];\n // Token identifying THIS i18n instance in the on-screen key registry\n // (#806 SeedSower instance-level producer — see _wrapInstanceT). Dropped\n // in stop() so accumulated keys don't outlive a remounted provider.\n private _registryToken = Symbol(\"sonenta.instance\");\n\n // Tracks which (version, locale, ns) we've actually fetched, and which came\n // back with ≥1 top-level key. The missing-key gate needs `_hasContent`\n // (200-with-content) to avoid the boot-flood when a namespace is unpublished\n // or a fetch came back empty. i18next's own resource store can't carry this\n // distinction (an empty {} bundle still `hasResourceBundle`).\n private _attempted = new Set<string>();\n private _hasContent = new Set<string>();\n\n // Language catalog (#803): dir()/nativeName()/languageMeta(). Primed from\n // config.languageCatalog and/or the public GET /v1/languages in start().\n private _catalog = new LanguageCatalog();\n private _catalogDisabled = false;\n\n private _config: Required<\n Pick<SonentaConfig, \"apiBase\" | \"cdnBase\" | \"missingHandler\">\n > & {\n token: string;\n projectUuid: string;\n namespaces: string[];\n flushIntervalMs: number;\n flushBatchSize: number;\n missingEventsBufferSize: number;\n version: string;\n env: \"prod\" | \"dev\";\n };\n\n // Surface variant (#911). When set, each loaded (locale,ns) is composed as\n // base ⊕ sparse `{ns}.{surface}.json` overlay (overlay wins per key). We\n // cache the raw base tree + raw overlay trees so a surface switch recomposes\n // WITHOUT refetching the base, and so a switch cleanly rebuilds base-then-\n // overlay (no stale overlay keys). `undefined` ⇒ surface resolution off.\n private _surface: Surface | undefined;\n private _baseTree = new Map<string, Bundle>(); // bundleKey -> raw base JSON\n private _overlayTree = new Map<string, Bundle>(); // `${bundleKey}#${surface}` -> raw overlay JSON ({} sentinel on miss)\n // Resolved asset refs (#911 minimal v1), keyed `${locale}/${ns}/${keyPath}`\n // for the CURRENT composition (base assets, then overlay assets override).\n private _assets = new Map<string, AssetRef>();\n\n private _missing: MissingKeyManager;\n private _listeners = new Set<Listener>();\n // Stable snapshot reference for useSyncExternalStore. Rebuilt ONLY in _notify\n // (when state actually changed) and returned as-is between notifications.\n private _snapshot!: I18nInstance;\n\n // Effective key separator (#754): false = flat (literal lookups, so dotted\n // keys like \"App Version 6.3.8\" work), a string = nested. Set from\n // config.keySeparator (explicit override, wins) or auto-detected from the\n // version's key_style/key_separator on start(). Defaults to \".\".\n private _keySeparatorExplicit = false;\n // Namespace separator (#754): false = no ns prefix parsing. Default ':'.\n private _nsSeparator: string | false = \":\";\n // Developer-supplied {{value, format}} formatter (#805-1), re-bound onto the\n // live interpolator after init (i18next overwrites it during init).\n private _userFormat?: SonentaConfig[\"interpolation\"] extends infer T\n ? T extends { format?: infer F }\n ? F\n : undefined\n : undefined;\n\n constructor(config: SonentaConfig) {\n // Fail-loud: realtime config moved out of core in 0.9.0. If a caller still\n // passes `liveUpdates` or ANY `centrifugo*` key, throw with a clear,\n // actionable migration message rather than silently no-op'ing.\n const removedRealtimeKeys = Object.keys(config).filter(\n (k) => k === \"liveUpdates\" || k.startsWith(\"centrifugo\"),\n );\n if (removedRealtimeKeys.length > 0) {\n throw new Error(\n `@sonenta/react-i18next: ${removedRealtimeKeys.join(\", \")} ${\n removedRealtimeKeys.length > 1 ? \"were\" : \"was\"\n } removed in 0.9.0 — realtime is now the @sonenta/realtime plugin. ` +\n \"Remove them and pass `plugins: [sonentaRealtime({ wsUrl })]` to <SonentaProvider> instead.\",\n );\n }\n\n this.locale = config.defaultLocale;\n this.fallbackLng = config.fallbackLng;\n this._surface = config.surface;\n\n // keySeparator (#754): explicit override wins verbatim and skips the\n // version-metadata lookup; otherwise default \".\" and auto-detect in start().\n let keySeparator: string | false = \".\";\n if (config.keySeparator !== undefined) {\n keySeparator = config.keySeparator;\n this._keySeparatorExplicit = true;\n }\n if (config.nsSeparator !== undefined) this._nsSeparator = config.nsSeparator;\n\n this._config = {\n apiBase: config.apiBase ?? DEFAULT_API_BASE,\n cdnBase: config.cdnBase ?? DEFAULT_CDN_BASE,\n missingHandler: config.missingHandler ?? \"send\",\n token: config.token,\n projectUuid: config.projectUuid,\n namespaces: config.namespaces?.length\n ? config.namespaces\n : config.defaultNS\n ? [config.defaultNS]\n : [\"common\"],\n flushIntervalMs: config.flushIntervalMs ?? DEFAULT_FLUSH_MS,\n flushBatchSize: config.flushBatchSize ?? DEFAULT_BATCH,\n missingEventsBufferSize: config.missingEventsBufferSize ?? DEFAULT_BUFFER,\n version: config.version ?? config.versionSlug ?? DEFAULT_VERSION_SLUG,\n env: config.env ?? \"prod\",\n };\n\n // Missing-key streaming (#805 Stage 2) — same transport selection + flush\n // defaults as the legacy engine.\n const transport: Transport =\n config.transport ??\n (this._config.missingHandler === \"log\"\n ? logTransport\n : defaultTransport({\n apiBase: this._config.apiBase,\n token: this._config.token,\n projectUuid: this._config.projectUuid,\n }));\n this._missing = new MissingKeyManager({\n transport,\n missingHandler: this._config.missingHandler,\n flushIntervalMs: this._config.flushIntervalMs,\n flushBatchSize: this._config.flushBatchSize,\n bufferSize: this._config.missingEventsBufferSize,\n });\n\n this._userFormat = config.interpolation?.format as typeof this._userFormat;\n\n // Embedded build-time snapshot (#757): prime i18next's `resources`\n // synchronously so the first render is instant + works OFFLINE before the\n // first fetch. initialBundles share i18next's resources shape but BYPASS\n // the backend, so the nested→suffixed plural transform must be applied here\n // too (else nested CLDR plurals never resolve). We also mirror the snapshot\n // into _hasContent (but NOT _attempted) so the missing-key gate stays\n // closed until a real fetch confirms.\n const resources: Record<string, Record<string, Bundle>> = {};\n if (config.initialBundles) {\n for (const [loc, byNs] of Object.entries(config.initialBundles)) {\n for (const [ns, tree] of Object.entries(byNs)) {\n if (!tree || typeof tree !== \"object\") continue;\n (resources[loc] ??= {})[ns] = flattenPlurals(tree as Bundle, loc);\n // Prime the raw base cache too (#911) so a surface recompose before\n // the first fetch composes against the snapshot instead of wiping it.\n this._baseTree.set(this._bundleKey(loc, ns), tree as Bundle);\n if (Object.keys(tree).length > 0) {\n this._hasContent.add(this._bundleKey(loc, ns));\n }\n }\n }\n }\n\n // escapeValue stays false (React escapes); `format` is the optional\n // developer value formatter for `{{value, format}}` (#805-1). i18next v26's\n // InterpolationOptions type omits `format` (it lives on FormatFunction) —\n // the intersection types it without a cast.\n const interpolation: InterpolationOptions & { format?: FormatFunction } = {\n escapeValue: false,\n format: this._userFormat as FormatFunction | undefined,\n };\n\n const ns = this._config.namespaces;\n this._i18next = createInstance();\n this._i18next.use(initReactI18next);\n // SYNC init (initAsync: false — i18next v26's rename of initImmediate), NO\n // backend module → i18next never kicks off a network load. So `t()` +\n // `ready` work offline pre-`start()`, matching the legacy sync constructor.\n // partialBundledLanguages lets dynamically-added bundles coexist with the\n // snapshot resources.\n void this._i18next.init({\n lng: this.locale,\n fallbackLng: config.fallbackLng ?? false,\n ns,\n defaultNS: ns[0],\n fallbackNS: false,\n initAsync: false,\n keySeparator,\n nsSeparator: this._nsSeparator,\n // #806 bug2: re-render react-i18next-native consumers when our async\n // loads addResourceBundle (bindI18nStore catches 'added'/'removed'). The\n // missing-key park is addResource SILENT, so it never emits 'added' →\n // no re-render loop (the rc.1 forceStoreRerender-on-saveMissing regression).\n react: {\n bindI18n: \"languageChanged loaded\",\n bindI18nStore: \"added removed\",\n useSuspense: false,\n },\n resources,\n partialBundledLanguages: true,\n // A nested-object (non-plural) key resolves to an object; legacy `t()`\n // returns the raw key in that case, so map it back to the key here.\n returnedObjectHandler: (key) => key,\n // GATE (legacy three-condition gate): only report once the active\n // locale's bundle exists AND has ≥1 key, AND we've flipped `ready` after a\n // real fetch (`_attempted` + `_hasContent`). i18next fires the handler\n // even for empty/absent bundles, so the filtering lives here.\n saveMissing: this._config.missingHandler !== \"off\",\n saveMissingTo: \"all\",\n interpolation,\n missingKeyHandler: this._handleMissing,\n });\n\n // #806 bug3: a drop-in `i18next.changeLanguage(lng)` (what react-i18next's\n // own useTranslation calls) must lazy-load the target language's bundles +\n // re-render — but our instance has NO backend. Route it through setLocale\n // (fetch + addResourceBundle + missing-gate + variant/fallback chain +\n // _notify), preserving the language feature. Keep the ORIGINAL bound for\n // internal _syncLanguage use to avoid recursion.\n this._origChangeLanguage = this._i18next.changeLanguage.bind(this._i18next);\n this._i18next.changeLanguage = ((lng?: string, ...rest: unknown[]) => {\n if (typeof lng !== \"string\" || lng === this.locale) {\n return (\n this._origChangeLanguage as (...a: unknown[]) => Promise<unknown>\n )(lng, ...rest);\n }\n return this.setLocale(lng).then(() =>\n this._i18next.getFixedT(this.locale, null),\n );\n }) as typeof this._i18next.changeLanguage;\n\n // #806 SeedSower P1 — instance-level on-screen registry producer.\n // The 1.0.2 thin-wrapper contract lets host apps keep their\n // `from 'react-i18next'` imports; those calls resolve through the\n // shared i18next instance but never touch our hook-level `_set`\n // tracker, so a feedback widget on a host like that sees an empty\n // registry (\"no strings on this view\"). Wrap `i18next.t` so EVERY\n // resolved key — native useTranslation/Trans via getFixedT (which\n // calls `this.t`, see i18next v26 source ~line 2060), direct\n // `i18n.t()`, and our own adapter `t` that delegates here — funnels\n // into `keyRegistry._track(this._registryToken, …)`. The wrap is\n // best-effort + non-throwing: a registry bookkeeping error must\n // NEVER break translation.\n this._wrapInstanceT();\n\n // #805-1: i18next v26 ALWAYS overwrites `interpolation.format` with its\n // built-in Formatter during init. Re-bind the live interpolator's `format`\n // to the developer fn AFTER init so `{{value, format}}` honors it, falling\n // back to the built-in Intl formatter when the dev fn passes through.\n this._rebindFormat();\n\n // Language catalog (#803): prime from an embedded catalog (offline/SSR/RN)\n // so dir()/nativeName() work before/without the network fetch.\n this._catalogDisabled = config.disableLanguageCatalog === true;\n this._catalog.merge(config.languageCatalog);\n\n // ready=true synchronously only when the snapshot fully covers the active\n // locale's configured namespaces; otherwise wait for the fetch to flip it.\n const active = config.initialBundles?.[this.locale];\n if (\n active &&\n this._config.namespaces.every(\n (n) => active[n] && Object.keys(active[n]!).length > 0,\n )\n ) {\n this.ready = true;\n }\n\n this._snapshot = this._buildSnapshot();\n }\n\n /** Re-bind the developer `{{value, format}}` formatter onto the live\n * interpolator (i18next overwrites `interpolation.format` during init). */\n private _rebindFormat(): void {\n const userFormat = this._userFormat as FormatFunction | undefined;\n if (!userFormat) return;\n const interpolator = (\n this._i18next as unknown as {\n services?: { interpolator?: { format?: FormatFunction } };\n }\n ).services?.interpolator;\n if (!interpolator) return;\n const builtIn = (\n this._i18next.options.interpolation as { format?: FormatFunction } | undefined\n )?.format;\n interpolator.format = ((value, format, lng, opts) => {\n const out = userFormat(\n value,\n format,\n lng,\n (opts ?? {}) as Record<string, unknown>,\n );\n if (out === (value as unknown) && builtIn) {\n return builtIn(value, format, lng, opts);\n }\n return out;\n }) as FormatFunction;\n }\n\n /** Monkey-patch `i18next.t` so every resolved key feeds the on-screen\n * registry (#806). The override is an OWN property on the instance,\n * shadowing the prototype `t()` method; getFixedT-returned t functions\n * look up `this.t` dynamically (i18next v26 source ~line 2060), so\n * native `react-i18next` `useTranslation` / `<Trans>` go through it\n * too. Bookkeeping is wrapped in try/catch — a registry failure must\n * never break translation. */\n private _wrapInstanceT(): void {\n const i18n = this._i18next;\n const orig = i18n.t.bind(i18n);\n const token = this._registryToken;\n const defaultNs = (): string => this.defaultNamespace;\n const wrapped = ((...args: unknown[]) => {\n const result = (orig as unknown as (...a: unknown[]) => unknown)(...args);\n try {\n const rawKey = args[0];\n const opts =\n args[1] && typeof args[1] === \"object\" && !Array.isArray(args[1])\n ? (args[1] as { ns?: string | string[] })\n : undefined;\n // i18next.t accepts (key | key[] | selectorFn). Record the FIRST\n // string key — selector functions and non-string arrays are skipped\n // (not the SeedSower drop-in path, not worth the encoding risk).\n let keyStr: string | undefined;\n if (typeof rawKey === \"string\") {\n keyStr = rawKey;\n } else if (Array.isArray(rawKey) && typeof rawKey[0] === \"string\") {\n keyStr = rawKey[0];\n }\n if (keyStr) {\n const optsNs = opts?.ns;\n const ns =\n typeof optsNs === \"string\"\n ? optsNs\n : Array.isArray(optsNs)\n ? optsNs[0]\n : undefined;\n // If the call specified a namespace via options and the key\n // doesn't already carry one inline, compose `ns:key` so the\n // registry encoding matches the hook-level path.\n const fullKey =\n ns && !keyStr.includes(\":\") ? `${ns}:${keyStr}` : keyStr;\n keyRegistry._track(token, keyRegistry.encode(fullKey, defaultNs()));\n }\n } catch {\n // registry bookkeeping must never break t().\n }\n return result;\n }) as unknown as typeof i18n.t;\n i18n.t = wrapped;\n }\n\n // ---- React subscription ----\n\n subscribe = (listener: Listener): (() => void) => {\n this._listeners.add(listener);\n return () => this._listeners.delete(listener) as unknown as void;\n };\n\n /** Stable snapshot accessor for useSyncExternalStore. The returned object\n * reference is identical between renders unless _notify fired. */\n getSnapshot = (): I18nInstance => this._snapshot;\n\n private _buildSnapshot(): I18nInstance {\n return {\n ready: this.ready,\n locale: this.locale,\n language: this.locale,\n setLocale: this.setLocale,\n changeLanguage: this.changeLanguage,\n t: this.t,\n missingEvents: this.missingEvents,\n flushMissing: this.flushMissing,\n reload: this.reload,\n refresh: this.refresh,\n surface: this._surface,\n setSurface: this.setSurface,\n asset: this.asset,\n dir: this.dir,\n nativeName: this.nativeName,\n languageMeta: this.languageMeta,\n i18next: this._i18next,\n };\n }\n\n /** Bundle cache-key builder. Includes `version` so providers with different\n * `version` values never share state. Segments are slugs (no '/'). */\n private _bundleKey(locale: Locale, ns: Namespace): string {\n return `${this._config.version}/${locale}/${ns}`;\n }\n\n private _notify(): void {\n this._snapshot = this._buildSnapshot();\n for (const l of this._listeners) l();\n }\n\n /** Force react-i18next-NATIVE consumers (raw `useTranslation`/`<Trans>` on the\n * exposed instance) to re-render in the READY state, by emitting i18next's\n * `loaded` event (which react-i18next binds via `bindI18n`). Called AFTER\n * `ready=true`, so a missing key they render is re-evaluated once the gate is\n * open → it streams to `/v1/missing` (#806 native path; our own\n * `useTranslation` already re-renders via `_notify`). The missing-key park is\n * a SILENT addResource, so this never reopens the render loop (rc.1). */\n private _signalLoaded(): void {\n (\n this._i18next as unknown as { emit: (e: string, ...a: unknown[]) => void }\n ).emit(\"loaded\", {});\n }\n\n // ---- Lifecycle ----\n\n /** Default namespace (the first configured one) — used to attribute a bare\n * `t(\"key\")` call when recording on-screen keys. */\n get defaultNamespace(): string {\n return this._config.namespaces[0]!;\n }\n\n /**\n * Ordered lookup chain for an active locale: the locale itself, its\n * base-language truncations (`fr-CA → fr`), then the configured\n * `fallbackLng`(s) — deduped, order-preserving (the `fr-CA → fr → source`\n * chain, #803). Used to drive bundle loading; i18next handles resolution\n * fallback itself via its `languages` list.\n */\n private _resolutionChain(locale: Locale): Locale[] {\n const out: Locale[] = [];\n const seen = new Set<string>();\n for (const l of [...localeChain(locale), ...asArray(this.fallbackLng)]) {\n if (l && !seen.has(l)) {\n seen.add(l);\n out.push(l);\n }\n }\n return out;\n }\n\n /** Loads the configured namespaces for the active locale's full resolution\n * chain (locale → base → fallback), wires up the key separator + catalog,\n * flips `ready`, and arms the missing-key flush loop. */\n async start(fetchImpl: typeof fetch = fetch): Promise<void> {\n // Publish the on-screen key registry so a mounted feedback widget lists\n // only the strings rendered on the current view (spec ltm 373).\n keyRegistry.attach();\n const targets = new Set<string>(this._resolutionChain(this.locale));\n await Promise.all([\n ...[...targets].flatMap((loc) =>\n this._config.namespaces.map((ns) =>\n this._loadBundle(loc, ns, fetchImpl),\n ),\n ),\n // Best-effort: align the key separator with the version's key_style (#754).\n this._loadKeyStyle(fetchImpl),\n // Best-effort: load the public language catalog for dir()/nativeName().\n this._loadCatalog(fetchImpl),\n ]);\n // Keep i18next's language chain in step with the active locale + fallbacks\n // so its native resolution walks variant→base→fallback correctly.\n await this._syncLanguage();\n this.ready = true;\n this._missing.start();\n this._notify();\n this._signalLoaded();\n }\n\n /** Best-effort: read the version's `key_style` / `key_separator` (#754) so the\n * SDK resolves keys the way the project's bundles are shaped. Skipped when\n * the dev set `keySeparator` explicitly; on 403/404/offline keep the default. */\n private async _loadKeyStyle(fetchImpl: typeof fetch): Promise<void> {\n if (this._keySeparatorExplicit) return;\n const sep = await detectKeySeparator(\n this._config.apiBase,\n this._config.projectUuid,\n this._config.version,\n this._config.token,\n fetchImpl,\n );\n // Apply to the live i18next instance — its translator reads\n // `options.keySeparator` on each call, so a post-init flip takes effect.\n this._i18next.options.keySeparator = sep;\n }\n\n /** Best-effort: fetch the PUBLIC language catalog and merge it in (#803).\n * Skipped when disabled; a failure keeps any embedded catalog. */\n private async _loadCatalog(fetchImpl: typeof fetch): Promise<void> {\n if (this._catalogDisabled) return;\n this._catalog.merge(await loadCatalog(this._config.apiBase, fetchImpl));\n }\n\n /** Re-derive i18next's active language chain from the adapter's locale +\n * fallbackLng. `changeLanguage` recomputes `instance.languages`\n * (variant→base→fallback) so native resolution fallback is correct. */\n private async _syncLanguage(): Promise<void> {\n if (this._i18next.language !== this.locale) {\n // Use the ORIGINAL changeLanguage — our override (#806) routes through\n // setLocale, which calls this; the override here would recurse.\n await this._origChangeLanguage(this.locale);\n }\n }\n\n setLocale = async (next: Locale): Promise<void> => {\n if (next === this.locale) return;\n this.locale = next;\n this.ready = false;\n this._notify();\n // Load the new locale's full chain (locale → base → fallback), skipping any\n // (locale, ns) already fetched (e.g. a fallback loaded at start()).\n await Promise.all(\n this._resolutionChain(next).flatMap((loc) =>\n this._config.namespaces\n .filter((ns) => !this._attempted.has(this._bundleKey(loc, ns)))\n .map((ns) => this._loadBundle(loc, ns)),\n ),\n );\n await this._syncLanguage();\n this.ready = true;\n this._notify();\n this._signalLoaded();\n };\n\n /** Alias of {@link setLocale} for react-i18next compatibility. */\n changeLanguage = (next: Locale): Promise<void> => this.setLocale(next);\n\n /** Alias of {@link locale} for react-i18next compatibility. */\n get language(): Locale {\n return this.locale;\n }\n\n /** The underlying real `i18next` instance (#806 bug1 / #805 drop-in). Exposed\n * on the class too (not just the snapshot) so `getI18n().i18next` works for\n * react-i18next drop-in (`<Trans i18n={…}>`, `useTranslation(ns, { i18n })`). */\n get i18next(): I18nextInstance {\n return this._i18next;\n }\n\n /** Text direction for a locale (default: active locale) — i18next parity.\n * Catalog `rtl` is authoritative (variant→base); falls back to the built-in\n * RTL list before/without the catalog (#803). */\n dir = (lng?: Locale): \"ltr\" | \"rtl\" =>\n this._catalog.dir(lng ?? this.locale);\n\n /** Endonym (native name) for a locale from the catalog (default: active) —\n * the fallback for runtimes without `Intl.DisplayNames` (#803). */\n nativeName = (lng?: Locale): string | undefined =>\n this._catalog.nativeName(lng ?? this.locale);\n\n /** Full language-catalog entry for a locale (default: active); `undefined`\n * when the catalog has no matching entry. */\n languageMeta = (lng?: Locale): LanguageMeta | undefined =>\n this._catalog.languageMeta(lng ?? this.locale);\n\n stop(): void {\n // Drop THIS instance's accumulated keys explicitly so a mid-life\n // provider unmount (when another provider is still keeping the\n // global published) doesn't leak this instance's contribution.\n keyRegistry._delete(this._registryToken);\n keyRegistry.detach();\n this._missing.stop();\n }\n\n /**\n * #806 plugin-context backing for `SonentaPluginContext.onLanguageChange`.\n * Subscribes to i18next's `languageChanged` emitter and returns the\n * unsubscribe function. Wraps the i18next event so plugins never have\n * to touch the (prefix-private) `_i18next` field directly. Fires on\n * every successful language change — `setLocale()` /\n * `changeLanguage()` / the drop-in `i18next.changeLanguage()` (whose\n * #806-bug3 override routes through `setLocale` which then runs the\n * native `_origChangeLanguage` that actually fires the event).\n */\n onLanguageChange = (cb: (lng: Locale) => void): (() => void) => {\n const handler = (lng: string): void => cb(lng);\n this._i18next.on(\"languageChanged\", handler);\n return () => {\n // i18next types ship `.off` as a public EventEmitter method; cast\n // through unknown to side-step a minor d.ts gap in @types/i18next\n // for the handler signature (it accepts `(...args: unknown[])`).\n (\n this._i18next as unknown as {\n off: (e: string, h: (lng: string) => void) => void;\n }\n ).off(\"languageChanged\", handler);\n };\n };\n\n /**\n * Bust-refetch already-loaded bundles and re-render once. Iterate the\n * `_attempted` cache keys (`${version}/${locale}/${ns}`), optionally filtered\n * by `opts.locale` / `opts.namespace`, and re-pull each with `{ bust: true }`\n * (so the mutable CDN `latest/` alias bypasses the HTTP cache). After all\n * settle, `_notify()` once so React re-renders. Used by `@sonenta/realtime`\n * on a `translations_published` push and as a manual refresh hook.\n */\n reload = async (\n opts: { locale?: Locale; namespace?: Namespace } = {},\n ): Promise<void> => {\n const targets: Array<{ locale: Locale; ns: Namespace }> = [];\n for (const key of this._attempted) {\n // `${version}/${locale}/${ns}` — none of the segments contain '/'.\n const parts = key.split(\"/\");\n const locale = parts[1];\n const ns = parts[2];\n if (!locale || !ns) continue;\n if (opts.locale && opts.locale !== locale) continue;\n if (opts.namespace && opts.namespace !== ns) continue;\n targets.push({ locale, ns });\n }\n if (targets.length === 0) return;\n await Promise.all(\n targets.map((t) =>\n this._loadBundle(t.locale, t.ns, fetch, { bust: true }),\n ),\n );\n this._notify();\n };\n\n /**\n * Force every `useTranslation` consumer to re-render WITHOUT refetching —\n * re-resolves `t()` against the CURRENT resources. For plugins that mutate\n * i18next resources directly (e.g. `@sonenta/in-context` applies an edit\n * via `i18next.addResource`) and need the snapshot to repaint right away.\n * Repaints BOTH this SDK's `useTranslation`/`<Trans>` (via the store\n * `_notify`) AND react-i18next-NATIVE consumers bound to the exposed\n * instance (via the `loaded` event). Does NO bundle fetch, so an in-place\n * override is never clobbered — unlike {@link reload}, which bust-refetches\n * the CDN bundles. Additive (1.0.6); safe to call any time after mount.\n */\n refresh = (): void => {\n this._notify();\n this._signalLoaded();\n };\n\n get surface(): Surface | undefined {\n return this._surface;\n }\n\n /** Switch the active surface (#911) and recompose every loaded (locale, ns)\n * as base ⊕ the new surface's overlay, then re-render. `undefined` drops to\n * base-only. No-op when unchanged. Overlays are fetched on demand + cached. */\n setSurface = async (surface: Surface | undefined): Promise<void> => {\n if (surface === this._surface) return;\n this._surface = surface;\n const targets: Array<{ locale: Locale; ns: Namespace }> = [];\n for (const key of this._attempted) {\n // `${version}/${locale}/${ns}` — none of the segments contain '/'.\n const parts = key.split(\"/\");\n const locale = parts[1];\n const ns = parts[2];\n if (locale && ns) targets.push({ locale, ns });\n }\n await Promise.all(\n targets.map((t) => this._composeBundle(t.locale, t.ns)),\n );\n this._notify();\n this._signalLoaded();\n };\n\n /** Asset variant ref (#911) for `key` under the active locale+surface, or\n * `undefined`. Walks the locale fallback chain like `t()` would. */\n asset = (key: string, namespace?: Namespace): AssetRef | undefined => {\n let ns = namespace ?? this.defaultNamespace;\n let bareKey = key;\n if (typeof this._nsSeparator === \"string\" && this._nsSeparator) {\n const idx = key.indexOf(this._nsSeparator);\n if (idx > 0) {\n ns = key.slice(0, idx);\n bareKey = key.slice(idx + this._nsSeparator.length);\n }\n }\n for (const loc of this._resolutionChain(this.locale)) {\n const ref = this._assets.get(`${loc}/${ns}/${bareKey}`);\n if (ref) return ref;\n }\n return undefined;\n };\n\n // ---- Translation ----\n\n t = (\n key: string,\n optionsOrDefault?:\n | (Record<string, unknown> & { defaultValue?: string; count?: number })\n | string,\n maybeOptions?: Record<string, unknown> & {\n defaultValue?: string;\n count?: number;\n },\n ): string => {\n // react-i18next-style positional fallback: a string 2nd arg is the default\n // value; an optional 3rd arg carries interpolation/options merged under it.\n const options:\n | (Record<string, unknown> & { defaultValue?: string; count?: number })\n | undefined =\n typeof optionsOrDefault === \"string\"\n ? { ...(maybeOptions ?? {}), defaultValue: optionsOrDefault }\n : optionsOrDefault;\n\n // Literal-first resolution (#754): an EXACT literal wins so a dotted-literal\n // key (e.g. \"App Version 6.3.8\") resolves even in nested mode. We probe the\n // literal ourselves (rather than via sonentaResolveKey) ONLY so we can also\n // INTERPOLATE the hit — legacy `_render` interpolated literal hits (a flat\n // key \"greet\" → \"Hello {{name}}\" must fill {{name}}). On a miss we defer to\n // i18next's native t(), which handles nested/plural/context/interpolation +\n // the variant→base→fallback chain.\n const literal = this._probeLiteral(key);\n if (literal !== undefined) {\n const interpolator = (\n this._i18next as unknown as {\n services?: {\n interpolator?: {\n interpolate: (\n s: string,\n d: Record<string, unknown>,\n lng: string,\n opts: Record<string, unknown>,\n ) => string;\n };\n };\n }\n ).services?.interpolator;\n if (interpolator && options) {\n return interpolator.interpolate(\n literal,\n options as Record<string, unknown>,\n this.locale,\n {},\n );\n }\n return literal;\n }\n return this._i18next.t(\n key,\n options as Record<string, unknown> | undefined,\n ) as string;\n };\n\n /** Probe the EXACT literal value for a key across the active language chain\n * (no key split), honoring the configured nsSeparator to point at the right\n * (lng, ns). Returns the string hit, or `undefined` to fall through to the\n * native nested/plural resolution. Mirrors `sonentaResolveKey`'s probe but\n * is local so the adapter can interpolate the result. */\n private _probeLiteral(key: string): string | undefined {\n const nsSeparator = this._i18next.options.nsSeparator;\n let probeNs = this.defaultNamespace;\n let probeKey = key;\n if (typeof nsSeparator === \"string\" && nsSeparator !== \"\") {\n const idx = key.indexOf(nsSeparator);\n if (idx > 0) {\n probeNs = key.slice(0, idx);\n probeKey = key.slice(idx + nsSeparator.length);\n }\n }\n const langs = this._i18next.languages ?? [this._i18next.language].filter(Boolean);\n for (const lng of langs) {\n if (!lng) continue;\n const v = this._i18next.getResource(lng, probeNs, probeKey, {\n keySeparator: false,\n });\n if (typeof v === \"string\") return v;\n }\n return undefined;\n }\n\n /**\n * i18next `missingKeyHandler`: the legacy three-condition gate + #746\n * source_value rule. Only report once we've fetched the active locale's\n * bundle (`ready` + `_attempted` + `_hasContent`), so the first paint /\n * an unpublished namespace / a 404→{} / 200→{} never floods the dashboard.\n * `fallbackValue` is ignored — when no defaultValue was passed it === key,\n * which must NEVER become a source_value.\n */\n private _handleMissing = (\n lngs: readonly string[],\n ns: string,\n key: string,\n _fallbackValue: string,\n _updateMissing: boolean,\n options: { defaultValue?: unknown } | undefined,\n ): void => {\n void _fallbackValue;\n void _updateMissing;\n const lng = lngs[0] ?? this.locale;\n // GATE: report only after a real fetch confirmed content for the active\n // locale's bundle. (Pre-start, or an empty/404 bundle, stays silent.)\n const cacheKey = this._bundleKey(this.locale, ns);\n if (\n !this.ready ||\n !this._attempted.has(cacheKey) ||\n !this._hasContent.has(cacheKey)\n ) {\n return;\n }\n const recorded = this._missing.record({\n key,\n namespace: ns,\n language_code: lng,\n source_value: this._sourceValueFor(key, ns, options),\n });\n // Re-render ONLY on a NEWLY recorded event. A deduped report (the same\n // missing key re-encountered on the next render) must NOT notify — an\n // unconditional notify re-renders the store, which re-runs `t()`, which\n // re-fires this handler → infinite loop (\"Maximum update depth\"; fatal in\n // React Native via react-i18next@17's forceStoreRerender on saveMissing).\n // The legacy engine dedup'd BEFORE notifying; this restores that.\n if (!recorded) return;\n // Park the reported key in the store so i18next stops treating it as\n // missing (and thus stops firing saveMissing for it on every render).\n // Belt-and-suspenders against the forceStoreRerender path; stored as a\n // literal (keySeparator:false) + silent (no 'added' event) so our\n // literal-first probe short-circuits subsequent renders. The value is what\n // we'd render anyway (explicit defaultValue, else the bare key).\n const rendered =\n typeof options?.defaultValue === \"string\" ? options.defaultValue : key;\n try {\n // keySeparator:false stores it as a flat literal (so our literal-first\n // probe finds it); silent:true suppresses the 'added' event so nothing\n // re-renders. i18next honors both at runtime; the cast is only because\n // its options type declares keySeparator as `string`.\n this._i18next.addResource(lng, ns, key, rendered, {\n keySeparator: false,\n silent: true,\n } as unknown as Parameters<typeof this._i18next.addResource>[4]);\n } catch {\n // best-effort — the dedup-gated notify above already breaks the loop.\n }\n this.missingEvents = this._missing.missingEvents;\n this._notify();\n };\n\n flushMissing = (): Promise<void> => this._missing.flush();\n\n // ---- Internals ----\n\n /**\n * Resolve the `source_value` for a missing-key report (Option A, #746):\n * 1. `options.defaultValue` — explicit developer-provided string.\n * 2. The configured fallbackLng bundle's plain-string value (the\n * source/canonical locale). Excludes the active locale + its derived\n * base (display fallbacks, not the promotable \"source\").\n * 3. Otherwise `undefined` — we never send the key name as a value (the key\n * already travels in `event.key`).\n */\n private _sourceValueFor(\n key: string,\n ns: string,\n options?: { defaultValue?: unknown },\n ): string | undefined {\n if (typeof options?.defaultValue === \"string\") {\n return options.defaultValue;\n }\n for (const loc of asArray(this.fallbackLng)) {\n if (loc === this.locale) continue;\n const v = this._i18next.getResource(loc, ns, key);\n if (typeof v === \"string\") return v;\n }\n return undefined;\n }\n\n private async _loadBundle(\n locale: Locale,\n ns: Namespace,\n fetchImpl: typeof fetch = fetch,\n opts: { bust?: boolean } = {},\n ): Promise<void> {\n const cacheKey = this._bundleKey(locale, ns);\n // env routing — prod hits the CDN cache; dev hits the live runtime endpoint\n // authenticated with the API key.\n let url: string;\n let init: RequestInit;\n if (this._config.env === \"dev\") {\n // #806 Hermes URLSearchParams polyfill safety (see backend.ts): RN\n // ships only the constructor + toString(). Build the param record\n // first, then construct ONCE; no .set after construction.\n const qp: Record<string, string> = { language: locale, namespace: ns };\n if (this._config.version && this._config.version !== \"main\") {\n qp.version_slug = this._config.version;\n }\n const params = new URLSearchParams(qp);\n url = `${this._config.apiBase.replace(/\\/+$/, \"\")}/v1/projects/${this._config.projectUuid}/translations/runtime?${params.toString()}`;\n init = {\n method: \"GET\",\n headers: { Authorization: `ApiKey ${this._config.token}` },\n credentials: \"omit\",\n };\n } else {\n url = `${this._config.cdnBase.replace(/\\/+$/, \"\")}/p/${this._config.projectUuid}/${this._config.version}/latest/${locale}/${ns}.json`;\n init = { method: \"GET\", credentials: \"omit\" };\n }\n // On a live-republish refetch, bypass the browser HTTP cache so the mutable\n // `latest/` alias is re-pulled even within its max-age window.\n if (opts.bust) {\n init.cache = \"reload\";\n }\n // A failed live refetch must NOT downgrade good translations to keys — keep\n // the last-known-good bundle. Only the initial (non-bust) load may cache an\n // empty object as the \"no bundle\" sentinel.\n const hadContent = this._hasContent.has(cacheKey);\n try {\n const r = await fetchImpl(url, init);\n if (r.ok) {\n const data = (await r.json()) as Bundle;\n if (data && typeof data === \"object\" && Object.keys(data).length > 0) {\n this._baseTree.set(cacheKey, data);\n this._hasContent.add(cacheKey);\n } else {\n // 200 → {} (fresh project, no published keys): clear content flag so\n // the missing-key gate stays closed.\n this._baseTree.set(cacheKey, {});\n this._hasContent.delete(cacheKey);\n }\n } else if (hadContent) {\n // Non-OK but we already have content (snapshot or prior fetch) — keep\n // last-known-good (offline/transient first loads #757, refetch blips).\n } else {\n // 404 = no published bundle yet. Cache empty so the gate suppresses\n // reportMissing in this state.\n this._baseTree.set(cacheKey, {});\n this._hasContent.delete(cacheKey);\n }\n } catch {\n if (hadContent) {\n // Network error but we have content (snapshot/prior fetch) — keep it.\n } else {\n this._baseTree.set(cacheKey, {});\n this._hasContent.delete(cacheKey);\n }\n } finally {\n this._attempted.add(cacheKey);\n }\n // Compose base ⊕ surface overlay into i18next (overlay fetched on demand).\n await this._composeBundle(locale, ns, fetchImpl, opts.bust);\n }\n\n /**\n * Compose the i18next bundle for (locale, ns) as base ⊕ surface overlay\n * (#911). Base is applied wholesale (replace), then — when a surface is\n * active — the sparse overlay is deep-merged on top (overlay wins per key).\n * Rebuilding base-first on every compose means a surface SWITCH cleanly\n * resets to base before applying the new overlay (no stale overlay keys).\n * Plurals: both base and overlay flatten through {@link flattenPlurals}, so\n * an overlay plural dict fully replaces the base key's plural set.\n */\n private async _composeBundle(\n locale: Locale,\n ns: Namespace,\n fetchImpl: typeof fetch = fetch,\n bust = false,\n ): Promise<void> {\n const base = this._baseTree.get(this._bundleKey(locale, ns)) ?? {};\n // Reset this (locale, ns)'s asset entries — recomputed from base+overlay.\n const assetPrefix = `${locale}/${ns}/`;\n for (const k of this._assets.keys()) {\n if (k.startsWith(assetPrefix)) this._assets.delete(k);\n }\n const baseTree = this._unwrapAssets(base, locale, ns);\n // deep=false, overwrite=true → replace the bundle wholesale with base.\n this._i18next.addResourceBundle(\n locale,\n ns,\n flattenPlurals(baseTree, locale),\n false,\n true,\n );\n if (!this._surface) return;\n const overlay = await this._loadOverlay(\n locale,\n ns,\n this._surface,\n fetchImpl,\n bust,\n );\n if (!overlay || Object.keys(overlay).length === 0) return;\n const overlayTree = this._unwrapAssets(overlay, locale, ns);\n // deep=true, overwrite=true → merge the sparse overlay on top (per-key win).\n this._i18next.addResourceBundle(\n locale,\n ns,\n flattenPlurals(overlayTree, locale),\n true,\n true,\n );\n }\n\n /** Fetch (and cache) the sparse surface overlay `{ns}.{surface}.json` from\n * the CDN. Cached per (locale, ns, surface); a `{}` is cached on miss so a\n * surface switch never re-hits the network for a known-absent overlay.\n * Overlays live on the CDN only (the dev runtime endpoint has no overlay\n * shape yet) — `env: \"dev\"` returns `{}`. */\n private async _loadOverlay(\n locale: Locale,\n ns: Namespace,\n surface: Surface,\n fetchImpl: typeof fetch = fetch,\n bust = false,\n ): Promise<Bundle> {\n const key = `${this._bundleKey(locale, ns)}#${surface}`;\n if (!bust && this._overlayTree.has(key)) return this._overlayTree.get(key)!;\n if (this._config.env === \"dev\") {\n this._overlayTree.set(key, {});\n return {};\n }\n const url = `${this._config.cdnBase.replace(/\\/+$/, \"\")}/p/${this._config.projectUuid}/${this._config.version}/latest/${locale}/${ns}.${surface}.json`;\n const init: RequestInit = { method: \"GET\", credentials: \"omit\" };\n if (bust) init.cache = \"reload\";\n let overlay: Bundle = {};\n try {\n const r = await fetchImpl(url, init);\n if (r.ok) {\n const data = (await r.json()) as Bundle;\n if (data && typeof data === \"object\") overlay = data;\n }\n // 404 / non-OK = no overlay for this surface → {} (base only).\n } catch {\n // Network error → {} (base only); never downgrade the base render.\n }\n this._overlayTree.set(key, overlay);\n return overlay;\n }\n\n /**\n * Strip `{ \"$value\", \"$asset\" }` asset envelopes (#911 minimal v1) out of a\n * raw bundle tree: each envelope is replaced by its `$value` (string or\n * plural dict) so `t()` resolves normally, and its `$asset` is recorded\n * under `${locale}/${ns}/${keyPath}` for {@link asset}. Returns a NEW tree;\n * the input is not mutated. Non-envelope nodes recurse as namespace groups.\n */\n private _unwrapAssets(tree: Bundle, locale: Locale, ns: Namespace): Bundle {\n const sep =\n typeof this._i18next.options.keySeparator === \"string\"\n ? this._i18next.options.keySeparator\n : \".\";\n const walk = (node: unknown, path: string[]): unknown => {\n if (!node || typeof node !== \"object\") return node;\n const obj = node as Record<string, unknown>;\n // Asset envelope: has own `$value`.\n if (Object.prototype.hasOwnProperty.call(obj, \"$value\")) {\n const a = obj.$asset as AssetRef | undefined;\n if (a && typeof a.kind === \"string\" && typeof a.ref === \"string\") {\n this._assets.set(`${locale}/${ns}/${path.join(sep)}`, {\n kind: a.kind,\n ref: a.ref,\n });\n }\n return obj.$value; // string | pluralDict — resolves as a normal leaf.\n }\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(obj)) {\n out[k] = walk(v, [...path, k]);\n }\n return out;\n };\n return walk(tree, []) as Bundle;\n }\n}\n","import type { LanguageMeta, Locale } from \"./types\";\n\n/**\n * Language catalog (#803) — the framework-agnostic half of the SDK's\n * direction/endonym support, ported out of the legacy `SonentaI18n` store so\n * the new thin-wrapper engine (#805) can expose `dir()` / `nativeName()` /\n * `languageMeta()` with identical semantics.\n *\n * Source of truth is the PUBLIC catalog (`GET {apiBase}/v1/languages`, no\n * auth, CDN-cached). Regional variants inherit `rtl`/`script` from their base\n * (`fr-CA` → `fr`); a built-in RTL-language list keeps direction correct\n * before/without the network fetch.\n */\n\n/**\n * Primary-subtag RTL fallback used by {@link LanguageCatalog.dir} BEFORE or\n * WITHOUT the language catalog (offline, catalog disabled, or pre-fetch). The\n * catalog's `rtl` is authoritative; this only covers the common RTL languages\n * so direction is still correct without a network round-trip. Mirrors the\n * legacy `RTL_LANGS` set verbatim.\n */\nexport const RTL_LANGS = new Set<string>([\n \"ar\", \"arc\", \"ckb\", \"dv\", \"fa\", \"ha\", \"he\", \"khw\", \"ks\", \"ku\",\n \"nqo\", \"ps\", \"sd\", \"ug\", \"ur\", \"yi\",\n]);\n\n/**\n * Progressive BCP-47 subtag truncation, native-i18next style:\n * \"zh-Hant-TW\" → [\"zh-Hant-TW\", \"zh-Hant\", \"zh\"]\n * \"fr-CA\" → [\"fr-CA\", \"fr\"]\n * \"fr\" → [\"fr\"]\n * Lets a regional variant fall back to its base language when its catalog\n * entry is missing — the `fr-CA → fr` step of variant→base inheritance (#803).\n */\nexport function localeChain(locale: Locale): Locale[] {\n const parts = locale.split(\"-\").filter(Boolean);\n if (parts.length <= 1) return [locale];\n const chain: Locale[] = [];\n for (let i = parts.length; i >= 1; i--) chain.push(parts.slice(0, i).join(\"-\"));\n return chain;\n}\n\n/**\n * Best-effort fetch of the PUBLIC language catalog\n * (`GET {apiBase}/v1/languages`, no auth, `credentials: 'omit'`, CDN-cached)\n * powering `dir()` / `nativeName()` (#803). Returns the parsed array on a 200\n * with an array body; returns `null` on any non-OK, throw, or non-array body —\n * NEVER throws to the caller, so a failed fetch keeps any embedded catalog.\n */\nexport async function loadCatalog(\n apiBase: string,\n fetchImpl: typeof fetch = fetch,\n): Promise<LanguageMeta[] | null> {\n try {\n const url = `${apiBase.replace(/\\/+$/, \"\")}/v1/languages`;\n const r = await fetchImpl(url, { method: \"GET\", credentials: \"omit\" });\n if (!r.ok) return null;\n const data = (await r.json()) as unknown;\n return Array.isArray(data) ? (data as LanguageMeta[]) : null;\n } catch {\n // best-effort — keep any embedded / last-known catalog\n return null;\n }\n}\n\n/**\n * A per-instance language catalog: a `Map<lower-cased BCP-47 code,\n * LanguageMeta>` plus the `dir`/`nativeName`/`languageMeta` lookups, matching\n * the legacy `SonentaI18n` semantics exactly. One of these rides alongside\n * each engine instance (see `engine.ts`).\n */\nexport class LanguageCatalog {\n private _catalog = new Map<string, LanguageMeta>();\n\n /** Merge catalog entries into the map, keyed by lower-cased BCP-47 code. */\n merge(items: LanguageMeta[] | null | undefined): void {\n if (!items) return;\n for (const item of items) {\n if (item && typeof item.code === \"string\") {\n this._catalog.set(item.code.toLowerCase(), item);\n }\n }\n }\n\n /** Catalog entry for a locale, walking variant→base (`fr-CA` → `fr`);\n * `undefined` when no entry in the chain matches. */\n metaFor(locale: Locale): LanguageMeta | undefined {\n for (const l of localeChain(locale)) {\n const hit = this._catalog.get(l.toLowerCase());\n if (hit) return hit;\n }\n return undefined;\n }\n\n /**\n * Text direction for a locale — i18next parity. Catalog `rtl` is\n * authoritative (with variant→base inheritance); falls back to the built-in\n * {@link RTL_LANGS} primary-subtag check before/without the catalog, else\n * `'ltr'`.\n */\n dir(locale: Locale): \"ltr\" | \"rtl\" {\n const meta = this.metaFor(locale);\n if (meta && typeof meta.rtl === \"boolean\") return meta.rtl ? \"rtl\" : \"ltr\";\n const primary = locale.split(\"-\")[0]?.toLowerCase();\n return primary && RTL_LANGS.has(primary) ? \"rtl\" : \"ltr\";\n }\n\n /** Endonym (native name) for a locale from the catalog; `undefined` when the\n * catalog has no matching entry (no built-in fallback). */\n nativeName(locale: Locale): string | undefined {\n return this.metaFor(locale)?.native_name;\n }\n\n /** Full catalog entry for a locale; `undefined` when unknown. */\n languageMeta(locale: Locale): LanguageMeta | undefined {\n return this.metaFor(locale);\n }\n}\n","import {\n createInstance,\n type FormatFunction,\n type i18n as I18nextInstance,\n type InterpolationOptions,\n} from \"i18next\";\nimport { initReactI18next } from \"react-i18next\";\nimport { SonentaBackend, type SonentaBackendOptions } from \"./backend\";\nimport { LanguageCatalog, loadCatalog } from \"./catalog\";\nimport { MissingKeyManager } from \"./missing\";\nimport { flattenPlurals } from \"./plurals\";\nimport { defaultTransport, logTransport } from \"./transport\";\nimport type {\n LanguageMeta,\n Locale,\n MissingKeyEvent,\n Transport,\n SonentaConfig,\n} from \"./types\";\n\nconst DEFAULT_API_BASE = \"https://api.verbumia.dev\";\nconst DEFAULT_CDN_BASE = \"https://cdn.verbumia.ca\";\nconst DEFAULT_VERSION_SLUG = \"main\";\nconst DEFAULT_FLUSH_MS = 5_000;\nconst DEFAULT_BATCH = 50;\nconst DEFAULT_BUFFER = 200;\n\n/**\n * One {@link MissingKeyManager} per i18next instance, keyed off the instance\n * itself so the buffer/timer/transport state rides alongside the upstream\n * object without polluting it. Populated in {@link createSonentaI18next} and\n * read by the `sonenta*Missing` helpers below.\n */\nconst managers = new WeakMap<I18nextInstance, MissingKeyManager>();\n\n/**\n * One {@link LanguageCatalog} per engine instance (#803), keyed off the\n * instance like {@link managers}. Primed from `config.languageCatalog` and the\n * best-effort public `GET /v1/languages` fetch in\n * {@link createSonentaI18next}, and read by the `sonentaDir` /\n * `sonentaNativeName` / `sonentaLanguageMeta` helpers below.\n */\nconst catalogs = new WeakMap<I18nextInstance, LanguageCatalog>();\n\n/** Normalize `fallbackLng` (string | string[] | unset) → array. */\nfunction asArray(fb: Locale | Locale[] | undefined): Locale[] {\n if (fb == null) return [];\n return (Array.isArray(fb) ? fb : [fb]).filter(Boolean);\n}\n\n/**\n * Best-effort auto-detect of the version's key style (#754 parity with the\n * legacy `_loadKeyStyle`). Fetches the version metadata\n * (`GET {apiBase}/v1/projects/{projectUuid}/versions/{version}`, `ApiKey`\n * auth) and maps `key_style==='flat'` → `false` (flat: literal keys, no\n * split) else `key_separator || '.'` (nested). On ANY failure (non-OK, throw,\n * non-string body) keeps the default `'.'` — never throws to the caller. Only\n * called when `config.keySeparator === undefined`.\n */\nexport async function detectKeySeparator(\n apiBase: string,\n projectUuid: string,\n version: string,\n token: string,\n f: typeof fetch,\n): Promise<string | false> {\n try {\n const url = `${apiBase.replace(/\\/+$/, \"\")}/v1/projects/${projectUuid}/versions/${encodeURIComponent(version)}`;\n const r = await f(url, {\n method: \"GET\",\n headers: { Authorization: `ApiKey ${token}` },\n credentials: \"omit\",\n });\n if (!r.ok) return \".\";\n const meta = (await r.json()) as {\n key_style?: string;\n key_separator?: string;\n };\n return meta.key_style === \"flat\" ? false : meta.key_separator || \".\";\n } catch {\n return \".\";\n }\n}\n\n/**\n * Build a real `i18next` instance (#805, Option A) backed by\n * {@link SonentaBackend} and wired into react-i18next. This is the\n * thin-wrapper replacement for the hand-rolled `SonentaI18n` store; it loads\n * bundles over the same wire contract while delegating resolution, plurals,\n * interpolation, and the React subscription to the upstream libraries.\n *\n * `config.initialBundles` (locale → ns → tree) is passed straight through as\n * i18next `resources` for an instant, offline-first first render; combined\n * with `partialBundledLanguages: true` so the backend still fetches the rest.\n */\nexport async function createSonentaI18next(\n config: SonentaConfig,\n fetchImpl?: typeof fetch,\n): Promise<I18nextInstance> {\n const version = config.version ?? config.versionSlug ?? DEFAULT_VERSION_SLUG;\n const ns: string[] = config.namespaces?.length\n ? config.namespaces\n : config.defaultNS\n ? [config.defaultNS]\n : [\"common\"];\n const apiBase = config.apiBase ?? DEFAULT_API_BASE;\n\n // initialBundles share i18next's `resources` shape (locale -> ns -> tree),\n // but they BYPASS the backend, so the nested→suffixed plural transform that\n // the backend applies to fetched bundles must be applied here too — else a\n // snapshot's nested CLDR plurals would never resolve. Run each\n // initialBundles[locale][ns] tree through flattenPlurals(tree, locale)\n // (#805 Stage 3). Non-plural keys pass through untouched.\n const resources: SonentaConfig[\"initialBundles\"] = config.initialBundles\n ? Object.fromEntries(\n Object.entries(config.initialBundles).map(([loc, byNs]) => [\n loc,\n Object.fromEntries(\n Object.entries(byNs).map(([nsName, tree]) => [\n nsName,\n tree && typeof tree === \"object\"\n ? flattenPlurals(tree as Record<string, unknown>, loc)\n : tree,\n ]),\n ),\n ]),\n )\n : undefined;\n\n const backend: SonentaBackendOptions = {\n apiBase,\n cdnBase: config.cdnBase ?? DEFAULT_CDN_BASE,\n token: config.token,\n projectUuid: config.projectUuid,\n version,\n env: config.env ?? \"prod\",\n fetchImpl,\n // Nested CLDR → i18next-suffixed plural transform for every fetched bundle\n // (#805 Stage 3). The backfill in flattenPlurals covers i18next v26 NOT\n // falling back to `_other` for a category the locale needs.\n transform: (data, lng, _ns) => flattenPlurals(data, lng),\n };\n\n // Missing-key streaming (#805 Stage 2) — mirror the legacy engine's\n // transport selection + flush/buffer defaults exactly.\n const missingHandler = config.missingHandler ?? \"send\";\n const transport: Transport =\n config.transport ??\n (missingHandler === \"log\"\n ? logTransport\n : defaultTransport({\n apiBase,\n token: config.token,\n projectUuid: config.projectUuid,\n }));\n const manager = new MissingKeyManager({\n transport,\n missingHandler,\n flushIntervalMs: config.flushIntervalMs ?? DEFAULT_FLUSH_MS,\n flushBatchSize: config.flushBatchSize ?? DEFAULT_BATCH,\n bufferSize: config.missingEventsBufferSize ?? DEFAULT_BUFFER,\n });\n\n const instance = createInstance();\n instance.use(SonentaBackend).use(initReactI18next);\n\n // Resolve the #746 source_value for a missing key. Matches legacy\n // `_sourceValueFor`: explicit `options.defaultValue` wins; else walk the\n // configured fallbackLng(s) (skip the active locale) and return the first\n // plain-string `getResource(fb, ns, key)`; else undefined — NEVER the key.\n const sourceValueFor = (\n ns_: string,\n key: string,\n lng: Locale,\n options: { defaultValue?: unknown } | undefined,\n ): string | undefined => {\n if (typeof options?.defaultValue === \"string\") return options.defaultValue;\n for (const fb of asArray(config.fallbackLng)) {\n if (fb === lng) continue;\n const v = instance.getResource(fb, ns_, key);\n if (typeof v === \"string\") return v;\n }\n return undefined;\n };\n\n // keySeparator (#754 parity): an EXPLICIT config value wins verbatim and\n // skips the version fetch (mirrors legacy `_keySeparatorExplicit`); when\n // undefined, best-effort auto-detect from the version metadata BEFORE init.\n const keySeparator: string | false =\n config.keySeparator === undefined\n ? await detectKeySeparator(\n apiBase,\n config.projectUuid,\n version,\n config.token,\n fetchImpl ?? fetch,\n )\n : config.keySeparator;\n\n // escapeValue stays false (React escapes); `format` is the optional\n // developer-supplied value formatter for `{{value, format}}` (#805-1).\n // i18next accepts `interpolation.format` at runtime but its v26\n // `InterpolationOptions` type omits it (FormatFunction lives elsewhere) — the\n // `& { format?: FormatFunction }` intersection types it without a cast.\n const interpolation: InterpolationOptions & { format?: FormatFunction } = {\n escapeValue: false,\n format: config.interpolation?.format as FormatFunction | undefined,\n };\n\n await instance.init({\n lng: config.defaultLocale,\n fallbackLng: config.fallbackLng ?? false,\n ns,\n defaultNS: ns[0],\n fallbackNS: false,\n keySeparator,\n nsSeparator: config.nsSeparator === undefined ? \":\" : config.nsSeparator,\n resources,\n partialBundledLanguages: true,\n // When reporting is off we also flip saveMissing off so i18next never even\n // invokes the handler (matches legacy \"off skips reporting\"); the manager\n // is a hard no-op too, so this is belt-and-suspenders.\n saveMissing: missingHandler !== \"off\",\n saveMissingTo: \"all\",\n interpolation,\n missingKeyHandler: (lngs, ns_, key, fallbackValue, _updateMissing, options) => {\n // GATE (matches legacy three-condition gate): only report once the\n // bundle for the active locale exists AND has ≥1 key. Covers\n // not-loaded / 404→{} / 200→{} (the boot-flood guard). `fallbackValue`\n // is ignored — when no defaultValue was passed it === key, which must\n // NEVER become a source_value.\n void fallbackValue;\n const lng = lngs[0];\n if (!lng) return;\n if (!instance.hasResourceBundle(lng, ns_)) return;\n const bundle = instance.getResourceBundle(lng, ns_);\n if (!bundle || typeof bundle !== \"object\" || Object.keys(bundle).length === 0) {\n return;\n }\n manager.record({\n key,\n namespace: ns_,\n language_code: lng,\n source_value: sourceValueFor(ns_, key, lng, options),\n });\n },\n // i18next's InitOptions type doesn't know our backend option shape; keep\n // the cast localized to this one block.\n backend: backend as unknown as Record<string, unknown>,\n });\n\n // #805-1 quirk: i18next v26 ALWAYS registers a built-in `Formatter` module\n // and, during init, OVERWRITES `options.interpolation.format` with that\n // formatter's `format` (i18next.js: `this.options.interpolation.format =\n // s.formatter.format.bind(s.formatter)`). So the `format` we passed into\n // init() above is discarded for the default formatter, whose named formats\n // are only the Intl ones (number/currency/datetime/relativetime/list) — an\n // unknown token like `long` just warns and returns the raw value.\n //\n // To honor the documented contract (\"i18next calls YOUR `format` for\n // `{{value, format}}`\"), re-bind the live Interpolator's `format` to the\n // developer fn AFTER init. The built-in Intl formatters stay reachable: we\n // call the developer fn FIRST and, only when it returns the value byte-for-\n // byte unchanged (i.e. it didn't handle this token), fall back to the\n // built-in formatter — so `{{n, number}}` etc. keep working out of the box.\n const userFormat = config.interpolation?.format;\n if (userFormat) {\n const services = (\n instance as unknown as {\n services?: {\n interpolator?: { format?: FormatFunction };\n };\n }\n ).services;\n const interpolator = services?.interpolator;\n if (interpolator) {\n // The built-in Intl formatter, ALREADY bound to its module — i18next's\n // init set `options.interpolation.format = formatter.format.bind(...)`.\n // Capture that (not the raw unbound method, which would lose `this`).\n const builtIn = (\n instance.options.interpolation as\n | { format?: FormatFunction }\n | undefined\n )?.format;\n interpolator.format = ((value, format, lng, opts) => {\n const out = userFormat(\n value,\n format,\n lng,\n (opts ?? {}) as Record<string, unknown>,\n );\n // Defer to the built-in Intl formatter only when the developer fn was\n // a pass-through for this token (returned the value unchanged).\n if (out === (value as unknown) && builtIn) {\n return builtIn(value, format, lng, opts);\n }\n return out;\n }) as FormatFunction;\n }\n }\n\n managers.set(instance, manager);\n manager.start();\n\n // Language catalog (#803) on the engine. Prime from an embedded catalog\n // (offline/SSR/RN) so dir()/nativeName() work synchronously, then best-effort\n // augment from the PUBLIC GET /v1/languages (no auth) UNLESS disabled. A\n // failed fetch keeps the embedded/last-known catalog (loadCatalog never\n // throws and returns null on any failure).\n const catalog = new LanguageCatalog();\n catalog.merge(config.languageCatalog);\n catalogs.set(instance, catalog);\n if (config.disableLanguageCatalog !== true) {\n catalog.merge(await loadCatalog(apiBase, fetchImpl ?? fetch));\n }\n\n return instance;\n}\n\n/** Per-instance catalog accessor; lazily attaches an empty catalog for an\n * instance not created by {@link createSonentaI18next} so the helpers still\n * resolve via the built-in RTL fallback instead of throwing. */\nfunction catalogFor(instance: I18nextInstance): LanguageCatalog {\n let c = catalogs.get(instance);\n if (!c) {\n c = new LanguageCatalog();\n catalogs.set(instance, c);\n }\n return c;\n}\n\n/**\n * Text direction of a locale (defaults to the instance's active language) —\n * i18next parity (#803). Catalog `rtl` is authoritative (variant→base\n * inheritance); falls back to the built-in RTL-language list before/without\n * the catalog, else `'ltr'`.\n */\nexport function sonentaDir(\n instance: I18nextInstance,\n lng?: Locale,\n): \"ltr\" | \"rtl\" {\n const locale = lng ?? instance.language ?? \"\";\n return catalogFor(instance).dir(locale);\n}\n\n/**\n * Endonym (native name) of a locale from the catalog (defaults to the active\n * language) — the fallback for runtimes without `Intl.DisplayNames` (#803).\n * `undefined` when the catalog has no entry.\n */\nexport function sonentaNativeName(\n instance: I18nextInstance,\n lng?: Locale,\n): string | undefined {\n const locale = lng ?? instance.language ?? \"\";\n return catalogFor(instance).nativeName(locale);\n}\n\n/**\n * Full language-catalog entry for a locale (script, plural categories, parent,\n * …), defaulting to the active language; `undefined` if unknown (#803).\n */\nexport function sonentaLanguageMeta(\n instance: I18nextInstance,\n lng?: Locale,\n): LanguageMeta | undefined {\n const locale = lng ?? instance.language ?? \"\";\n return catalogFor(instance).languageMeta(locale);\n}\n\n/** Force-flush the missing-key batch for an instance now. No-op for an\n * instance not created by {@link createSonentaI18next}. */\nexport function sonentaFlushMissing(instance: I18nextInstance): Promise<void> {\n return managers.get(instance)?.flush() ?? Promise.resolve();\n}\n\n/** Recently captured missing-key events (newest first) for an instance.\n * Empty for an instance not created by {@link createSonentaI18next}. */\nexport function sonentaMissingEvents(\n instance: I18nextInstance,\n): MissingKeyEvent[] {\n return managers.get(instance)?.missingEvents ?? [];\n}\n\n/** Stop the periodic missing-key flush loop for an instance. No-op for an\n * instance not created by {@link createSonentaI18next}. */\nexport function sonentaStopMissing(instance: I18nextInstance): void {\n managers.get(instance)?.stop();\n}\n\n/**\n * Bust-refetch loaded resources and re-render. Thin wrapper over i18next's\n * `reloadResources`; pass `locale`/`namespace` to narrow the reload. Used by\n * the realtime plugin on a `translations_published` push and for manual\n * refresh.\n */\nexport function sonentaReload(\n instance: I18nextInstance,\n opts?: { locale?: string; namespace?: string },\n): Promise<void> {\n return instance.reloadResources(opts?.locale, opts?.namespace);\n}\n\n/**\n * Literal-first key resolution (#754 parity with the legacy `resolve()`).\n *\n * i18next v26 is NOT literal-first in the CONFLICT case: with a bundle\n * `{ \"a.b\": \"LITERAL\", a: { b: \"NESTED\" } }` and `keySeparator: '.'`,\n * `instance.t(\"a.b\")` returns \"NESTED\" (it splits before probing the literal).\n * Legacy `resolve()` always lets an exact `bundle[key]` win, so dotted-literal\n * keys (e.g. `\"App Version 6.3.8\"`) resolve even in nested mode.\n *\n * This helper restores that contract: it first probes the EXACT literal via\n * `getResource(lng, ns, key, { keySeparator: false })` (a guaranteed\n * no-split lookup) across the active language(s); a string hit wins. Otherwise\n * it defers to `instance.t(key, options)` for normal nested/plural/context/\n * interpolation behavior. Plural literals (whose stored value is an object,\n * not a string) intentionally fall through to `t` so count selection works.\n *\n * Note: the literal probe is a plain `getResource` (no interpolation), matching\n * legacy `resolve()` which returned the raw value; pass interpolation vars to\n * the non-literal `t` path as usual. If you need interpolation on a literal\n * hit, the value contains no `{{var}}` in practice for dotted-literal keys.\n */\nexport function sonentaResolveKey(\n instance: I18nextInstance,\n key: string,\n ns?: string,\n options?: Record<string, unknown>,\n): string {\n // Determine the namespace + bare key the same way i18next would, honoring\n // the configured nsSeparator (false/'' disables ns parsing). We only need\n // this to point getResource at the right (lng, ns) — t() still does its own\n // parsing on the original key.\n const nsSeparator = instance.options.nsSeparator;\n const defaultNs =\n ns ??\n (Array.isArray(instance.options.defaultNS)\n ? instance.options.defaultNS[0]\n : instance.options.defaultNS) ??\n \"translation\";\n let probeNs = defaultNs;\n let probeKey = key;\n if (typeof nsSeparator === \"string\" && nsSeparator !== \"\") {\n const idx = key.indexOf(nsSeparator);\n if (idx > 0) {\n probeNs = key.slice(0, idx);\n probeKey = key.slice(idx + nsSeparator.length);\n }\n }\n\n // Active language(s): the resolved chain i18next would walk.\n const langs = instance.languages ?? [instance.language].filter(Boolean);\n for (const lng of langs) {\n if (!lng) continue;\n const literal = instance.getResource(lng, probeNs, probeKey, {\n keySeparator: false,\n });\n if (typeof literal === \"string\") return literal;\n }\n\n return instance.t(key, options) as string;\n}\n","import type { MissingHandlerMode, MissingKeyEvent, Transport } from \"./types\";\n\n/**\n * Streaming + batching for missing-key reports on the real-`i18next` engine\n * (#805, Stage 2). This is the parallel counterpart to the legacy\n * `SonentaI18n`'s in-class `_reportMissing`/`flushMissing`/`_startTimer`\n * machinery — extracted into a standalone manager because the i18next instance\n * is owned by upstream and can't carry our buffer/timer state.\n *\n * Behaviour mirrors the legacy engine exactly:\n * - `record()` dedups within the buffer (per `${lang}/${ns}/${key}`), keeps a\n * newest-first ring buffer capped at `bufferSize`, and force-flushes once\n * `pending.length >= flushBatchSize`.\n * - `flush()` is a no-op when `missingHandler === 'off'`; otherwise it\n * drains pending and `await`s the transport in a swallow-all try/catch\n * (best-effort: reporting must never break the host app).\n * - `start()` arms a `setInterval(flushIntervalMs)` flush loop (guarded for\n * non-browser/`off`); `stop()` clears it.\n *\n * The GATE (only-report-when-bundle-has-content) and the #746 `source_value`\n * rule live in the i18next `missingKeyHandler` (see `engine.ts`); by the time\n * an event reaches `record()` it is already vetted.\n */\nexport class MissingKeyManager {\n /** Newest-first ring buffer for in-app inspectors; capped at `bufferSize`. */\n missingEvents: MissingKeyEvent[] = [];\n\n private readonly transport: Transport;\n private readonly missingHandler: MissingHandlerMode;\n private readonly flushIntervalMs: number;\n private readonly flushBatchSize: number;\n private readonly bufferSize: number;\n\n private pending: MissingKeyEvent[] = [];\n private seen = new Set<string>(); // dedup `${language_code}/${namespace}/${key}`\n private timer: ReturnType<typeof setInterval> | null = null;\n\n constructor(opts: {\n transport: Transport;\n missingHandler: MissingHandlerMode;\n flushIntervalMs: number;\n flushBatchSize: number;\n bufferSize: number;\n }) {\n this.transport = opts.transport;\n this.missingHandler = opts.missingHandler;\n this.flushIntervalMs = opts.flushIntervalMs;\n this.flushBatchSize = opts.flushBatchSize;\n this.bufferSize = opts.bufferSize;\n }\n\n /**\n * Record a (already-vetted) missing-key event. Dedups within the buffer,\n * pushes onto the capped ring buffer + the pending batch, and force-flushes\n * when the batch reaches `flushBatchSize`. No-op when `missingHandler` is\n * `'off'` (matches legacy `_reportMissing`).\n *\n * Returns `true` only when the event was NEWLY recorded (not a dedup / not\n * `off`). Callers MUST gate any re-render (`_notify`) on this — notifying on\n * a deduped report is what caused the rc render-loop: a missing key re-fires\n * the handler on every render, so an unconditional notify loops forever.\n */\n record(event: MissingKeyEvent): boolean {\n if (this.missingHandler === \"off\") return false;\n const dedupKey = `${event.language_code}/${event.namespace}/${event.key}`;\n if (this.seen.has(dedupKey)) return false;\n this.seen.add(dedupKey);\n\n this.missingEvents = [event, ...this.missingEvents].slice(0, this.bufferSize);\n this.pending.push(event);\n if (this.pending.length >= this.flushBatchSize) {\n void this.flush();\n }\n return true;\n }\n\n /**\n * Drain the pending batch to the transport. No-op (and keeps pending intact)\n * when `'off'`; otherwise takes + clears pending and `await`s the transport,\n * swallowing any error (best-effort delivery).\n */\n async flush(): Promise<void> {\n if (this.missingHandler === \"off\") return;\n if (!this.pending.length) return;\n const batch = this.pending.slice(0);\n this.pending = [];\n try {\n await this.transport(batch);\n } catch {\n // best-effort — missing-key reporting must never break the host app\n }\n }\n\n /** Arm the periodic flush loop. Guarded for non-browser runtimes / `'off'`. */\n start(): void {\n if (this.missingHandler === \"off\") return;\n if (typeof setInterval !== \"function\") return;\n if (this.timer) return;\n this.timer = setInterval(() => {\n void this.flush();\n }, this.flushIntervalMs);\n }\n\n /** Clear the periodic flush loop. */\n stop(): void {\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = null;\n }\n }\n}\n","/**\n * Nested CLDR → i18next-suffixed plural transform (#805 Stage 3).\n *\n * Sonenta CDN/runtime bundles ship plurals as a NESTED CLDR object\n * (`{ one, other, … }`, `other` always present), NOT i18next's `_<category>`\n * suffixed flat keys. i18next v26 also will NOT fall back to `_other` for a\n * missing plural form, so we additionally BACKFILL whichever CLDR categories\n * the bundle's locale actually needs (from `Intl.PluralRules`) using the\n * `other` value — mirroring the legacy engine's `selectPluralForm` fallback.\n *\n * Example (en): `{ items: { one: \"1 item\", other: \"{{count}} items\" } }`\n * → `{ items_one: \"1 item\", items_other: \"{{count}} items\" }`\n * Example (ru, bundle only has `other`):\n * `{ notif: { other: \"{{count}} увед.\" } }`\n * → `{ notif_one, notif_few, notif_many, notif_other }` (all = the `other`\n * value) so `t('notif', {count:2})` (few) still renders.\n */\n\nconst CLDR = [\"zero\", \"one\", \"two\", \"few\", \"many\", \"other\"];\n\n/** Resolve the CLDR categories a locale needs; default to `[\"other\"]`. */\nfunction localeCategories(locale: string): string[] {\n try {\n return new Intl.PluralRules(locale).resolvedOptions()\n .pluralCategories as string[];\n } catch {\n return [\"other\"];\n }\n}\n\n/**\n * Recursively rewrite a bundle tree, converting nested CLDR plural objects to\n * i18next-suffixed flat keys (`key_one`, `key_other`, …) and backfilling the\n * locale's needed categories from `other`. Non-plural nested objects recurse;\n * plain leaf values pass through untouched.\n */\nexport function flattenPlurals(\n tree: Record<string, unknown>,\n locale: string,\n): Record<string, unknown> {\n const cats = localeCategories(locale);\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(tree)) {\n if (v && typeof v === \"object\" && !Array.isArray(v)) {\n const keys = Object.keys(v as object);\n const isPlural =\n keys.length > 0 &&\n keys.some((c) => CLDR.includes(c)) &&\n keys.every((c) => typeof (v as Record<string, unknown>)[c] === \"string\");\n if (isPlural) {\n const vv = v as Record<string, string>;\n const fallback = vv.other ?? vv[keys[0]!];\n for (const cat of new Set([...keys, ...cats])) {\n out[`${k}_${cat}`] = vv[cat] ?? fallback;\n }\n } else {\n out[k] = flattenPlurals(v as Record<string, unknown>, locale);\n }\n } else {\n out[k] = v;\n }\n }\n return out;\n}\n","import type { MissingKeyEvent, Transport } from \"./types\";\n\nconst SDK_LIB = \"@sonenta/react-i18next\";\n// Replaced at build time by tsup `define` with the package.json version, so\n// `sdk_meta.ver` can never drift again. Falls back in non-bundled contexts\n// (e.g. unit tests, where the define isn't applied).\ndeclare const __SDK_VER__: string;\nconst SDK_VER =\n typeof __SDK_VER__ !== \"undefined\" ? __SDK_VER__ : \"0.0.0-dev\";\n\n/** Default transport: POST to `${apiBase}/v1/missing` with the API key. */\nexport function defaultTransport(opts: {\n apiBase: string;\n token: string;\n projectUuid: string;\n}): Transport {\n return async (batch) => {\n if (!batch.length) return;\n const body = {\n project_uuid: opts.projectUuid,\n events: batch.map((e) => ({\n key: e.key,\n namespace: e.namespace,\n language_code: e.language_code,\n // Option A (#746): only send source_value when there's a real value;\n // omit it otherwise (never the key name). Absent = \"no default\".\n ...(e.source_value !== undefined\n ? { source_value: e.source_value }\n : {}),\n sdk_meta: {\n lib: SDK_LIB,\n ver: SDK_VER,\n ...(typeof window !== \"undefined\"\n ? { url: window.location?.href }\n : {}),\n ...(e.sdk_meta ?? {}),\n },\n })),\n };\n try {\n await fetch(`${opts.apiBase.replace(/\\/+$/, \"\")}/v1/missing`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `ApiKey ${opts.token}`,\n },\n body: JSON.stringify(body),\n // SDKs are best-effort; never block the render path\n keepalive: true,\n });\n } catch {\n // swallow — missing-key reporting must never break the host app\n }\n };\n}\n\n/** Logs each event to console.warn — handy for dev. */\nexport const logTransport: Transport = (batch: MissingKeyEvent[]) => {\n for (const e of batch) {\n // eslint-disable-next-line no-console\n console.warn(\"[sonenta] missing key\", e);\n }\n};\n","/**\n * On-screen key registry — the PRODUCER side of the tiny cross-package\n * contract that `@sonenta/feedback` consumes via\n * `globalThis.__verbumia_key_registry__` (see `@sonenta/feedback`'s\n * `core/keys.ts`).\n *\n * Why this exists: the feedback widget must list only the translation\n * strings RENDERED on the current screen (spec ltm 373) — NOT every\n * project string. The widget can't know what's on screen; the i18n SDK\n * does, because it resolves the keys. The registry exposes a minimal\n * global with TWO producer paths feeding it:\n *\n * 1. HOOK-LEVEL — our own `useTranslation` / `Trans` push their\n * per-render key set via `_set(token, set)`. Mount-tracking handles\n * navigation: when a component unmounts, its keys drop out of the\n * union automatically.\n * 2. INSTANCE-LEVEL — SonentaI18n wraps `i18next.t` so EVERY resolved\n * key (including those resolved through react-i18next's NATIVE\n * `useTranslation` / `<Trans>` bound to the exposed i18next, or\n * through a direct `i18n.t()` call) feeds `_track(token, id)`. This\n * makes the registry \"instance-level producer\", per #806 SeedSower\n * diagnosis: a 1.0.2 thin-wrapper drop-in lets host apps keep their\n * `from 'react-i18next'` imports, so the hook-level producer alone\n * misses 100% of those keys. Without this, the widget shows \"no\n * strings on this view\" even when the view is full of text.\n *\n * `snapshot()` is the UNION of both, deduped. The instance-level\n * contribution accumulates for the i18n instance's lifetime (no per-\n * component unmount signal), which is by-design: a stale superset is\n * strictly better than a false empty.\n *\n * The published shape is intentionally tiny so any framework port of the\n * i18n SDK can implement the same global without depending on feedback:\n *\n * globalThis.__verbumia_key_registry__ = {\n * snapshot(): { namespace: string; key: string }[];\n * isPopulated(): boolean;\n * reset(): void;\n * }\n */\n\nexport interface DeclaredKey {\n namespace: string;\n key: string;\n}\n\nconst GLOBAL = \"__verbumia_key_registry__\";\n// Internal id separator. NUL never appears in an i18next namespace or\n// key, so it round-trips even when a key itself contains ':'.\nconst SEP = \"\u0000\";\n\n/** Split an i18next-style `ns:key` (mirrors SonentaI18n._splitNamespace). */\nfunction split(fullKey: string, defaultNamespace: string): DeclaredKey {\n const idx = fullKey.indexOf(\":\");\n if (idx > 0) {\n return { namespace: fullKey.slice(0, idx), key: fullKey.slice(idx + 1) };\n }\n return { namespace: defaultNamespace, key: fullKey };\n}\n\nclass KeyRegistry {\n // One Set per mounted hook/Trans instance (keyed by an opaque token).\n // The on-screen set is the UNION of all live instances' latest render.\n private _instances = new Map<symbol, Set<string>>();\n // Provider mounts that have published us onto globalThis. Ref-counted so\n // a multi-provider tree (or fast unmount/remount in tests) never leaves\n // a stale global or unpublishes while another provider is still live.\n private _providers = 0;\n\n /** Replace an instance's contributed key set (per-render hook producer). */\n _set(token: symbol, keys: Set<string>): void {\n this._instances.set(token, keys);\n }\n\n /** Append ONE id to a token's set, lazy-creating it. Used by the\n * instance-level i18n.t wrap, which accumulates over the i18n\n * instance's lifetime (no per-render reset semantics). Safe to call\n * before `attach()` — the global publishes whenever a provider mounts. */\n _track(token: symbol, id: string): void {\n let set = this._instances.get(token);\n if (!set) {\n set = new Set();\n this._instances.set(token, set);\n }\n set.add(id);\n }\n\n /** Drop an instance entirely (called on hook unmount or i18n stop). */\n _delete(token: symbol): void {\n this._instances.delete(token);\n }\n\n /** Keys rendered by currently-mounted consumers. Stable insertion order. */\n snapshot(): DeclaredKey[] {\n const seen = new Set<string>();\n const out: DeclaredKey[] = [];\n for (const set of this._instances.values()) {\n for (const id of set) {\n if (seen.has(id)) continue;\n seen.add(id);\n const c = id.indexOf(SEP);\n out.push({ namespace: id.slice(0, c), key: id.slice(c + 1) });\n }\n }\n return out;\n }\n\n /** True when ANY producer has contributed ≥1 key. Cheap O(producers)\n * check exposed for DEV-time integration asserts (\"did my\n * useTranslation imports end up wired to @sonenta/react-i18next?\"). */\n isPopulated(): boolean {\n for (const set of this._instances.values()) {\n if (set.size > 0) return true;\n }\n return false;\n }\n\n /** Escape hatch (router integrations / tests). Mount-tracking already\n * handles navigation, so this is rarely needed. */\n reset(): void {\n this._instances.clear();\n }\n\n /** Encode a resolved key into the internal id used by `_set`. */\n encode(fullKey: string, defaultNamespace: string): string {\n const k = split(fullKey, defaultNamespace);\n return `${k.namespace}${SEP}${k.key}`;\n }\n\n /** Provider mounted — publish the global (idempotent, ref-counted). */\n attach(): void {\n this._providers += 1;\n if (this._providers === 1) {\n (globalThis as Record<string, unknown>)[GLOBAL] = {\n snapshot: () => this.snapshot(),\n isPopulated: () => this.isPopulated(),\n reset: () => this.reset(),\n };\n }\n }\n\n /** Provider unmounted — unpublish when the last one goes away. */\n detach(): void {\n this._providers = Math.max(0, this._providers - 1);\n if (this._providers === 0) {\n this._instances.clear();\n const g = globalThis as Record<string, unknown>;\n if (g[GLOBAL]) delete g[GLOBAL];\n }\n }\n}\n\n/** Process-wide singleton — there is exactly one on-screen registry. */\nexport const keyRegistry = new KeyRegistry();\n","import type { I18nInstance } from \"./types\";\nimport type { SonentaI18n } from \"./i18n\";\n\n// Active instance registered by the mounted <SonentaProvider>. Lets code\n// OUTSIDE React (utilities, stores, non-component modules) reach the i18n\n// instance the way react-i18next exposes its default singleton.\nlet _active: SonentaI18n | null = null;\n\n/** @internal — SonentaProvider registers its instance on mount. */\nexport function _setActiveInstance(instance: SonentaI18n): void {\n _active = instance;\n}\n\n/** @internal — SonentaProvider clears its instance on unmount. */\nexport function _clearActiveInstance(instance: SonentaI18n): void {\n if (_active === instance) _active = null;\n}\n\n/**\n * Access the active i18n instance OUTSIDE React components — the\n * react-i18next-style standalone singleton (e.g. for `t()`/`changeLanguage()`\n * in plain modules, stores, or helpers).\n *\n * Returns the instance created by the mounted `<SonentaProvider>`; throws a\n * clear error if no provider is mounted yet. Assumes a single app-wide\n * provider (the common case); with multiple concurrent providers it returns\n * the most recently mounted one.\n */\nexport function getI18n(): I18nInstance {\n if (!_active) {\n throw new Error(\n \"@sonenta/react-i18next: getI18n() was called before <SonentaProvider> mounted (no active i18n instance).\",\n );\n }\n return _active;\n}\n\n/**\n * SAFE variant of {@link getI18n} (#805): returns the active i18n instance, or\n * `null` when no provider is mounted yet — it does NOT throw. Use at module\n * load / in plain helpers where a provider may not be mounted.\n */\nexport function getI18nSafe(): I18nInstance | null {\n return _active;\n}\n\n/**\n * SAFE out-of-React translate (#805). Resolves against the active i18n instance\n * when a provider is mounted; otherwise returns `options.defaultValue` (when a\n * string) or the `key` — it NEVER throws. This makes module-load-time / helper\n * `t()` calls safe before mount (the throwing {@link getI18n} would break them).\n * Accepts both shapes: `t('k', { defaultValue })` and `t('k', 'Default', opts?)`.\n */\nexport function t(\n key: string,\n optionsOrDefault?:\n | (Record<string, unknown> & { defaultValue?: string; count?: number })\n | string,\n maybeOptions?: Record<string, unknown> & {\n defaultValue?: string;\n count?: number;\n },\n): string {\n if (_active) return _active.t(key, optionsOrDefault, maybeOptions);\n const dv =\n typeof optionsOrDefault === \"string\"\n ? optionsOrDefault\n : optionsOrDefault?.defaultValue;\n return typeof dv === \"string\" ? dv : key;\n}\n","/**\n * Surface variants (#911) — a second resolution dimension layered ON TOP of\n * the locale fallback chain. A \"surface\" (`desktop` / `mobile` / `tablet`) is\n * an additive overlay: the base bundle applies to every surface, and a sparse\n * surface overlay overrides individual keys for that surface only.\n *\n * This module is the framework-neutral surface helpers; the engine\n * (`SonentaI18n`) owns the overlay loading/compose, and the provider wires\n * the reactive viewport listener (web) — see `provider.tsx`.\n */\n\nexport type Surface = \"desktop\" | \"mobile\" | \"tablet\";\n\n/** Min-width (px) thresholds that map a viewport width to a surface. A width\n * `< mobile` → `mobile`; `< tablet` → `tablet`; otherwise `desktop`. Mirrors\n * the common mobile-first breakpoint ladder. */\nexport interface SurfaceBreakpoints {\n /** Upper bound (exclusive) of the `mobile` surface, in px. Default 640. */\n mobile: number;\n /** Upper bound (exclusive) of the `tablet` surface, in px. Default 1024. */\n tablet: number;\n}\n\nexport const DEFAULT_SURFACE_BREAKPOINTS: SurfaceBreakpoints = {\n mobile: 640,\n tablet: 1024,\n};\n\n/**\n * Map a viewport width (px) to a {@link Surface} using `breakpoints`\n * (defaults to {@link DEFAULT_SURFACE_BREAKPOINTS}). Framework-neutral and\n * pure — React Native callers can feed it `useWindowDimensions().width` and\n * pass the result to `i18n.setSurface(...)`; the web provider uses it\n * internally against `window.innerWidth`.\n */\nexport function surfaceForWidth(\n width: number,\n breakpoints: SurfaceBreakpoints = DEFAULT_SURFACE_BREAKPOINTS,\n): Surface {\n if (width < breakpoints.mobile) return \"mobile\";\n if (width < breakpoints.tablet) return \"tablet\";\n return \"desktop\";\n}\n","import { useEffect, useMemo, useRef } from \"react\";\nimport { useI18n, useI18nSnapshot } from \"./provider\";\nimport { keyRegistry } from \"./key-registry\";\nimport type {\n I18nInstance,\n TranslationFunction,\n TranslationOptions,\n} from \"./types\";\n\nexport interface UseTranslationResult {\n t: TranslationFunction;\n i18n: I18nInstance;\n}\n\n/** React hook — returns `{ t, i18n }`. Optional `defaultNamespace` lets you\n * drop the `ns:` prefix on every call.\n *\n * Every key this hook resolves during a render is recorded into the\n * on-screen key registry (so a mounted `@sonenta/feedback` widget lists\n * only the strings rendered on the current view — spec ltm 373). The\n * contribution is keyed to THIS hook instance and dropped on unmount, so\n * navigating away removes its keys automatically. */\nexport function useTranslation(defaultNamespace?: string): UseTranslationResult {\n const i18n = useI18n();\n const snapshot = useI18nSnapshot();\n\n // Keys resolved in the CURRENT render pass. The hook body runs before\n // the component's own `t()` calls, so clearing here yields a set that\n // reflects exactly this render once the component finishes.\n const renderedRef = useRef<Set<string>>(new Set());\n renderedRef.current = new Set<string>();\n // Opaque, stable token identifying this hook instance in the registry.\n const tokenRef = useRef<symbol>(Symbol(\"sonenta.t\"));\n\n const t = useMemo<TranslationFunction>(() => {\n // Forwards both call shapes — `t(key, opts)` and the react-i18next\n // positional `t(key, 'Default', opts?)` — straight to `i18n.t`, which\n // normalizes them. Registry tracking is keyed on the resolved fullKey.\n const fn = (\n key: string,\n optionsOrDefault?: TranslationOptions | string,\n maybeOptions?: TranslationOptions,\n ): string => {\n const fullKey =\n defaultNamespace && !key.includes(\":\")\n ? `${defaultNamespace}:${key}`\n : key;\n renderedRef.current.add(\n keyRegistry.encode(fullKey, i18n.defaultNamespace),\n );\n return i18n.t(fullKey, optionsOrDefault, maybeOptions);\n };\n return fn as TranslationFunction;\n }, [i18n, defaultNamespace]);\n\n // After every commit, publish this instance's latest rendered-key set.\n useEffect(() => {\n keyRegistry._set(tokenRef.current, renderedRef.current);\n });\n // Unmount only: drop this instance entirely so its keys leave the\n // on-screen snapshot when the component is gone (e.g. route change).\n useEffect(() => {\n const token = tokenRef.current;\n return () => keyRegistry._delete(token);\n }, []);\n\n return { t, i18n: snapshot };\n}\n","import { Children, cloneElement, isValidElement, type ReactNode } from \"react\";\nimport { useTranslation } from \"./hooks\";\n\nexport interface TransProps {\n /** The translation key (optionally `ns:key`). */\n i18nKey: string;\n /** Default value if the key is missing — used as the fallback string. */\n defaults?: string;\n /** Variables interpolated into `{{var}}` placeholders. */\n values?: Record<string, unknown>;\n /** JSX components mapped by 0-based numeric index — `<0>bold</0>` etc. */\n components?: ReactNode[];\n /** Optional namespace shortcut. */\n namespace?: string;\n}\n\n/** Bare-bones Trans component: resolves the key, interpolates values, and\n * swaps `<0>...</0>` placeholders into the supplied React components.\n * Keeps the surface minimal — full Trans semantics (nested keys, plural\n * trees, gender) land in V1.1. */\nexport function Trans({\n i18nKey,\n defaults,\n values,\n components,\n namespace,\n}: TransProps) {\n const { t } = useTranslation(namespace);\n const raw = t(i18nKey, { ...(values ?? {}), defaultValue: defaults ?? i18nKey });\n if (!components || !components.length) return <>{raw}</>;\n return <>{splitOnComponents(raw, components)}</>;\n}\n\nfunction splitOnComponents(text: string, components: ReactNode[]): ReactNode[] {\n const out: ReactNode[] = [];\n // Match <N>...</N> where N is a 0-based index into `components`.\n const re = /<(\\d+)>(.*?)<\\/\\1>/g;\n let lastIndex = 0;\n let m: RegExpExecArray | null;\n while ((m = re.exec(text)) !== null) {\n if (m.index > lastIndex) out.push(text.slice(lastIndex, m.index));\n const idx = Number(m[1]);\n const inner = m[2];\n const node = components[idx];\n if (isValidElement(node)) {\n out.push(\n cloneElement(node, { key: `t-${m.index}` }, ...Children.toArray(inner ?? \"\"))\n );\n } else if (node !== undefined) {\n out.push(node);\n } else {\n out.push(inner ?? \"\");\n }\n lastIndex = re.lastIndex;\n }\n if (lastIndex < text.length) out.push(text.slice(lastIndex));\n return out;\n}\n"],"mappings":"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","names":["createInstance","initReactI18next","ns","createInstance","initReactI18next","t","t","useEffect","useMemo","t","useMemo","useEffect","Fragment","jsx","t"]}
|
|
1
|
+
{"version":3,"sources":["../src/provider.tsx","../src/i18n.ts","../src/catalog.ts","../src/engine.ts","../src/missing.ts","../src/plurals.ts","../src/transport.ts","../src/key-registry.ts","../src/singleton.ts","../src/surface.ts","../src/hooks.ts","../src/trans.tsx"],"sourcesContent":["import {\n createContext,\n Fragment,\n useContext,\n useEffect,\n useMemo,\n useSyncExternalStore,\n type ReactNode,\n} from \"react\";\nimport { SonentaI18n } from \"./i18n\";\nimport { _clearActiveInstance, _setActiveInstance } from \"./singleton\";\nimport type { I18nInstance, SonentaConfig } from \"./types\";\nimport {\n DEFAULT_SURFACE_BREAKPOINTS,\n surfaceForWidth,\n type SurfaceBreakpoints,\n} from \"./surface\";\n\ninterface SonentaContextValue {\n i18n: SonentaI18n;\n}\n\nconst SonentaContext = createContext<SonentaContextValue | null>(null);\n\nexport interface SonentaProviderProps extends SonentaConfig {\n children: ReactNode;\n}\n\nexport function SonentaProvider({\n children,\n ...config\n}: SonentaProviderProps) {\n // Stable instance for the lifetime of the provider mount.\n const i18n = useMemo(() => new SonentaI18n(config), []); // eslint-disable-line react-hooks/exhaustive-deps\n\n useEffect(() => {\n // Register as the active instance so `getI18n()` works outside React.\n _setActiveInstance(i18n);\n void i18n.start();\n // Plugins (e.g. @sonenta/feedback) hook the SAME i18n instance —\n // no second context. setup() runs once; optional teardown on unmount.\n // 1.0.5 (#806 SeedSower lang-change propagation): the plugin context\n // now carries `onLanguageChange` — a stable subscribe→unsubscribe\n // hook backed by SonentaI18n's own languageChanged emitter so\n // plugins (feedback ≥0.2.6) can re-sync per-language state without\n // poking at the private _i18next. Additive — older plugins ignore.\n const teardowns = (config.plugins ?? [])\n .map((p) =>\n p.setup?.({\n i18n,\n config,\n onLanguageChange: i18n.onLanguageChange,\n }),\n )\n .filter((t): t is () => void => typeof t === \"function\");\n return () => {\n teardowns.forEach((t) => t());\n i18n.stop();\n _clearActiveInstance(i18n);\n };\n }, [i18n]); // eslint-disable-line react-hooks/exhaustive-deps\n\n // #911 — reactive surface detection from the viewport (web). Enabled only\n // when surface resolution is on (`surface` set) AND `surfaceBreakpoints` is\n // truthy, and a `matchMedia`/`window` exists (so SSR + React Native, which\n // have no `window`, simply keep the initial `surface` — drive RN changes via\n // `i18n.setSurface(surfaceForWidth(width))` from `useWindowDimensions`).\n useEffect(() => {\n if (!config.surface || !config.surfaceBreakpoints) return;\n if (typeof window === \"undefined\" || typeof window.matchMedia !== \"function\")\n return;\n const bp: SurfaceBreakpoints =\n config.surfaceBreakpoints === true\n ? DEFAULT_SURFACE_BREAKPOINTS\n : config.surfaceBreakpoints;\n const sync = () => void i18n.setSurface(surfaceForWidth(window.innerWidth, bp));\n sync(); // align to the actual viewport on mount (initial `surface` is the SSR floor)\n // matchMedia listeners at each threshold — fire only on a surface boundary\n // crossing (cheaper + steadier than a raw resize listener).\n const mqls = [bp.mobile, bp.tablet].map((px) =>\n window.matchMedia(`(min-width: ${px}px)`),\n );\n mqls.forEach((mql) => mql.addEventListener(\"change\", sync));\n return () => mqls.forEach((mql) => mql.removeEventListener(\"change\", sync));\n }, [i18n]); // eslint-disable-line react-hooks/exhaustive-deps\n\n const value = useMemo<SonentaContextValue>(() => ({ i18n }), [i18n]);\n return (\n <SonentaContext.Provider value={value}>\n {children}\n {/* Plugin outlets: isolated sibling leaves AFTER children. Their\n internal state never propagates to the host app subtree. */}\n {(config.plugins ?? []).map((p) =>\n p.render ? <Fragment key={p.name}>{p.render()}</Fragment> : null,\n )}\n </SonentaContext.Provider>\n );\n}\n\n/** Internal — used by useTranslation + Trans. */\nexport function useI18n(): SonentaI18n {\n const ctx = useContext(SonentaContext);\n if (!ctx) {\n throw new Error(\"useTranslation/Trans must be used inside <SonentaProvider>\");\n }\n return ctx.i18n;\n}\n\n/** Subscribes to the i18n store and returns a snapshot the React tree can render.\n *\n * `getSnapshot` MUST return a stable reference between notifications,\n * otherwise React loops forever (Maximum update depth exceeded). The\n * SonentaI18n instance caches its snapshot internally — see\n * `_notify` / `_buildSnapshot`. */\nexport function useI18nSnapshot(): I18nInstance {\n const i18n = useI18n();\n return useSyncExternalStore(i18n.subscribe, i18n.getSnapshot, i18n.getSnapshot);\n}\n","import {\n createInstance,\n type FormatFunction,\n type i18n as I18nextInstance,\n type InterpolationOptions,\n} from \"i18next\";\nimport { initReactI18next } from \"react-i18next\";\nimport { LanguageCatalog, loadCatalog, localeChain } from \"./catalog\";\nimport { detectKeySeparator } from \"./engine\";\nimport { MissingKeyManager } from \"./missing\";\nimport { flattenPlurals } from \"./plurals\";\nimport { defaultTransport, logTransport } from \"./transport\";\nimport type {\n AssetRef,\n I18nInstance,\n LanguageMeta,\n Locale,\n MissingKeyEvent,\n Namespace,\n Transport,\n SonentaConfig,\n} from \"./types\";\nimport type { Surface } from \"./surface\";\nimport { keyRegistry } from \"./key-registry\";\n\nconst DEFAULT_API_BASE = \"https://api.sonenta.com\";\nconst DEFAULT_CDN_BASE = \"https://cdn.sonenta.com\";\nconst DEFAULT_FLUSH_MS = 5_000;\nconst DEFAULT_BATCH = 50;\nconst DEFAULT_BUFFER = 200;\nconst DEFAULT_VERSION_SLUG = \"main\";\n\ntype Bundle = Record<string, unknown>;\ntype Listener = () => void;\n\n/** Normalize `fallbackLng` (single | ordered chain | unset) to an array. */\nfunction asArray(fb: Locale | Locale[] | undefined): Locale[] {\n if (fb == null) return [];\n return (Array.isArray(fb) ? fb : [fb]).filter(Boolean);\n}\n\n/**\n * `@sonenta/react-i18next`'s public store — now a thin adapter over a real\n * `i18next` engine (#805, Option A). The class keeps its exact legacy surface\n * and behavior (the test suite is the contract), but delegates key resolution,\n * plurals, interpolation, context, and the variant/fallback chain to i18next.\n *\n * Architecture (why it's wired this way):\n * - The i18next instance is `init()`-ed SYNCHRONOUSLY in the constructor with\n * `initImmediate: false` and the (plural-flattened) `initialBundles` as\n * `resources`, and NO backend module. So `t()` + `ready` work offline,\n * pre-`start()` — matching the legacy sync constructor — and i18next never\n * fires a network load on its own (the env-routing tests pin that bundle\n * fetches happen in `start(fetchImpl)`, with the passed fetch, at the exact\n * legacy URLs/auth/cache semantics).\n * - `start()` / `setLocale()` / `reload()` perform the bundle fetches\n * themselves (ported `_loadBundle`: prod CDN, dev runtime, 404→{} sentinel,\n * cache-bust, offline last-known-good) and `addResourceBundle()` the result\n * into i18next, then `changeLanguage()` so i18next's language chain\n * (variant→base→fallbackLng) stays correct.\n * - The adapter owns its own `ready` / `locale` / listeners / snapshot so\n * `getSnapshot()` returns a STABLE reference between state changes (the P0\n * useSyncExternalStore contract) instead of riding i18next events.\n */\nexport class SonentaI18n implements I18nInstance {\n ready = false;\n locale: Locale;\n fallbackLng: Locale | Locale[] | undefined;\n missingEvents: MissingKeyEvent[] = [];\n\n // The underlying real i18next instance (engine). Resolution/plurals/\n // interpolation/context all run through this; the adapter only orchestrates\n // loading + the missing-key gate + the React subscription.\n private _i18next: I18nextInstance;\n // Original (native) i18next.changeLanguage, captured before we override the\n // instance's changeLanguage to route through setLocale (#806 bug3). Used by\n // _syncLanguage to switch the language WITHOUT re-entering our loader.\n private _origChangeLanguage!: I18nextInstance[\"changeLanguage\"];\n // Token identifying THIS i18n instance in the on-screen key registry\n // (#806 SeedSower instance-level producer — see _wrapInstanceT). Dropped\n // in stop() so accumulated keys don't outlive a remounted provider.\n private _registryToken = Symbol(\"sonenta.instance\");\n\n // Tracks which (version, locale, ns) we've actually fetched, and which came\n // back with ≥1 top-level key. The missing-key gate needs `_hasContent`\n // (200-with-content) to avoid the boot-flood when a namespace is unpublished\n // or a fetch came back empty. i18next's own resource store can't carry this\n // distinction (an empty {} bundle still `hasResourceBundle`).\n private _attempted = new Set<string>();\n private _hasContent = new Set<string>();\n\n // Language catalog (#803): dir()/nativeName()/languageMeta(). Primed from\n // config.languageCatalog and/or the public GET /v1/languages in start().\n private _catalog = new LanguageCatalog();\n private _catalogDisabled = false;\n\n private _config: Required<\n Pick<SonentaConfig, \"apiBase\" | \"cdnBase\" | \"missingHandler\">\n > & {\n token: string;\n projectUuid: string;\n namespaces: string[];\n flushIntervalMs: number;\n flushBatchSize: number;\n missingEventsBufferSize: number;\n version: string;\n env: \"prod\" | \"dev\";\n };\n\n // Surface variant (#911). When set, each loaded (locale,ns) is composed as\n // base ⊕ sparse `{ns}.{surface}.json` overlay (overlay wins per key). We\n // cache the raw base tree + raw overlay trees so a surface switch recomposes\n // WITHOUT refetching the base, and so a switch cleanly rebuilds base-then-\n // overlay (no stale overlay keys). `undefined` ⇒ surface resolution off.\n private _surface: Surface | undefined;\n private _baseTree = new Map<string, Bundle>(); // bundleKey -> raw base JSON\n private _overlayTree = new Map<string, Bundle>(); // `${bundleKey}#${surface}` -> raw overlay JSON ({} sentinel on miss)\n // Resolved asset refs (#911 minimal v1), keyed `${locale}/${ns}/${keyPath}`\n // for the CURRENT composition (base assets, then overlay assets override).\n private _assets = new Map<string, AssetRef>();\n\n private _missing: MissingKeyManager;\n private _listeners = new Set<Listener>();\n // Stable snapshot reference for useSyncExternalStore. Rebuilt ONLY in _notify\n // (when state actually changed) and returned as-is between notifications.\n private _snapshot!: I18nInstance;\n\n // Effective key separator (#754): false = flat (literal lookups, so dotted\n // keys like \"App Version 6.3.8\" work), a string = nested. Set from\n // config.keySeparator (explicit override, wins) or auto-detected from the\n // version's key_style/key_separator on start(). Defaults to \".\".\n private _keySeparatorExplicit = false;\n // Namespace separator (#754): false = no ns prefix parsing. Default ':'.\n private _nsSeparator: string | false = \":\";\n // Developer-supplied {{value, format}} formatter (#805-1), re-bound onto the\n // live interpolator after init (i18next overwrites it during init).\n private _userFormat?: SonentaConfig[\"interpolation\"] extends infer T\n ? T extends { format?: infer F }\n ? F\n : undefined\n : undefined;\n\n constructor(config: SonentaConfig) {\n // Fail-loud: realtime config moved out of core in 0.9.0. If a caller still\n // passes `liveUpdates` or ANY `centrifugo*` key, throw with a clear,\n // actionable migration message rather than silently no-op'ing.\n const removedRealtimeKeys = Object.keys(config).filter(\n (k) => k === \"liveUpdates\" || k.startsWith(\"centrifugo\"),\n );\n if (removedRealtimeKeys.length > 0) {\n throw new Error(\n `@sonenta/react-i18next: ${removedRealtimeKeys.join(\", \")} ${\n removedRealtimeKeys.length > 1 ? \"were\" : \"was\"\n } removed in 0.9.0 — realtime is now the @sonenta/realtime plugin. ` +\n \"Remove them and pass `plugins: [sonentaRealtime({ wsUrl })]` to <SonentaProvider> instead.\",\n );\n }\n\n this.locale = config.defaultLocale;\n this.fallbackLng = config.fallbackLng;\n this._surface = config.surface;\n\n // keySeparator (#754): explicit override wins verbatim and skips the\n // version-metadata lookup; otherwise default \".\" and auto-detect in start().\n let keySeparator: string | false = \".\";\n if (config.keySeparator !== undefined) {\n keySeparator = config.keySeparator;\n this._keySeparatorExplicit = true;\n }\n if (config.nsSeparator !== undefined) this._nsSeparator = config.nsSeparator;\n\n this._config = {\n apiBase: config.apiBase ?? DEFAULT_API_BASE,\n cdnBase: config.cdnBase ?? DEFAULT_CDN_BASE,\n missingHandler: config.missingHandler ?? \"send\",\n token: config.token,\n projectUuid: config.projectUuid,\n namespaces: config.namespaces?.length\n ? config.namespaces\n : config.defaultNS\n ? [config.defaultNS]\n : [\"common\"],\n flushIntervalMs: config.flushIntervalMs ?? DEFAULT_FLUSH_MS,\n flushBatchSize: config.flushBatchSize ?? DEFAULT_BATCH,\n missingEventsBufferSize: config.missingEventsBufferSize ?? DEFAULT_BUFFER,\n version: config.version ?? config.versionSlug ?? DEFAULT_VERSION_SLUG,\n env: config.env ?? \"prod\",\n };\n\n // Missing-key streaming (#805 Stage 2) — same transport selection + flush\n // defaults as the legacy engine.\n const transport: Transport =\n config.transport ??\n (this._config.missingHandler === \"log\"\n ? logTransport\n : defaultTransport({\n apiBase: this._config.apiBase,\n token: this._config.token,\n projectUuid: this._config.projectUuid,\n }));\n this._missing = new MissingKeyManager({\n transport,\n missingHandler: this._config.missingHandler,\n flushIntervalMs: this._config.flushIntervalMs,\n flushBatchSize: this._config.flushBatchSize,\n bufferSize: this._config.missingEventsBufferSize,\n });\n\n this._userFormat = config.interpolation?.format as typeof this._userFormat;\n\n // Embedded build-time snapshot (#757): prime i18next's `resources`\n // synchronously so the first render is instant + works OFFLINE before the\n // first fetch. initialBundles share i18next's resources shape but BYPASS\n // the backend, so the nested→suffixed plural transform must be applied here\n // too (else nested CLDR plurals never resolve). We also mirror the snapshot\n // into _hasContent (but NOT _attempted) so the missing-key gate stays\n // closed until a real fetch confirms.\n const resources: Record<string, Record<string, Bundle>> = {};\n if (config.initialBundles) {\n for (const [loc, byNs] of Object.entries(config.initialBundles)) {\n for (const [ns, tree] of Object.entries(byNs)) {\n if (!tree || typeof tree !== \"object\") continue;\n (resources[loc] ??= {})[ns] = flattenPlurals(tree as Bundle, loc);\n // Prime the raw base cache too (#911) so a surface recompose before\n // the first fetch composes against the snapshot instead of wiping it.\n this._baseTree.set(this._bundleKey(loc, ns), tree as Bundle);\n if (Object.keys(tree).length > 0) {\n this._hasContent.add(this._bundleKey(loc, ns));\n }\n }\n }\n }\n\n // escapeValue stays false (React escapes); `format` is the optional\n // developer value formatter for `{{value, format}}` (#805-1). i18next v26's\n // InterpolationOptions type omits `format` (it lives on FormatFunction) —\n // the intersection types it without a cast.\n const interpolation: InterpolationOptions & { format?: FormatFunction } = {\n escapeValue: false,\n format: this._userFormat as FormatFunction | undefined,\n };\n\n const ns = this._config.namespaces;\n this._i18next = createInstance();\n this._i18next.use(initReactI18next);\n // SYNC init (initAsync: false — i18next v26's rename of initImmediate), NO\n // backend module → i18next never kicks off a network load. So `t()` +\n // `ready` work offline pre-`start()`, matching the legacy sync constructor.\n // partialBundledLanguages lets dynamically-added bundles coexist with the\n // snapshot resources.\n void this._i18next.init({\n lng: this.locale,\n fallbackLng: config.fallbackLng ?? false,\n ns,\n defaultNS: ns[0],\n fallbackNS: false,\n initAsync: false,\n keySeparator,\n nsSeparator: this._nsSeparator,\n // #806 bug2: re-render react-i18next-native consumers when our async\n // loads addResourceBundle (bindI18nStore catches 'added'/'removed'). The\n // missing-key park is addResource SILENT, so it never emits 'added' →\n // no re-render loop (the rc.1 forceStoreRerender-on-saveMissing regression).\n react: {\n bindI18n: \"languageChanged loaded\",\n bindI18nStore: \"added removed\",\n useSuspense: false,\n },\n resources,\n partialBundledLanguages: true,\n // A nested-object (non-plural) key resolves to an object; legacy `t()`\n // returns the raw key in that case, so map it back to the key here.\n returnedObjectHandler: (key) => key,\n // GATE (legacy three-condition gate): only report once the active\n // locale's bundle exists AND has ≥1 key, AND we've flipped `ready` after a\n // real fetch (`_attempted` + `_hasContent`). i18next fires the handler\n // even for empty/absent bundles, so the filtering lives here.\n saveMissing: this._config.missingHandler !== \"off\",\n saveMissingTo: \"all\",\n interpolation,\n missingKeyHandler: this._handleMissing,\n });\n\n // #806 bug3: a drop-in `i18next.changeLanguage(lng)` (what react-i18next's\n // own useTranslation calls) must lazy-load the target language's bundles +\n // re-render — but our instance has NO backend. Route it through setLocale\n // (fetch + addResourceBundle + missing-gate + variant/fallback chain +\n // _notify), preserving the language feature. Keep the ORIGINAL bound for\n // internal _syncLanguage use to avoid recursion.\n this._origChangeLanguage = this._i18next.changeLanguage.bind(this._i18next);\n this._i18next.changeLanguage = ((lng?: string, ...rest: unknown[]) => {\n if (typeof lng !== \"string\" || lng === this.locale) {\n return (\n this._origChangeLanguage as (...a: unknown[]) => Promise<unknown>\n )(lng, ...rest);\n }\n return this.setLocale(lng).then(() =>\n this._i18next.getFixedT(this.locale, null),\n );\n }) as typeof this._i18next.changeLanguage;\n\n // #806 SeedSower P1 — instance-level on-screen registry producer.\n // The 1.0.2 thin-wrapper contract lets host apps keep their\n // `from 'react-i18next'` imports; those calls resolve through the\n // shared i18next instance but never touch our hook-level `_set`\n // tracker, so a feedback widget on a host like that sees an empty\n // registry (\"no strings on this view\"). Wrap `i18next.t` so EVERY\n // resolved key — native useTranslation/Trans via getFixedT (which\n // calls `this.t`, see i18next v26 source ~line 2060), direct\n // `i18n.t()`, and our own adapter `t` that delegates here — funnels\n // into `keyRegistry._track(this._registryToken, …)`. The wrap is\n // best-effort + non-throwing: a registry bookkeeping error must\n // NEVER break translation.\n this._wrapInstanceT();\n\n // #805-1: i18next v26 ALWAYS overwrites `interpolation.format` with its\n // built-in Formatter during init. Re-bind the live interpolator's `format`\n // to the developer fn AFTER init so `{{value, format}}` honors it, falling\n // back to the built-in Intl formatter when the dev fn passes through.\n this._rebindFormat();\n\n // Language catalog (#803): prime from an embedded catalog (offline/SSR/RN)\n // so dir()/nativeName() work before/without the network fetch.\n this._catalogDisabled = config.disableLanguageCatalog === true;\n this._catalog.merge(config.languageCatalog);\n\n // ready=true synchronously only when the snapshot fully covers the active\n // locale's configured namespaces; otherwise wait for the fetch to flip it.\n const active = config.initialBundles?.[this.locale];\n if (\n active &&\n this._config.namespaces.every(\n (n) => active[n] && Object.keys(active[n]!).length > 0,\n )\n ) {\n this.ready = true;\n }\n\n this._snapshot = this._buildSnapshot();\n }\n\n /** Re-bind the developer `{{value, format}}` formatter onto the live\n * interpolator (i18next overwrites `interpolation.format` during init). */\n private _rebindFormat(): void {\n const userFormat = this._userFormat as FormatFunction | undefined;\n if (!userFormat) return;\n const interpolator = (\n this._i18next as unknown as {\n services?: { interpolator?: { format?: FormatFunction } };\n }\n ).services?.interpolator;\n if (!interpolator) return;\n const builtIn = (\n this._i18next.options.interpolation as { format?: FormatFunction } | undefined\n )?.format;\n interpolator.format = ((value, format, lng, opts) => {\n const out = userFormat(\n value,\n format,\n lng,\n (opts ?? {}) as Record<string, unknown>,\n );\n if (out === (value as unknown) && builtIn) {\n return builtIn(value, format, lng, opts);\n }\n return out;\n }) as FormatFunction;\n }\n\n /** Monkey-patch `i18next.t` so every resolved key feeds the on-screen\n * registry (#806). The override is an OWN property on the instance,\n * shadowing the prototype `t()` method; getFixedT-returned t functions\n * look up `this.t` dynamically (i18next v26 source ~line 2060), so\n * native `react-i18next` `useTranslation` / `<Trans>` go through it\n * too. Bookkeeping is wrapped in try/catch — a registry failure must\n * never break translation. */\n private _wrapInstanceT(): void {\n const i18n = this._i18next;\n const orig = i18n.t.bind(i18n);\n const token = this._registryToken;\n const defaultNs = (): string => this.defaultNamespace;\n const wrapped = ((...args: unknown[]) => {\n const result = (orig as unknown as (...a: unknown[]) => unknown)(...args);\n try {\n const rawKey = args[0];\n const opts =\n args[1] && typeof args[1] === \"object\" && !Array.isArray(args[1])\n ? (args[1] as { ns?: string | string[] })\n : undefined;\n // i18next.t accepts (key | key[] | selectorFn). Record the FIRST\n // string key — selector functions and non-string arrays are skipped\n // (not the SeedSower drop-in path, not worth the encoding risk).\n let keyStr: string | undefined;\n if (typeof rawKey === \"string\") {\n keyStr = rawKey;\n } else if (Array.isArray(rawKey) && typeof rawKey[0] === \"string\") {\n keyStr = rawKey[0];\n }\n if (keyStr) {\n const optsNs = opts?.ns;\n const ns =\n typeof optsNs === \"string\"\n ? optsNs\n : Array.isArray(optsNs)\n ? optsNs[0]\n : undefined;\n // If the call specified a namespace via options and the key\n // doesn't already carry one inline, compose `ns:key` so the\n // registry encoding matches the hook-level path.\n const fullKey =\n ns && !keyStr.includes(\":\") ? `${ns}:${keyStr}` : keyStr;\n keyRegistry._track(token, keyRegistry.encode(fullKey, defaultNs()));\n }\n } catch {\n // registry bookkeeping must never break t().\n }\n return result;\n }) as unknown as typeof i18n.t;\n i18n.t = wrapped;\n }\n\n // ---- React subscription ----\n\n subscribe = (listener: Listener): (() => void) => {\n this._listeners.add(listener);\n return () => this._listeners.delete(listener) as unknown as void;\n };\n\n /** Stable snapshot accessor for useSyncExternalStore. The returned object\n * reference is identical between renders unless _notify fired. */\n getSnapshot = (): I18nInstance => this._snapshot;\n\n private _buildSnapshot(): I18nInstance {\n return {\n ready: this.ready,\n locale: this.locale,\n language: this.locale,\n setLocale: this.setLocale,\n changeLanguage: this.changeLanguage,\n t: this.t,\n missingEvents: this.missingEvents,\n flushMissing: this.flushMissing,\n reload: this.reload,\n refresh: this.refresh,\n surface: this._surface,\n setSurface: this.setSurface,\n asset: this.asset,\n dir: this.dir,\n nativeName: this.nativeName,\n languageMeta: this.languageMeta,\n i18next: this._i18next,\n };\n }\n\n /** Bundle cache-key builder. Includes `version` so providers with different\n * `version` values never share state. Segments are slugs (no '/'). */\n private _bundleKey(locale: Locale, ns: Namespace): string {\n return `${this._config.version}/${locale}/${ns}`;\n }\n\n private _notify(): void {\n this._snapshot = this._buildSnapshot();\n for (const l of this._listeners) l();\n }\n\n /** Force react-i18next-NATIVE consumers (raw `useTranslation`/`<Trans>` on the\n * exposed instance) to re-render in the READY state, by emitting i18next's\n * `loaded` event (which react-i18next binds via `bindI18n`). Called AFTER\n * `ready=true`, so a missing key they render is re-evaluated once the gate is\n * open → it streams to `/v1/missing` (#806 native path; our own\n * `useTranslation` already re-renders via `_notify`). The missing-key park is\n * a SILENT addResource, so this never reopens the render loop (rc.1). */\n private _signalLoaded(): void {\n (\n this._i18next as unknown as { emit: (e: string, ...a: unknown[]) => void }\n ).emit(\"loaded\", {});\n }\n\n // ---- Lifecycle ----\n\n /** Default namespace (the first configured one) — used to attribute a bare\n * `t(\"key\")` call when recording on-screen keys. */\n get defaultNamespace(): string {\n return this._config.namespaces[0]!;\n }\n\n /**\n * Ordered lookup chain for an active locale: the locale itself, its\n * base-language truncations (`fr-CA → fr`), then the configured\n * `fallbackLng`(s) — deduped, order-preserving (the `fr-CA → fr → source`\n * chain, #803). Used to drive bundle loading; i18next handles resolution\n * fallback itself via its `languages` list.\n */\n private _resolutionChain(locale: Locale): Locale[] {\n const out: Locale[] = [];\n const seen = new Set<string>();\n for (const l of [...localeChain(locale), ...asArray(this.fallbackLng)]) {\n if (l && !seen.has(l)) {\n seen.add(l);\n out.push(l);\n }\n }\n return out;\n }\n\n /** Loads the configured namespaces for the active locale's full resolution\n * chain (locale → base → fallback), wires up the key separator + catalog,\n * flips `ready`, and arms the missing-key flush loop. */\n async start(fetchImpl: typeof fetch = fetch): Promise<void> {\n // Publish the on-screen key registry so a mounted feedback widget lists\n // only the strings rendered on the current view (spec ltm 373).\n keyRegistry.attach();\n const targets = new Set<string>(this._resolutionChain(this.locale));\n await Promise.all([\n ...[...targets].flatMap((loc) =>\n this._config.namespaces.map((ns) =>\n this._loadBundle(loc, ns, fetchImpl),\n ),\n ),\n // Best-effort: align the key separator with the version's key_style (#754).\n this._loadKeyStyle(fetchImpl),\n // Best-effort: load the public language catalog for dir()/nativeName().\n this._loadCatalog(fetchImpl),\n ]);\n // Keep i18next's language chain in step with the active locale + fallbacks\n // so its native resolution walks variant→base→fallback correctly.\n await this._syncLanguage();\n this.ready = true;\n this._missing.start();\n this._notify();\n this._signalLoaded();\n }\n\n /** Best-effort: read the version's `key_style` / `key_separator` (#754) so the\n * SDK resolves keys the way the project's bundles are shaped. Skipped when\n * the dev set `keySeparator` explicitly; on 403/404/offline keep the default. */\n private async _loadKeyStyle(fetchImpl: typeof fetch): Promise<void> {\n if (this._keySeparatorExplicit) return;\n const sep = await detectKeySeparator(\n this._config.apiBase,\n this._config.projectUuid,\n this._config.version,\n this._config.token,\n fetchImpl,\n );\n // Apply to the live i18next instance — its translator reads\n // `options.keySeparator` on each call, so a post-init flip takes effect.\n this._i18next.options.keySeparator = sep;\n }\n\n /** Best-effort: fetch the PUBLIC language catalog and merge it in (#803).\n * Skipped when disabled; a failure keeps any embedded catalog. */\n private async _loadCatalog(fetchImpl: typeof fetch): Promise<void> {\n if (this._catalogDisabled) return;\n this._catalog.merge(await loadCatalog(this._config.apiBase, fetchImpl));\n }\n\n /** Re-derive i18next's active language chain from the adapter's locale +\n * fallbackLng. `changeLanguage` recomputes `instance.languages`\n * (variant→base→fallback) so native resolution fallback is correct. */\n private async _syncLanguage(): Promise<void> {\n if (this._i18next.language !== this.locale) {\n // Use the ORIGINAL changeLanguage — our override (#806) routes through\n // setLocale, which calls this; the override here would recurse.\n await this._origChangeLanguage(this.locale);\n }\n }\n\n setLocale = async (next: Locale): Promise<void> => {\n if (next === this.locale) return;\n this.locale = next;\n this.ready = false;\n this._notify();\n // Load the new locale's full chain (locale → base → fallback), skipping any\n // (locale, ns) already fetched (e.g. a fallback loaded at start()).\n await Promise.all(\n this._resolutionChain(next).flatMap((loc) =>\n this._config.namespaces\n .filter((ns) => !this._attempted.has(this._bundleKey(loc, ns)))\n .map((ns) => this._loadBundle(loc, ns)),\n ),\n );\n await this._syncLanguage();\n this.ready = true;\n this._notify();\n this._signalLoaded();\n };\n\n /** Alias of {@link setLocale} for react-i18next compatibility. */\n changeLanguage = (next: Locale): Promise<void> => this.setLocale(next);\n\n /** Alias of {@link locale} for react-i18next compatibility. */\n get language(): Locale {\n return this.locale;\n }\n\n /** The underlying real `i18next` instance (#806 bug1 / #805 drop-in). Exposed\n * on the class too (not just the snapshot) so `getI18n().i18next` works for\n * react-i18next drop-in (`<Trans i18n={…}>`, `useTranslation(ns, { i18n })`). */\n get i18next(): I18nextInstance {\n return this._i18next;\n }\n\n /** Text direction for a locale (default: active locale) — i18next parity.\n * Catalog `rtl` is authoritative (variant→base); falls back to the built-in\n * RTL list before/without the catalog (#803). */\n dir = (lng?: Locale): \"ltr\" | \"rtl\" =>\n this._catalog.dir(lng ?? this.locale);\n\n /** Endonym (native name) for a locale from the catalog (default: active) —\n * the fallback for runtimes without `Intl.DisplayNames` (#803). */\n nativeName = (lng?: Locale): string | undefined =>\n this._catalog.nativeName(lng ?? this.locale);\n\n /** Full language-catalog entry for a locale (default: active); `undefined`\n * when the catalog has no matching entry. */\n languageMeta = (lng?: Locale): LanguageMeta | undefined =>\n this._catalog.languageMeta(lng ?? this.locale);\n\n stop(): void {\n // Drop THIS instance's accumulated keys explicitly so a mid-life\n // provider unmount (when another provider is still keeping the\n // global published) doesn't leak this instance's contribution.\n keyRegistry._delete(this._registryToken);\n keyRegistry.detach();\n this._missing.stop();\n }\n\n /**\n * #806 plugin-context backing for `SonentaPluginContext.onLanguageChange`.\n * Subscribes to i18next's `languageChanged` emitter and returns the\n * unsubscribe function. Wraps the i18next event so plugins never have\n * to touch the (prefix-private) `_i18next` field directly. Fires on\n * every successful language change — `setLocale()` /\n * `changeLanguage()` / the drop-in `i18next.changeLanguage()` (whose\n * #806-bug3 override routes through `setLocale` which then runs the\n * native `_origChangeLanguage` that actually fires the event).\n */\n onLanguageChange = (cb: (lng: Locale) => void): (() => void) => {\n const handler = (lng: string): void => cb(lng);\n this._i18next.on(\"languageChanged\", handler);\n return () => {\n // i18next types ship `.off` as a public EventEmitter method; cast\n // through unknown to side-step a minor d.ts gap in @types/i18next\n // for the handler signature (it accepts `(...args: unknown[])`).\n (\n this._i18next as unknown as {\n off: (e: string, h: (lng: string) => void) => void;\n }\n ).off(\"languageChanged\", handler);\n };\n };\n\n /**\n * Bust-refetch already-loaded bundles and re-render once. Iterate the\n * `_attempted` cache keys (`${version}/${locale}/${ns}`), optionally filtered\n * by `opts.locale` / `opts.namespace`, and re-pull each with `{ bust: true }`\n * (so the mutable CDN `latest/` alias bypasses the HTTP cache). After all\n * settle, `_notify()` once so React re-renders. Used by `@sonenta/realtime`\n * on a `translations_published` push and as a manual refresh hook.\n */\n reload = async (\n opts: { locale?: Locale; namespace?: Namespace } = {},\n ): Promise<void> => {\n const targets: Array<{ locale: Locale; ns: Namespace }> = [];\n for (const key of this._attempted) {\n // `${version}/${locale}/${ns}` — none of the segments contain '/'.\n const parts = key.split(\"/\");\n const locale = parts[1];\n const ns = parts[2];\n if (!locale || !ns) continue;\n if (opts.locale && opts.locale !== locale) continue;\n if (opts.namespace && opts.namespace !== ns) continue;\n targets.push({ locale, ns });\n }\n if (targets.length === 0) return;\n await Promise.all(\n targets.map((t) =>\n this._loadBundle(t.locale, t.ns, fetch, { bust: true }),\n ),\n );\n this._notify();\n };\n\n /**\n * Force every `useTranslation` consumer to re-render WITHOUT refetching —\n * re-resolves `t()` against the CURRENT resources. For plugins that mutate\n * i18next resources directly (e.g. `@sonenta/in-context` applies an edit\n * via `i18next.addResource`) and need the snapshot to repaint right away.\n * Repaints BOTH this SDK's `useTranslation`/`<Trans>` (via the store\n * `_notify`) AND react-i18next-NATIVE consumers bound to the exposed\n * instance (via the `loaded` event). Does NO bundle fetch, so an in-place\n * override is never clobbered — unlike {@link reload}, which bust-refetches\n * the CDN bundles. Additive (1.0.6); safe to call any time after mount.\n */\n refresh = (): void => {\n this._notify();\n this._signalLoaded();\n };\n\n get surface(): Surface | undefined {\n return this._surface;\n }\n\n /** Switch the active surface (#911) and recompose every loaded (locale, ns)\n * as base ⊕ the new surface's overlay, then re-render. `undefined` drops to\n * base-only. No-op when unchanged. Overlays are fetched on demand + cached. */\n setSurface = async (surface: Surface | undefined): Promise<void> => {\n if (surface === this._surface) return;\n this._surface = surface;\n const targets: Array<{ locale: Locale; ns: Namespace }> = [];\n for (const key of this._attempted) {\n // `${version}/${locale}/${ns}` — none of the segments contain '/'.\n const parts = key.split(\"/\");\n const locale = parts[1];\n const ns = parts[2];\n if (locale && ns) targets.push({ locale, ns });\n }\n await Promise.all(\n targets.map((t) => this._composeBundle(t.locale, t.ns)),\n );\n this._notify();\n this._signalLoaded();\n };\n\n /** Asset variant ref (#911) for `key` under the active locale+surface, or\n * `undefined`. Walks the locale fallback chain like `t()` would. */\n asset = (key: string, namespace?: Namespace): AssetRef | undefined => {\n let ns = namespace ?? this.defaultNamespace;\n let bareKey = key;\n if (typeof this._nsSeparator === \"string\" && this._nsSeparator) {\n const idx = key.indexOf(this._nsSeparator);\n if (idx > 0) {\n ns = key.slice(0, idx);\n bareKey = key.slice(idx + this._nsSeparator.length);\n }\n }\n for (const loc of this._resolutionChain(this.locale)) {\n const ref = this._assets.get(`${loc}/${ns}/${bareKey}`);\n if (ref) return ref;\n }\n return undefined;\n };\n\n // ---- Translation ----\n\n t = (\n key: string,\n optionsOrDefault?:\n | (Record<string, unknown> & { defaultValue?: string; count?: number })\n | string,\n maybeOptions?: Record<string, unknown> & {\n defaultValue?: string;\n count?: number;\n },\n ): string => {\n // react-i18next-style positional fallback: a string 2nd arg is the default\n // value; an optional 3rd arg carries interpolation/options merged under it.\n const options:\n | (Record<string, unknown> & { defaultValue?: string; count?: number })\n | undefined =\n typeof optionsOrDefault === \"string\"\n ? { ...(maybeOptions ?? {}), defaultValue: optionsOrDefault }\n : optionsOrDefault;\n\n // Literal-first resolution (#754): an EXACT literal wins so a dotted-literal\n // key (e.g. \"App Version 6.3.8\") resolves even in nested mode. We probe the\n // literal ourselves (rather than via sonentaResolveKey) ONLY so we can also\n // INTERPOLATE the hit — legacy `_render` interpolated literal hits (a flat\n // key \"greet\" → \"Hello {{name}}\" must fill {{name}}). On a miss we defer to\n // i18next's native t(), which handles nested/plural/context/interpolation +\n // the variant→base→fallback chain.\n const literal = this._probeLiteral(key);\n if (literal !== undefined) {\n const interpolator = (\n this._i18next as unknown as {\n services?: {\n interpolator?: {\n interpolate: (\n s: string,\n d: Record<string, unknown>,\n lng: string,\n opts: Record<string, unknown>,\n ) => string;\n };\n };\n }\n ).services?.interpolator;\n if (interpolator && options) {\n return interpolator.interpolate(\n literal,\n options as Record<string, unknown>,\n this.locale,\n {},\n );\n }\n return literal;\n }\n return this._i18next.t(\n key,\n options as Record<string, unknown> | undefined,\n ) as string;\n };\n\n /** Probe the EXACT literal value for a key across the active language chain\n * (no key split), honoring the configured nsSeparator to point at the right\n * (lng, ns). Returns the string hit, or `undefined` to fall through to the\n * native nested/plural resolution. Mirrors `sonentaResolveKey`'s probe but\n * is local so the adapter can interpolate the result. */\n private _probeLiteral(key: string): string | undefined {\n const nsSeparator = this._i18next.options.nsSeparator;\n let probeNs = this.defaultNamespace;\n let probeKey = key;\n if (typeof nsSeparator === \"string\" && nsSeparator !== \"\") {\n const idx = key.indexOf(nsSeparator);\n if (idx > 0) {\n probeNs = key.slice(0, idx);\n probeKey = key.slice(idx + nsSeparator.length);\n }\n }\n const langs = this._i18next.languages ?? [this._i18next.language].filter(Boolean);\n for (const lng of langs) {\n if (!lng) continue;\n const v = this._i18next.getResource(lng, probeNs, probeKey, {\n keySeparator: false,\n });\n if (typeof v === \"string\") return v;\n }\n return undefined;\n }\n\n /**\n * i18next `missingKeyHandler`: the legacy three-condition gate + #746\n * source_value rule. Only report once we've fetched the active locale's\n * bundle (`ready` + `_attempted` + `_hasContent`), so the first paint /\n * an unpublished namespace / a 404→{} / 200→{} never floods the dashboard.\n * `fallbackValue` is ignored — when no defaultValue was passed it === key,\n * which must NEVER become a source_value.\n */\n private _handleMissing = (\n lngs: readonly string[],\n ns: string,\n key: string,\n _fallbackValue: string,\n _updateMissing: boolean,\n options: { defaultValue?: unknown } | undefined,\n ): void => {\n void _fallbackValue;\n void _updateMissing;\n const lng = lngs[0] ?? this.locale;\n // GATE: report only after a real fetch confirmed content for the active\n // locale's bundle. (Pre-start, or an empty/404 bundle, stays silent.)\n const cacheKey = this._bundleKey(this.locale, ns);\n if (\n !this.ready ||\n !this._attempted.has(cacheKey) ||\n !this._hasContent.has(cacheKey)\n ) {\n return;\n }\n const recorded = this._missing.record({\n key,\n namespace: ns,\n language_code: lng,\n source_value: this._sourceValueFor(key, ns, options),\n });\n // Re-render ONLY on a NEWLY recorded event. A deduped report (the same\n // missing key re-encountered on the next render) must NOT notify — an\n // unconditional notify re-renders the store, which re-runs `t()`, which\n // re-fires this handler → infinite loop (\"Maximum update depth\"; fatal in\n // React Native via react-i18next@17's forceStoreRerender on saveMissing).\n // The legacy engine dedup'd BEFORE notifying; this restores that.\n if (!recorded) return;\n // Park the reported key in the store so i18next stops treating it as\n // missing (and thus stops firing saveMissing for it on every render).\n // Belt-and-suspenders against the forceStoreRerender path; stored as a\n // literal (keySeparator:false) + silent (no 'added' event) so our\n // literal-first probe short-circuits subsequent renders. The value is what\n // we'd render anyway (explicit defaultValue, else the bare key).\n const rendered =\n typeof options?.defaultValue === \"string\" ? options.defaultValue : key;\n try {\n // keySeparator:false stores it as a flat literal (so our literal-first\n // probe finds it); silent:true suppresses the 'added' event so nothing\n // re-renders. i18next honors both at runtime; the cast is only because\n // its options type declares keySeparator as `string`.\n this._i18next.addResource(lng, ns, key, rendered, {\n keySeparator: false,\n silent: true,\n } as unknown as Parameters<typeof this._i18next.addResource>[4]);\n } catch {\n // best-effort — the dedup-gated notify above already breaks the loop.\n }\n this.missingEvents = this._missing.missingEvents;\n this._notify();\n };\n\n flushMissing = (): Promise<void> => this._missing.flush();\n\n // ---- Internals ----\n\n /**\n * Resolve the `source_value` for a missing-key report (Option A, #746):\n * 1. `options.defaultValue` — explicit developer-provided string.\n * 2. The configured fallbackLng bundle's plain-string value (the\n * source/canonical locale). Excludes the active locale + its derived\n * base (display fallbacks, not the promotable \"source\").\n * 3. Otherwise `undefined` — we never send the key name as a value (the key\n * already travels in `event.key`).\n */\n private _sourceValueFor(\n key: string,\n ns: string,\n options?: { defaultValue?: unknown },\n ): string | undefined {\n if (typeof options?.defaultValue === \"string\") {\n return options.defaultValue;\n }\n for (const loc of asArray(this.fallbackLng)) {\n if (loc === this.locale) continue;\n const v = this._i18next.getResource(loc, ns, key);\n if (typeof v === \"string\") return v;\n }\n return undefined;\n }\n\n private async _loadBundle(\n locale: Locale,\n ns: Namespace,\n fetchImpl: typeof fetch = fetch,\n opts: { bust?: boolean } = {},\n ): Promise<void> {\n const cacheKey = this._bundleKey(locale, ns);\n // env routing — prod hits the CDN cache; dev hits the live runtime endpoint\n // authenticated with the API key.\n let url: string;\n let init: RequestInit;\n if (this._config.env === \"dev\") {\n // #806 Hermes URLSearchParams polyfill safety (see backend.ts): RN\n // ships only the constructor + toString(). Build the param record\n // first, then construct ONCE; no .set after construction.\n const qp: Record<string, string> = { language: locale, namespace: ns };\n if (this._config.version && this._config.version !== \"main\") {\n qp.version_slug = this._config.version;\n }\n const params = new URLSearchParams(qp);\n url = `${this._config.apiBase.replace(/\\/+$/, \"\")}/v1/projects/${this._config.projectUuid}/translations/runtime?${params.toString()}`;\n init = {\n method: \"GET\",\n headers: { Authorization: `ApiKey ${this._config.token}` },\n credentials: \"omit\",\n };\n } else {\n url = `${this._config.cdnBase.replace(/\\/+$/, \"\")}/p/${this._config.projectUuid}/${this._config.version}/latest/${locale}/${ns}.json`;\n init = { method: \"GET\", credentials: \"omit\" };\n }\n // On a live-republish refetch, bypass the browser HTTP cache so the mutable\n // `latest/` alias is re-pulled even within its max-age window.\n if (opts.bust) {\n init.cache = \"reload\";\n }\n // A failed live refetch must NOT downgrade good translations to keys — keep\n // the last-known-good bundle. Only the initial (non-bust) load may cache an\n // empty object as the \"no bundle\" sentinel.\n const hadContent = this._hasContent.has(cacheKey);\n try {\n const r = await fetchImpl(url, init);\n if (r.ok) {\n const data = (await r.json()) as Bundle;\n if (data && typeof data === \"object\" && Object.keys(data).length > 0) {\n this._baseTree.set(cacheKey, data);\n this._hasContent.add(cacheKey);\n } else {\n // 200 → {} (fresh project, no published keys): clear content flag so\n // the missing-key gate stays closed.\n this._baseTree.set(cacheKey, {});\n this._hasContent.delete(cacheKey);\n }\n } else if (hadContent) {\n // Non-OK but we already have content (snapshot or prior fetch) — keep\n // last-known-good (offline/transient first loads #757, refetch blips).\n } else {\n // 404 = no published bundle yet. Cache empty so the gate suppresses\n // reportMissing in this state.\n this._baseTree.set(cacheKey, {});\n this._hasContent.delete(cacheKey);\n }\n } catch {\n if (hadContent) {\n // Network error but we have content (snapshot/prior fetch) — keep it.\n } else {\n this._baseTree.set(cacheKey, {});\n this._hasContent.delete(cacheKey);\n }\n } finally {\n this._attempted.add(cacheKey);\n }\n // Compose base ⊕ surface overlay into i18next (overlay fetched on demand).\n await this._composeBundle(locale, ns, fetchImpl, opts.bust);\n }\n\n /**\n * Compose the i18next bundle for (locale, ns) as base ⊕ surface overlay\n * (#911). Base is applied wholesale (replace), then — when a surface is\n * active — the sparse overlay is deep-merged on top (overlay wins per key).\n * Rebuilding base-first on every compose means a surface SWITCH cleanly\n * resets to base before applying the new overlay (no stale overlay keys).\n * Plurals: both base and overlay flatten through {@link flattenPlurals}, so\n * an overlay plural dict fully replaces the base key's plural set.\n */\n private async _composeBundle(\n locale: Locale,\n ns: Namespace,\n fetchImpl: typeof fetch = fetch,\n bust = false,\n ): Promise<void> {\n const base = this._baseTree.get(this._bundleKey(locale, ns)) ?? {};\n // Reset this (locale, ns)'s asset entries — recomputed from base+overlay.\n const assetPrefix = `${locale}/${ns}/`;\n for (const k of this._assets.keys()) {\n if (k.startsWith(assetPrefix)) this._assets.delete(k);\n }\n const baseTree = this._unwrapAssets(base, locale, ns);\n // deep=false, overwrite=true → replace the bundle wholesale with base.\n this._i18next.addResourceBundle(\n locale,\n ns,\n flattenPlurals(baseTree, locale),\n false,\n true,\n );\n if (!this._surface) return;\n const overlay = await this._loadOverlay(\n locale,\n ns,\n this._surface,\n fetchImpl,\n bust,\n );\n if (!overlay || Object.keys(overlay).length === 0) return;\n const overlayTree = this._unwrapAssets(overlay, locale, ns);\n // deep=true, overwrite=true → merge the sparse overlay on top (per-key win).\n this._i18next.addResourceBundle(\n locale,\n ns,\n flattenPlurals(overlayTree, locale),\n true,\n true,\n );\n }\n\n /** Fetch (and cache) the sparse surface overlay `{ns}.{surface}.json` from\n * the CDN. Cached per (locale, ns, surface); a `{}` is cached on miss so a\n * surface switch never re-hits the network for a known-absent overlay.\n * Overlays live on the CDN only (the dev runtime endpoint has no overlay\n * shape yet) — `env: \"dev\"` returns `{}`. */\n private async _loadOverlay(\n locale: Locale,\n ns: Namespace,\n surface: Surface,\n fetchImpl: typeof fetch = fetch,\n bust = false,\n ): Promise<Bundle> {\n const key = `${this._bundleKey(locale, ns)}#${surface}`;\n if (!bust && this._overlayTree.has(key)) return this._overlayTree.get(key)!;\n if (this._config.env === \"dev\") {\n this._overlayTree.set(key, {});\n return {};\n }\n const url = `${this._config.cdnBase.replace(/\\/+$/, \"\")}/p/${this._config.projectUuid}/${this._config.version}/latest/${locale}/${ns}.${surface}.json`;\n const init: RequestInit = { method: \"GET\", credentials: \"omit\" };\n if (bust) init.cache = \"reload\";\n let overlay: Bundle = {};\n try {\n const r = await fetchImpl(url, init);\n if (r.ok) {\n const data = (await r.json()) as Bundle;\n if (data && typeof data === \"object\") overlay = data;\n }\n // 404 / non-OK = no overlay for this surface → {} (base only).\n } catch {\n // Network error → {} (base only); never downgrade the base render.\n }\n this._overlayTree.set(key, overlay);\n return overlay;\n }\n\n /**\n * Strip `{ \"$value\", \"$asset\" }` asset envelopes (#911 minimal v1) out of a\n * raw bundle tree: each envelope is replaced by its `$value` (string or\n * plural dict) so `t()` resolves normally, and its `$asset` is recorded\n * under `${locale}/${ns}/${keyPath}` for {@link asset}. Returns a NEW tree;\n * the input is not mutated. Non-envelope nodes recurse as namespace groups.\n */\n private _unwrapAssets(tree: Bundle, locale: Locale, ns: Namespace): Bundle {\n const sep =\n typeof this._i18next.options.keySeparator === \"string\"\n ? this._i18next.options.keySeparator\n : \".\";\n const walk = (node: unknown, path: string[]): unknown => {\n if (!node || typeof node !== \"object\") return node;\n const obj = node as Record<string, unknown>;\n // Asset envelope: has own `$value`.\n if (Object.prototype.hasOwnProperty.call(obj, \"$value\")) {\n const a = obj.$asset as AssetRef | undefined;\n if (a && typeof a.kind === \"string\" && typeof a.ref === \"string\") {\n this._assets.set(`${locale}/${ns}/${path.join(sep)}`, {\n kind: a.kind,\n ref: a.ref,\n });\n }\n return obj.$value; // string | pluralDict — resolves as a normal leaf.\n }\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(obj)) {\n out[k] = walk(v, [...path, k]);\n }\n return out;\n };\n return walk(tree, []) as Bundle;\n }\n}\n","import type { LanguageMeta, Locale } from \"./types\";\n\n/**\n * Language catalog (#803) — the framework-agnostic half of the SDK's\n * direction/endonym support, ported out of the legacy `SonentaI18n` store so\n * the new thin-wrapper engine (#805) can expose `dir()` / `nativeName()` /\n * `languageMeta()` with identical semantics.\n *\n * Source of truth is the PUBLIC catalog (`GET {apiBase}/v1/languages`, no\n * auth, CDN-cached). Regional variants inherit `rtl`/`script` from their base\n * (`fr-CA` → `fr`); a built-in RTL-language list keeps direction correct\n * before/without the network fetch.\n */\n\n/**\n * Primary-subtag RTL fallback used by {@link LanguageCatalog.dir} BEFORE or\n * WITHOUT the language catalog (offline, catalog disabled, or pre-fetch). The\n * catalog's `rtl` is authoritative; this only covers the common RTL languages\n * so direction is still correct without a network round-trip. Mirrors the\n * legacy `RTL_LANGS` set verbatim.\n */\nexport const RTL_LANGS = new Set<string>([\n \"ar\", \"arc\", \"ckb\", \"dv\", \"fa\", \"ha\", \"he\", \"khw\", \"ks\", \"ku\",\n \"nqo\", \"ps\", \"sd\", \"ug\", \"ur\", \"yi\",\n]);\n\n/**\n * Progressive BCP-47 subtag truncation, native-i18next style:\n * \"zh-Hant-TW\" → [\"zh-Hant-TW\", \"zh-Hant\", \"zh\"]\n * \"fr-CA\" → [\"fr-CA\", \"fr\"]\n * \"fr\" → [\"fr\"]\n * Lets a regional variant fall back to its base language when its catalog\n * entry is missing — the `fr-CA → fr` step of variant→base inheritance (#803).\n */\nexport function localeChain(locale: Locale): Locale[] {\n const parts = locale.split(\"-\").filter(Boolean);\n if (parts.length <= 1) return [locale];\n const chain: Locale[] = [];\n for (let i = parts.length; i >= 1; i--) chain.push(parts.slice(0, i).join(\"-\"));\n return chain;\n}\n\n/**\n * Best-effort fetch of the PUBLIC language catalog\n * (`GET {apiBase}/v1/languages`, no auth, `credentials: 'omit'`, CDN-cached)\n * powering `dir()` / `nativeName()` (#803). Returns the parsed array on a 200\n * with an array body; returns `null` on any non-OK, throw, or non-array body —\n * NEVER throws to the caller, so a failed fetch keeps any embedded catalog.\n */\nexport async function loadCatalog(\n apiBase: string,\n fetchImpl: typeof fetch = fetch,\n): Promise<LanguageMeta[] | null> {\n try {\n const url = `${apiBase.replace(/\\/+$/, \"\")}/v1/languages`;\n const r = await fetchImpl(url, { method: \"GET\", credentials: \"omit\" });\n if (!r.ok) return null;\n const data = (await r.json()) as unknown;\n return Array.isArray(data) ? (data as LanguageMeta[]) : null;\n } catch {\n // best-effort — keep any embedded / last-known catalog\n return null;\n }\n}\n\n/**\n * A per-instance language catalog: a `Map<lower-cased BCP-47 code,\n * LanguageMeta>` plus the `dir`/`nativeName`/`languageMeta` lookups, matching\n * the legacy `SonentaI18n` semantics exactly. One of these rides alongside\n * each engine instance (see `engine.ts`).\n */\nexport class LanguageCatalog {\n private _catalog = new Map<string, LanguageMeta>();\n\n /** Merge catalog entries into the map, keyed by lower-cased BCP-47 code. */\n merge(items: LanguageMeta[] | null | undefined): void {\n if (!items) return;\n for (const item of items) {\n if (item && typeof item.code === \"string\") {\n this._catalog.set(item.code.toLowerCase(), item);\n }\n }\n }\n\n /** Catalog entry for a locale, walking variant→base (`fr-CA` → `fr`);\n * `undefined` when no entry in the chain matches. */\n metaFor(locale: Locale): LanguageMeta | undefined {\n for (const l of localeChain(locale)) {\n const hit = this._catalog.get(l.toLowerCase());\n if (hit) return hit;\n }\n return undefined;\n }\n\n /**\n * Text direction for a locale — i18next parity. Catalog `rtl` is\n * authoritative (with variant→base inheritance); falls back to the built-in\n * {@link RTL_LANGS} primary-subtag check before/without the catalog, else\n * `'ltr'`.\n */\n dir(locale: Locale): \"ltr\" | \"rtl\" {\n const meta = this.metaFor(locale);\n if (meta && typeof meta.rtl === \"boolean\") return meta.rtl ? \"rtl\" : \"ltr\";\n const primary = locale.split(\"-\")[0]?.toLowerCase();\n return primary && RTL_LANGS.has(primary) ? \"rtl\" : \"ltr\";\n }\n\n /** Endonym (native name) for a locale from the catalog; `undefined` when the\n * catalog has no matching entry (no built-in fallback). */\n nativeName(locale: Locale): string | undefined {\n return this.metaFor(locale)?.native_name;\n }\n\n /** Full catalog entry for a locale; `undefined` when unknown. */\n languageMeta(locale: Locale): LanguageMeta | undefined {\n return this.metaFor(locale);\n }\n}\n","import {\n createInstance,\n type FormatFunction,\n type i18n as I18nextInstance,\n type InterpolationOptions,\n} from \"i18next\";\nimport { initReactI18next } from \"react-i18next\";\nimport { SonentaBackend, type SonentaBackendOptions } from \"./backend\";\nimport { LanguageCatalog, loadCatalog } from \"./catalog\";\nimport { MissingKeyManager } from \"./missing\";\nimport { flattenPlurals } from \"./plurals\";\nimport { defaultTransport, logTransport } from \"./transport\";\nimport type {\n LanguageMeta,\n Locale,\n MissingKeyEvent,\n Transport,\n SonentaConfig,\n} from \"./types\";\n\nconst DEFAULT_API_BASE = \"https://api.sonenta.com\";\nconst DEFAULT_CDN_BASE = \"https://cdn.sonenta.com\";\nconst DEFAULT_VERSION_SLUG = \"main\";\nconst DEFAULT_FLUSH_MS = 5_000;\nconst DEFAULT_BATCH = 50;\nconst DEFAULT_BUFFER = 200;\n\n/**\n * One {@link MissingKeyManager} per i18next instance, keyed off the instance\n * itself so the buffer/timer/transport state rides alongside the upstream\n * object without polluting it. Populated in {@link createSonentaI18next} and\n * read by the `sonenta*Missing` helpers below.\n */\nconst managers = new WeakMap<I18nextInstance, MissingKeyManager>();\n\n/**\n * One {@link LanguageCatalog} per engine instance (#803), keyed off the\n * instance like {@link managers}. Primed from `config.languageCatalog` and the\n * best-effort public `GET /v1/languages` fetch in\n * {@link createSonentaI18next}, and read by the `sonentaDir` /\n * `sonentaNativeName` / `sonentaLanguageMeta` helpers below.\n */\nconst catalogs = new WeakMap<I18nextInstance, LanguageCatalog>();\n\n/** Normalize `fallbackLng` (string | string[] | unset) → array. */\nfunction asArray(fb: Locale | Locale[] | undefined): Locale[] {\n if (fb == null) return [];\n return (Array.isArray(fb) ? fb : [fb]).filter(Boolean);\n}\n\n/**\n * Best-effort auto-detect of the version's key style (#754 parity with the\n * legacy `_loadKeyStyle`). Fetches the version metadata\n * (`GET {apiBase}/v1/projects/{projectUuid}/versions/{version}`, `ApiKey`\n * auth) and maps `key_style==='flat'` → `false` (flat: literal keys, no\n * split) else `key_separator || '.'` (nested). On ANY failure (non-OK, throw,\n * non-string body) keeps the default `'.'` — never throws to the caller. Only\n * called when `config.keySeparator === undefined`.\n */\nexport async function detectKeySeparator(\n apiBase: string,\n projectUuid: string,\n version: string,\n token: string,\n f: typeof fetch,\n): Promise<string | false> {\n try {\n const url = `${apiBase.replace(/\\/+$/, \"\")}/v1/projects/${projectUuid}/versions/${encodeURIComponent(version)}`;\n const r = await f(url, {\n method: \"GET\",\n headers: { Authorization: `ApiKey ${token}` },\n credentials: \"omit\",\n });\n if (!r.ok) return \".\";\n const meta = (await r.json()) as {\n key_style?: string;\n key_separator?: string;\n };\n return meta.key_style === \"flat\" ? false : meta.key_separator || \".\";\n } catch {\n return \".\";\n }\n}\n\n/**\n * Build a real `i18next` instance (#805, Option A) backed by\n * {@link SonentaBackend} and wired into react-i18next. This is the\n * thin-wrapper replacement for the hand-rolled `SonentaI18n` store; it loads\n * bundles over the same wire contract while delegating resolution, plurals,\n * interpolation, and the React subscription to the upstream libraries.\n *\n * `config.initialBundles` (locale → ns → tree) is passed straight through as\n * i18next `resources` for an instant, offline-first first render; combined\n * with `partialBundledLanguages: true` so the backend still fetches the rest.\n */\nexport async function createSonentaI18next(\n config: SonentaConfig,\n fetchImpl?: typeof fetch,\n): Promise<I18nextInstance> {\n const version = config.version ?? config.versionSlug ?? DEFAULT_VERSION_SLUG;\n const ns: string[] = config.namespaces?.length\n ? config.namespaces\n : config.defaultNS\n ? [config.defaultNS]\n : [\"common\"];\n const apiBase = config.apiBase ?? DEFAULT_API_BASE;\n\n // initialBundles share i18next's `resources` shape (locale -> ns -> tree),\n // but they BYPASS the backend, so the nested→suffixed plural transform that\n // the backend applies to fetched bundles must be applied here too — else a\n // snapshot's nested CLDR plurals would never resolve. Run each\n // initialBundles[locale][ns] tree through flattenPlurals(tree, locale)\n // (#805 Stage 3). Non-plural keys pass through untouched.\n const resources: SonentaConfig[\"initialBundles\"] = config.initialBundles\n ? Object.fromEntries(\n Object.entries(config.initialBundles).map(([loc, byNs]) => [\n loc,\n Object.fromEntries(\n Object.entries(byNs).map(([nsName, tree]) => [\n nsName,\n tree && typeof tree === \"object\"\n ? flattenPlurals(tree as Record<string, unknown>, loc)\n : tree,\n ]),\n ),\n ]),\n )\n : undefined;\n\n const backend: SonentaBackendOptions = {\n apiBase,\n cdnBase: config.cdnBase ?? DEFAULT_CDN_BASE,\n token: config.token,\n projectUuid: config.projectUuid,\n version,\n env: config.env ?? \"prod\",\n fetchImpl,\n // Nested CLDR → i18next-suffixed plural transform for every fetched bundle\n // (#805 Stage 3). The backfill in flattenPlurals covers i18next v26 NOT\n // falling back to `_other` for a category the locale needs.\n transform: (data, lng, _ns) => flattenPlurals(data, lng),\n };\n\n // Missing-key streaming (#805 Stage 2) — mirror the legacy engine's\n // transport selection + flush/buffer defaults exactly.\n const missingHandler = config.missingHandler ?? \"send\";\n const transport: Transport =\n config.transport ??\n (missingHandler === \"log\"\n ? logTransport\n : defaultTransport({\n apiBase,\n token: config.token,\n projectUuid: config.projectUuid,\n }));\n const manager = new MissingKeyManager({\n transport,\n missingHandler,\n flushIntervalMs: config.flushIntervalMs ?? DEFAULT_FLUSH_MS,\n flushBatchSize: config.flushBatchSize ?? DEFAULT_BATCH,\n bufferSize: config.missingEventsBufferSize ?? DEFAULT_BUFFER,\n });\n\n const instance = createInstance();\n instance.use(SonentaBackend).use(initReactI18next);\n\n // Resolve the #746 source_value for a missing key. Matches legacy\n // `_sourceValueFor`: explicit `options.defaultValue` wins; else walk the\n // configured fallbackLng(s) (skip the active locale) and return the first\n // plain-string `getResource(fb, ns, key)`; else undefined — NEVER the key.\n const sourceValueFor = (\n ns_: string,\n key: string,\n lng: Locale,\n options: { defaultValue?: unknown } | undefined,\n ): string | undefined => {\n if (typeof options?.defaultValue === \"string\") return options.defaultValue;\n for (const fb of asArray(config.fallbackLng)) {\n if (fb === lng) continue;\n const v = instance.getResource(fb, ns_, key);\n if (typeof v === \"string\") return v;\n }\n return undefined;\n };\n\n // keySeparator (#754 parity): an EXPLICIT config value wins verbatim and\n // skips the version fetch (mirrors legacy `_keySeparatorExplicit`); when\n // undefined, best-effort auto-detect from the version metadata BEFORE init.\n const keySeparator: string | false =\n config.keySeparator === undefined\n ? await detectKeySeparator(\n apiBase,\n config.projectUuid,\n version,\n config.token,\n fetchImpl ?? fetch,\n )\n : config.keySeparator;\n\n // escapeValue stays false (React escapes); `format` is the optional\n // developer-supplied value formatter for `{{value, format}}` (#805-1).\n // i18next accepts `interpolation.format` at runtime but its v26\n // `InterpolationOptions` type omits it (FormatFunction lives elsewhere) — the\n // `& { format?: FormatFunction }` intersection types it without a cast.\n const interpolation: InterpolationOptions & { format?: FormatFunction } = {\n escapeValue: false,\n format: config.interpolation?.format as FormatFunction | undefined,\n };\n\n await instance.init({\n lng: config.defaultLocale,\n fallbackLng: config.fallbackLng ?? false,\n ns,\n defaultNS: ns[0],\n fallbackNS: false,\n keySeparator,\n nsSeparator: config.nsSeparator === undefined ? \":\" : config.nsSeparator,\n resources,\n partialBundledLanguages: true,\n // When reporting is off we also flip saveMissing off so i18next never even\n // invokes the handler (matches legacy \"off skips reporting\"); the manager\n // is a hard no-op too, so this is belt-and-suspenders.\n saveMissing: missingHandler !== \"off\",\n saveMissingTo: \"all\",\n interpolation,\n missingKeyHandler: (lngs, ns_, key, fallbackValue, _updateMissing, options) => {\n // GATE (matches legacy three-condition gate): only report once the\n // bundle for the active locale exists AND has ≥1 key. Covers\n // not-loaded / 404→{} / 200→{} (the boot-flood guard). `fallbackValue`\n // is ignored — when no defaultValue was passed it === key, which must\n // NEVER become a source_value.\n void fallbackValue;\n const lng = lngs[0];\n if (!lng) return;\n if (!instance.hasResourceBundle(lng, ns_)) return;\n const bundle = instance.getResourceBundle(lng, ns_);\n if (!bundle || typeof bundle !== \"object\" || Object.keys(bundle).length === 0) {\n return;\n }\n manager.record({\n key,\n namespace: ns_,\n language_code: lng,\n source_value: sourceValueFor(ns_, key, lng, options),\n });\n },\n // i18next's InitOptions type doesn't know our backend option shape; keep\n // the cast localized to this one block.\n backend: backend as unknown as Record<string, unknown>,\n });\n\n // #805-1 quirk: i18next v26 ALWAYS registers a built-in `Formatter` module\n // and, during init, OVERWRITES `options.interpolation.format` with that\n // formatter's `format` (i18next.js: `this.options.interpolation.format =\n // s.formatter.format.bind(s.formatter)`). So the `format` we passed into\n // init() above is discarded for the default formatter, whose named formats\n // are only the Intl ones (number/currency/datetime/relativetime/list) — an\n // unknown token like `long` just warns and returns the raw value.\n //\n // To honor the documented contract (\"i18next calls YOUR `format` for\n // `{{value, format}}`\"), re-bind the live Interpolator's `format` to the\n // developer fn AFTER init. The built-in Intl formatters stay reachable: we\n // call the developer fn FIRST and, only when it returns the value byte-for-\n // byte unchanged (i.e. it didn't handle this token), fall back to the\n // built-in formatter — so `{{n, number}}` etc. keep working out of the box.\n const userFormat = config.interpolation?.format;\n if (userFormat) {\n const services = (\n instance as unknown as {\n services?: {\n interpolator?: { format?: FormatFunction };\n };\n }\n ).services;\n const interpolator = services?.interpolator;\n if (interpolator) {\n // The built-in Intl formatter, ALREADY bound to its module — i18next's\n // init set `options.interpolation.format = formatter.format.bind(...)`.\n // Capture that (not the raw unbound method, which would lose `this`).\n const builtIn = (\n instance.options.interpolation as\n | { format?: FormatFunction }\n | undefined\n )?.format;\n interpolator.format = ((value, format, lng, opts) => {\n const out = userFormat(\n value,\n format,\n lng,\n (opts ?? {}) as Record<string, unknown>,\n );\n // Defer to the built-in Intl formatter only when the developer fn was\n // a pass-through for this token (returned the value unchanged).\n if (out === (value as unknown) && builtIn) {\n return builtIn(value, format, lng, opts);\n }\n return out;\n }) as FormatFunction;\n }\n }\n\n managers.set(instance, manager);\n manager.start();\n\n // Language catalog (#803) on the engine. Prime from an embedded catalog\n // (offline/SSR/RN) so dir()/nativeName() work synchronously, then best-effort\n // augment from the PUBLIC GET /v1/languages (no auth) UNLESS disabled. A\n // failed fetch keeps the embedded/last-known catalog (loadCatalog never\n // throws and returns null on any failure).\n const catalog = new LanguageCatalog();\n catalog.merge(config.languageCatalog);\n catalogs.set(instance, catalog);\n if (config.disableLanguageCatalog !== true) {\n catalog.merge(await loadCatalog(apiBase, fetchImpl ?? fetch));\n }\n\n return instance;\n}\n\n/** Per-instance catalog accessor; lazily attaches an empty catalog for an\n * instance not created by {@link createSonentaI18next} so the helpers still\n * resolve via the built-in RTL fallback instead of throwing. */\nfunction catalogFor(instance: I18nextInstance): LanguageCatalog {\n let c = catalogs.get(instance);\n if (!c) {\n c = new LanguageCatalog();\n catalogs.set(instance, c);\n }\n return c;\n}\n\n/**\n * Text direction of a locale (defaults to the instance's active language) —\n * i18next parity (#803). Catalog `rtl` is authoritative (variant→base\n * inheritance); falls back to the built-in RTL-language list before/without\n * the catalog, else `'ltr'`.\n */\nexport function sonentaDir(\n instance: I18nextInstance,\n lng?: Locale,\n): \"ltr\" | \"rtl\" {\n const locale = lng ?? instance.language ?? \"\";\n return catalogFor(instance).dir(locale);\n}\n\n/**\n * Endonym (native name) of a locale from the catalog (defaults to the active\n * language) — the fallback for runtimes without `Intl.DisplayNames` (#803).\n * `undefined` when the catalog has no entry.\n */\nexport function sonentaNativeName(\n instance: I18nextInstance,\n lng?: Locale,\n): string | undefined {\n const locale = lng ?? instance.language ?? \"\";\n return catalogFor(instance).nativeName(locale);\n}\n\n/**\n * Full language-catalog entry for a locale (script, plural categories, parent,\n * …), defaulting to the active language; `undefined` if unknown (#803).\n */\nexport function sonentaLanguageMeta(\n instance: I18nextInstance,\n lng?: Locale,\n): LanguageMeta | undefined {\n const locale = lng ?? instance.language ?? \"\";\n return catalogFor(instance).languageMeta(locale);\n}\n\n/** Force-flush the missing-key batch for an instance now. No-op for an\n * instance not created by {@link createSonentaI18next}. */\nexport function sonentaFlushMissing(instance: I18nextInstance): Promise<void> {\n return managers.get(instance)?.flush() ?? Promise.resolve();\n}\n\n/** Recently captured missing-key events (newest first) for an instance.\n * Empty for an instance not created by {@link createSonentaI18next}. */\nexport function sonentaMissingEvents(\n instance: I18nextInstance,\n): MissingKeyEvent[] {\n return managers.get(instance)?.missingEvents ?? [];\n}\n\n/** Stop the periodic missing-key flush loop for an instance. No-op for an\n * instance not created by {@link createSonentaI18next}. */\nexport function sonentaStopMissing(instance: I18nextInstance): void {\n managers.get(instance)?.stop();\n}\n\n/**\n * Bust-refetch loaded resources and re-render. Thin wrapper over i18next's\n * `reloadResources`; pass `locale`/`namespace` to narrow the reload. Used by\n * the realtime plugin on a `translations_published` push and for manual\n * refresh.\n */\nexport function sonentaReload(\n instance: I18nextInstance,\n opts?: { locale?: string; namespace?: string },\n): Promise<void> {\n return instance.reloadResources(opts?.locale, opts?.namespace);\n}\n\n/**\n * Literal-first key resolution (#754 parity with the legacy `resolve()`).\n *\n * i18next v26 is NOT literal-first in the CONFLICT case: with a bundle\n * `{ \"a.b\": \"LITERAL\", a: { b: \"NESTED\" } }` and `keySeparator: '.'`,\n * `instance.t(\"a.b\")` returns \"NESTED\" (it splits before probing the literal).\n * Legacy `resolve()` always lets an exact `bundle[key]` win, so dotted-literal\n * keys (e.g. `\"App Version 6.3.8\"`) resolve even in nested mode.\n *\n * This helper restores that contract: it first probes the EXACT literal via\n * `getResource(lng, ns, key, { keySeparator: false })` (a guaranteed\n * no-split lookup) across the active language(s); a string hit wins. Otherwise\n * it defers to `instance.t(key, options)` for normal nested/plural/context/\n * interpolation behavior. Plural literals (whose stored value is an object,\n * not a string) intentionally fall through to `t` so count selection works.\n *\n * Note: the literal probe is a plain `getResource` (no interpolation), matching\n * legacy `resolve()` which returned the raw value; pass interpolation vars to\n * the non-literal `t` path as usual. If you need interpolation on a literal\n * hit, the value contains no `{{var}}` in practice for dotted-literal keys.\n */\nexport function sonentaResolveKey(\n instance: I18nextInstance,\n key: string,\n ns?: string,\n options?: Record<string, unknown>,\n): string {\n // Determine the namespace + bare key the same way i18next would, honoring\n // the configured nsSeparator (false/'' disables ns parsing). We only need\n // this to point getResource at the right (lng, ns) — t() still does its own\n // parsing on the original key.\n const nsSeparator = instance.options.nsSeparator;\n const defaultNs =\n ns ??\n (Array.isArray(instance.options.defaultNS)\n ? instance.options.defaultNS[0]\n : instance.options.defaultNS) ??\n \"translation\";\n let probeNs = defaultNs;\n let probeKey = key;\n if (typeof nsSeparator === \"string\" && nsSeparator !== \"\") {\n const idx = key.indexOf(nsSeparator);\n if (idx > 0) {\n probeNs = key.slice(0, idx);\n probeKey = key.slice(idx + nsSeparator.length);\n }\n }\n\n // Active language(s): the resolved chain i18next would walk.\n const langs = instance.languages ?? [instance.language].filter(Boolean);\n for (const lng of langs) {\n if (!lng) continue;\n const literal = instance.getResource(lng, probeNs, probeKey, {\n keySeparator: false,\n });\n if (typeof literal === \"string\") return literal;\n }\n\n return instance.t(key, options) as string;\n}\n","import type { MissingHandlerMode, MissingKeyEvent, Transport } from \"./types\";\n\n/**\n * Streaming + batching for missing-key reports on the real-`i18next` engine\n * (#805, Stage 2). This is the parallel counterpart to the legacy\n * `SonentaI18n`'s in-class `_reportMissing`/`flushMissing`/`_startTimer`\n * machinery — extracted into a standalone manager because the i18next instance\n * is owned by upstream and can't carry our buffer/timer state.\n *\n * Behaviour mirrors the legacy engine exactly:\n * - `record()` dedups within the buffer (per `${lang}/${ns}/${key}`), keeps a\n * newest-first ring buffer capped at `bufferSize`, and force-flushes once\n * `pending.length >= flushBatchSize`.\n * - `flush()` is a no-op when `missingHandler === 'off'`; otherwise it\n * drains pending and `await`s the transport in a swallow-all try/catch\n * (best-effort: reporting must never break the host app).\n * - `start()` arms a `setInterval(flushIntervalMs)` flush loop (guarded for\n * non-browser/`off`); `stop()` clears it.\n *\n * The GATE (only-report-when-bundle-has-content) and the #746 `source_value`\n * rule live in the i18next `missingKeyHandler` (see `engine.ts`); by the time\n * an event reaches `record()` it is already vetted.\n */\nexport class MissingKeyManager {\n /** Newest-first ring buffer for in-app inspectors; capped at `bufferSize`. */\n missingEvents: MissingKeyEvent[] = [];\n\n private readonly transport: Transport;\n private readonly missingHandler: MissingHandlerMode;\n private readonly flushIntervalMs: number;\n private readonly flushBatchSize: number;\n private readonly bufferSize: number;\n\n private pending: MissingKeyEvent[] = [];\n private seen = new Set<string>(); // dedup `${language_code}/${namespace}/${key}`\n private timer: ReturnType<typeof setInterval> | null = null;\n\n constructor(opts: {\n transport: Transport;\n missingHandler: MissingHandlerMode;\n flushIntervalMs: number;\n flushBatchSize: number;\n bufferSize: number;\n }) {\n this.transport = opts.transport;\n this.missingHandler = opts.missingHandler;\n this.flushIntervalMs = opts.flushIntervalMs;\n this.flushBatchSize = opts.flushBatchSize;\n this.bufferSize = opts.bufferSize;\n }\n\n /**\n * Record a (already-vetted) missing-key event. Dedups within the buffer,\n * pushes onto the capped ring buffer + the pending batch, and force-flushes\n * when the batch reaches `flushBatchSize`. No-op when `missingHandler` is\n * `'off'` (matches legacy `_reportMissing`).\n *\n * Returns `true` only when the event was NEWLY recorded (not a dedup / not\n * `off`). Callers MUST gate any re-render (`_notify`) on this — notifying on\n * a deduped report is what caused the rc render-loop: a missing key re-fires\n * the handler on every render, so an unconditional notify loops forever.\n */\n record(event: MissingKeyEvent): boolean {\n if (this.missingHandler === \"off\") return false;\n const dedupKey = `${event.language_code}/${event.namespace}/${event.key}`;\n if (this.seen.has(dedupKey)) return false;\n this.seen.add(dedupKey);\n\n this.missingEvents = [event, ...this.missingEvents].slice(0, this.bufferSize);\n this.pending.push(event);\n if (this.pending.length >= this.flushBatchSize) {\n void this.flush();\n }\n return true;\n }\n\n /**\n * Drain the pending batch to the transport. No-op (and keeps pending intact)\n * when `'off'`; otherwise takes + clears pending and `await`s the transport,\n * swallowing any error (best-effort delivery).\n */\n async flush(): Promise<void> {\n if (this.missingHandler === \"off\") return;\n if (!this.pending.length) return;\n const batch = this.pending.slice(0);\n this.pending = [];\n try {\n await this.transport(batch);\n } catch {\n // best-effort — missing-key reporting must never break the host app\n }\n }\n\n /** Arm the periodic flush loop. Guarded for non-browser runtimes / `'off'`. */\n start(): void {\n if (this.missingHandler === \"off\") return;\n if (typeof setInterval !== \"function\") return;\n if (this.timer) return;\n this.timer = setInterval(() => {\n void this.flush();\n }, this.flushIntervalMs);\n }\n\n /** Clear the periodic flush loop. */\n stop(): void {\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = null;\n }\n }\n}\n","/**\n * Nested CLDR → i18next-suffixed plural transform (#805 Stage 3).\n *\n * Sonenta CDN/runtime bundles ship plurals as a NESTED CLDR object\n * (`{ one, other, … }`, `other` always present), NOT i18next's `_<category>`\n * suffixed flat keys. i18next v26 also will NOT fall back to `_other` for a\n * missing plural form, so we additionally BACKFILL whichever CLDR categories\n * the bundle's locale actually needs (from `Intl.PluralRules`) using the\n * `other` value — mirroring the legacy engine's `selectPluralForm` fallback.\n *\n * Example (en): `{ items: { one: \"1 item\", other: \"{{count}} items\" } }`\n * → `{ items_one: \"1 item\", items_other: \"{{count}} items\" }`\n * Example (ru, bundle only has `other`):\n * `{ notif: { other: \"{{count}} увед.\" } }`\n * → `{ notif_one, notif_few, notif_many, notif_other }` (all = the `other`\n * value) so `t('notif', {count:2})` (few) still renders.\n */\n\nconst CLDR = [\"zero\", \"one\", \"two\", \"few\", \"many\", \"other\"];\n\n/** Resolve the CLDR categories a locale needs; default to `[\"other\"]`. */\nfunction localeCategories(locale: string): string[] {\n try {\n return new Intl.PluralRules(locale).resolvedOptions()\n .pluralCategories as string[];\n } catch {\n return [\"other\"];\n }\n}\n\n/**\n * Recursively rewrite a bundle tree, converting nested CLDR plural objects to\n * i18next-suffixed flat keys (`key_one`, `key_other`, …) and backfilling the\n * locale's needed categories from `other`. Non-plural nested objects recurse;\n * plain leaf values pass through untouched.\n */\nexport function flattenPlurals(\n tree: Record<string, unknown>,\n locale: string,\n): Record<string, unknown> {\n const cats = localeCategories(locale);\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(tree)) {\n if (v && typeof v === \"object\" && !Array.isArray(v)) {\n const keys = Object.keys(v as object);\n const isPlural =\n keys.length > 0 &&\n keys.some((c) => CLDR.includes(c)) &&\n keys.every((c) => typeof (v as Record<string, unknown>)[c] === \"string\");\n if (isPlural) {\n const vv = v as Record<string, string>;\n const fallback = vv.other ?? vv[keys[0]!];\n for (const cat of new Set([...keys, ...cats])) {\n out[`${k}_${cat}`] = vv[cat] ?? fallback;\n }\n } else {\n out[k] = flattenPlurals(v as Record<string, unknown>, locale);\n }\n } else {\n out[k] = v;\n }\n }\n return out;\n}\n","import type { MissingKeyEvent, Transport } from \"./types\";\n\nconst SDK_LIB = \"@sonenta/react-i18next\";\n// Replaced at build time by tsup `define` with the package.json version, so\n// `sdk_meta.ver` can never drift again. Falls back in non-bundled contexts\n// (e.g. unit tests, where the define isn't applied).\ndeclare const __SDK_VER__: string;\nconst SDK_VER =\n typeof __SDK_VER__ !== \"undefined\" ? __SDK_VER__ : \"0.0.0-dev\";\n\n/** Default transport: POST to `${apiBase}/v1/missing` with the API key. */\nexport function defaultTransport(opts: {\n apiBase: string;\n token: string;\n projectUuid: string;\n}): Transport {\n return async (batch) => {\n if (!batch.length) return;\n const body = {\n project_uuid: opts.projectUuid,\n events: batch.map((e) => ({\n key: e.key,\n namespace: e.namespace,\n language_code: e.language_code,\n // Option A (#746): only send source_value when there's a real value;\n // omit it otherwise (never the key name). Absent = \"no default\".\n ...(e.source_value !== undefined\n ? { source_value: e.source_value }\n : {}),\n sdk_meta: {\n lib: SDK_LIB,\n ver: SDK_VER,\n ...(typeof window !== \"undefined\"\n ? { url: window.location?.href }\n : {}),\n ...(e.sdk_meta ?? {}),\n },\n })),\n };\n try {\n await fetch(`${opts.apiBase.replace(/\\/+$/, \"\")}/v1/missing`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `ApiKey ${opts.token}`,\n },\n body: JSON.stringify(body),\n // SDKs are best-effort; never block the render path\n keepalive: true,\n });\n } catch {\n // swallow — missing-key reporting must never break the host app\n }\n };\n}\n\n/** Logs each event to console.warn — handy for dev. */\nexport const logTransport: Transport = (batch: MissingKeyEvent[]) => {\n for (const e of batch) {\n // eslint-disable-next-line no-console\n console.warn(\"[sonenta] missing key\", e);\n }\n};\n","/**\n * On-screen key registry — the PRODUCER side of the tiny cross-package\n * contract that `@sonenta/feedback` consumes via\n * `globalThis.__verbumia_key_registry__` (see `@sonenta/feedback`'s\n * `core/keys.ts`).\n *\n * Why this exists: the feedback widget must list only the translation\n * strings RENDERED on the current screen (spec ltm 373) — NOT every\n * project string. The widget can't know what's on screen; the i18n SDK\n * does, because it resolves the keys. The registry exposes a minimal\n * global with TWO producer paths feeding it:\n *\n * 1. HOOK-LEVEL — our own `useTranslation` / `Trans` push their\n * per-render key set via `_set(token, set)`. Mount-tracking handles\n * navigation: when a component unmounts, its keys drop out of the\n * union automatically.\n * 2. INSTANCE-LEVEL — SonentaI18n wraps `i18next.t` so EVERY resolved\n * key (including those resolved through react-i18next's NATIVE\n * `useTranslation` / `<Trans>` bound to the exposed i18next, or\n * through a direct `i18n.t()` call) feeds `_track(token, id)`. This\n * makes the registry \"instance-level producer\", per #806 SeedSower\n * diagnosis: a 1.0.2 thin-wrapper drop-in lets host apps keep their\n * `from 'react-i18next'` imports, so the hook-level producer alone\n * misses 100% of those keys. Without this, the widget shows \"no\n * strings on this view\" even when the view is full of text.\n *\n * `snapshot()` is the UNION of both, deduped. The instance-level\n * contribution accumulates for the i18n instance's lifetime (no per-\n * component unmount signal), which is by-design: a stale superset is\n * strictly better than a false empty.\n *\n * The published shape is intentionally tiny so any framework port of the\n * i18n SDK can implement the same global without depending on feedback:\n *\n * globalThis.__verbumia_key_registry__ = {\n * snapshot(): { namespace: string; key: string }[];\n * isPopulated(): boolean;\n * reset(): void;\n * }\n */\n\nexport interface DeclaredKey {\n namespace: string;\n key: string;\n}\n\nconst GLOBAL = \"__verbumia_key_registry__\";\n// Internal id separator. NUL never appears in an i18next namespace or\n// key, so it round-trips even when a key itself contains ':'.\nconst SEP = \"\u0000\";\n\n/** Split an i18next-style `ns:key` (mirrors SonentaI18n._splitNamespace). */\nfunction split(fullKey: string, defaultNamespace: string): DeclaredKey {\n const idx = fullKey.indexOf(\":\");\n if (idx > 0) {\n return { namespace: fullKey.slice(0, idx), key: fullKey.slice(idx + 1) };\n }\n return { namespace: defaultNamespace, key: fullKey };\n}\n\nclass KeyRegistry {\n // One Set per mounted hook/Trans instance (keyed by an opaque token).\n // The on-screen set is the UNION of all live instances' latest render.\n private _instances = new Map<symbol, Set<string>>();\n // Provider mounts that have published us onto globalThis. Ref-counted so\n // a multi-provider tree (or fast unmount/remount in tests) never leaves\n // a stale global or unpublishes while another provider is still live.\n private _providers = 0;\n\n /** Replace an instance's contributed key set (per-render hook producer). */\n _set(token: symbol, keys: Set<string>): void {\n this._instances.set(token, keys);\n }\n\n /** Append ONE id to a token's set, lazy-creating it. Used by the\n * instance-level i18n.t wrap, which accumulates over the i18n\n * instance's lifetime (no per-render reset semantics). Safe to call\n * before `attach()` — the global publishes whenever a provider mounts. */\n _track(token: symbol, id: string): void {\n let set = this._instances.get(token);\n if (!set) {\n set = new Set();\n this._instances.set(token, set);\n }\n set.add(id);\n }\n\n /** Drop an instance entirely (called on hook unmount or i18n stop). */\n _delete(token: symbol): void {\n this._instances.delete(token);\n }\n\n /** Keys rendered by currently-mounted consumers. Stable insertion order. */\n snapshot(): DeclaredKey[] {\n const seen = new Set<string>();\n const out: DeclaredKey[] = [];\n for (const set of this._instances.values()) {\n for (const id of set) {\n if (seen.has(id)) continue;\n seen.add(id);\n const c = id.indexOf(SEP);\n out.push({ namespace: id.slice(0, c), key: id.slice(c + 1) });\n }\n }\n return out;\n }\n\n /** True when ANY producer has contributed ≥1 key. Cheap O(producers)\n * check exposed for DEV-time integration asserts (\"did my\n * useTranslation imports end up wired to @sonenta/react-i18next?\"). */\n isPopulated(): boolean {\n for (const set of this._instances.values()) {\n if (set.size > 0) return true;\n }\n return false;\n }\n\n /** Escape hatch (router integrations / tests). Mount-tracking already\n * handles navigation, so this is rarely needed. */\n reset(): void {\n this._instances.clear();\n }\n\n /** Encode a resolved key into the internal id used by `_set`. */\n encode(fullKey: string, defaultNamespace: string): string {\n const k = split(fullKey, defaultNamespace);\n return `${k.namespace}${SEP}${k.key}`;\n }\n\n /** Provider mounted — publish the global (idempotent, ref-counted). */\n attach(): void {\n this._providers += 1;\n if (this._providers === 1) {\n (globalThis as Record<string, unknown>)[GLOBAL] = {\n snapshot: () => this.snapshot(),\n isPopulated: () => this.isPopulated(),\n reset: () => this.reset(),\n };\n }\n }\n\n /** Provider unmounted — unpublish when the last one goes away. */\n detach(): void {\n this._providers = Math.max(0, this._providers - 1);\n if (this._providers === 0) {\n this._instances.clear();\n const g = globalThis as Record<string, unknown>;\n if (g[GLOBAL]) delete g[GLOBAL];\n }\n }\n}\n\n/** Process-wide singleton — there is exactly one on-screen registry. */\nexport const keyRegistry = new KeyRegistry();\n","import type { I18nInstance } from \"./types\";\nimport type { SonentaI18n } from \"./i18n\";\n\n// Active instance registered by the mounted <SonentaProvider>. Lets code\n// OUTSIDE React (utilities, stores, non-component modules) reach the i18n\n// instance the way react-i18next exposes its default singleton.\nlet _active: SonentaI18n | null = null;\n\n/** @internal — SonentaProvider registers its instance on mount. */\nexport function _setActiveInstance(instance: SonentaI18n): void {\n _active = instance;\n}\n\n/** @internal — SonentaProvider clears its instance on unmount. */\nexport function _clearActiveInstance(instance: SonentaI18n): void {\n if (_active === instance) _active = null;\n}\n\n/**\n * Access the active i18n instance OUTSIDE React components — the\n * react-i18next-style standalone singleton (e.g. for `t()`/`changeLanguage()`\n * in plain modules, stores, or helpers).\n *\n * Returns the instance created by the mounted `<SonentaProvider>`; throws a\n * clear error if no provider is mounted yet. Assumes a single app-wide\n * provider (the common case); with multiple concurrent providers it returns\n * the most recently mounted one.\n */\nexport function getI18n(): I18nInstance {\n if (!_active) {\n throw new Error(\n \"@sonenta/react-i18next: getI18n() was called before <SonentaProvider> mounted (no active i18n instance).\",\n );\n }\n return _active;\n}\n\n/**\n * SAFE variant of {@link getI18n} (#805): returns the active i18n instance, or\n * `null` when no provider is mounted yet — it does NOT throw. Use at module\n * load / in plain helpers where a provider may not be mounted.\n */\nexport function getI18nSafe(): I18nInstance | null {\n return _active;\n}\n\n/**\n * SAFE out-of-React translate (#805). Resolves against the active i18n instance\n * when a provider is mounted; otherwise returns `options.defaultValue` (when a\n * string) or the `key` — it NEVER throws. This makes module-load-time / helper\n * `t()` calls safe before mount (the throwing {@link getI18n} would break them).\n * Accepts both shapes: `t('k', { defaultValue })` and `t('k', 'Default', opts?)`.\n */\nexport function t(\n key: string,\n optionsOrDefault?:\n | (Record<string, unknown> & { defaultValue?: string; count?: number })\n | string,\n maybeOptions?: Record<string, unknown> & {\n defaultValue?: string;\n count?: number;\n },\n): string {\n if (_active) return _active.t(key, optionsOrDefault, maybeOptions);\n const dv =\n typeof optionsOrDefault === \"string\"\n ? optionsOrDefault\n : optionsOrDefault?.defaultValue;\n return typeof dv === \"string\" ? dv : key;\n}\n","/**\n * Surface variants (#911) — a second resolution dimension layered ON TOP of\n * the locale fallback chain. A \"surface\" (`desktop` / `mobile` / `tablet`) is\n * an additive overlay: the base bundle applies to every surface, and a sparse\n * surface overlay overrides individual keys for that surface only.\n *\n * This module is the framework-neutral surface helpers; the engine\n * (`SonentaI18n`) owns the overlay loading/compose, and the provider wires\n * the reactive viewport listener (web) — see `provider.tsx`.\n */\n\nexport type Surface = \"desktop\" | \"mobile\" | \"tablet\";\n\n/** Min-width (px) thresholds that map a viewport width to a surface. A width\n * `< mobile` → `mobile`; `< tablet` → `tablet`; otherwise `desktop`. Mirrors\n * the common mobile-first breakpoint ladder. */\nexport interface SurfaceBreakpoints {\n /** Upper bound (exclusive) of the `mobile` surface, in px. Default 640. */\n mobile: number;\n /** Upper bound (exclusive) of the `tablet` surface, in px. Default 1024. */\n tablet: number;\n}\n\nexport const DEFAULT_SURFACE_BREAKPOINTS: SurfaceBreakpoints = {\n mobile: 640,\n tablet: 1024,\n};\n\n/**\n * Map a viewport width (px) to a {@link Surface} using `breakpoints`\n * (defaults to {@link DEFAULT_SURFACE_BREAKPOINTS}). Framework-neutral and\n * pure — React Native callers can feed it `useWindowDimensions().width` and\n * pass the result to `i18n.setSurface(...)`; the web provider uses it\n * internally against `window.innerWidth`.\n */\nexport function surfaceForWidth(\n width: number,\n breakpoints: SurfaceBreakpoints = DEFAULT_SURFACE_BREAKPOINTS,\n): Surface {\n if (width < breakpoints.mobile) return \"mobile\";\n if (width < breakpoints.tablet) return \"tablet\";\n return \"desktop\";\n}\n","import { useEffect, useMemo, useRef } from \"react\";\nimport { useI18n, useI18nSnapshot } from \"./provider\";\nimport { keyRegistry } from \"./key-registry\";\nimport type {\n I18nInstance,\n TranslationFunction,\n TranslationOptions,\n} from \"./types\";\n\nexport interface UseTranslationResult {\n t: TranslationFunction;\n i18n: I18nInstance;\n}\n\n/** React hook — returns `{ t, i18n }`. Optional `defaultNamespace` lets you\n * drop the `ns:` prefix on every call.\n *\n * Every key this hook resolves during a render is recorded into the\n * on-screen key registry (so a mounted `@sonenta/feedback` widget lists\n * only the strings rendered on the current view — spec ltm 373). The\n * contribution is keyed to THIS hook instance and dropped on unmount, so\n * navigating away removes its keys automatically. */\nexport function useTranslation(defaultNamespace?: string): UseTranslationResult {\n const i18n = useI18n();\n const snapshot = useI18nSnapshot();\n\n // Keys resolved in the CURRENT render pass. The hook body runs before\n // the component's own `t()` calls, so clearing here yields a set that\n // reflects exactly this render once the component finishes.\n const renderedRef = useRef<Set<string>>(new Set());\n renderedRef.current = new Set<string>();\n // Opaque, stable token identifying this hook instance in the registry.\n const tokenRef = useRef<symbol>(Symbol(\"sonenta.t\"));\n\n const t = useMemo<TranslationFunction>(() => {\n // Forwards both call shapes — `t(key, opts)` and the react-i18next\n // positional `t(key, 'Default', opts?)` — straight to `i18n.t`, which\n // normalizes them. Registry tracking is keyed on the resolved fullKey.\n const fn = (\n key: string,\n optionsOrDefault?: TranslationOptions | string,\n maybeOptions?: TranslationOptions,\n ): string => {\n const fullKey =\n defaultNamespace && !key.includes(\":\")\n ? `${defaultNamespace}:${key}`\n : key;\n renderedRef.current.add(\n keyRegistry.encode(fullKey, i18n.defaultNamespace),\n );\n return i18n.t(fullKey, optionsOrDefault, maybeOptions);\n };\n return fn as TranslationFunction;\n }, [i18n, defaultNamespace]);\n\n // After every commit, publish this instance's latest rendered-key set.\n useEffect(() => {\n keyRegistry._set(tokenRef.current, renderedRef.current);\n });\n // Unmount only: drop this instance entirely so its keys leave the\n // on-screen snapshot when the component is gone (e.g. route change).\n useEffect(() => {\n const token = tokenRef.current;\n return () => keyRegistry._delete(token);\n }, []);\n\n return { t, i18n: snapshot };\n}\n","import { Children, cloneElement, isValidElement, type ReactNode } from \"react\";\nimport { useTranslation } from \"./hooks\";\n\nexport interface TransProps {\n /** The translation key (optionally `ns:key`). */\n i18nKey: string;\n /** Default value if the key is missing — used as the fallback string. */\n defaults?: string;\n /** Variables interpolated into `{{var}}` placeholders. */\n values?: Record<string, unknown>;\n /** JSX components mapped by 0-based numeric index — `<0>bold</0>` etc. */\n components?: ReactNode[];\n /** Optional namespace shortcut. */\n namespace?: string;\n}\n\n/** Bare-bones Trans component: resolves the key, interpolates values, and\n * swaps `<0>...</0>` placeholders into the supplied React components.\n * Keeps the surface minimal — full Trans semantics (nested keys, plural\n * trees, gender) land in V1.1. */\nexport function Trans({\n i18nKey,\n defaults,\n values,\n components,\n namespace,\n}: TransProps) {\n const { t } = useTranslation(namespace);\n const raw = t(i18nKey, { ...(values ?? {}), defaultValue: defaults ?? i18nKey });\n if (!components || !components.length) return <>{raw}</>;\n return <>{splitOnComponents(raw, components)}</>;\n}\n\nfunction splitOnComponents(text: string, components: ReactNode[]): ReactNode[] {\n const out: ReactNode[] = [];\n // Match <N>...</N> where N is a 0-based index into `components`.\n const re = /<(\\d+)>(.*?)<\\/\\1>/g;\n let lastIndex = 0;\n let m: RegExpExecArray | null;\n while ((m = re.exec(text)) !== null) {\n if (m.index > lastIndex) out.push(text.slice(lastIndex, m.index));\n const idx = Number(m[1]);\n const inner = m[2];\n const node = components[idx];\n if (isValidElement(node)) {\n out.push(\n cloneElement(node, { key: `t-${m.index}` }, ...Children.toArray(inner ?? \"\"))\n );\n } else if (node !== undefined) {\n out.push(node);\n } else {\n out.push(inner ?? \"\");\n }\n lastIndex = re.lastIndex;\n }\n if (lastIndex < text.length) out.push(text.slice(lastIndex));\n return out;\n}\n"],"mappings":";AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;;;ACRP;AAAA,EACE,kBAAAA;AAAA,OAIK;AACP,SAAS,oBAAAC,yBAAwB;;;ACe1B,IAAM,YAAY,oBAAI,IAAY;AAAA,EACvC;AAAA,EAAM;AAAA,EAAO;AAAA,EAAO;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAO;AAAA,EAAM;AAAA,EACzD;AAAA,EAAO;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AACjC,CAAC;AAUM,SAAS,YAAY,QAA0B;AACpD,QAAM,QAAQ,OAAO,MAAM,GAAG,EAAE,OAAO,OAAO;AAC9C,MAAI,MAAM,UAAU,EAAG,QAAO,CAAC,MAAM;AACrC,QAAM,QAAkB,CAAC;AACzB,WAAS,IAAI,MAAM,QAAQ,KAAK,GAAG,IAAK,OAAM,KAAK,MAAM,MAAM,GAAG,CAAC,EAAE,KAAK,GAAG,CAAC;AAC9E,SAAO;AACT;AASA,eAAsB,YACpB,SACA,YAA0B,OACM;AAChC,MAAI;AACF,UAAM,MAAM,GAAG,QAAQ,QAAQ,QAAQ,EAAE,CAAC;AAC1C,UAAM,IAAI,MAAM,UAAU,KAAK,EAAE,QAAQ,OAAO,aAAa,OAAO,CAAC;AACrE,QAAI,CAAC,EAAE,GAAI,QAAO;AAClB,UAAM,OAAQ,MAAM,EAAE,KAAK;AAC3B,WAAO,MAAM,QAAQ,IAAI,IAAK,OAA0B;AAAA,EAC1D,QAAQ;AAEN,WAAO;AAAA,EACT;AACF;AAQO,IAAM,kBAAN,MAAsB;AAAA,EACnB,WAAW,oBAAI,IAA0B;AAAA;AAAA,EAGjD,MAAM,OAAgD;AACpD,QAAI,CAAC,MAAO;AACZ,eAAW,QAAQ,OAAO;AACxB,UAAI,QAAQ,OAAO,KAAK,SAAS,UAAU;AACzC,aAAK,SAAS,IAAI,KAAK,KAAK,YAAY,GAAG,IAAI;AAAA,MACjD;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA,EAIA,QAAQ,QAA0C;AAChD,eAAW,KAAK,YAAY,MAAM,GAAG;AACnC,YAAM,MAAM,KAAK,SAAS,IAAI,EAAE,YAAY,CAAC;AAC7C,UAAI,IAAK,QAAO;AAAA,IAClB;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,IAAI,QAA+B;AACjC,UAAM,OAAO,KAAK,QAAQ,MAAM;AAChC,QAAI,QAAQ,OAAO,KAAK,QAAQ,UAAW,QAAO,KAAK,MAAM,QAAQ;AACrE,UAAM,UAAU,OAAO,MAAM,GAAG,EAAE,CAAC,GAAG,YAAY;AAClD,WAAO,WAAW,UAAU,IAAI,OAAO,IAAI,QAAQ;AAAA,EACrD;AAAA;AAAA;AAAA,EAIA,WAAW,QAAoC;AAC7C,WAAO,KAAK,QAAQ,MAAM,GAAG;AAAA,EAC/B;AAAA;AAAA,EAGA,aAAa,QAA0C;AACrD,WAAO,KAAK,QAAQ,MAAM;AAAA,EAC5B;AACF;;;ACrHA;AAAA,EACE;AAAA,OAIK;AACP,SAAS,wBAAwB;;;ACiB1B,IAAM,oBAAN,MAAwB;AAAA;AAAA,EAE7B,gBAAmC,CAAC;AAAA,EAEnB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAET,UAA6B,CAAC;AAAA,EAC9B,OAAO,oBAAI,IAAY;AAAA;AAAA,EACvB,QAA+C;AAAA,EAEvD,YAAY,MAMT;AACD,SAAK,YAAY,KAAK;AACtB,SAAK,iBAAiB,KAAK;AAC3B,SAAK,kBAAkB,KAAK;AAC5B,SAAK,iBAAiB,KAAK;AAC3B,SAAK,aAAa,KAAK;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,OAAO,OAAiC;AACtC,QAAI,KAAK,mBAAmB,MAAO,QAAO;AAC1C,UAAM,WAAW,GAAG,MAAM,aAAa,IAAI,MAAM,SAAS,IAAI,MAAM,GAAG;AACvE,QAAI,KAAK,KAAK,IAAI,QAAQ,EAAG,QAAO;AACpC,SAAK,KAAK,IAAI,QAAQ;AAEtB,SAAK,gBAAgB,CAAC,OAAO,GAAG,KAAK,aAAa,EAAE,MAAM,GAAG,KAAK,UAAU;AAC5E,SAAK,QAAQ,KAAK,KAAK;AACvB,QAAI,KAAK,QAAQ,UAAU,KAAK,gBAAgB;AAC9C,WAAK,KAAK,MAAM;AAAA,IAClB;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,QAAuB;AAC3B,QAAI,KAAK,mBAAmB,MAAO;AACnC,QAAI,CAAC,KAAK,QAAQ,OAAQ;AAC1B,UAAM,QAAQ,KAAK,QAAQ,MAAM,CAAC;AAClC,SAAK,UAAU,CAAC;AAChB,QAAI;AACF,YAAM,KAAK,UAAU,KAAK;AAAA,IAC5B,QAAQ;AAAA,IAER;AAAA,EACF;AAAA;AAAA,EAGA,QAAc;AACZ,QAAI,KAAK,mBAAmB,MAAO;AACnC,QAAI,OAAO,gBAAgB,WAAY;AACvC,QAAI,KAAK,MAAO;AAChB,SAAK,QAAQ,YAAY,MAAM;AAC7B,WAAK,KAAK,MAAM;AAAA,IAClB,GAAG,KAAK,eAAe;AAAA,EACzB;AAAA;AAAA,EAGA,OAAa;AACX,QAAI,KAAK,OAAO;AACd,oBAAc,KAAK,KAAK;AACxB,WAAK,QAAQ;AAAA,IACf;AAAA,EACF;AACF;;;AC5FA,IAAM,OAAO,CAAC,QAAQ,OAAO,OAAO,OAAO,QAAQ,OAAO;AAG1D,SAAS,iBAAiB,QAA0B;AAClD,MAAI;AACF,WAAO,IAAI,KAAK,YAAY,MAAM,EAAE,gBAAgB,EACjD;AAAA,EACL,QAAQ;AACN,WAAO,CAAC,OAAO;AAAA,EACjB;AACF;AAQO,SAAS,eACd,MACA,QACyB;AACzB,QAAM,OAAO,iBAAiB,MAAM;AACpC,QAAM,MAA+B,CAAC;AACtC,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,IAAI,GAAG;AACzC,QAAI,KAAK,OAAO,MAAM,YAAY,CAAC,MAAM,QAAQ,CAAC,GAAG;AACnD,YAAM,OAAO,OAAO,KAAK,CAAW;AACpC,YAAM,WACJ,KAAK,SAAS,KACd,KAAK,KAAK,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,KACjC,KAAK,MAAM,CAAC,MAAM,OAAQ,EAA8B,CAAC,MAAM,QAAQ;AACzE,UAAI,UAAU;AACZ,cAAM,KAAK;AACX,cAAM,WAAW,GAAG,SAAS,GAAG,KAAK,CAAC,CAAE;AACxC,mBAAW,OAAO,oBAAI,IAAI,CAAC,GAAG,MAAM,GAAG,IAAI,CAAC,GAAG;AAC7C,cAAI,GAAG,CAAC,IAAI,GAAG,EAAE,IAAI,GAAG,GAAG,KAAK;AAAA,QAClC;AAAA,MACF,OAAO;AACL,YAAI,CAAC,IAAI,eAAe,GAA8B,MAAM;AAAA,MAC9D;AAAA,IACF,OAAO;AACL,UAAI,CAAC,IAAI;AAAA,IACX;AAAA,EACF;AACA,SAAO;AACT;;;AC7DA,IAAM,UAAU;AAKhB,IAAM,UACJ,OAAqC,UAAc;AAG9C,SAAS,iBAAiB,MAInB;AACZ,SAAO,OAAO,UAAU;AACtB,QAAI,CAAC,MAAM,OAAQ;AACnB,UAAM,OAAO;AAAA,MACX,cAAc,KAAK;AAAA,MACnB,QAAQ,MAAM,IAAI,CAAC,OAAO;AAAA,QACxB,KAAK,EAAE;AAAA,QACP,WAAW,EAAE;AAAA,QACb,eAAe,EAAE;AAAA;AAAA;AAAA,QAGjB,GAAI,EAAE,iBAAiB,SACnB,EAAE,cAAc,EAAE,aAAa,IAC/B,CAAC;AAAA,QACL,UAAU;AAAA,UACR,KAAK;AAAA,UACL,KAAK;AAAA,UACL,GAAI,OAAO,WAAW,cAClB,EAAE,KAAK,OAAO,UAAU,KAAK,IAC7B,CAAC;AAAA,UACL,GAAI,EAAE,YAAY,CAAC;AAAA,QACrB;AAAA,MACF,EAAE;AAAA,IACJ;AACA,QAAI;AACF,YAAM,MAAM,GAAG,KAAK,QAAQ,QAAQ,QAAQ,EAAE,CAAC,eAAe;AAAA,QAC5D,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,eAAe,UAAU,KAAK,KAAK;AAAA,QACrC;AAAA,QACA,MAAM,KAAK,UAAU,IAAI;AAAA;AAAA,QAEzB,WAAW;AAAA,MACb,CAAC;AAAA,IACH,QAAQ;AAAA,IAER;AAAA,EACF;AACF;AAGO,IAAM,eAA0B,CAAC,UAA6B;AACnE,aAAW,KAAK,OAAO;AAErB,YAAQ,KAAK,yBAAyB,CAAC;AAAA,EACzC;AACF;;;AHHA,eAAsB,mBACpB,SACA,aACA,SACA,OACA,GACyB;AACzB,MAAI;AACF,UAAM,MAAM,GAAG,QAAQ,QAAQ,QAAQ,EAAE,CAAC,gBAAgB,WAAW,aAAa,mBAAmB,OAAO,CAAC;AAC7G,UAAM,IAAI,MAAM,EAAE,KAAK;AAAA,MACrB,QAAQ;AAAA,MACR,SAAS,EAAE,eAAe,UAAU,KAAK,GAAG;AAAA,MAC5C,aAAa;AAAA,IACf,CAAC;AACD,QAAI,CAAC,EAAE,GAAI,QAAO;AAClB,UAAM,OAAQ,MAAM,EAAE,KAAK;AAI3B,WAAO,KAAK,cAAc,SAAS,QAAQ,KAAK,iBAAiB;AAAA,EACnE,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;AIpCA,IAAM,SAAS;AAGf,IAAM,MAAM;AAGZ,SAAS,MAAM,SAAiB,kBAAuC;AACrE,QAAM,MAAM,QAAQ,QAAQ,GAAG;AAC/B,MAAI,MAAM,GAAG;AACX,WAAO,EAAE,WAAW,QAAQ,MAAM,GAAG,GAAG,GAAG,KAAK,QAAQ,MAAM,MAAM,CAAC,EAAE;AAAA,EACzE;AACA,SAAO,EAAE,WAAW,kBAAkB,KAAK,QAAQ;AACrD;AAEA,IAAM,cAAN,MAAkB;AAAA;AAAA;AAAA,EAGR,aAAa,oBAAI,IAAyB;AAAA;AAAA;AAAA;AAAA,EAI1C,aAAa;AAAA;AAAA,EAGrB,KAAK,OAAe,MAAyB;AAC3C,SAAK,WAAW,IAAI,OAAO,IAAI;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAO,OAAe,IAAkB;AACtC,QAAI,MAAM,KAAK,WAAW,IAAI,KAAK;AACnC,QAAI,CAAC,KAAK;AACR,YAAM,oBAAI,IAAI;AACd,WAAK,WAAW,IAAI,OAAO,GAAG;AAAA,IAChC;AACA,QAAI,IAAI,EAAE;AAAA,EACZ;AAAA;AAAA,EAGA,QAAQ,OAAqB;AAC3B,SAAK,WAAW,OAAO,KAAK;AAAA,EAC9B;AAAA;AAAA,EAGA,WAA0B;AACxB,UAAM,OAAO,oBAAI,IAAY;AAC7B,UAAM,MAAqB,CAAC;AAC5B,eAAW,OAAO,KAAK,WAAW,OAAO,GAAG;AAC1C,iBAAW,MAAM,KAAK;AACpB,YAAI,KAAK,IAAI,EAAE,EAAG;AAClB,aAAK,IAAI,EAAE;AACX,cAAM,IAAI,GAAG,QAAQ,GAAG;AACxB,YAAI,KAAK,EAAE,WAAW,GAAG,MAAM,GAAG,CAAC,GAAG,KAAK,GAAG,MAAM,IAAI,CAAC,EAAE,CAAC;AAAA,MAC9D;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,cAAuB;AACrB,eAAW,OAAO,KAAK,WAAW,OAAO,GAAG;AAC1C,UAAI,IAAI,OAAO,EAAG,QAAO;AAAA,IAC3B;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA,EAIA,QAAc;AACZ,SAAK,WAAW,MAAM;AAAA,EACxB;AAAA;AAAA,EAGA,OAAO,SAAiB,kBAAkC;AACxD,UAAM,IAAI,MAAM,SAAS,gBAAgB;AACzC,WAAO,GAAG,EAAE,SAAS,GAAG,GAAG,GAAG,EAAE,GAAG;AAAA,EACrC;AAAA;AAAA,EAGA,SAAe;AACb,SAAK,cAAc;AACnB,QAAI,KAAK,eAAe,GAAG;AACzB,MAAC,WAAuC,MAAM,IAAI;AAAA,QAChD,UAAU,MAAM,KAAK,SAAS;AAAA,QAC9B,aAAa,MAAM,KAAK,YAAY;AAAA,QACpC,OAAO,MAAM,KAAK,MAAM;AAAA,MAC1B;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAGA,SAAe;AACb,SAAK,aAAa,KAAK,IAAI,GAAG,KAAK,aAAa,CAAC;AACjD,QAAI,KAAK,eAAe,GAAG;AACzB,WAAK,WAAW,MAAM;AACtB,YAAM,IAAI;AACV,UAAI,EAAE,MAAM,EAAG,QAAO,EAAE,MAAM;AAAA,IAChC;AAAA,EACF;AACF;AAGO,IAAM,cAAc,IAAI,YAAY;;;ANhI3C,IAAM,mBAAmB;AACzB,IAAM,mBAAmB;AACzB,IAAM,mBAAmB;AACzB,IAAM,gBAAgB;AACtB,IAAM,iBAAiB;AACvB,IAAM,uBAAuB;AAM7B,SAAS,QAAQ,IAA6C;AAC5D,MAAI,MAAM,KAAM,QAAO,CAAC;AACxB,UAAQ,MAAM,QAAQ,EAAE,IAAI,KAAK,CAAC,EAAE,GAAG,OAAO,OAAO;AACvD;AAyBO,IAAM,cAAN,MAA0C;AAAA,EAC/C,QAAQ;AAAA,EACR;AAAA,EACA;AAAA,EACA,gBAAmC,CAAC;AAAA;AAAA;AAAA;AAAA,EAK5B;AAAA;AAAA;AAAA;AAAA,EAIA;AAAA;AAAA;AAAA;AAAA,EAIA,iBAAiB,uBAAO,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAO1C,aAAa,oBAAI,IAAY;AAAA,EAC7B,cAAc,oBAAI,IAAY;AAAA;AAAA;AAAA,EAI9B,WAAW,IAAI,gBAAgB;AAAA,EAC/B,mBAAmB;AAAA,EAEnB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBA;AAAA,EACA,YAAY,oBAAI,IAAoB;AAAA;AAAA,EACpC,eAAe,oBAAI,IAAoB;AAAA;AAAA;AAAA;AAAA,EAGvC,UAAU,oBAAI,IAAsB;AAAA,EAEpC;AAAA,EACA,aAAa,oBAAI,IAAc;AAAA;AAAA;AAAA,EAG/B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,wBAAwB;AAAA;AAAA,EAExB,eAA+B;AAAA;AAAA;AAAA,EAG/B;AAAA,EAMR,YAAY,QAAuB;AAIjC,UAAM,sBAAsB,OAAO,KAAK,MAAM,EAAE;AAAA,MAC9C,CAAC,MAAM,MAAM,iBAAiB,EAAE,WAAW,YAAY;AAAA,IACzD;AACA,QAAI,oBAAoB,SAAS,GAAG;AAClC,YAAM,IAAI;AAAA,QACR,2BAA2B,oBAAoB,KAAK,IAAI,CAAC,IACvD,oBAAoB,SAAS,IAAI,SAAS,KAC5C;AAAA,MAEF;AAAA,IACF;AAEA,SAAK,SAAS,OAAO;AACrB,SAAK,cAAc,OAAO;AAC1B,SAAK,WAAW,OAAO;AAIvB,QAAI,eAA+B;AACnC,QAAI,OAAO,iBAAiB,QAAW;AACrC,qBAAe,OAAO;AACtB,WAAK,wBAAwB;AAAA,IAC/B;AACA,QAAI,OAAO,gBAAgB,OAAW,MAAK,eAAe,OAAO;AAEjE,SAAK,UAAU;AAAA,MACb,SAAS,OAAO,WAAW;AAAA,MAC3B,SAAS,OAAO,WAAW;AAAA,MAC3B,gBAAgB,OAAO,kBAAkB;AAAA,MACzC,OAAO,OAAO;AAAA,MACd,aAAa,OAAO;AAAA,MACpB,YAAY,OAAO,YAAY,SAC3B,OAAO,aACP,OAAO,YACL,CAAC,OAAO,SAAS,IACjB,CAAC,QAAQ;AAAA,MACf,iBAAiB,OAAO,mBAAmB;AAAA,MAC3C,gBAAgB,OAAO,kBAAkB;AAAA,MACzC,yBAAyB,OAAO,2BAA2B;AAAA,MAC3D,SAAS,OAAO,WAAW,OAAO,eAAe;AAAA,MACjD,KAAK,OAAO,OAAO;AAAA,IACrB;AAIA,UAAM,YACJ,OAAO,cACN,KAAK,QAAQ,mBAAmB,QAC7B,eACA,iBAAiB;AAAA,MACf,SAAS,KAAK,QAAQ;AAAA,MACtB,OAAO,KAAK,QAAQ;AAAA,MACpB,aAAa,KAAK,QAAQ;AAAA,IAC5B,CAAC;AACP,SAAK,WAAW,IAAI,kBAAkB;AAAA,MACpC;AAAA,MACA,gBAAgB,KAAK,QAAQ;AAAA,MAC7B,iBAAiB,KAAK,QAAQ;AAAA,MAC9B,gBAAgB,KAAK,QAAQ;AAAA,MAC7B,YAAY,KAAK,QAAQ;AAAA,IAC3B,CAAC;AAED,SAAK,cAAc,OAAO,eAAe;AASzC,UAAM,YAAoD,CAAC;AAC3D,QAAI,OAAO,gBAAgB;AACzB,iBAAW,CAAC,KAAK,IAAI,KAAK,OAAO,QAAQ,OAAO,cAAc,GAAG;AAC/D,mBAAW,CAACC,KAAI,IAAI,KAAK,OAAO,QAAQ,IAAI,GAAG;AAC7C,cAAI,CAAC,QAAQ,OAAO,SAAS,SAAU;AACvC,WAAC,UAAU,GAAG,MAAM,CAAC,GAAGA,GAAE,IAAI,eAAe,MAAgB,GAAG;AAGhE,eAAK,UAAU,IAAI,KAAK,WAAW,KAAKA,GAAE,GAAG,IAAc;AAC3D,cAAI,OAAO,KAAK,IAAI,EAAE,SAAS,GAAG;AAChC,iBAAK,YAAY,IAAI,KAAK,WAAW,KAAKA,GAAE,CAAC;AAAA,UAC/C;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAMA,UAAM,gBAAoE;AAAA,MACxE,aAAa;AAAA,MACb,QAAQ,KAAK;AAAA,IACf;AAEA,UAAM,KAAK,KAAK,QAAQ;AACxB,SAAK,WAAWC,gBAAe;AAC/B,SAAK,SAAS,IAAIC,iBAAgB;AAMlC,SAAK,KAAK,SAAS,KAAK;AAAA,MACtB,KAAK,KAAK;AAAA,MACV,aAAa,OAAO,eAAe;AAAA,MACnC;AAAA,MACA,WAAW,GAAG,CAAC;AAAA,MACf,YAAY;AAAA,MACZ,WAAW;AAAA,MACX;AAAA,MACA,aAAa,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA,MAKlB,OAAO;AAAA,QACL,UAAU;AAAA,QACV,eAAe;AAAA,QACf,aAAa;AAAA,MACf;AAAA,MACA;AAAA,MACA,yBAAyB;AAAA;AAAA;AAAA,MAGzB,uBAAuB,CAAC,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA,MAKhC,aAAa,KAAK,QAAQ,mBAAmB;AAAA,MAC7C,eAAe;AAAA,MACf;AAAA,MACA,mBAAmB,KAAK;AAAA,IAC1B,CAAC;AAQD,SAAK,sBAAsB,KAAK,SAAS,eAAe,KAAK,KAAK,QAAQ;AAC1E,SAAK,SAAS,kBAAkB,CAAC,QAAiB,SAAoB;AACpE,UAAI,OAAO,QAAQ,YAAY,QAAQ,KAAK,QAAQ;AAClD,eACE,KAAK,oBACL,KAAK,GAAG,IAAI;AAAA,MAChB;AACA,aAAO,KAAK,UAAU,GAAG,EAAE;AAAA,QAAK,MAC9B,KAAK,SAAS,UAAU,KAAK,QAAQ,IAAI;AAAA,MAC3C;AAAA,IACF;AAcA,SAAK,eAAe;AAMpB,SAAK,cAAc;AAInB,SAAK,mBAAmB,OAAO,2BAA2B;AAC1D,SAAK,SAAS,MAAM,OAAO,eAAe;AAI1C,UAAM,SAAS,OAAO,iBAAiB,KAAK,MAAM;AAClD,QACE,UACA,KAAK,QAAQ,WAAW;AAAA,MACtB,CAAC,MAAM,OAAO,CAAC,KAAK,OAAO,KAAK,OAAO,CAAC,CAAE,EAAE,SAAS;AAAA,IACvD,GACA;AACA,WAAK,QAAQ;AAAA,IACf;AAEA,SAAK,YAAY,KAAK,eAAe;AAAA,EACvC;AAAA;AAAA;AAAA,EAIQ,gBAAsB;AAC5B,UAAM,aAAa,KAAK;AACxB,QAAI,CAAC,WAAY;AACjB,UAAM,eACJ,KAAK,SAGL,UAAU;AACZ,QAAI,CAAC,aAAc;AACnB,UAAM,UACJ,KAAK,SAAS,QAAQ,eACrB;AACH,iBAAa,UAAU,CAAC,OAAO,QAAQ,KAAK,SAAS;AACnD,YAAM,MAAM;AAAA,QACV;AAAA,QACA;AAAA,QACA;AAAA,QACC,QAAQ,CAAC;AAAA,MACZ;AACA,UAAI,QAAS,SAAqB,SAAS;AACzC,eAAO,QAAQ,OAAO,QAAQ,KAAK,IAAI;AAAA,MACzC;AACA,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,iBAAuB;AAC7B,UAAM,OAAO,KAAK;AAClB,UAAM,OAAO,KAAK,EAAE,KAAK,IAAI;AAC7B,UAAM,QAAQ,KAAK;AACnB,UAAM,YAAY,MAAc,KAAK;AACrC,UAAM,WAAW,IAAI,SAAoB;AACvC,YAAM,SAAU,KAAiD,GAAG,IAAI;AACxE,UAAI;AACF,cAAM,SAAS,KAAK,CAAC;AACrB,cAAM,OACJ,KAAK,CAAC,KAAK,OAAO,KAAK,CAAC,MAAM,YAAY,CAAC,MAAM,QAAQ,KAAK,CAAC,CAAC,IAC3D,KAAK,CAAC,IACP;AAIN,YAAI;AACJ,YAAI,OAAO,WAAW,UAAU;AAC9B,mBAAS;AAAA,QACX,WAAW,MAAM,QAAQ,MAAM,KAAK,OAAO,OAAO,CAAC,MAAM,UAAU;AACjE,mBAAS,OAAO,CAAC;AAAA,QACnB;AACA,YAAI,QAAQ;AACV,gBAAM,SAAS,MAAM;AACrB,gBAAM,KACJ,OAAO,WAAW,WACd,SACA,MAAM,QAAQ,MAAM,IAClB,OAAO,CAAC,IACR;AAIR,gBAAM,UACJ,MAAM,CAAC,OAAO,SAAS,GAAG,IAAI,GAAG,EAAE,IAAI,MAAM,KAAK;AACpD,sBAAY,OAAO,OAAO,YAAY,OAAO,SAAS,UAAU,CAAC,CAAC;AAAA,QACpE;AAAA,MACF,QAAQ;AAAA,MAER;AACA,aAAO;AAAA,IACT;AACA,SAAK,IAAI;AAAA,EACX;AAAA;AAAA,EAIA,YAAY,CAAC,aAAqC;AAChD,SAAK,WAAW,IAAI,QAAQ;AAC5B,WAAO,MAAM,KAAK,WAAW,OAAO,QAAQ;AAAA,EAC9C;AAAA;AAAA;AAAA,EAIA,cAAc,MAAoB,KAAK;AAAA,EAE/B,iBAA+B;AACrC,WAAO;AAAA,MACL,OAAO,KAAK;AAAA,MACZ,QAAQ,KAAK;AAAA,MACb,UAAU,KAAK;AAAA,MACf,WAAW,KAAK;AAAA,MAChB,gBAAgB,KAAK;AAAA,MACrB,GAAG,KAAK;AAAA,MACR,eAAe,KAAK;AAAA,MACpB,cAAc,KAAK;AAAA,MACnB,QAAQ,KAAK;AAAA,MACb,SAAS,KAAK;AAAA,MACd,SAAS,KAAK;AAAA,MACd,YAAY,KAAK;AAAA,MACjB,OAAO,KAAK;AAAA,MACZ,KAAK,KAAK;AAAA,MACV,YAAY,KAAK;AAAA,MACjB,cAAc,KAAK;AAAA,MACnB,SAAS,KAAK;AAAA,IAChB;AAAA,EACF;AAAA;AAAA;AAAA,EAIQ,WAAW,QAAgB,IAAuB;AACxD,WAAO,GAAG,KAAK,QAAQ,OAAO,IAAI,MAAM,IAAI,EAAE;AAAA,EAChD;AAAA,EAEQ,UAAgB;AACtB,SAAK,YAAY,KAAK,eAAe;AACrC,eAAW,KAAK,KAAK,WAAY,GAAE;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,gBAAsB;AAC5B,IACE,KAAK,SACL,KAAK,UAAU,CAAC,CAAC;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA,EAMA,IAAI,mBAA2B;AAC7B,WAAO,KAAK,QAAQ,WAAW,CAAC;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,iBAAiB,QAA0B;AACjD,UAAM,MAAgB,CAAC;AACvB,UAAM,OAAO,oBAAI,IAAY;AAC7B,eAAW,KAAK,CAAC,GAAG,YAAY,MAAM,GAAG,GAAG,QAAQ,KAAK,WAAW,CAAC,GAAG;AACtE,UAAI,KAAK,CAAC,KAAK,IAAI,CAAC,GAAG;AACrB,aAAK,IAAI,CAAC;AACV,YAAI,KAAK,CAAC;AAAA,MACZ;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,MAAM,YAA0B,OAAsB;AAG1D,gBAAY,OAAO;AACnB,UAAM,UAAU,IAAI,IAAY,KAAK,iBAAiB,KAAK,MAAM,CAAC;AAClE,UAAM,QAAQ,IAAI;AAAA,MAChB,GAAG,CAAC,GAAG,OAAO,EAAE;AAAA,QAAQ,CAAC,QACvB,KAAK,QAAQ,WAAW;AAAA,UAAI,CAAC,OAC3B,KAAK,YAAY,KAAK,IAAI,SAAS;AAAA,QACrC;AAAA,MACF;AAAA;AAAA,MAEA,KAAK,cAAc,SAAS;AAAA;AAAA,MAE5B,KAAK,aAAa,SAAS;AAAA,IAC7B,CAAC;AAGD,UAAM,KAAK,cAAc;AACzB,SAAK,QAAQ;AACb,SAAK,SAAS,MAAM;AACpB,SAAK,QAAQ;AACb,SAAK,cAAc;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,cAAc,WAAwC;AAClE,QAAI,KAAK,sBAAuB;AAChC,UAAM,MAAM,MAAM;AAAA,MAChB,KAAK,QAAQ;AAAA,MACb,KAAK,QAAQ;AAAA,MACb,KAAK,QAAQ;AAAA,MACb,KAAK,QAAQ;AAAA,MACb;AAAA,IACF;AAGA,SAAK,SAAS,QAAQ,eAAe;AAAA,EACvC;AAAA;AAAA;AAAA,EAIA,MAAc,aAAa,WAAwC;AACjE,QAAI,KAAK,iBAAkB;AAC3B,SAAK,SAAS,MAAM,MAAM,YAAY,KAAK,QAAQ,SAAS,SAAS,CAAC;AAAA,EACxE;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,gBAA+B;AAC3C,QAAI,KAAK,SAAS,aAAa,KAAK,QAAQ;AAG1C,YAAM,KAAK,oBAAoB,KAAK,MAAM;AAAA,IAC5C;AAAA,EACF;AAAA,EAEA,YAAY,OAAO,SAAgC;AACjD,QAAI,SAAS,KAAK,OAAQ;AAC1B,SAAK,SAAS;AACd,SAAK,QAAQ;AACb,SAAK,QAAQ;AAGb,UAAM,QAAQ;AAAA,MACZ,KAAK,iBAAiB,IAAI,EAAE;AAAA,QAAQ,CAAC,QACnC,KAAK,QAAQ,WACV,OAAO,CAAC,OAAO,CAAC,KAAK,WAAW,IAAI,KAAK,WAAW,KAAK,EAAE,CAAC,CAAC,EAC7D,IAAI,CAAC,OAAO,KAAK,YAAY,KAAK,EAAE,CAAC;AAAA,MAC1C;AAAA,IACF;AACA,UAAM,KAAK,cAAc;AACzB,SAAK,QAAQ;AACb,SAAK,QAAQ;AACb,SAAK,cAAc;AAAA,EACrB;AAAA;AAAA,EAGA,iBAAiB,CAAC,SAAgC,KAAK,UAAU,IAAI;AAAA;AAAA,EAGrE,IAAI,WAAmB;AACrB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,UAA2B;AAC7B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,CAAC,QACL,KAAK,SAAS,IAAI,OAAO,KAAK,MAAM;AAAA;AAAA;AAAA,EAItC,aAAa,CAAC,QACZ,KAAK,SAAS,WAAW,OAAO,KAAK,MAAM;AAAA;AAAA;AAAA,EAI7C,eAAe,CAAC,QACd,KAAK,SAAS,aAAa,OAAO,KAAK,MAAM;AAAA,EAE/C,OAAa;AAIX,gBAAY,QAAQ,KAAK,cAAc;AACvC,gBAAY,OAAO;AACnB,SAAK,SAAS,KAAK;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,mBAAmB,CAAC,OAA4C;AAC9D,UAAM,UAAU,CAAC,QAAsB,GAAG,GAAG;AAC7C,SAAK,SAAS,GAAG,mBAAmB,OAAO;AAC3C,WAAO,MAAM;AAIX,MACE,KAAK,SAGL,IAAI,mBAAmB,OAAO;AAAA,IAClC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,SAAS,OACP,OAAmD,CAAC,MAClC;AAClB,UAAM,UAAoD,CAAC;AAC3D,eAAW,OAAO,KAAK,YAAY;AAEjC,YAAM,QAAQ,IAAI,MAAM,GAAG;AAC3B,YAAM,SAAS,MAAM,CAAC;AACtB,YAAM,KAAK,MAAM,CAAC;AAClB,UAAI,CAAC,UAAU,CAAC,GAAI;AACpB,UAAI,KAAK,UAAU,KAAK,WAAW,OAAQ;AAC3C,UAAI,KAAK,aAAa,KAAK,cAAc,GAAI;AAC7C,cAAQ,KAAK,EAAE,QAAQ,GAAG,CAAC;AAAA,IAC7B;AACA,QAAI,QAAQ,WAAW,EAAG;AAC1B,UAAM,QAAQ;AAAA,MACZ,QAAQ;AAAA,QAAI,CAACC,OACX,KAAK,YAAYA,GAAE,QAAQA,GAAE,IAAI,OAAO,EAAE,MAAM,KAAK,CAAC;AAAA,MACxD;AAAA,IACF;AACA,SAAK,QAAQ;AAAA,EACf;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,UAAU,MAAY;AACpB,SAAK,QAAQ;AACb,SAAK,cAAc;AAAA,EACrB;AAAA,EAEA,IAAI,UAA+B;AACjC,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,aAAa,OAAO,YAAgD;AAClE,QAAI,YAAY,KAAK,SAAU;AAC/B,SAAK,WAAW;AAChB,UAAM,UAAoD,CAAC;AAC3D,eAAW,OAAO,KAAK,YAAY;AAEjC,YAAM,QAAQ,IAAI,MAAM,GAAG;AAC3B,YAAM,SAAS,MAAM,CAAC;AACtB,YAAM,KAAK,MAAM,CAAC;AAClB,UAAI,UAAU,GAAI,SAAQ,KAAK,EAAE,QAAQ,GAAG,CAAC;AAAA,IAC/C;AACA,UAAM,QAAQ;AAAA,MACZ,QAAQ,IAAI,CAACA,OAAM,KAAK,eAAeA,GAAE,QAAQA,GAAE,EAAE,CAAC;AAAA,IACxD;AACA,SAAK,QAAQ;AACb,SAAK,cAAc;AAAA,EACrB;AAAA;AAAA;AAAA,EAIA,QAAQ,CAAC,KAAa,cAAgD;AACpE,QAAI,KAAK,aAAa,KAAK;AAC3B,QAAI,UAAU;AACd,QAAI,OAAO,KAAK,iBAAiB,YAAY,KAAK,cAAc;AAC9D,YAAM,MAAM,IAAI,QAAQ,KAAK,YAAY;AACzC,UAAI,MAAM,GAAG;AACX,aAAK,IAAI,MAAM,GAAG,GAAG;AACrB,kBAAU,IAAI,MAAM,MAAM,KAAK,aAAa,MAAM;AAAA,MACpD;AAAA,IACF;AACA,eAAW,OAAO,KAAK,iBAAiB,KAAK,MAAM,GAAG;AACpD,YAAM,MAAM,KAAK,QAAQ,IAAI,GAAG,GAAG,IAAI,EAAE,IAAI,OAAO,EAAE;AACtD,UAAI,IAAK,QAAO;AAAA,IAClB;AACA,WAAO;AAAA,EACT;AAAA;AAAA,EAIA,IAAI,CACF,KACA,kBAGA,iBAIW;AAGX,UAAM,UAGJ,OAAO,qBAAqB,WACxB,EAAE,GAAI,gBAAgB,CAAC,GAAI,cAAc,iBAAiB,IAC1D;AASN,UAAM,UAAU,KAAK,cAAc,GAAG;AACtC,QAAI,YAAY,QAAW;AACzB,YAAM,eACJ,KAAK,SAYL,UAAU;AACZ,UAAI,gBAAgB,SAAS;AAC3B,eAAO,aAAa;AAAA,UAClB;AAAA,UACA;AAAA,UACA,KAAK;AAAA,UACL,CAAC;AAAA,QACH;AAAA,MACF;AACA,aAAO;AAAA,IACT;AACA,WAAO,KAAK,SAAS;AAAA,MACnB;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,cAAc,KAAiC;AACrD,UAAM,cAAc,KAAK,SAAS,QAAQ;AAC1C,QAAI,UAAU,KAAK;AACnB,QAAI,WAAW;AACf,QAAI,OAAO,gBAAgB,YAAY,gBAAgB,IAAI;AACzD,YAAM,MAAM,IAAI,QAAQ,WAAW;AACnC,UAAI,MAAM,GAAG;AACX,kBAAU,IAAI,MAAM,GAAG,GAAG;AAC1B,mBAAW,IAAI,MAAM,MAAM,YAAY,MAAM;AAAA,MAC/C;AAAA,IACF;AACA,UAAM,QAAQ,KAAK,SAAS,aAAa,CAAC,KAAK,SAAS,QAAQ,EAAE,OAAO,OAAO;AAChF,eAAW,OAAO,OAAO;AACvB,UAAI,CAAC,IAAK;AACV,YAAM,IAAI,KAAK,SAAS,YAAY,KAAK,SAAS,UAAU;AAAA,QAC1D,cAAc;AAAA,MAChB,CAAC;AACD,UAAI,OAAO,MAAM,SAAU,QAAO;AAAA,IACpC;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUQ,iBAAiB,CACvB,MACA,IACA,KACA,gBACA,gBACA,YACS;AACT,SAAK;AACL,SAAK;AACL,UAAM,MAAM,KAAK,CAAC,KAAK,KAAK;AAG5B,UAAM,WAAW,KAAK,WAAW,KAAK,QAAQ,EAAE;AAChD,QACE,CAAC,KAAK,SACN,CAAC,KAAK,WAAW,IAAI,QAAQ,KAC7B,CAAC,KAAK,YAAY,IAAI,QAAQ,GAC9B;AACA;AAAA,IACF;AACA,UAAM,WAAW,KAAK,SAAS,OAAO;AAAA,MACpC;AAAA,MACA,WAAW;AAAA,MACX,eAAe;AAAA,MACf,cAAc,KAAK,gBAAgB,KAAK,IAAI,OAAO;AAAA,IACrD,CAAC;AAOD,QAAI,CAAC,SAAU;AAOf,UAAM,WACJ,OAAO,SAAS,iBAAiB,WAAW,QAAQ,eAAe;AACrE,QAAI;AAKF,WAAK,SAAS,YAAY,KAAK,IAAI,KAAK,UAAU;AAAA,QAChD,cAAc;AAAA,QACd,QAAQ;AAAA,MACV,CAA+D;AAAA,IACjE,QAAQ;AAAA,IAER;AACA,SAAK,gBAAgB,KAAK,SAAS;AACnC,SAAK,QAAQ;AAAA,EACf;AAAA,EAEA,eAAe,MAAqB,KAAK,SAAS,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAahD,gBACN,KACA,IACA,SACoB;AACpB,QAAI,OAAO,SAAS,iBAAiB,UAAU;AAC7C,aAAO,QAAQ;AAAA,IACjB;AACA,eAAW,OAAO,QAAQ,KAAK,WAAW,GAAG;AAC3C,UAAI,QAAQ,KAAK,OAAQ;AACzB,YAAM,IAAI,KAAK,SAAS,YAAY,KAAK,IAAI,GAAG;AAChD,UAAI,OAAO,MAAM,SAAU,QAAO;AAAA,IACpC;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,YACZ,QACA,IACA,YAA0B,OAC1B,OAA2B,CAAC,GACb;AACf,UAAM,WAAW,KAAK,WAAW,QAAQ,EAAE;AAG3C,QAAI;AACJ,QAAI;AACJ,QAAI,KAAK,QAAQ,QAAQ,OAAO;AAI9B,YAAM,KAA6B,EAAE,UAAU,QAAQ,WAAW,GAAG;AACrE,UAAI,KAAK,QAAQ,WAAW,KAAK,QAAQ,YAAY,QAAQ;AAC3D,WAAG,eAAe,KAAK,QAAQ;AAAA,MACjC;AACA,YAAM,SAAS,IAAI,gBAAgB,EAAE;AACrC,YAAM,GAAG,KAAK,QAAQ,QAAQ,QAAQ,QAAQ,EAAE,CAAC,gBAAgB,KAAK,QAAQ,WAAW,yBAAyB,OAAO,SAAS,CAAC;AACnI,aAAO;AAAA,QACL,QAAQ;AAAA,QACR,SAAS,EAAE,eAAe,UAAU,KAAK,QAAQ,KAAK,GAAG;AAAA,QACzD,aAAa;AAAA,MACf;AAAA,IACF,OAAO;AACL,YAAM,GAAG,KAAK,QAAQ,QAAQ,QAAQ,QAAQ,EAAE,CAAC,MAAM,KAAK,QAAQ,WAAW,IAAI,KAAK,QAAQ,OAAO,WAAW,MAAM,IAAI,EAAE;AAC9H,aAAO,EAAE,QAAQ,OAAO,aAAa,OAAO;AAAA,IAC9C;AAGA,QAAI,KAAK,MAAM;AACb,WAAK,QAAQ;AAAA,IACf;AAIA,UAAM,aAAa,KAAK,YAAY,IAAI,QAAQ;AAChD,QAAI;AACF,YAAM,IAAI,MAAM,UAAU,KAAK,IAAI;AACnC,UAAI,EAAE,IAAI;AACR,cAAM,OAAQ,MAAM,EAAE,KAAK;AAC3B,YAAI,QAAQ,OAAO,SAAS,YAAY,OAAO,KAAK,IAAI,EAAE,SAAS,GAAG;AACpE,eAAK,UAAU,IAAI,UAAU,IAAI;AACjC,eAAK,YAAY,IAAI,QAAQ;AAAA,QAC/B,OAAO;AAGL,eAAK,UAAU,IAAI,UAAU,CAAC,CAAC;AAC/B,eAAK,YAAY,OAAO,QAAQ;AAAA,QAClC;AAAA,MACF,WAAW,YAAY;AAAA,MAGvB,OAAO;AAGL,aAAK,UAAU,IAAI,UAAU,CAAC,CAAC;AAC/B,aAAK,YAAY,OAAO,QAAQ;AAAA,MAClC;AAAA,IACF,QAAQ;AACN,UAAI,YAAY;AAAA,MAEhB,OAAO;AACL,aAAK,UAAU,IAAI,UAAU,CAAC,CAAC;AAC/B,aAAK,YAAY,OAAO,QAAQ;AAAA,MAClC;AAAA,IACF,UAAE;AACA,WAAK,WAAW,IAAI,QAAQ;AAAA,IAC9B;AAEA,UAAM,KAAK,eAAe,QAAQ,IAAI,WAAW,KAAK,IAAI;AAAA,EAC5D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAc,eACZ,QACA,IACA,YAA0B,OAC1B,OAAO,OACQ;AACf,UAAM,OAAO,KAAK,UAAU,IAAI,KAAK,WAAW,QAAQ,EAAE,CAAC,KAAK,CAAC;AAEjE,UAAM,cAAc,GAAG,MAAM,IAAI,EAAE;AACnC,eAAW,KAAK,KAAK,QAAQ,KAAK,GAAG;AACnC,UAAI,EAAE,WAAW,WAAW,EAAG,MAAK,QAAQ,OAAO,CAAC;AAAA,IACtD;AACA,UAAM,WAAW,KAAK,cAAc,MAAM,QAAQ,EAAE;AAEpD,SAAK,SAAS;AAAA,MACZ;AAAA,MACA;AAAA,MACA,eAAe,UAAU,MAAM;AAAA,MAC/B;AAAA,MACA;AAAA,IACF;AACA,QAAI,CAAC,KAAK,SAAU;AACpB,UAAM,UAAU,MAAM,KAAK;AAAA,MACzB;AAAA,MACA;AAAA,MACA,KAAK;AAAA,MACL;AAAA,MACA;AAAA,IACF;AACA,QAAI,CAAC,WAAW,OAAO,KAAK,OAAO,EAAE,WAAW,EAAG;AACnD,UAAM,cAAc,KAAK,cAAc,SAAS,QAAQ,EAAE;AAE1D,SAAK,SAAS;AAAA,MACZ;AAAA,MACA;AAAA,MACA,eAAe,aAAa,MAAM;AAAA,MAClC;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,aACZ,QACA,IACA,SACA,YAA0B,OAC1B,OAAO,OACU;AACjB,UAAM,MAAM,GAAG,KAAK,WAAW,QAAQ,EAAE,CAAC,IAAI,OAAO;AACrD,QAAI,CAAC,QAAQ,KAAK,aAAa,IAAI,GAAG,EAAG,QAAO,KAAK,aAAa,IAAI,GAAG;AACzE,QAAI,KAAK,QAAQ,QAAQ,OAAO;AAC9B,WAAK,aAAa,IAAI,KAAK,CAAC,CAAC;AAC7B,aAAO,CAAC;AAAA,IACV;AACA,UAAM,MAAM,GAAG,KAAK,QAAQ,QAAQ,QAAQ,QAAQ,EAAE,CAAC,MAAM,KAAK,QAAQ,WAAW,IAAI,KAAK,QAAQ,OAAO,WAAW,MAAM,IAAI,EAAE,IAAI,OAAO;AAC/I,UAAM,OAAoB,EAAE,QAAQ,OAAO,aAAa,OAAO;AAC/D,QAAI,KAAM,MAAK,QAAQ;AACvB,QAAI,UAAkB,CAAC;AACvB,QAAI;AACF,YAAM,IAAI,MAAM,UAAU,KAAK,IAAI;AACnC,UAAI,EAAE,IAAI;AACR,cAAM,OAAQ,MAAM,EAAE,KAAK;AAC3B,YAAI,QAAQ,OAAO,SAAS,SAAU,WAAU;AAAA,MAClD;AAAA,IAEF,QAAQ;AAAA,IAER;AACA,SAAK,aAAa,IAAI,KAAK,OAAO;AAClC,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,cAAc,MAAc,QAAgB,IAAuB;AACzE,UAAM,MACJ,OAAO,KAAK,SAAS,QAAQ,iBAAiB,WAC1C,KAAK,SAAS,QAAQ,eACtB;AACN,UAAM,OAAO,CAAC,MAAe,SAA4B;AACvD,UAAI,CAAC,QAAQ,OAAO,SAAS,SAAU,QAAO;AAC9C,YAAM,MAAM;AAEZ,UAAI,OAAO,UAAU,eAAe,KAAK,KAAK,QAAQ,GAAG;AACvD,cAAM,IAAI,IAAI;AACd,YAAI,KAAK,OAAO,EAAE,SAAS,YAAY,OAAO,EAAE,QAAQ,UAAU;AAChE,eAAK,QAAQ,IAAI,GAAG,MAAM,IAAI,EAAE,IAAI,KAAK,KAAK,GAAG,CAAC,IAAI;AAAA,YACpD,MAAM,EAAE;AAAA,YACR,KAAK,EAAE;AAAA,UACT,CAAC;AAAA,QACH;AACA,eAAO,IAAI;AAAA,MACb;AACA,YAAM,MAA+B,CAAC;AACtC,iBAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,GAAG,GAAG;AACxC,YAAI,CAAC,IAAI,KAAK,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC;AAAA,MAC/B;AACA,aAAO;AAAA,IACT;AACA,WAAO,KAAK,MAAM,CAAC,CAAC;AAAA,EACtB;AACF;;;AO3lCA,IAAI,UAA8B;AAG3B,SAAS,mBAAmB,UAA6B;AAC9D,YAAU;AACZ;AAGO,SAAS,qBAAqB,UAA6B;AAChE,MAAI,YAAY,SAAU,WAAU;AACtC;AAYO,SAAS,UAAwB;AACtC,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAOO,SAAS,cAAmC;AACjD,SAAO;AACT;AASO,SAAS,EACd,KACA,kBAGA,cAIQ;AACR,MAAI,QAAS,QAAO,QAAQ,EAAE,KAAK,kBAAkB,YAAY;AACjE,QAAM,KACJ,OAAO,qBAAqB,WACxB,mBACA,kBAAkB;AACxB,SAAO,OAAO,OAAO,WAAW,KAAK;AACvC;;;AC9CO,IAAM,8BAAkD;AAAA,EAC7D,QAAQ;AAAA,EACR,QAAQ;AACV;AASO,SAAS,gBACd,OACA,cAAkC,6BACzB;AACT,MAAI,QAAQ,YAAY,OAAQ,QAAO;AACvC,MAAI,QAAQ,YAAY,OAAQ,QAAO;AACvC,SAAO;AACT;;;AT8CI,SAKe,KALf;AAlEJ,IAAM,iBAAiB,cAA0C,IAAI;AAM9D,SAAS,gBAAgB;AAAA,EAC9B;AAAA,EACA,GAAG;AACL,GAAyB;AAEvB,QAAM,OAAO,QAAQ,MAAM,IAAI,YAAY,MAAM,GAAG,CAAC,CAAC;AAEtD,YAAU,MAAM;AAEd,uBAAmB,IAAI;AACvB,SAAK,KAAK,MAAM;AAQhB,UAAM,aAAa,OAAO,WAAW,CAAC,GACnC;AAAA,MAAI,CAAC,MACJ,EAAE,QAAQ;AAAA,QACR;AAAA,QACA;AAAA,QACA,kBAAkB,KAAK;AAAA,MACzB,CAAC;AAAA,IACH,EACC,OAAO,CAACC,OAAuB,OAAOA,OAAM,UAAU;AACzD,WAAO,MAAM;AACX,gBAAU,QAAQ,CAACA,OAAMA,GAAE,CAAC;AAC5B,WAAK,KAAK;AACV,2BAAqB,IAAI;AAAA,IAC3B;AAAA,EACF,GAAG,CAAC,IAAI,CAAC;AAOT,YAAU,MAAM;AACd,QAAI,CAAC,OAAO,WAAW,CAAC,OAAO,mBAAoB;AACnD,QAAI,OAAO,WAAW,eAAe,OAAO,OAAO,eAAe;AAChE;AACF,UAAM,KACJ,OAAO,uBAAuB,OAC1B,8BACA,OAAO;AACb,UAAM,OAAO,MAAM,KAAK,KAAK,WAAW,gBAAgB,OAAO,YAAY,EAAE,CAAC;AAC9E,SAAK;AAGL,UAAM,OAAO,CAAC,GAAG,QAAQ,GAAG,MAAM,EAAE;AAAA,MAAI,CAAC,OACvC,OAAO,WAAW,eAAe,EAAE,KAAK;AAAA,IAC1C;AACA,SAAK,QAAQ,CAAC,QAAQ,IAAI,iBAAiB,UAAU,IAAI,CAAC;AAC1D,WAAO,MAAM,KAAK,QAAQ,CAAC,QAAQ,IAAI,oBAAoB,UAAU,IAAI,CAAC;AAAA,EAC5E,GAAG,CAAC,IAAI,CAAC;AAET,QAAM,QAAQ,QAA6B,OAAO,EAAE,KAAK,IAAI,CAAC,IAAI,CAAC;AACnE,SACE,qBAAC,eAAe,UAAf,EAAwB,OACtB;AAAA;AAAA,KAGC,OAAO,WAAW,CAAC,GAAG;AAAA,MAAI,CAAC,MAC3B,EAAE,SAAS,oBAAC,YAAuB,YAAE,OAAO,KAAlB,EAAE,IAAkB,IAAc;AAAA,IAC9D;AAAA,KACF;AAEJ;AAGO,SAAS,UAAuB;AACrC,QAAM,MAAM,WAAW,cAAc;AACrC,MAAI,CAAC,KAAK;AACR,UAAM,IAAI,MAAM,4DAA4D;AAAA,EAC9E;AACA,SAAO,IAAI;AACb;AAQO,SAAS,kBAAgC;AAC9C,QAAM,OAAO,QAAQ;AACrB,SAAO,qBAAqB,KAAK,WAAW,KAAK,aAAa,KAAK,WAAW;AAChF;;;AUrHA,SAAS,aAAAC,YAAW,WAAAC,UAAS,cAAc;AAsBpC,SAAS,eAAe,kBAAiD;AAC9E,QAAM,OAAO,QAAQ;AACrB,QAAM,WAAW,gBAAgB;AAKjC,QAAM,cAAc,OAAoB,oBAAI,IAAI,CAAC;AACjD,cAAY,UAAU,oBAAI,IAAY;AAEtC,QAAM,WAAW,OAAe,uBAAO,WAAW,CAAC;AAEnD,QAAMC,KAAIC,SAA6B,MAAM;AAI3C,UAAM,KAAK,CACT,KACA,kBACA,iBACW;AACX,YAAM,UACJ,oBAAoB,CAAC,IAAI,SAAS,GAAG,IACjC,GAAG,gBAAgB,IAAI,GAAG,KAC1B;AACN,kBAAY,QAAQ;AAAA,QAClB,YAAY,OAAO,SAAS,KAAK,gBAAgB;AAAA,MACnD;AACA,aAAO,KAAK,EAAE,SAAS,kBAAkB,YAAY;AAAA,IACvD;AACA,WAAO;AAAA,EACT,GAAG,CAAC,MAAM,gBAAgB,CAAC;AAG3B,EAAAC,WAAU,MAAM;AACd,gBAAY,KAAK,SAAS,SAAS,YAAY,OAAO;AAAA,EACxD,CAAC;AAGD,EAAAA,WAAU,MAAM;AACd,UAAM,QAAQ,SAAS;AACvB,WAAO,MAAM,YAAY,QAAQ,KAAK;AAAA,EACxC,GAAG,CAAC,CAAC;AAEL,SAAO,EAAE,GAAAF,IAAG,MAAM,SAAS;AAC7B;;;ACnEA,SAAS,UAAU,cAAc,sBAAsC;AA6BvB,qBAAAG,WAAA,OAAAC,YAAA;AATzC,SAAS,MAAM;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAe;AACb,QAAM,EAAE,GAAAC,GAAE,IAAI,eAAe,SAAS;AACtC,QAAM,MAAMA,GAAE,SAAS,EAAE,GAAI,UAAU,CAAC,GAAI,cAAc,YAAY,QAAQ,CAAC;AAC/E,MAAI,CAAC,cAAc,CAAC,WAAW,OAAQ,QAAO,gBAAAD,KAAAD,WAAA,EAAG,eAAI;AACrD,SAAO,gBAAAC,KAAAD,WAAA,EAAG,4BAAkB,KAAK,UAAU,GAAE;AAC/C;AAEA,SAAS,kBAAkB,MAAc,YAAsC;AAC7E,QAAM,MAAmB,CAAC;AAE1B,QAAM,KAAK;AACX,MAAI,YAAY;AAChB,MAAI;AACJ,UAAQ,IAAI,GAAG,KAAK,IAAI,OAAO,MAAM;AACnC,QAAI,EAAE,QAAQ,UAAW,KAAI,KAAK,KAAK,MAAM,WAAW,EAAE,KAAK,CAAC;AAChE,UAAM,MAAM,OAAO,EAAE,CAAC,CAAC;AACvB,UAAM,QAAQ,EAAE,CAAC;AACjB,UAAM,OAAO,WAAW,GAAG;AAC3B,QAAI,eAAe,IAAI,GAAG;AACxB,UAAI;AAAA,QACF,aAAa,MAAM,EAAE,KAAK,KAAK,EAAE,KAAK,GAAG,GAAG,GAAG,SAAS,QAAQ,SAAS,EAAE,CAAC;AAAA,MAC9E;AAAA,IACF,WAAW,SAAS,QAAW;AAC7B,UAAI,KAAK,IAAI;AAAA,IACf,OAAO;AACL,UAAI,KAAK,SAAS,EAAE;AAAA,IACtB;AACA,gBAAY,GAAG;AAAA,EACjB;AACA,MAAI,YAAY,KAAK,OAAQ,KAAI,KAAK,KAAK,MAAM,SAAS,CAAC;AAC3D,SAAO;AACT;","names":["createInstance","initReactI18next","ns","createInstance","initReactI18next","t","t","useEffect","useMemo","t","useMemo","useEffect","Fragment","jsx","t"]}
|