@let-value/translate-react 1.1.6-beta.3 → 1.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -4,12 +4,118 @@ let react = require("react");
4
4
  //#region src/context.ts
5
5
  const localeContext = (0, react.createContext)(void 0);
6
6
  const translatorContext = (0, react.createContext)(void 0);
7
+ const dehydrationContext = (0, react.createContext)(void 0);
7
8
  //#endregion
8
9
  //#region src/components/LocaleProvider.ts
9
10
  function LocaleProvider({ locale, children }) {
10
11
  return (0, react.createElement)(localeContext.Provider, { value: locale }, children);
11
12
  }
12
13
  //#endregion
14
+ //#region src/dehydration.ts
15
+ /**
16
+ * Attribute that marks an inlined catalog. The value is the `useId()` of the
17
+ * `TranslationsProvider` that emitted it — React guarantees that id is identical
18
+ * between the server render and the hydration render of the same tree position.
19
+ */
20
+ const DEHYDRATION_ATTRIBUTE = "data-translations";
21
+ /** Locale keys of a `translations` map, sorted so server and client agree. */
22
+ function localeKeys(translations) {
23
+ return Object.keys(translations).sort();
24
+ }
25
+ /** True when resolving `locale` from `translations` requires awaiting a loader. */
26
+ function isAsyncLocale(translations, locale) {
27
+ if (!locale) return false;
28
+ const entry = translations[locale];
29
+ if (!entry) return false;
30
+ return typeof entry === "function" || "then" in entry;
31
+ }
32
+ /**
33
+ * `<\/script` is the only sequence that can terminate the script element early;
34
+ * escaping `<` as a JSON unicode escape keeps the text byte-identical to what
35
+ * `textContent` yields on the client, so hydration matches exactly.
36
+ */
37
+ function encode(payload) {
38
+ return JSON.stringify(payload).replaceAll("<", "\\u003c");
39
+ }
40
+ function dehydrate(keys, locale, catalog) {
41
+ if (!catalog) return void 0;
42
+ return encode({
43
+ k: keys,
44
+ l: locale,
45
+ c: catalog
46
+ });
47
+ }
48
+ /** Locales already seeded from the document, so a hit costs one DOM query. */
49
+ const seeded = /* @__PURE__ */ new WeakMap();
50
+ function markSeeded(translator, locale) {
51
+ let locales = seeded.get(translator);
52
+ if (!locales) {
53
+ locales = /* @__PURE__ */ new Set();
54
+ seeded.set(translator, locales);
55
+ }
56
+ locales.add(locale);
57
+ }
58
+ const warned = /* @__PURE__ */ new Set();
59
+ /**
60
+ * Two React roots on one page share the `useId` sequence unless they were given
61
+ * distinct `identifierPrefix` values, so their providers can emit scripts under
62
+ * the same id. There is no way to tell from render which one belongs to the tree
63
+ * currently hydrating, so refuse to guess: seeding is skipped and the catalog
64
+ * loads lazily, exactly as it did before this mechanism existed.
65
+ */
66
+ function isAmbiguous(id, count) {
67
+ if (count <= 1) return false;
68
+ if (!warned.has(id)) {
69
+ warned.add(id);
70
+ console.warn(`Found ${count} inlined translation catalogs for id "${id}". Give each React root a distinct \`identifierPrefix\` so their ids cannot collide. Falling back to loading the catalog.`);
71
+ }
72
+ return true;
73
+ }
74
+ /**
75
+ * Seed one provider's translator from the catalog the server inlined for it.
76
+ *
77
+ * Returns the raw payload text when one was applied, so the emitter can render an
78
+ * identical `<script>` back and keep hydration byte-for-byte stable.
79
+ *
80
+ * `useId` is stable across a server render and its hydration, but a provider
81
+ * mounted later by client-side navigation gets a fresh id that could collide with
82
+ * an id from the server tree. The locale keys in the payload guard against that:
83
+ * a mismatch is treated as a miss, never as a match.
84
+ */
85
+ function readPayload(entry, locale) {
86
+ if (typeof document === "undefined") return void 0;
87
+ if (seeded.get(entry.translator)?.has(locale)) return void 0;
88
+ const scripts = document.querySelectorAll(`script[${DEHYDRATION_ATTRIBUTE}="${entry.id}"]`);
89
+ if (isAmbiguous(entry.id, scripts.length)) return void 0;
90
+ const text = scripts[0]?.textContent;
91
+ if (!text) return void 0;
92
+ let payload;
93
+ try {
94
+ payload = JSON.parse(text);
95
+ } catch {
96
+ return;
97
+ }
98
+ if (payload.l !== locale) return void 0;
99
+ if (payload.k.length !== entry.keys.length || payload.k.some((key, index) => key !== entry.keys[index])) return;
100
+ entry.translator.prime(locale, payload.c);
101
+ markSeeded(entry.translator, locale);
102
+ return text;
103
+ }
104
+ /**
105
+ * Seed a whole provider chain, outermost first — a child's `getLocale` merges
106
+ * against its parent, so the parent has to be resolved by the time the child is.
107
+ *
108
+ * Called from `useTranslations` before it touches `fetchLocale`, which is what
109
+ * lets the app put its Suspense boundary anywhere: the consumer seeds itself at
110
+ * the exact moment it renders, whether that is with the provider or inside a
111
+ * boundary that hydrates much later.
112
+ */
113
+ function seedFromDocument(entry, locale) {
114
+ if (!entry) return;
115
+ seedFromDocument(entry.parent, locale);
116
+ readPayload(entry, locale);
117
+ }
118
+ //#endregion
13
119
  //#region src/hooks/useTranslations.ts
14
120
  /** @deprecated replace with `use` from react */
15
121
  function getPromiseState(promise) {
@@ -39,7 +145,9 @@ function getPromiseState(promise) {
39
145
  function useTranslations(locale) {
40
146
  const requestedLocale = locale ?? (0, react.use)(localeContext) ?? "unknown";
41
147
  const translator = (0, react.use)(translatorContext);
148
+ const dehydrated = (0, react.use)(dehydrationContext);
42
149
  if (!translator) throw new Error("TranslationsProvider is missing");
150
+ seedFromDocument(dehydrated, requestedLocale);
43
151
  const resource = translator.fetchLocale(requestedLocale);
44
152
  if (!(resource instanceof Promise)) return resource;
45
153
  const state = getPromiseState(resource);
@@ -93,7 +201,8 @@ function Message({ context, children }) {
93
201
  if (context) return translator.context(context).message(input);
94
202
  return translator.message(input);
95
203
  }
96
- const built = (0, _let_value_translate.message)(strings, ...values.map((_, i) => `\u0000${i}\u0000`));
204
+ const tokens = values.map((_, i) => `\u0000${i}\u0000`);
205
+ const built = (0, _let_value_translate.message)(strings, ...tokens);
97
206
  const parts = (context ? translator.translate({
98
207
  context,
99
208
  id: built
@@ -112,12 +221,14 @@ function Plural({ number, forms, context }) {
112
221
  const translator = useTranslations();
113
222
  const built = forms.map((child, i) => {
114
223
  const { strings, values } = buildTemplateFromChildren(child);
224
+ const tokens = values.map((_, j) => `\u0000${i}-${j}\u0000`);
115
225
  return {
116
- message: (0, _let_value_translate.message)(strings, ...values.map((_, j) => `\u0000${i}-${j}\u0000`)),
226
+ message: (0, _let_value_translate.message)(strings, ...tokens),
117
227
  values
118
228
  };
119
229
  });
120
- const input = (0, _let_value_translate.plural)(...built.map((b) => b.message), number);
230
+ const messages = built.map((b) => b.message);
231
+ const input = (0, _let_value_translate.plural)(...messages, number);
121
232
  const parts = (context ? translator.translate({
122
233
  context,
123
234
  id: input
@@ -187,9 +298,52 @@ function getCachedTranslator(translations, parent) {
187
298
  //#endregion
188
299
  //#region src/components/TranslationsProvider.ts
189
300
  const EMPTY_TRANSLATIONS = {};
301
+ /**
302
+ * Inlines the catalog for the active locale as a `<script type="application/json">`.
303
+ *
304
+ * Renders no children, which is the whole point: the provider wraps it in a
305
+ * Suspense boundary that contains nothing of the app's, so awaiting the catalog
306
+ * here can never intercept a fallback the app placed for its own content. The
307
+ * app's boundaries — inside the provider or outside it — behave exactly as
308
+ * written, and `useTranslations` still suspends where it always did.
309
+ */
310
+ function DehydrationScript({ entry, locale }) {
311
+ let text = readPayload(entry, locale);
312
+ if (!text) {
313
+ const resource = entry.translator.fetchLocale(locale);
314
+ if (resource instanceof Promise) (0, react.use)(resource);
315
+ text = dehydrate(entry.keys, locale, entry.translator.dehydrate(locale));
316
+ }
317
+ if (!text) return null;
318
+ return (0, react.createElement)("script", {
319
+ type: "application/json",
320
+ [DEHYDRATION_ATTRIBUTE]: entry.id,
321
+ dangerouslySetInnerHTML: { __html: text }
322
+ });
323
+ }
190
324
  function TranslationsProvider({ translations = EMPTY_TRANSLATIONS, children }) {
191
- const translator = getCachedTranslator(translations, (0, react.use)(translatorContext));
192
- return (0, react.createElement)(translatorContext.Provider, { value: translator }, children);
325
+ const id = (0, react.useId)();
326
+ const parent = (0, react.use)(translatorContext);
327
+ const parentEntry = (0, react.use)(dehydrationContext);
328
+ const translator = getCachedTranslator(translations, parent);
329
+ const locale = (0, react.use)(localeContext);
330
+ const entry = (0, react.useMemo)(() => ({
331
+ id,
332
+ translator,
333
+ keys: localeKeys(translations),
334
+ parent: parentEntry
335
+ }), [
336
+ id,
337
+ translator,
338
+ translations,
339
+ parentEntry
340
+ ]);
341
+ const provide = (inner) => (0, react.createElement)(translatorContext.Provider, { value: translator }, inner);
342
+ if (!isAsyncLocale(translations, locale)) return provide(children);
343
+ return provide((0, react.createElement)(dehydrationContext.Provider, { value: entry }, (0, react.createElement)(react.Fragment, null, (0, react.createElement)(react.Suspense, { fallback: null }, (0, react.createElement)(DehydrationScript, {
344
+ entry,
345
+ locale
346
+ })), children)));
193
347
  }
194
348
  //#endregion
195
349
  //#region src/hooks/useLocale.ts
package/dist/index.d.cts CHANGED
@@ -1,26 +1,19 @@
1
1
  import { Locale, LocaleTranslator, TranslationEntry } from "@let-value/translate";
2
2
  import { ReactElement, ReactNode } from "react";
3
3
  export * from "@let-value/translate";
4
-
5
4
  //#region src/components/LocaleProvider.d.ts
6
5
  interface LocaleProviderProps {
7
6
  locale: Locale;
8
7
  children?: ReactNode;
9
8
  }
10
- declare function LocaleProvider({
11
- locale,
12
- children
13
- }: LocaleProviderProps): import("react").FunctionComponentElement<import("react").ProviderProps<Locale | undefined>>;
9
+ declare function LocaleProvider({ locale, children }: LocaleProviderProps): import("react").FunctionComponentElement<import("react").ProviderProps<Locale | undefined>>;
14
10
  //#endregion
15
11
  //#region src/components/Message.d.ts
16
12
  interface MessageProps {
17
13
  context?: string;
18
14
  children: ReactNode;
19
15
  }
20
- declare function Message({
21
- context,
22
- children
23
- }: MessageProps): string | import("react").FunctionComponentElement<import("react").FragmentProps>;
16
+ declare function Message({ context, children }: MessageProps): string | import("react").FunctionComponentElement<import("react").FragmentProps>;
24
17
  //#endregion
25
18
  //#region src/components/Plural.d.ts
26
19
  interface PluralProps {
@@ -28,21 +21,14 @@ interface PluralProps {
28
21
  forms: readonly ReactNode[];
29
22
  context?: string;
30
23
  }
31
- declare function Plural({
32
- number,
33
- forms,
34
- context
35
- }: PluralProps): import("react").FunctionComponentElement<import("react").FragmentProps>;
24
+ declare function Plural({ number, forms, context }: PluralProps): import("react").FunctionComponentElement<import("react").FragmentProps>;
36
25
  //#endregion
37
26
  //#region src/components/TranslationsProvider.d.ts
38
27
  interface TranslationsProviderProps {
39
28
  translations?: Partial<Record<Locale, TranslationEntry>>;
40
29
  children?: ReactNode;
41
30
  }
42
- declare function TranslationsProvider({
43
- translations,
44
- children
45
- }: TranslationsProviderProps): ReactElement;
31
+ declare function TranslationsProvider({ translations, children }: TranslationsProviderProps): ReactElement;
46
32
  //#endregion
47
33
  //#region src/hooks/useLocale.d.ts
48
34
  declare function useLocale(): Locale | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../src/components/LocaleProvider.ts","../src/components/Message.ts","../src/components/Plural.ts","../src/components/TranslationsProvider.ts","../src/hooks/useLocale.ts","../src/hooks/useTranslations.ts"],"mappings":";;;;;UAIiB,mBAAA;EACb,MAAA,EAAQ,MAAA;EACR,QAAA,GAAW,SAAS;AAAA;AAAA,iBAGR,cAAA;EAAiB,MAAA;EAAQ;AAAA,GAAY,mBAAA,mBAAmB,wBAAA,iBAAA,aAAA,CAAA,MAAA;;;UCHvD,YAAA;EACb,OAAA;EACA,QAAA,EAAU,SAAS;AAAA;AAAA,iBAGP,OAAA;EAAU,OAAA;EAAS;AAAA,GAAY,YAAA,4BAAY,wBAAA,iBAAA,aAAA;;;UCL1C,WAAA;EACb,MAAA;EACA,KAAA,WAAgB,SAAS;EACzB,OAAA;AAAA;AAAA,iBAGY,MAAA;EAAS,MAAA;EAAQ,KAAA;EAAO;AAAA,GAAW,WAAA,mBAAW,wBAAA,iBAAA,aAAA;;;UCP7C,yBAAA;EACb,YAAA,GAAe,OAAA,CAAQ,MAAA,CAAO,MAAA,EAAQ,gBAAA;EACtC,QAAA,GAAW,SAAA;AAAA;AAAA,iBAKC,oBAAA;EACZ,YAAA;EACA;AAAA,GACD,yBAAA,GAA4B,YAAA;;;iBCXf,SAAA,IAAa,MAAM;;;iBCyBnB,eAAA,CAAgB,MAAA,GAAS,MAAA,GAAS,gBAAgB"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../src/components/LocaleProvider.ts","../src/components/Message.ts","../src/components/Plural.ts","../src/components/TranslationsProvider.ts","../src/hooks/useLocale.ts","../src/hooks/useTranslations.ts"],"mappings":";;;;UAIiB;EACb,QAAQ;EACR,WAAW;;iBAGC,iBAAiB,QAAQ,YAAY,sCAAmB,yCAAA,cAAA;;;UCHvD;EACb;EACA,UAAU;;iBAGE,UAAU,SAAS,YAAY,wCAAY,yCAAA;;;UCL1C;EACb;EACA,gBAAgB;EAChB;;iBAGY,SAAS,QAAQ,OAAO,WAAW,8BAAW,yCAAA;;;UCN7C;EACb,eAAe,QAAQ,OAAO,QAAQ;EACtC,WAAW;;iBA0CC,uBACZ,cACA,YACD,4BAA4B;;;iBCjDf,aAAa;;;iBC0Bb,gBAAgB,SAAS,SAAS"}
package/dist/index.d.mts CHANGED
@@ -1,26 +1,19 @@
1
1
  import { Locale, LocaleTranslator, TranslationEntry } from "@let-value/translate";
2
2
  import { ReactElement, ReactNode } from "react";
3
3
  export * from "@let-value/translate";
4
-
5
4
  //#region src/components/LocaleProvider.d.ts
6
5
  interface LocaleProviderProps {
7
6
  locale: Locale;
8
7
  children?: ReactNode;
9
8
  }
10
- declare function LocaleProvider({
11
- locale,
12
- children
13
- }: LocaleProviderProps): import("react").FunctionComponentElement<import("react").ProviderProps<Locale | undefined>>;
9
+ declare function LocaleProvider({ locale, children }: LocaleProviderProps): import("react").FunctionComponentElement<import("react").ProviderProps<Locale | undefined>>;
14
10
  //#endregion
15
11
  //#region src/components/Message.d.ts
16
12
  interface MessageProps {
17
13
  context?: string;
18
14
  children: ReactNode;
19
15
  }
20
- declare function Message({
21
- context,
22
- children
23
- }: MessageProps): string | import("react").FunctionComponentElement<import("react").FragmentProps>;
16
+ declare function Message({ context, children }: MessageProps): string | import("react").FunctionComponentElement<import("react").FragmentProps>;
24
17
  //#endregion
25
18
  //#region src/components/Plural.d.ts
26
19
  interface PluralProps {
@@ -28,21 +21,14 @@ interface PluralProps {
28
21
  forms: readonly ReactNode[];
29
22
  context?: string;
30
23
  }
31
- declare function Plural({
32
- number,
33
- forms,
34
- context
35
- }: PluralProps): import("react").FunctionComponentElement<import("react").FragmentProps>;
24
+ declare function Plural({ number, forms, context }: PluralProps): import("react").FunctionComponentElement<import("react").FragmentProps>;
36
25
  //#endregion
37
26
  //#region src/components/TranslationsProvider.d.ts
38
27
  interface TranslationsProviderProps {
39
28
  translations?: Partial<Record<Locale, TranslationEntry>>;
40
29
  children?: ReactNode;
41
30
  }
42
- declare function TranslationsProvider({
43
- translations,
44
- children
45
- }: TranslationsProviderProps): ReactElement;
31
+ declare function TranslationsProvider({ translations, children }: TranslationsProviderProps): ReactElement;
46
32
  //#endregion
47
33
  //#region src/hooks/useLocale.d.ts
48
34
  declare function useLocale(): Locale | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/components/LocaleProvider.ts","../src/components/Message.ts","../src/components/Plural.ts","../src/components/TranslationsProvider.ts","../src/hooks/useLocale.ts","../src/hooks/useTranslations.ts"],"mappings":";;;;;UAIiB,mBAAA;EACb,MAAA,EAAQ,MAAA;EACR,QAAA,GAAW,SAAS;AAAA;AAAA,iBAGR,cAAA;EAAiB,MAAA;EAAQ;AAAA,GAAY,mBAAA,mBAAmB,wBAAA,iBAAA,aAAA,CAAA,MAAA;;;UCHvD,YAAA;EACb,OAAA;EACA,QAAA,EAAU,SAAS;AAAA;AAAA,iBAGP,OAAA;EAAU,OAAA;EAAS;AAAA,GAAY,YAAA,4BAAY,wBAAA,iBAAA,aAAA;;;UCL1C,WAAA;EACb,MAAA;EACA,KAAA,WAAgB,SAAS;EACzB,OAAA;AAAA;AAAA,iBAGY,MAAA;EAAS,MAAA;EAAQ,KAAA;EAAO;AAAA,GAAW,WAAA,mBAAW,wBAAA,iBAAA,aAAA;;;UCP7C,yBAAA;EACb,YAAA,GAAe,OAAA,CAAQ,MAAA,CAAO,MAAA,EAAQ,gBAAA;EACtC,QAAA,GAAW,SAAA;AAAA;AAAA,iBAKC,oBAAA;EACZ,YAAA;EACA;AAAA,GACD,yBAAA,GAA4B,YAAA;;;iBCXf,SAAA,IAAa,MAAM;;;iBCyBnB,eAAA,CAAgB,MAAA,GAAS,MAAA,GAAS,gBAAgB"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/components/LocaleProvider.ts","../src/components/Message.ts","../src/components/Plural.ts","../src/components/TranslationsProvider.ts","../src/hooks/useLocale.ts","../src/hooks/useTranslations.ts"],"mappings":";;;;UAIiB;EACb,QAAQ;EACR,WAAW;;iBAGC,iBAAiB,QAAQ,YAAY,sCAAmB,yCAAA,cAAA;;;UCHvD;EACb;EACA,UAAU;;iBAGE,UAAU,SAAS,YAAY,wCAAY,yCAAA;;;UCL1C;EACb;EACA,gBAAgB;EAChB;;iBAGY,SAAS,QAAQ,OAAO,WAAW,8BAAW,yCAAA;;;UCN7C;EACb,eAAe,QAAQ,OAAO,QAAQ;EACtC,WAAW;;iBA0CC,uBACZ,cACA,YACD,4BAA4B;;;iBCjDf,aAAa;;;iBC0Bb,gBAAgB,SAAS,SAAS"}
package/dist/index.mjs CHANGED
@@ -1,15 +1,121 @@
1
1
  import { Translator, message, plural } from "@let-value/translate";
2
- import { Children, Fragment, createContext, createElement, isValidElement, use } from "react";
2
+ import { Children, Fragment, Suspense, createContext, createElement, isValidElement, use, useId, useMemo } from "react";
3
3
  export * from "@let-value/translate";
4
4
  //#region src/context.ts
5
5
  const localeContext = createContext(void 0);
6
6
  const translatorContext = createContext(void 0);
7
+ const dehydrationContext = createContext(void 0);
7
8
  //#endregion
8
9
  //#region src/components/LocaleProvider.ts
9
10
  function LocaleProvider({ locale, children }) {
10
11
  return createElement(localeContext.Provider, { value: locale }, children);
11
12
  }
12
13
  //#endregion
14
+ //#region src/dehydration.ts
15
+ /**
16
+ * Attribute that marks an inlined catalog. The value is the `useId()` of the
17
+ * `TranslationsProvider` that emitted it — React guarantees that id is identical
18
+ * between the server render and the hydration render of the same tree position.
19
+ */
20
+ const DEHYDRATION_ATTRIBUTE = "data-translations";
21
+ /** Locale keys of a `translations` map, sorted so server and client agree. */
22
+ function localeKeys(translations) {
23
+ return Object.keys(translations).sort();
24
+ }
25
+ /** True when resolving `locale` from `translations` requires awaiting a loader. */
26
+ function isAsyncLocale(translations, locale) {
27
+ if (!locale) return false;
28
+ const entry = translations[locale];
29
+ if (!entry) return false;
30
+ return typeof entry === "function" || "then" in entry;
31
+ }
32
+ /**
33
+ * `<\/script` is the only sequence that can terminate the script element early;
34
+ * escaping `<` as a JSON unicode escape keeps the text byte-identical to what
35
+ * `textContent` yields on the client, so hydration matches exactly.
36
+ */
37
+ function encode(payload) {
38
+ return JSON.stringify(payload).replaceAll("<", "\\u003c");
39
+ }
40
+ function dehydrate(keys, locale, catalog) {
41
+ if (!catalog) return void 0;
42
+ return encode({
43
+ k: keys,
44
+ l: locale,
45
+ c: catalog
46
+ });
47
+ }
48
+ /** Locales already seeded from the document, so a hit costs one DOM query. */
49
+ const seeded = /* @__PURE__ */ new WeakMap();
50
+ function markSeeded(translator, locale) {
51
+ let locales = seeded.get(translator);
52
+ if (!locales) {
53
+ locales = /* @__PURE__ */ new Set();
54
+ seeded.set(translator, locales);
55
+ }
56
+ locales.add(locale);
57
+ }
58
+ const warned = /* @__PURE__ */ new Set();
59
+ /**
60
+ * Two React roots on one page share the `useId` sequence unless they were given
61
+ * distinct `identifierPrefix` values, so their providers can emit scripts under
62
+ * the same id. There is no way to tell from render which one belongs to the tree
63
+ * currently hydrating, so refuse to guess: seeding is skipped and the catalog
64
+ * loads lazily, exactly as it did before this mechanism existed.
65
+ */
66
+ function isAmbiguous(id, count) {
67
+ if (count <= 1) return false;
68
+ if (!warned.has(id)) {
69
+ warned.add(id);
70
+ console.warn(`Found ${count} inlined translation catalogs for id "${id}". Give each React root a distinct \`identifierPrefix\` so their ids cannot collide. Falling back to loading the catalog.`);
71
+ }
72
+ return true;
73
+ }
74
+ /**
75
+ * Seed one provider's translator from the catalog the server inlined for it.
76
+ *
77
+ * Returns the raw payload text when one was applied, so the emitter can render an
78
+ * identical `<script>` back and keep hydration byte-for-byte stable.
79
+ *
80
+ * `useId` is stable across a server render and its hydration, but a provider
81
+ * mounted later by client-side navigation gets a fresh id that could collide with
82
+ * an id from the server tree. The locale keys in the payload guard against that:
83
+ * a mismatch is treated as a miss, never as a match.
84
+ */
85
+ function readPayload(entry, locale) {
86
+ if (typeof document === "undefined") return void 0;
87
+ if (seeded.get(entry.translator)?.has(locale)) return void 0;
88
+ const scripts = document.querySelectorAll(`script[${DEHYDRATION_ATTRIBUTE}="${entry.id}"]`);
89
+ if (isAmbiguous(entry.id, scripts.length)) return void 0;
90
+ const text = scripts[0]?.textContent;
91
+ if (!text) return void 0;
92
+ let payload;
93
+ try {
94
+ payload = JSON.parse(text);
95
+ } catch {
96
+ return;
97
+ }
98
+ if (payload.l !== locale) return void 0;
99
+ if (payload.k.length !== entry.keys.length || payload.k.some((key, index) => key !== entry.keys[index])) return;
100
+ entry.translator.prime(locale, payload.c);
101
+ markSeeded(entry.translator, locale);
102
+ return text;
103
+ }
104
+ /**
105
+ * Seed a whole provider chain, outermost first — a child's `getLocale` merges
106
+ * against its parent, so the parent has to be resolved by the time the child is.
107
+ *
108
+ * Called from `useTranslations` before it touches `fetchLocale`, which is what
109
+ * lets the app put its Suspense boundary anywhere: the consumer seeds itself at
110
+ * the exact moment it renders, whether that is with the provider or inside a
111
+ * boundary that hydrates much later.
112
+ */
113
+ function seedFromDocument(entry, locale) {
114
+ if (!entry) return;
115
+ seedFromDocument(entry.parent, locale);
116
+ readPayload(entry, locale);
117
+ }
118
+ //#endregion
13
119
  //#region src/hooks/useTranslations.ts
14
120
  /** @deprecated replace with `use` from react */
15
121
  function getPromiseState(promise) {
@@ -39,7 +145,9 @@ function getPromiseState(promise) {
39
145
  function useTranslations(locale) {
40
146
  const requestedLocale = locale ?? use(localeContext) ?? "unknown";
41
147
  const translator = use(translatorContext);
148
+ const dehydrated = use(dehydrationContext);
42
149
  if (!translator) throw new Error("TranslationsProvider is missing");
150
+ seedFromDocument(dehydrated, requestedLocale);
43
151
  const resource = translator.fetchLocale(requestedLocale);
44
152
  if (!(resource instanceof Promise)) return resource;
45
153
  const state = getPromiseState(resource);
@@ -93,7 +201,8 @@ function Message({ context, children }) {
93
201
  if (context) return translator.context(context).message(input);
94
202
  return translator.message(input);
95
203
  }
96
- const built = message(strings, ...values.map((_, i) => `\u0000${i}\u0000`));
204
+ const tokens = values.map((_, i) => `\u0000${i}\u0000`);
205
+ const built = message(strings, ...tokens);
97
206
  const parts = (context ? translator.translate({
98
207
  context,
99
208
  id: built
@@ -112,12 +221,14 @@ function Plural({ number, forms, context }) {
112
221
  const translator = useTranslations();
113
222
  const built = forms.map((child, i) => {
114
223
  const { strings, values } = buildTemplateFromChildren(child);
224
+ const tokens = values.map((_, j) => `\u0000${i}-${j}\u0000`);
115
225
  return {
116
- message: message(strings, ...values.map((_, j) => `\u0000${i}-${j}\u0000`)),
226
+ message: message(strings, ...tokens),
117
227
  values
118
228
  };
119
229
  });
120
- const input = plural(...built.map((b) => b.message), number);
230
+ const messages = built.map((b) => b.message);
231
+ const input = plural(...messages, number);
121
232
  const parts = (context ? translator.translate({
122
233
  context,
123
234
  id: input
@@ -187,9 +298,52 @@ function getCachedTranslator(translations, parent) {
187
298
  //#endregion
188
299
  //#region src/components/TranslationsProvider.ts
189
300
  const EMPTY_TRANSLATIONS = {};
301
+ /**
302
+ * Inlines the catalog for the active locale as a `<script type="application/json">`.
303
+ *
304
+ * Renders no children, which is the whole point: the provider wraps it in a
305
+ * Suspense boundary that contains nothing of the app's, so awaiting the catalog
306
+ * here can never intercept a fallback the app placed for its own content. The
307
+ * app's boundaries — inside the provider or outside it — behave exactly as
308
+ * written, and `useTranslations` still suspends where it always did.
309
+ */
310
+ function DehydrationScript({ entry, locale }) {
311
+ let text = readPayload(entry, locale);
312
+ if (!text) {
313
+ const resource = entry.translator.fetchLocale(locale);
314
+ if (resource instanceof Promise) use(resource);
315
+ text = dehydrate(entry.keys, locale, entry.translator.dehydrate(locale));
316
+ }
317
+ if (!text) return null;
318
+ return createElement("script", {
319
+ type: "application/json",
320
+ [DEHYDRATION_ATTRIBUTE]: entry.id,
321
+ dangerouslySetInnerHTML: { __html: text }
322
+ });
323
+ }
190
324
  function TranslationsProvider({ translations = EMPTY_TRANSLATIONS, children }) {
191
- const translator = getCachedTranslator(translations, use(translatorContext));
192
- return createElement(translatorContext.Provider, { value: translator }, children);
325
+ const id = useId();
326
+ const parent = use(translatorContext);
327
+ const parentEntry = use(dehydrationContext);
328
+ const translator = getCachedTranslator(translations, parent);
329
+ const locale = use(localeContext);
330
+ const entry = useMemo(() => ({
331
+ id,
332
+ translator,
333
+ keys: localeKeys(translations),
334
+ parent: parentEntry
335
+ }), [
336
+ id,
337
+ translator,
338
+ translations,
339
+ parentEntry
340
+ ]);
341
+ const provide = (inner) => createElement(translatorContext.Provider, { value: translator }, inner);
342
+ if (!isAsyncLocale(translations, locale)) return provide(children);
343
+ return provide(createElement(dehydrationContext.Provider, { value: entry }, createElement(Fragment, null, createElement(Suspense, { fallback: null }, createElement(DehydrationScript, {
344
+ entry,
345
+ locale
346
+ })), children)));
193
347
  }
194
348
  //#endregion
195
349
  //#region src/hooks/useLocale.ts
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/context.ts","../src/components/LocaleProvider.ts","../src/hooks/useTranslations.ts","../src/utils.ts","../src/components/Message.ts","../src/components/Plural.ts","../src/translatorCache.ts","../src/components/TranslationsProvider.ts","../src/hooks/useLocale.ts"],"sourcesContent":["import type { Locale, Translator } from \"@let-value/translate\";\nimport { createContext } from \"react\";\n\ntype AnyTranslator = Translator<any>;\n\nexport const localeContext = createContext<Locale | undefined>(undefined);\nexport const translatorContext = createContext<AnyTranslator | undefined>(undefined);\n","import type { Locale } from \"@let-value/translate\";\nimport { createElement, type ReactNode } from \"react\";\nimport { localeContext } from \"../context.ts\";\n\nexport interface LocaleProviderProps {\n locale: Locale;\n children?: ReactNode;\n}\n\nexport function LocaleProvider({ locale, children }: LocaleProviderProps) {\n return createElement(localeContext.Provider, { value: locale }, children);\n}\n","import type { Locale, LocaleTranslator } from \"@let-value/translate\";\nimport { use } from \"react\";\n\nimport { localeContext, translatorContext } from \"../context.ts\";\n\n/** @deprecated replace with `use` from react */\nfunction getPromiseState(promise: any) {\n switch (promise.status) {\n case \"pending\":\n return { status: \"pending\" };\n case \"fulfilled\":\n return { status: \"fulfilled\", value: promise.value };\n case \"rejected\":\n return { status: \"rejected\", reason: promise.reason };\n default: {\n promise.status = \"pending\";\n promise.then((value: unknown) => {\n promise.status = \"fulfilled\";\n promise.value = value;\n });\n promise.catch((reason: unknown) => {\n promise.status = \"rejected\";\n promise.reason = reason;\n });\n return getPromiseState(promise);\n }\n }\n}\n\nexport function useTranslations(locale?: Locale): LocaleTranslator {\n const requestedLocale = locale ?? use(localeContext) ?? (\"unknown\" as never);\n const translator = use(translatorContext);\n if (!translator) {\n throw new Error(\"TranslationsProvider is missing\");\n }\n\n const resource = translator.fetchLocale(requestedLocale);\n if (!(resource instanceof Promise)) {\n return resource;\n }\n\n const state = getPromiseState(resource);\n if (state.status === \"pending\") {\n throw resource;\n }\n if (state.status === \"rejected\") {\n throw state.reason;\n }\n if (state.status === \"fulfilled\") {\n return state.value;\n }\n\n return use(resource);\n}\n","import { Children, Fragment, isValidElement, type PropsWithChildren, type ReactNode } from \"react\";\n\nexport function buildTemplateFromChildren(children: ReactNode): {\n strings: string[];\n values: ReactNode[];\n} {\n function flatten(nodes: ReactNode): ReactNode[] {\n const result: ReactNode[] = [];\n Children.forEach(nodes, (child) => {\n if (isValidElement(child) && child.type === Fragment) {\n result.push(...flatten((child.props as PropsWithChildren).children));\n } else {\n result.push(child);\n }\n });\n return result;\n }\n\n const array = flatten(children);\n const strings: string[] = [\"\"];\n const values: ReactNode[] = [];\n let expectValue = false;\n\n array.forEach((child) => {\n if (typeof child === \"string\") {\n if (expectValue) {\n values.push(child);\n strings.push(\"\");\n expectValue = false;\n } else {\n strings[strings.length - 1] += child;\n expectValue = true;\n }\n } else if (typeof child === \"number\" || child != null) {\n values.push(child as ReactNode);\n strings.push(\"\");\n expectValue = false;\n }\n });\n\n return { strings, values };\n}\n","import { message } from \"@let-value/translate\";\nimport { createElement, Fragment, type ReactNode } from \"react\";\n\nimport { useTranslations } from \"../hooks/useTranslations.ts\";\nimport { buildTemplateFromChildren } from \"../utils.ts\";\n\nexport interface MessageProps {\n context?: string;\n children: ReactNode;\n}\n\nexport function Message({ context, children }: MessageProps) {\n const translator = useTranslations();\n const { strings, values } = buildTemplateFromChildren(children);\n\n if (values.length === 0) {\n const input = strings.join(\"\");\n if (context) {\n return translator.context(context as \"\").message(input as never);\n }\n return translator.message(input as never);\n }\n\n const tokens = values.map((_, i) => `\\u0000${i}\\u0000`);\n const built = message(strings as unknown as TemplateStringsArray, ...tokens);\n const translated = context\n ? translator.translate({ context: context as \"\", id: built })\n : translator.translate(built);\n\n // oxlint-disable-next-line no-control-regex -- using null separators\n const parts = translated.split(/\\u0000(\\d+)\\u0000/);\n const result: ReactNode[] = [];\n for (let i = 0; i < parts.length; i += 2) {\n result.push(parts[i]);\n const idx = parts[i + 1];\n if (idx !== undefined) {\n result.push(values[Number(idx)]);\n }\n }\n\n return createElement(Fragment, null, ...result);\n}\n","import { message, plural } from \"@let-value/translate\";\nimport { createElement, Fragment, type ReactNode } from \"react\";\n\nimport { useTranslations } from \"../hooks/useTranslations.ts\";\nimport { buildTemplateFromChildren } from \"../utils.ts\";\n\nexport interface PluralProps {\n number: number;\n forms: readonly ReactNode[];\n context?: string;\n}\n\nexport function Plural({ number, forms, context }: PluralProps) {\n const translator = useTranslations();\n\n const built = forms.map((child, i) => {\n const { strings, values } = buildTemplateFromChildren(child);\n const tokens = values.map((_, j) => `\\u0000${i}-${j}\\u0000`);\n return { message: message(strings as unknown as TemplateStringsArray, ...tokens), values };\n });\n\n const messages = built.map((b) => b.message);\n const input = plural(...messages, number);\n\n const translated = context\n ? translator.translate({ context: context as \"\", id: input })\n : translator.translate(input);\n\n // oxlint-disable-next-line no-control-regex -- using null separators\n const parts = translated.split(/\\u0000(\\d+)-(\\d+)\\u0000/);\n const result: ReactNode[] = [];\n for (let i = 0; i < parts.length; ) {\n result.push(parts[i]);\n if (i + 2 < parts.length) {\n const formIndex = Number(parts[i + 1]);\n const valueIndex = Number(parts[i + 2]);\n result.push(built[formIndex].values[valueIndex]);\n }\n i += 3;\n }\n\n return createElement(Fragment, null, ...result);\n}\n","import type { Locale, TranslationEntry } from \"@let-value/translate\";\nimport { Translator } from \"@let-value/translate\";\n\nexport type TranslationsMap = Partial<Record<Locale, TranslationEntry>>;\n\n// Fast path: WeakMap keyed on the translations object reference.\n// GC-safe — entries are collected when the object is no longer reachable.\nconst cacheNoParent = new WeakMap<object, Translator>();\nconst cacheWithParent = new WeakMap<object, WeakMap<Translator, Translator>>();\n\nfunction getFromWeakMap(translations: TranslationsMap, parent: Translator | undefined): Translator | undefined {\n if (!parent) return cacheNoParent.get(translations);\n return cacheWithParent.get(translations)?.get(parent);\n}\n\nfunction setInWeakMap(translations: TranslationsMap, parent: Translator | undefined, translator: Translator): void {\n if (!parent) {\n cacheNoParent.set(translations, translator);\n return;\n }\n let byParent = cacheWithParent.get(translations);\n if (!byParent) {\n byParent = new WeakMap();\n cacheWithParent.set(translations, byParent);\n }\n byParent.set(parent, translator);\n}\n\n// Slow path: bounded structural cache for dynamically-created translations objects\n// (e.g. `translations={{ en: data }}` — new wrapper object, stable value references).\n// Bounded to cap memory usage in infinite-render scenarios.\nconst MAX_STRUCTURAL_ENTRIES = 32;\n\ntype SortedEntry = [Locale, TranslationEntry];\n\ninterface StructuralEntry {\n sortedEntries: SortedEntry[];\n parent: Translator | undefined;\n translator: Translator;\n}\n\nconst structuralCache: StructuralEntry[] = [];\n\nfunction toSortedEntries(translations: TranslationsMap): SortedEntry[] {\n return (Object.entries(translations) as SortedEntry[]).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));\n}\n\nfunction findStructural(sortedEntries: SortedEntry[], parent: Translator | undefined): Translator | undefined {\n for (const entry of structuralCache) {\n if (entry.parent !== parent || entry.sortedEntries.length !== sortedEntries.length) continue;\n if (entry.sortedEntries.every(([k, v], i) => sortedEntries[i][0] === k && sortedEntries[i][1] === v)) {\n return entry.translator;\n }\n }\n return undefined;\n}\n\nexport function getCachedTranslator<T extends TranslationsMap>(\n translations: T,\n parent: Translator | undefined,\n): Translator<T> {\n // Fast path: same object reference\n const cached = getFromWeakMap(translations, parent);\n if (cached) return cached as Translator<T>;\n\n // Slow path: same locale keys and value references, different wrapper object\n const sortedEntries = toSortedEntries(translations);\n const structural = findStructural(sortedEntries, parent);\n if (structural) {\n // Backfill WeakMap so subsequent renders with the same new-object are O(1)\n setInWeakMap(translations, parent, structural);\n return structural as Translator<T>;\n }\n\n const translator = new Translator(translations, parent);\n setInWeakMap(translations, parent, translator);\n structuralCache.unshift({ sortedEntries, parent, translator });\n if (structuralCache.length > MAX_STRUCTURAL_ENTRIES) structuralCache.pop();\n return translator;\n}\n\n/** Exported only for tests — do not use in application code. */\nexport const _structuralCacheSize = (): number => structuralCache.length;\n","import type { Locale, TranslationEntry } from \"@let-value/translate\";\nimport { createElement, type ReactNode, use, type ReactElement } from \"react\";\nimport { translatorContext } from \"../context.ts\";\nimport { getCachedTranslator, type TranslationsMap } from \"../translatorCache.ts\";\n\nexport interface TranslationsProviderProps {\n translations?: Partial<Record<Locale, TranslationEntry>>;\n children?: ReactNode;\n}\n\nconst EMPTY_TRANSLATIONS: TranslationsMap = {};\n\nexport function TranslationsProvider({\n translations = EMPTY_TRANSLATIONS,\n children,\n}: TranslationsProviderProps): ReactElement {\n const parent = use(translatorContext);\n const translator = getCachedTranslator(translations, parent);\n return createElement(translatorContext.Provider, { value: translator }, children);\n}\n","import type { Locale } from \"@let-value/translate\";\nimport { use } from \"react\";\nimport { localeContext } from \"../context.ts\";\n\nexport function useLocale(): Locale | undefined {\n return use(localeContext);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/context.ts","../src/components/LocaleProvider.ts","../src/dehydration.ts","../src/hooks/useTranslations.ts","../src/utils.ts","../src/components/Message.ts","../src/components/Plural.ts","../src/translatorCache.ts","../src/components/TranslationsProvider.ts","../src/hooks/useLocale.ts"],"sourcesContent":["import type { Locale, Translator } from \"@let-value/translate\";\nimport { createContext } from \"react\";\n\ntype AnyTranslator = Translator<any>;\n\n/**\n * A provider's link in the dehydration chain, walked from `useTranslations` so a\n * consumer can seed itself — and every translator it merges against — from the\n * server payload before it would otherwise suspend.\n *\n * Only providers with a lazily-loaded catalog add a link; sync ones are simply\n * skipped, so a child links straight to its nearest lazy ancestor.\n */\nexport interface DehydrationEntry {\n /** `useId()` of the provider, matching the inlined script's attribute. */\n id: string;\n translator: AnyTranslator;\n /** Sorted locale keys of the provider's `translations` prop. */\n keys: string[];\n parent: DehydrationEntry | undefined;\n}\n\nexport const localeContext = createContext<Locale | undefined>(undefined);\nexport const translatorContext = createContext<AnyTranslator | undefined>(undefined);\nexport const dehydrationContext = createContext<DehydrationEntry | undefined>(undefined);\n","import type { Locale } from \"@let-value/translate\";\nimport { createElement, type ReactNode } from \"react\";\nimport { localeContext } from \"../context.ts\";\n\nexport interface LocaleProviderProps {\n locale: Locale;\n children?: ReactNode;\n}\n\nexport function LocaleProvider({ locale, children }: LocaleProviderProps) {\n return createElement(localeContext.Provider, { value: locale }, children);\n}\n","import type { Locale, Translator } from \"@let-value/translate\";\nimport type { GetTextTranslations } from \"gettext-parser\";\nimport type { DehydrationEntry } from \"./context.ts\";\nimport type { TranslationsMap } from \"./translatorCache.ts\";\n\n/**\n * Attribute that marks an inlined catalog. The value is the `useId()` of the\n * `TranslationsProvider` that emitted it — React guarantees that id is identical\n * between the server render and the hydration render of the same tree position.\n */\nexport const DEHYDRATION_ATTRIBUTE = \"data-translations\";\n\ninterface DehydratedPayload {\n /** Sorted locale keys of the provider's `translations` prop. */\n k: string[];\n /** Locale the catalog was resolved for. */\n l: string;\n /** The catalog itself. */\n c: GetTextTranslations;\n}\n\n/** Locale keys of a `translations` map, sorted so server and client agree. */\nexport function localeKeys(translations: TranslationsMap): string[] {\n return Object.keys(translations).sort();\n}\n\n/** True when resolving `locale` from `translations` requires awaiting a loader. */\nexport function isAsyncLocale(translations: TranslationsMap, locale: Locale | undefined): boolean {\n if (!locale) return false;\n const entry = translations[locale];\n if (!entry) return false;\n return typeof entry === \"function\" || \"then\" in entry;\n}\n\n/**\n * `</script` is the only sequence that can terminate the script element early;\n * escaping `<` as a JSON unicode escape keeps the text byte-identical to what\n * `textContent` yields on the client, so hydration matches exactly.\n */\nfunction encode(payload: DehydratedPayload): string {\n return JSON.stringify(payload).replaceAll(\"<\", \"\\\\u003c\");\n}\n\nexport function dehydrate(\n keys: string[],\n locale: Locale,\n catalog: GetTextTranslations | undefined,\n): string | undefined {\n if (!catalog) return undefined;\n return encode({ k: keys, l: locale, c: catalog });\n}\n\n/** Locales already seeded from the document, so a hit costs one DOM query. */\nconst seeded = new WeakMap<Translator, Set<string>>();\n\nfunction markSeeded(translator: Translator, locale: Locale): void {\n let locales = seeded.get(translator);\n if (!locales) {\n locales = new Set();\n seeded.set(translator, locales);\n }\n locales.add(locale);\n}\n\nconst warned = new Set<string>();\n\n/**\n * Two React roots on one page share the `useId` sequence unless they were given\n * distinct `identifierPrefix` values, so their providers can emit scripts under\n * the same id. There is no way to tell from render which one belongs to the tree\n * currently hydrating, so refuse to guess: seeding is skipped and the catalog\n * loads lazily, exactly as it did before this mechanism existed.\n */\nfunction isAmbiguous(id: string, count: number): boolean {\n if (count <= 1) return false;\n if (!warned.has(id)) {\n warned.add(id);\n console.warn(\n `Found ${count} inlined translation catalogs for id \"${id}\". ` +\n \"Give each React root a distinct `identifierPrefix` so their ids cannot collide. \" +\n \"Falling back to loading the catalog.\",\n );\n }\n return true;\n}\n\n/**\n * Seed one provider's translator from the catalog the server inlined for it.\n *\n * Returns the raw payload text when one was applied, so the emitter can render an\n * identical `<script>` back and keep hydration byte-for-byte stable.\n *\n * `useId` is stable across a server render and its hydration, but a provider\n * mounted later by client-side navigation gets a fresh id that could collide with\n * an id from the server tree. The locale keys in the payload guard against that:\n * a mismatch is treated as a miss, never as a match.\n */\nexport function readPayload(entry: DehydrationEntry, locale: Locale): string | undefined {\n if (typeof document === \"undefined\") return undefined;\n if (seeded.get(entry.translator)?.has(locale)) return undefined;\n\n const scripts = document.querySelectorAll(`script[${DEHYDRATION_ATTRIBUTE}=\"${entry.id}\"]`);\n if (isAmbiguous(entry.id, scripts.length)) return undefined;\n\n const text = scripts[0]?.textContent;\n if (!text) return undefined;\n\n let payload: DehydratedPayload;\n try {\n payload = JSON.parse(text) as DehydratedPayload;\n } catch {\n return undefined;\n }\n\n if (payload.l !== locale) return undefined;\n if (payload.k.length !== entry.keys.length || payload.k.some((key, index) => key !== entry.keys[index])) {\n return undefined;\n }\n\n entry.translator.prime(locale, payload.c);\n markSeeded(entry.translator, locale);\n return text;\n}\n\n/**\n * Seed a whole provider chain, outermost first — a child's `getLocale` merges\n * against its parent, so the parent has to be resolved by the time the child is.\n *\n * Called from `useTranslations` before it touches `fetchLocale`, which is what\n * lets the app put its Suspense boundary anywhere: the consumer seeds itself at\n * the exact moment it renders, whether that is with the provider or inside a\n * boundary that hydrates much later.\n */\nexport function seedFromDocument(entry: DehydrationEntry | undefined, locale: Locale): void {\n if (!entry) return;\n seedFromDocument(entry.parent, locale);\n readPayload(entry, locale);\n}\n","import type { Locale, LocaleTranslator } from \"@let-value/translate\";\nimport { use } from \"react\";\n\nimport { dehydrationContext, localeContext, translatorContext } from \"../context.ts\";\nimport { seedFromDocument } from \"../dehydration.ts\";\n\n/** @deprecated replace with `use` from react */\nfunction getPromiseState(promise: any) {\n switch (promise.status) {\n case \"pending\":\n return { status: \"pending\" };\n case \"fulfilled\":\n return { status: \"fulfilled\", value: promise.value };\n case \"rejected\":\n return { status: \"rejected\", reason: promise.reason };\n default: {\n promise.status = \"pending\";\n promise.then((value: unknown) => {\n promise.status = \"fulfilled\";\n promise.value = value;\n });\n promise.catch((reason: unknown) => {\n promise.status = \"rejected\";\n promise.reason = reason;\n });\n return getPromiseState(promise);\n }\n }\n}\n\nexport function useTranslations(locale?: Locale): LocaleTranslator {\n const requestedLocale = locale ?? use(localeContext) ?? (\"unknown\" as never);\n const translator = use(translatorContext);\n const dehydrated = use(dehydrationContext);\n if (!translator) {\n throw new Error(\"TranslationsProvider is missing\");\n }\n\n // Seed before `fetchLocale`, so a catalog the server already inlined never\n // starts its loader — the chunk is not fetched and this render does not\n // suspend, which is what lets React keep the server markup.\n seedFromDocument(dehydrated, requestedLocale);\n\n const resource = translator.fetchLocale(requestedLocale);\n if (!(resource instanceof Promise)) {\n return resource;\n }\n\n const state = getPromiseState(resource);\n if (state.status === \"pending\") {\n throw resource;\n }\n if (state.status === \"rejected\") {\n throw state.reason;\n }\n if (state.status === \"fulfilled\") {\n return state.value;\n }\n\n return use(resource);\n}\n","import { Children, Fragment, isValidElement, type PropsWithChildren, type ReactNode } from \"react\";\n\nexport function buildTemplateFromChildren(children: ReactNode): {\n strings: string[];\n values: ReactNode[];\n} {\n function flatten(nodes: ReactNode): ReactNode[] {\n const result: ReactNode[] = [];\n Children.forEach(nodes, (child) => {\n if (isValidElement(child) && child.type === Fragment) {\n result.push(...flatten((child.props as PropsWithChildren).children));\n } else {\n result.push(child);\n }\n });\n return result;\n }\n\n const array = flatten(children);\n const strings: string[] = [\"\"];\n const values: ReactNode[] = [];\n let expectValue = false;\n\n array.forEach((child) => {\n if (typeof child === \"string\") {\n if (expectValue) {\n values.push(child);\n strings.push(\"\");\n expectValue = false;\n } else {\n strings[strings.length - 1] += child;\n expectValue = true;\n }\n } else if (typeof child === \"number\" || child != null) {\n values.push(child as ReactNode);\n strings.push(\"\");\n expectValue = false;\n }\n });\n\n return { strings, values };\n}\n","import { message } from \"@let-value/translate\";\nimport { createElement, Fragment, type ReactNode } from \"react\";\n\nimport { useTranslations } from \"../hooks/useTranslations.ts\";\nimport { buildTemplateFromChildren } from \"../utils.ts\";\n\nexport interface MessageProps {\n context?: string;\n children: ReactNode;\n}\n\nexport function Message({ context, children }: MessageProps) {\n const translator = useTranslations();\n const { strings, values } = buildTemplateFromChildren(children);\n\n if (values.length === 0) {\n const input = strings.join(\"\");\n if (context) {\n return translator.context(context as \"\").message(input as never);\n }\n return translator.message(input as never);\n }\n\n const tokens = values.map((_, i) => `\\u0000${i}\\u0000`);\n const built = message(strings as unknown as TemplateStringsArray, ...tokens);\n const translated = context\n ? translator.translate({ context: context as \"\", id: built })\n : translator.translate(built);\n\n // oxlint-disable-next-line no-control-regex -- using null separators\n const parts = translated.split(/\\u0000(\\d+)\\u0000/);\n const result: ReactNode[] = [];\n for (let i = 0; i < parts.length; i += 2) {\n result.push(parts[i]);\n const idx = parts[i + 1];\n if (idx !== undefined) {\n result.push(values[Number(idx)]);\n }\n }\n\n return createElement(Fragment, null, ...result);\n}\n","import { message, plural } from \"@let-value/translate\";\nimport { createElement, Fragment, type ReactNode } from \"react\";\n\nimport { useTranslations } from \"../hooks/useTranslations.ts\";\nimport { buildTemplateFromChildren } from \"../utils.ts\";\n\nexport interface PluralProps {\n number: number;\n forms: readonly ReactNode[];\n context?: string;\n}\n\nexport function Plural({ number, forms, context }: PluralProps) {\n const translator = useTranslations();\n\n const built = forms.map((child, i) => {\n const { strings, values } = buildTemplateFromChildren(child);\n const tokens = values.map((_, j) => `\\u0000${i}-${j}\\u0000`);\n return { message: message(strings as unknown as TemplateStringsArray, ...tokens), values };\n });\n\n const messages = built.map((b) => b.message);\n const input = plural(...messages, number);\n\n const translated = context\n ? translator.translate({ context: context as \"\", id: input })\n : translator.translate(input);\n\n // oxlint-disable-next-line no-control-regex -- using null separators\n const parts = translated.split(/\\u0000(\\d+)-(\\d+)\\u0000/);\n const result: ReactNode[] = [];\n for (let i = 0; i < parts.length; ) {\n result.push(parts[i]);\n if (i + 2 < parts.length) {\n const formIndex = Number(parts[i + 1]);\n const valueIndex = Number(parts[i + 2]);\n result.push(built[formIndex].values[valueIndex]);\n }\n i += 3;\n }\n\n return createElement(Fragment, null, ...result);\n}\n","import type { Locale, TranslationEntry } from \"@let-value/translate\";\nimport { Translator } from \"@let-value/translate\";\n\nexport type TranslationsMap = Partial<Record<Locale, TranslationEntry>>;\n\n// Fast path: WeakMap keyed on the translations object reference.\n// GC-safe — entries are collected when the object is no longer reachable.\nconst cacheNoParent = new WeakMap<object, Translator>();\nconst cacheWithParent = new WeakMap<object, WeakMap<Translator, Translator>>();\n\nfunction getFromWeakMap(translations: TranslationsMap, parent: Translator | undefined): Translator | undefined {\n if (!parent) return cacheNoParent.get(translations);\n return cacheWithParent.get(translations)?.get(parent);\n}\n\nfunction setInWeakMap(translations: TranslationsMap, parent: Translator | undefined, translator: Translator): void {\n if (!parent) {\n cacheNoParent.set(translations, translator);\n return;\n }\n let byParent = cacheWithParent.get(translations);\n if (!byParent) {\n byParent = new WeakMap();\n cacheWithParent.set(translations, byParent);\n }\n byParent.set(parent, translator);\n}\n\n// Slow path: bounded structural cache for dynamically-created translations objects\n// (e.g. `translations={{ en: data }}` — new wrapper object, stable value references).\n// Bounded to cap memory usage in infinite-render scenarios.\nconst MAX_STRUCTURAL_ENTRIES = 32;\n\ntype SortedEntry = [Locale, TranslationEntry];\n\ninterface StructuralEntry {\n sortedEntries: SortedEntry[];\n parent: Translator | undefined;\n translator: Translator;\n}\n\nconst structuralCache: StructuralEntry[] = [];\n\nfunction toSortedEntries(translations: TranslationsMap): SortedEntry[] {\n return (Object.entries(translations) as SortedEntry[]).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));\n}\n\nfunction findStructural(sortedEntries: SortedEntry[], parent: Translator | undefined): Translator | undefined {\n for (const entry of structuralCache) {\n if (entry.parent !== parent || entry.sortedEntries.length !== sortedEntries.length) continue;\n if (entry.sortedEntries.every(([k, v], i) => sortedEntries[i][0] === k && sortedEntries[i][1] === v)) {\n return entry.translator;\n }\n }\n return undefined;\n}\n\nexport function getCachedTranslator<T extends TranslationsMap>(\n translations: T,\n parent: Translator | undefined,\n): Translator<T> {\n // Fast path: same object reference\n const cached = getFromWeakMap(translations, parent);\n if (cached) return cached as Translator<T>;\n\n // Slow path: same locale keys and value references, different wrapper object\n const sortedEntries = toSortedEntries(translations);\n const structural = findStructural(sortedEntries, parent);\n if (structural) {\n // Backfill WeakMap so subsequent renders with the same new-object are O(1)\n setInWeakMap(translations, parent, structural);\n return structural as Translator<T>;\n }\n\n const translator = new Translator(translations, parent);\n setInWeakMap(translations, parent, translator);\n structuralCache.unshift({ sortedEntries, parent, translator });\n if (structuralCache.length > MAX_STRUCTURAL_ENTRIES) structuralCache.pop();\n return translator;\n}\n\n/** Exported only for tests — do not use in application code. */\nexport const _structuralCacheSize = (): number => structuralCache.length;\n","import type { Locale, TranslationEntry } from \"@let-value/translate\";\nimport { createElement, Fragment, type ReactElement, type ReactNode, Suspense, use, useId, useMemo } from \"react\";\nimport { type DehydrationEntry, dehydrationContext, localeContext, translatorContext } from \"../context.ts\";\nimport { DEHYDRATION_ATTRIBUTE, dehydrate, isAsyncLocale, localeKeys, readPayload } from \"../dehydration.ts\";\nimport { getCachedTranslator, type TranslationsMap } from \"../translatorCache.ts\";\n\nexport interface TranslationsProviderProps {\n translations?: Partial<Record<Locale, TranslationEntry>>;\n children?: ReactNode;\n}\n\nconst EMPTY_TRANSLATIONS: TranslationsMap = {};\n\ninterface DehydrationScriptProps {\n entry: DehydrationEntry;\n locale: Locale;\n}\n\n/**\n * Inlines the catalog for the active locale as a `<script type=\"application/json\">`.\n *\n * Renders no children, which is the whole point: the provider wraps it in a\n * Suspense boundary that contains nothing of the app's, so awaiting the catalog\n * here can never intercept a fallback the app placed for its own content. The\n * app's boundaries — inside the provider or outside it — behave exactly as\n * written, and `useTranslations` still suspends where it always did.\n */\nfunction DehydrationScript({ entry, locale }: DehydrationScriptProps) {\n // During hydration the payload is already in the document; re-render it\n // verbatim rather than re-encoding an equivalent one, and never call the\n // loader for a catalog the server already sent.\n let text = readPayload(entry, locale);\n\n if (!text) {\n const resource = entry.translator.fetchLocale(locale as never);\n if (resource instanceof Promise) {\n use(resource);\n }\n text = dehydrate(entry.keys, locale, entry.translator.dehydrate(locale));\n }\n\n if (!text) return null;\n\n return createElement(\"script\", {\n type: \"application/json\",\n [DEHYDRATION_ATTRIBUTE]: entry.id,\n dangerouslySetInnerHTML: { __html: text },\n });\n}\n\nexport function TranslationsProvider({\n translations = EMPTY_TRANSLATIONS,\n children,\n}: TranslationsProviderProps): ReactElement {\n const id = useId();\n const parent = use(translatorContext);\n const parentEntry = use(dehydrationContext);\n const translator = getCachedTranslator(translations, parent);\n const locale = use(localeContext);\n\n const entry = useMemo<DehydrationEntry>(\n () => ({ id, translator, keys: localeKeys(translations), parent: parentEntry }),\n [id, translator, translations, parentEntry],\n );\n\n const provide = (inner: ReactNode) =>\n createElement(translatorContext.Provider, { value: translator }, inner) as ReactElement;\n\n if (!isAsyncLocale(translations, locale)) {\n return provide(children);\n }\n\n return provide(\n createElement(\n dehydrationContext.Provider,\n { value: entry },\n createElement(\n Fragment,\n null,\n createElement(\n Suspense,\n { fallback: null },\n createElement(DehydrationScript, { entry, locale: locale as Locale }),\n ),\n children,\n ),\n ),\n );\n}\n","import type { Locale } from \"@let-value/translate\";\nimport { use } from \"react\";\nimport { localeContext } from \"../context.ts\";\n\nexport function useLocale(): Locale | undefined {\n return use(localeContext);\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@let-value/translate-react",
3
- "version": "1.1.6-beta.3",
3
+ "version": "1.2.3",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/let-value/translate"
@@ -21,7 +21,7 @@
21
21
  "./package.json": "./package.json"
22
22
  },
23
23
  "dependencies": {
24
- "@let-value/translate": "1.1.6-beta.3"
24
+ "@let-value/translate": "1.2.3"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@types/gettext-parser": "9.0.0",