@xndrjs/i18n-react 0.8.0-alpha.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.
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+ import { spawnSync } from "node:child_process";
3
+ import { fileURLToPath } from "node:url";
4
+ import { dirname, join } from "node:path";
5
+
6
+ const script = join(
7
+ dirname(fileURLToPath(import.meta.url)),
8
+ "../src/codegen/generate-react-bindings.ts"
9
+ );
10
+
11
+ const result = spawnSync("tsx", [script, ...process.argv.slice(2)], {
12
+ stdio: "inherit",
13
+ cwd: process.cwd(),
14
+ env: process.env,
15
+ });
16
+
17
+ process.exit(result.status ?? 1);
@@ -0,0 +1,247 @@
1
+ import { ReactNode, ForwardedRef, ForwardRefExoticComponent, RefAttributes } from 'react';
2
+ import { I18nCreateInput, OnMissingTranslation, FetchArtifact } from '@xndrjs/i18n';
3
+
4
+ /**
5
+ * Internal runtime types for `@xndrjs/i18n-react` implementation modules.
6
+ */
7
+ /** Locale-bound multi-namespace `t(ns, key, …)`. */
8
+ type ScopedTranslateFn = (namespace: string, key: string, params?: Record<string, unknown>) => string;
9
+ /** Partition key for load deduplication — locale (or delivery area when custom). */
10
+ type LoadPartition = string | undefined;
11
+ /** Key inputs for load deduplication. */
12
+ interface LoadCoordinatorKey {
13
+ engineRef: unknown;
14
+ partition: LoadPartition;
15
+ namespaces: readonly string[];
16
+ }
17
+ /** Request payload for {@link createLoadCoordinator.ensure}. */
18
+ interface LoadCoordinatorRequest<Scope> extends LoadCoordinatorKey {
19
+ load: () => Promise<Scope>;
20
+ /**
21
+ * Optional sync resolve for already-seeded resources. When it returns a scope,
22
+ * the entry is marked resolved on the same snapshot (no pending → fallback flash).
23
+ */
24
+ tryResolveSync?: () => Scope | null;
25
+ }
26
+ /** Snapshot of a coordinator cache entry. */
27
+ type LoadCoordinatorEntry<Scope> = {
28
+ status: "pending";
29
+ } | {
30
+ status: "resolved";
31
+ scope: Scope;
32
+ } | {
33
+ status: "error";
34
+ error: unknown;
35
+ };
36
+ /** Display entry for gates — includes keep-previous and stable bound `t`. */
37
+ type LoadDisplayEntry<Scope> = {
38
+ status: "pending";
39
+ display: {
40
+ scope: Scope;
41
+ locale: string;
42
+ } | null;
43
+ pendingLocale: string | undefined;
44
+ t: ScopedTranslateFn | null;
45
+ } | {
46
+ status: "ready";
47
+ scope: Scope;
48
+ locale: string;
49
+ pendingLocale?: undefined;
50
+ t: ScopedTranslateFn;
51
+ } | {
52
+ status: "error";
53
+ error: unknown;
54
+ display: {
55
+ scope: Scope;
56
+ locale: string;
57
+ } | null;
58
+ pendingLocale: string | undefined;
59
+ t: ScopedTranslateFn | null;
60
+ retry: () => void;
61
+ };
62
+ /** Options for building a display entry (namespace-bound `t`). */
63
+ interface GetDisplayEntryOptions {
64
+ locale: string;
65
+ namespaces: readonly string[];
66
+ /** When false, do not keep previous scope across partition changes (default true). */
67
+ keepPrevious?: boolean;
68
+ }
69
+ /** Mutable load coordinator returned by {@link createLoadCoordinator}. */
70
+ interface LoadCoordinator<Scope> {
71
+ readonly revision: number;
72
+ /** @deprecated Prefer {@link ensure}. */
73
+ request(input: LoadCoordinatorRequest<Scope>): void;
74
+ /** Idempotent: start load if missing. Safe from getSnapshot. */
75
+ ensure(input: LoadCoordinatorRequest<Scope>): void;
76
+ getEntry(key: LoadCoordinatorKey): LoadCoordinatorEntry<Scope>;
77
+ getDisplayEntry(key: LoadCoordinatorKey, options: GetDisplayEntryOptions): LoadDisplayEntry<Scope>;
78
+ /** Evict entry so the next ensure() re-fetches. */
79
+ retry(key: LoadCoordinatorKey): void;
80
+ subscribe(listener: () => void): () => void;
81
+ getPromise(): Promise<Scope>;
82
+ getResolvedScope(): Scope | null;
83
+ }
84
+ /** Options for {@link createScopedT}. */
85
+ interface CreateScopedTOptions {
86
+ namespaces: readonly string[];
87
+ locale: string;
88
+ }
89
+ /** Minimal scope shape used by {@link createScopedT}. */
90
+ interface ScopedScopeLike {
91
+ t: (...args: unknown[]) => string;
92
+ forLocale?: (locale: string) => ScopedScopeLike;
93
+ locale?: string;
94
+ }
95
+ /** Factory passed to {@link I18nRootProvider} — typically codegen `createI18n`. */
96
+ type CreateI18nFactory = (options?: any) => unknown;
97
+ /** Minimal handle shape from `@xndrjs/i18n` createI18n(). */
98
+ interface I18nHandleLike {
99
+ load: (input: {
100
+ namespaces: readonly string[];
101
+ locale: string;
102
+ }) => Promise<ScopedScopeLike>;
103
+ peek: (input: {
104
+ namespaces: readonly string[];
105
+ locale: string;
106
+ }) => ScopedScopeLike | null;
107
+ serialize: () => {
108
+ dictionary: unknown;
109
+ resources: readonly (readonly [string, string])[];
110
+ };
111
+ }
112
+ /** Single React context value for the lazy i18n tree. */
113
+ interface I18nRootContextValue {
114
+ handle: I18nHandleLike;
115
+ coordinator: LoadCoordinator<ScopedScopeLike>;
116
+ locale: string;
117
+ }
118
+
119
+ /**
120
+ * Builds the scoped `t` function used by providers and load gates.
121
+ *
122
+ * Takes a loaded `@xndrjs/i18n` scope and returns a capability-safe translator:
123
+ * namespaces outside the provider list throw at runtime. Locale is always bound.
124
+ */
125
+
126
+ /**
127
+ * Wraps a loaded scope `t` with namespace guards and locale binding.
128
+ */
129
+ declare function createScopedT(scope: ScopedScopeLike, options: CreateScopedTOptions): ScopedTranslateFn;
130
+ /**
131
+ * Curries a multi-namespace context `t(ns, key, …)` into a namespace-bound `t(key, …)`.
132
+ * Used when composing gate values without introducing a separate hooks API.
133
+ */
134
+ declare function bindNamespaceTranslate(t: ScopedTranslateFn, namespace: string): (key: string, params?: Record<string, unknown>) => string;
135
+
136
+ /**
137
+ * Dedupes in-flight loads by `(engineRef, partition, namespaces)` and exposes
138
+ * display snapshots + retry for manual pending/resolved/error rendering.
139
+ */
140
+ declare function createLoadCoordinator<Scope = ScopedScopeLike>(): LoadCoordinator<Scope>;
141
+
142
+ /**
143
+ * Manual (non-Suspense) i18n load gate: ensure + subscribe via the load
144
+ * coordinator, then render fallback / keep-previous / error UI.
145
+ */
146
+
147
+ /** Inputs for {@link useNamespaceLoad}. */
148
+ interface UseNamespaceLoadInput<Scope> {
149
+ coordinator: LoadCoordinator<Scope>;
150
+ engineRef: unknown;
151
+ partition: LoadPartition;
152
+ namespaces: readonly string[];
153
+ locale: string;
154
+ load: () => Promise<Scope>;
155
+ tryResolveSync?: () => Scope | null;
156
+ keepPrevious?: boolean;
157
+ }
158
+ /**
159
+ * Starts (or reuses) a coordinator load and re-renders when that entry settles.
160
+ * Kick-off is idempotent inside the snapshot getter (safe with useSyncExternalStore).
161
+ */
162
+ declare function useNamespaceLoad<Scope>(input: UseNamespaceLoadInput<Scope>): LoadDisplayEntry<Scope>;
163
+ /** Scoped `{ t, locale }` injected into gate children / HOC when ready (or kept). */
164
+ interface I18nLoadGateValue {
165
+ t: ScopedTranslateFn;
166
+ locale: string;
167
+ /** Set during keep-then-switch while a new partition is loading. */
168
+ pendingLocale?: string;
169
+ error?: unknown;
170
+ retry?: () => void;
171
+ }
172
+ /** Load wiring from React context (coordinator, engine, partition, load). */
173
+ interface I18nLoadArgs {
174
+ coordinator: LoadCoordinator<ScopedScopeLike>;
175
+ engineRef: unknown;
176
+ partition: LoadPartition;
177
+ locale: string;
178
+ load: (namespaces: readonly string[]) => Promise<ScopedScopeLike>;
179
+ tryResolveSync?: (namespaces: readonly string[]) => ScopedScopeLike | null;
180
+ }
181
+ interface CreateI18nLoadGateOptions {
182
+ /**
183
+ * When true (default), keep last resolved `{ t, locale }` while a new
184
+ * partition is pending instead of showing fallback.
185
+ */
186
+ keepPreviousOnPartitionChange?: boolean;
187
+ /** Hook that resolves coordinator + load inputs from React context. */
188
+ useLoadArgs: () => I18nLoadArgs;
189
+ }
190
+ interface I18nGateProps {
191
+ namespaces: readonly string[];
192
+ fallback?: ReactNode;
193
+ /** When set, called for error with no keep-previous display. */
194
+ renderError?: (args: {
195
+ error: unknown;
196
+ retry: () => void;
197
+ }) => ReactNode;
198
+ children: (value: I18nLoadGateValue) => ReactNode;
199
+ }
200
+ /** Pending UI for `withI18n` — static node or derived from own props. */
201
+ type WithI18nFallback<P extends object> = ReactNode | ((props: P) => ReactNode);
202
+ interface I18nHocOptions<P extends object = object> {
203
+ namespaces: readonly string[];
204
+ fallback?: WithI18nFallback<P>;
205
+ renderError?: (args: {
206
+ error: unknown;
207
+ retry: () => void;
208
+ props: P;
209
+ }) => ReactNode;
210
+ }
211
+ /** Render fn for `withI18n`: own props, i18n bag, optional forwarded ref. */
212
+ type WithI18nRender<P extends object, R = never> = (props: P, i18n: I18nLoadGateValue, ref?: ForwardedRef<R>) => ReactNode;
213
+ /**
214
+ * Factory for generic `I18n` gate + `withI18n` HOC over the same Outer.
215
+ * Codegen emits typed wrappers; tests use this directly.
216
+ */
217
+ declare function createI18nLoadGate(options: CreateI18nLoadGateOptions): {
218
+ I18n: (props: I18nGateProps) => ReactNode;
219
+ withI18n: <P extends object = object, R = never>(hocOptions: I18nHocOptions<P>, render: WithI18nRender<P, R>) => ForwardRefExoticComponent<P & RefAttributes<R>>;
220
+ };
221
+
222
+ /** Resolves the shared root from an ancestor {@link I18nRootProvider}. */
223
+ declare function useI18nRootContext(): I18nRootContextValue;
224
+
225
+ /**
226
+ * Single provider that shares handle + coordinator + locale across a lazy subtree.
227
+ */
228
+
229
+ interface I18nRootProviderProps {
230
+ createI18n: CreateI18nFactory;
231
+ locale: string;
232
+ /**
233
+ * Cold-start / eager dictionary seed (defaults to `{}` when neither prop is set).
234
+ * Mutually exclusive with {@link state}.
235
+ */
236
+ dictionary?: Record<string, unknown>;
237
+ /** Full create/hydrate input from `handle.serialize()` — mutually exclusive with {@link dictionary}. */
238
+ state?: I18nCreateInput | undefined;
239
+ onMissing?: OnMissingTranslation;
240
+ /** Custom JSON loader for `loaderStrategy: "fetch"` projects (ignored by import loaders). */
241
+ fetchImpl?: FetchArtifact;
242
+ children: ReactNode;
243
+ }
244
+ /** Creates and shares one handle + load coordinator for descendant gates. */
245
+ declare function I18nRootProvider({ createI18n, locale, dictionary, state, onMissing, fetchImpl, children, }: I18nRootProviderProps): ReactNode;
246
+
247
+ export { type I18nGateProps, type I18nHocOptions, type I18nLoadGateValue, I18nRootProvider, type I18nRootProviderProps, type WithI18nFallback, type WithI18nRender, bindNamespaceTranslate, createI18nLoadGate, createLoadCoordinator, createScopedT, useI18nRootContext, useNamespaceLoad };
package/dist/index.js ADDED
@@ -0,0 +1,418 @@
1
+ "use client";
2
+ import { createContext, useSyncExternalStore, useContext, useRef, forwardRef } from 'react';
3
+ import { jsx } from 'react/jsx-runtime';
4
+
5
+ // src/create-scoped-t.ts
6
+ function assertNamespace(namespace, namespaces) {
7
+ if (!namespaces.includes(namespace)) {
8
+ throw new Error(
9
+ `Namespace "${namespace}" is not loaded by this provider. Allowed: [${namespaces.join(", ")}]`
10
+ );
11
+ }
12
+ }
13
+ function resolveActiveScope(scope, locale) {
14
+ if (typeof scope.forLocale === "function" && scope.locale !== locale) {
15
+ return scope.forLocale(locale);
16
+ }
17
+ return scope;
18
+ }
19
+ function createScopedT(scope, options) {
20
+ const activeScope = resolveActiveScope(scope, options.locale);
21
+ const { namespaces } = options;
22
+ return ((namespace, key, ...rest) => {
23
+ assertNamespace(namespace, namespaces);
24
+ return activeScope.t(namespace, key, ...rest);
25
+ });
26
+ }
27
+ function bindNamespaceTranslate(t, namespace) {
28
+ return (key, params) => params === void 0 ? t(namespace, key) : t(namespace, key, params);
29
+ }
30
+
31
+ // src/create-load-coordinator.ts
32
+ var engineIds = /* @__PURE__ */ new WeakMap();
33
+ var nextEngineId = 1;
34
+ function engineId(ref) {
35
+ if (ref === null || typeof ref !== "object" && typeof ref !== "function") {
36
+ return 0;
37
+ }
38
+ const obj = ref;
39
+ let id = engineIds.get(obj);
40
+ if (id === void 0) {
41
+ id = nextEngineId++;
42
+ engineIds.set(obj, id);
43
+ }
44
+ return id;
45
+ }
46
+ function cacheKey(engineRef, partition, namespaces) {
47
+ return `${engineId(engineRef)}\0${partition ?? ""}\0${namespaces.join("\0")}`;
48
+ }
49
+ function gateId(engineRef, namespaces) {
50
+ return `${engineId(engineRef)}\0${namespaces.join("\0")}`;
51
+ }
52
+ function createLoadCoordinator() {
53
+ let revision = 0;
54
+ let nextRequestId = 0;
55
+ const entries = /* @__PURE__ */ new Map();
56
+ const displayByGate = /* @__PURE__ */ new Map();
57
+ const listeners = /* @__PURE__ */ new Set();
58
+ let lastKey = null;
59
+ function notify() {
60
+ for (const listener of listeners) {
61
+ listener();
62
+ }
63
+ }
64
+ function bumpAndNotify() {
65
+ revision += 1;
66
+ for (const display of displayByGate.values()) {
67
+ display.displaySnapshot = null;
68
+ display.displayKey = null;
69
+ }
70
+ notify();
71
+ }
72
+ function ensure(input) {
73
+ const key = cacheKey(input.engineRef, input.partition, input.namespaces);
74
+ lastKey = key;
75
+ const existing = entries.get(key);
76
+ if (existing !== void 0) {
77
+ return;
78
+ }
79
+ if (input.tryResolveSync !== void 0) {
80
+ const syncScope = input.tryResolveSync();
81
+ if (syncScope !== null) {
82
+ const entrySnapshot = {
83
+ status: "resolved",
84
+ scope: syncScope
85
+ };
86
+ entries.set(key, {
87
+ status: "resolved",
88
+ promise: Promise.resolve(syncScope),
89
+ resolvedScope: syncScope,
90
+ error: null,
91
+ requestId: ++nextRequestId,
92
+ entrySnapshot
93
+ });
94
+ return;
95
+ }
96
+ }
97
+ const requestId = ++nextRequestId;
98
+ const promise = input.load().then(
99
+ (scope) => {
100
+ const entry = entries.get(key);
101
+ if (entry === void 0 || entry.requestId !== requestId) {
102
+ return scope;
103
+ }
104
+ entry.status = "resolved";
105
+ entry.resolvedScope = scope;
106
+ entry.error = null;
107
+ entry.entrySnapshot = { status: "resolved", scope };
108
+ bumpAndNotify();
109
+ return scope;
110
+ },
111
+ (error) => {
112
+ const entry = entries.get(key);
113
+ if (entry !== void 0 && entry.requestId === requestId) {
114
+ entry.status = "error";
115
+ entry.error = error;
116
+ entry.resolvedScope = null;
117
+ entry.entrySnapshot = { status: "error", error };
118
+ bumpAndNotify();
119
+ }
120
+ throw error;
121
+ }
122
+ );
123
+ void promise.catch(() => void 0);
124
+ entries.set(key, {
125
+ status: "pending",
126
+ promise,
127
+ resolvedScope: null,
128
+ error: null,
129
+ requestId,
130
+ entrySnapshot: { status: "pending" }
131
+ });
132
+ }
133
+ function getEntry(keyInput) {
134
+ const key = cacheKey(keyInput.engineRef, keyInput.partition, keyInput.namespaces);
135
+ const entry = entries.get(key);
136
+ if (entry === void 0) {
137
+ return { status: "pending" };
138
+ }
139
+ return entry.entrySnapshot;
140
+ }
141
+ function getOrCreateDisplay(engineRef, namespaces) {
142
+ const id = gateId(engineRef, namespaces);
143
+ let display = displayByGate.get(id);
144
+ if (display === void 0) {
145
+ display = {
146
+ lastReady: null,
147
+ displaySnapshot: null,
148
+ displayKey: null,
149
+ boundT: null,
150
+ boundTKey: null
151
+ };
152
+ displayByGate.set(id, display);
153
+ }
154
+ return display;
155
+ }
156
+ function bindT(display, scope, locale, namespaces, scopeToken) {
157
+ const tKey = `${scopeToken}\0${locale}\0${namespaces.join("\0")}`;
158
+ if (display.boundT !== null && display.boundTKey === tKey) {
159
+ return display.boundT;
160
+ }
161
+ const t = createScopedT(scope, { namespaces, locale });
162
+ display.boundT = t;
163
+ display.boundTKey = tKey;
164
+ return t;
165
+ }
166
+ function getDisplayEntry(keyInput, options) {
167
+ const key = cacheKey(keyInput.engineRef, keyInput.partition, keyInput.namespaces);
168
+ const entry = entries.get(key);
169
+ const display = getOrCreateDisplay(keyInput.engineRef, options.namespaces);
170
+ const keepPrevious = options.keepPrevious !== false;
171
+ const retry = () => {
172
+ retryKey(keyInput);
173
+ };
174
+ let result;
175
+ if (entry === void 0 || entry.status === "pending") {
176
+ const kept = keepPrevious ? display.lastReady : null;
177
+ result = {
178
+ status: "pending",
179
+ display: kept,
180
+ pendingLocale: kept ? options.locale : void 0,
181
+ t: kept ? bindT(display, kept.scope, kept.locale, options.namespaces, `kept:${kept.locale}`) : null
182
+ };
183
+ } else if (entry.status === "resolved") {
184
+ const scope = entry.resolvedScope;
185
+ display.lastReady = { scope, locale: options.locale };
186
+ result = {
187
+ status: "ready",
188
+ scope,
189
+ locale: options.locale,
190
+ t: bindT(display, scope, options.locale, options.namespaces, `ready:${key}`)
191
+ };
192
+ } else {
193
+ const kept = keepPrevious ? display.lastReady : null;
194
+ result = {
195
+ status: "error",
196
+ error: entry.error,
197
+ display: kept,
198
+ pendingLocale: kept ? options.locale : void 0,
199
+ t: kept ? bindT(display, kept.scope, kept.locale, options.namespaces, `kept:${kept.locale}`) : null,
200
+ retry
201
+ };
202
+ }
203
+ const displayKey = `${key}\0${result.status}\0${options.locale}\0${String(!!result.t)}`;
204
+ if (display.displaySnapshot !== null && display.displayKey === displayKey) {
205
+ return display.displaySnapshot;
206
+ }
207
+ display.displaySnapshot = result;
208
+ display.displayKey = displayKey;
209
+ return result;
210
+ }
211
+ function retryKey(keyInput) {
212
+ const key = cacheKey(keyInput.engineRef, keyInput.partition, keyInput.namespaces);
213
+ if (!entries.has(key)) {
214
+ return;
215
+ }
216
+ entries.delete(key);
217
+ bumpAndNotify();
218
+ }
219
+ function subscribe(listener) {
220
+ listeners.add(listener);
221
+ return () => {
222
+ listeners.delete(listener);
223
+ };
224
+ }
225
+ function getPromise() {
226
+ if (lastKey === null) {
227
+ throw new Error("createLoadCoordinator: call ensure()/request() before getPromise()");
228
+ }
229
+ const entry = entries.get(lastKey);
230
+ if (entry === void 0) {
231
+ throw new Error("createLoadCoordinator: call ensure()/request() before getPromise()");
232
+ }
233
+ return entry.promise;
234
+ }
235
+ function getResolvedScope() {
236
+ if (lastKey === null) {
237
+ return null;
238
+ }
239
+ const entry = entries.get(lastKey);
240
+ return entry?.status === "resolved" ? entry.resolvedScope : null;
241
+ }
242
+ return {
243
+ get revision() {
244
+ return revision;
245
+ },
246
+ request: ensure,
247
+ ensure,
248
+ getEntry,
249
+ getDisplayEntry,
250
+ retry: retryKey,
251
+ subscribe,
252
+ getPromise,
253
+ getResolvedScope
254
+ };
255
+ }
256
+ function useNamespaceLoad(input) {
257
+ const {
258
+ coordinator,
259
+ engineRef,
260
+ partition,
261
+ namespaces,
262
+ locale,
263
+ load,
264
+ tryResolveSync,
265
+ keepPrevious
266
+ } = input;
267
+ const request = {
268
+ engineRef,
269
+ partition,
270
+ namespaces,
271
+ load,
272
+ ...tryResolveSync !== void 0 ? { tryResolveSync } : {}
273
+ };
274
+ return useSyncExternalStore(
275
+ coordinator.subscribe,
276
+ () => {
277
+ coordinator.ensure(request);
278
+ return coordinator.getDisplayEntry(
279
+ { engineRef, partition, namespaces },
280
+ {
281
+ locale,
282
+ namespaces,
283
+ ...keepPrevious !== void 0 ? { keepPrevious } : {}
284
+ }
285
+ );
286
+ },
287
+ () => {
288
+ coordinator.ensure(request);
289
+ return coordinator.getDisplayEntry(
290
+ { engineRef, partition, namespaces },
291
+ {
292
+ locale,
293
+ namespaces,
294
+ ...keepPrevious !== void 0 ? { keepPrevious } : {}
295
+ }
296
+ );
297
+ }
298
+ );
299
+ }
300
+ function resolveNamespaces(namespaces) {
301
+ if (namespaces === void 0 || namespaces.length === 0) {
302
+ throw new Error("withI18n / I18n: requires a non-empty namespaces list");
303
+ }
304
+ return namespaces;
305
+ }
306
+ function renderDisplayEntry(entry, options) {
307
+ if (entry.status === "ready") {
308
+ return options.children({ t: entry.t, locale: entry.locale });
309
+ }
310
+ if (entry.status === "error" && entry.t === null) {
311
+ if (options.renderError) {
312
+ return options.renderError({ error: entry.error, retry: entry.retry });
313
+ }
314
+ return options.fallback ?? null;
315
+ }
316
+ if (entry.t !== null && entry.display) {
317
+ const value = {
318
+ t: entry.t,
319
+ locale: entry.display.locale
320
+ };
321
+ if (entry.pendingLocale !== void 0) {
322
+ value.pendingLocale = entry.pendingLocale;
323
+ }
324
+ if (entry.status === "error") {
325
+ value.error = entry.error;
326
+ value.retry = entry.retry;
327
+ }
328
+ return options.children(value);
329
+ }
330
+ return options.fallback ?? null;
331
+ }
332
+ function createI18nLoadGate(options) {
333
+ const { useLoadArgs } = options;
334
+ const keepPrevious = options.keepPreviousOnPartitionChange !== false;
335
+ function useGateEntry(namespaces) {
336
+ const resolved = resolveNamespaces(namespaces);
337
+ const args = useLoadArgs();
338
+ return useNamespaceLoad({
339
+ coordinator: args.coordinator,
340
+ engineRef: args.engineRef,
341
+ partition: args.partition,
342
+ namespaces: resolved,
343
+ locale: args.locale,
344
+ load: () => args.load(resolved),
345
+ ...args.tryResolveSync !== void 0 ? { tryResolveSync: () => args.tryResolveSync(resolved) } : {},
346
+ keepPrevious
347
+ });
348
+ }
349
+ function I18n({ namespaces, fallback, renderError, children }) {
350
+ const entry = useGateEntry(namespaces);
351
+ return renderDisplayEntry(entry, {
352
+ ...fallback !== void 0 ? { fallback } : {},
353
+ ...renderError !== void 0 ? { renderError } : {},
354
+ children
355
+ });
356
+ }
357
+ function withI18n(hocOptions, render) {
358
+ const { fallback, renderError } = hocOptions;
359
+ const Outer = forwardRef(function I18nHocOuter(props, ref) {
360
+ const entry = useGateEntry(hocOptions.namespaces);
361
+ const needsFallback = entry.status !== "ready" && entry.t === null;
362
+ const resolvedFallback = !needsFallback || fallback === void 0 ? void 0 : typeof fallback === "function" ? fallback(props) : fallback;
363
+ return renderDisplayEntry(entry, {
364
+ ...resolvedFallback !== void 0 ? { fallback: resolvedFallback } : {},
365
+ ...renderError !== void 0 ? {
366
+ renderError: ({ error, retry }) => renderError({ error, retry, props })
367
+ } : {},
368
+ children: (value) => render(props, value, ref)
369
+ });
370
+ });
371
+ const displayName = render.name || "Component";
372
+ Outer.displayName = `withI18n(${displayName})`;
373
+ return Outer;
374
+ }
375
+ return { I18n, withI18n };
376
+ }
377
+ var I18nRootContext = createContext(null);
378
+ function useI18nRootContext() {
379
+ const value = useContext(I18nRootContext);
380
+ if (value === null) {
381
+ throw new Error("useI18nRoot must be used within I18nRoot / I18nRootProvider");
382
+ }
383
+ return value;
384
+ }
385
+ function I18nRootProvider({
386
+ createI18n,
387
+ locale,
388
+ dictionary,
389
+ state,
390
+ onMissing,
391
+ fetchImpl,
392
+ children
393
+ }) {
394
+ if (state !== void 0 && dictionary !== void 0) {
395
+ throw new Error("I18nRootProvider: pass either `state` or `dictionary`, not both.");
396
+ }
397
+ const valueRef = useRef(null);
398
+ if (valueRef.current === null) {
399
+ const seed = state ?? { dictionary: dictionary ?? {} };
400
+ const handle = createI18n({
401
+ state: seed,
402
+ ...onMissing !== void 0 ? { onMissing } : {},
403
+ ...fetchImpl !== void 0 ? { fetchImpl } : {}
404
+ });
405
+ valueRef.current = {
406
+ handle,
407
+ coordinator: createLoadCoordinator(),
408
+ locale
409
+ };
410
+ } else {
411
+ valueRef.current.locale = locale;
412
+ }
413
+ return /* @__PURE__ */ jsx(I18nRootContext.Provider, { value: valueRef.current, children });
414
+ }
415
+
416
+ export { I18nRootProvider, bindNamespaceTranslate, createI18nLoadGate, createLoadCoordinator, createScopedT, useI18nRootContext, useNamespaceLoad };
417
+ //# sourceMappingURL=index.js.map
418
+ //# sourceMappingURL=index.js.map