@nxgt/i18n 2.0.1 → 3.0.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 CHANGED
@@ -14,9 +14,9 @@ language. That is why `LocaleKey` lives this low in the layering.
14
14
  bun add @nxgt/i18n
15
15
  ```
16
16
 
17
- Public on npmjs; no token needed to install. TypeScript is a peer, pinned to
18
- `^6.0.3` across every `@nxgt/*` package — the set is unsatisfiable if one of
19
- them widens it.
17
+ Public on npmjs; no token needed to install. TypeScript is a peer:
18
+ `^6.0.3 || ^7.0.0`, the same range in every `@nxgt/*` package, so the set
19
+ installs with either.
20
20
 
21
21
  ## Usage
22
22
 
@@ -25,7 +25,7 @@ import {
25
25
  createTranslator,
26
26
  getLanguage,
27
27
  translate,
28
- FALLBACK_LANGUAGE,
28
+ getFallbackLanguage,
29
29
  SUPPORTED_LANGUAGES,
30
30
  LANGUAGE_KEY,
31
31
  } from '@nxgt/i18n';
@@ -36,16 +36,74 @@ const t = createTranslator(myCatalogues, getLanguage);
36
36
  t('users.greeting', { name });
37
37
  ```
38
38
 
39
- `translate` is bound to this package's catalogues (`en` and `fr`, under
40
- `errors`, `common`, `validation`, `zod`). Pass your own catalogues to
41
- `createTranslator` when a service has messages this package does not. A missing
42
- key returns the key itself, not a throw.
39
+ `translate` reads this package's catalogues (`en` and `fr`, under `errors`,
40
+ `common`, `validation`, `zod`) and every catalogue registered on top of them
41
+ (see below). Pass your own catalogues to `createTranslator` when a service has
42
+ messages this package does not. A key the language lacks, or a language with no
43
+ catalogue at all, is looked up in the fallback language. Only when that misses
44
+ too does the translator answer the key itself, never a throw.
43
45
 
44
46
  `LocaleKey` is `keyof` the flattened English catalogue — that is the type
45
- `CustomException.message` uses. `Language` is `'en' | 'fr'`. `resources`,
46
- `en` and `fr` are the catalogues themselves. Shared keys include
47
- `errors.not-found`, `errors.unauthenticated`, `errors.insufficient-permissions`
48
- and `errors.service-unavailable`.
47
+ `CustomException.message` uses. `Language` is `string` since 3.0, and
48
+ `BuiltInLanguage` is `'en' | 'fr'`, the languages this package ships.
49
+ `resources`, `en` and `fr` are the package's catalogues themselves, never
50
+ changed by a registration. Shared keys include `errors.not-found`,
51
+ `errors.unauthenticated`, `errors.insufficient-permissions` and
52
+ `errors.service-unavailable`.
53
+
54
+ ## Adding a language, overriding a message
55
+
56
+ A language is data an app registers, not a list this package fixes:
57
+
58
+ ```ts
59
+ import { registerCatalogue, setFallbackLanguage } from '@nxgt/i18n';
60
+
61
+ registerCatalogue('de', await loadCatalogue('de')); // from a file, a database…
62
+ registerCatalogue('fr', { errors: { 'not-found': 'Introuvable' } });
63
+ setFallbackLanguage('fr');
64
+ ```
65
+
66
+ - `registerCatalogue(language, catalogue)` merges `catalogue` deeply over what
67
+ that language already has. A key it names wins; a key it leaves out keeps the
68
+ package's message. A new language becomes supported: it joins
69
+ `SUPPORTED_LANGUAGES`, `getLanguage()` accepts it from a source or
70
+ `localStorage`, and `translate` answers in it. Registering the same language
71
+ again merges again; nothing is removed.
72
+ - `setFallbackLanguage(language)` changes what `getLanguage()` answers when
73
+ nothing names a language (`'en'` until then). The language must have a
74
+ catalogue; otherwise it throws a `RangeError`, so register a new language
75
+ first. `getFallbackLanguage()` reads it.
76
+ - `registeredCatalogues()` answers the catalogues `translate` reads, by
77
+ language.
78
+
79
+ The state lives on `globalThis`, as the language sources do: two 3.x copies of
80
+ this package in one app share it, and the keys a newer copy ships reach both.
81
+ A 2.x copy shares the language sources but not the catalogues: it refuses a
82
+ language it does not ship and answers in English. Every `@nxgt/*` package an
83
+ app uses has to resolve `@nxgt/i18n` 3 before its registrations reach it.
84
+
85
+ A language registered with only the app's keys has none of the package's
86
+ `errors`, `validation` and `zod` messages, and `translate` answers those in the
87
+ fallback language. Register them in that language too to have them translated.
88
+
89
+ ### `mergeCatalogues`
90
+
91
+ `mergeCatalogues(base, ...overrides)` is the merge `registerCatalogue` uses,
92
+ for an app that builds its catalogues itself:
93
+
94
+ ```ts
95
+ import { mergeCatalogues, resources } from '@nxgt/i18n';
96
+
97
+ export const en = mergeCatalogues(resources.en, appEn, overridesFromTheDatabase.en);
98
+ ```
99
+
100
+ A spread replaces whole branches: `{ ...resources.en, validation }` drops every
101
+ package key under `validation` that the app did not repeat. `mergeCatalogues`
102
+ merges a branch key by key, and the last override wins leaf by leaf. A leaf
103
+ replaces a branch, and the other way round. `undefined` and `null` are
104
+ skipped, so an override never erases a message. The inputs are not changed,
105
+ and a `__proto__` key is ignored. The result is typed as `base`; keys an
106
+ override adds are not in that type.
49
107
 
50
108
  ## Where the language comes from
51
109
 
@@ -53,7 +111,7 @@ and `errors.service-unavailable`.
53
111
 
54
112
  1. every registered **language source**, in the order registered
55
113
  2. `localStorage.language`, when that object exists
56
- 3. `FALLBACK_LANGUAGE` (`'en'`)
114
+ 3. the fallback language: `'en'`, or what `setFallbackLanguage()` set
57
115
 
58
116
  A source is a function answering the current request's language, or nothing.
59
117
  This package knows no server; the server says where its request keeps the
@@ -76,13 +134,25 @@ skipped. Registering one twice keeps one. The registry is on `globalThis`, so
76
134
  two copies of this package in one app — one a dependency of
77
135
  `@nxgt/shared-hono`, one the app's own — share it.
78
136
 
79
- The two consuming monorepos had forked exactly this — one read the request, the
80
- other read `localStorage` — and neither could run where the other did. Asking
81
- both serves either without a caller changing anything. Pass a provider (a
82
- function or a `Language` literal) to `createTranslator` if you want one source
83
- and not the other.
137
+ A server knows the language from its request, and a browser keeps it in
138
+ `localStorage`. Asking both serves either without a caller changing anything.
139
+ Pass a provider (a function or a language name) to `createTranslator` if you
140
+ want one source and not the other.
141
+
142
+ `SUPPORTED_LANGUAGES` is derived from the registered catalogues, not typed out,
143
+ and it is live: a registration shows in it.
144
+
145
+ ### Since 3.0
84
146
 
85
- `SUPPORTED_LANGUAGES` is derived from the catalogue keys, not typed out.
147
+ - `Language` is `string`, no longer `'en' | 'fr'`; that union is
148
+ `BuiltInLanguage`. Code that narrowed `getLanguage()` to the union now
149
+ narrows it itself, for example with `SUPPORTED_LANGUAGES.includes()`.
150
+ - `FALLBACK_LANGUAGE` is removed. Read `getFallbackLanguage()`, and change it
151
+ with `setFallbackLanguage()`.
152
+ - `SUPPORTED_LANGUAGES` is a `readonly string[]` that grows when a language is
153
+ registered.
154
+ - A key the language lacks is looked up in the fallback language before the
155
+ key itself is answered. Until 3.0, the key itself was answered right away.
86
156
 
87
157
  ### Since 2.0
88
158
 
@@ -112,3 +182,8 @@ a source as a server does, or relies on `localStorage`.
112
182
  - **ICU formatting failures are swallowed** (`console.error`) and the
113
183
  unformatted message is returned. A bad `{name}` in a catalogue is a log
114
184
  line, not a 500.
185
+ - **Registering throws on bad input, at startup rather than per request.**
186
+ `registerCatalogue('', …)`, `registerCatalogue('__proto__', …)` and a
187
+ catalogue that is not an object throw a `TypeError`, and so does
188
+ `mergeCatalogues(null, …)`. `setFallbackLanguage()` for a language with no
189
+ catalogue throws a `RangeError`.
@@ -0,0 +1,30 @@
1
+ import type { Catalogue, Language } from './types';
2
+ type ByLanguage = Record<Language, Catalogue>;
3
+ /**
4
+ * The catalogues `translate` reads, by language: this package's `en` and `fr`
5
+ * and whatever was registered on top. The object is live — a registration
6
+ * shows in it — and is read-only to callers.
7
+ */
8
+ export declare function registeredCatalogues(): Readonly<ByLanguage>;
9
+ /**
10
+ * The languages a catalogue is registered for. The array is live, the same
11
+ * one `SUPPORTED_LANGUAGES` is.
12
+ */
13
+ export declare function supportedLanguages(): readonly Language[];
14
+ /**
15
+ * Registers `catalogue` for `language`, merged deeply over what that language
16
+ * already has (`mergeCatalogues`): a key it names wins, a key it leaves out
17
+ * keeps the package's message. A language with no catalogue yet becomes
18
+ * supported — `getLanguage()` accepts it from a source or `localStorage`, and
19
+ * `translate` answers in it.
20
+ */
21
+ export declare function registerCatalogue(language: Language, catalogue: Catalogue): void;
22
+ /** The language `getLanguage()` answers when nothing else names one. */
23
+ export declare function getFallbackLanguage(): Language;
24
+ /**
25
+ * Changes the fallback language, `'en'` until then. It must have a catalogue:
26
+ * register one first for a language this package does not ship.
27
+ */
28
+ export declare function setFallbackLanguage(language: Language): void;
29
+ export {};
30
+ //# sourceMappingURL=catalogues.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalogues.d.ts","sourceRoot":"","sources":["../src/catalogues.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEnD,KAAK,UAAU,GAAG,MAAM,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;AA8D9C;;;;GAIG;AACH,wBAAgB,oBAAoB,IAAI,QAAQ,CAAC,UAAU,CAAC,CAE3D;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,IAAI,SAAS,QAAQ,EAAE,CAExD;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAChC,QAAQ,EAAE,QAAQ,EAClB,SAAS,EAAE,SAAS,GAClB,IAAI,CAgBN;AAED,wEAAwE;AACxE,wBAAgB,mBAAmB,IAAI,QAAQ,CAE9C;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAO5D"}
@@ -1,3 +1,33 @@
1
+ // src/merge.ts
2
+ function isBranch(value) {
3
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4
+ }
5
+ function mergeInto(target, source) {
6
+ for (const key of Object.keys(source)) {
7
+ if (key === "__proto__")
8
+ continue;
9
+ const value = source[key];
10
+ if (value === undefined || value === null)
11
+ continue;
12
+ if (isBranch(value)) {
13
+ const current = Object.hasOwn(target, key) ? target[key] : undefined;
14
+ target[key] = mergeInto(isBranch(current) ? current : {}, value);
15
+ } else {
16
+ target[key] = value;
17
+ }
18
+ }
19
+ return target;
20
+ }
21
+ function mergeCatalogues2(base, ...overrides) {
22
+ const result = {};
23
+ for (const catalogue of [base, ...overrides]) {
24
+ if (!isBranch(catalogue)) {
25
+ throw new TypeError(`@nxgt/i18n: mergeCatalogues takes catalogue objects, got ${catalogue === null ? "null" : typeof catalogue}`);
26
+ }
27
+ mergeInto(result, catalogue);
28
+ }
29
+ return result;
30
+ }
1
31
  // src/resources/en/common.json
2
32
  var common_default = {
3
33
  mail: {
@@ -455,10 +485,59 @@ var resources2 = {
455
485
  fr
456
486
  };
457
487
 
488
+ // src/catalogues.ts
489
+ var STATE = Symbol.for("@nxgt/i18n/catalogues@3");
490
+ var byLanguage = () => Object.create(null);
491
+ function refresh(state, language) {
492
+ state.catalogues[language] = mergeCatalogues2(state.base[language] ?? {}, state.registered[language] ?? {});
493
+ if (!state.languages.includes(language))
494
+ state.languages.push(language);
495
+ }
496
+ function state() {
497
+ const global = globalThis;
498
+ global[STATE] ??= {
499
+ base: byLanguage(),
500
+ registered: byLanguage(),
501
+ catalogues: byLanguage(),
502
+ languages: [],
503
+ fallback: "en"
504
+ };
505
+ return global[STATE];
506
+ }
507
+ {
508
+ const shared = state();
509
+ for (const [language, catalogue] of Object.entries(resources2)) {
510
+ shared.base[language] = mergeCatalogues2(catalogue, shared.base[language] ?? {});
511
+ refresh(shared, language);
512
+ }
513
+ }
514
+ function registeredCatalogues2() {
515
+ return state().catalogues;
516
+ }
517
+ function supportedLanguages2() {
518
+ return state().languages;
519
+ }
520
+ function registerCatalogue2(language, catalogue) {
521
+ if (typeof language !== "string" || language === "" || language === "__proto__") {
522
+ throw new TypeError(`@nxgt/i18n: registerCatalogue needs a language name, got ${JSON.stringify(language)}`);
523
+ }
524
+ const shared = state();
525
+ shared.registered[language] = mergeCatalogues2(shared.registered[language] ?? {}, catalogue);
526
+ refresh(shared, language);
527
+ }
528
+ function getFallbackLanguage2() {
529
+ return state().fallback;
530
+ }
531
+ function setFallbackLanguage2(language) {
532
+ if (!state().languages.includes(language)) {
533
+ throw new RangeError(`@nxgt/i18n: no catalogue is registered for "${language}"; call registerCatalogue("${language}", …) first`);
534
+ }
535
+ state().fallback = language;
536
+ }
537
+
458
538
  // src/consts.ts
459
539
  var LANGUAGE_KEY2 = "language";
460
- var SUPPORTED_LANGUAGES2 = Object.keys(resources2);
461
- var FALLBACK_LANGUAGE2 = "en";
540
+ var SUPPORTED_LANGUAGES2 = supportedLanguages2();
462
541
 
463
542
  // src/sources.ts
464
543
  var REGISTRY = Symbol.for("@nxgt/i18n/language-sources");
@@ -485,7 +564,7 @@ function languageFromSources2() {
485
564
  } catch {
486
565
  continue;
487
566
  }
488
- if (SUPPORTED_LANGUAGES2.includes(answer)) {
567
+ if (typeof answer === "string" && SUPPORTED_LANGUAGES2.includes(answer)) {
489
568
  return answer;
490
569
  }
491
570
  }
@@ -501,26 +580,38 @@ function getLanguage2() {
501
580
  return fromSource;
502
581
  if (typeof localStorage !== "undefined") {
503
582
  const stored = localStorage.getItem(LANGUAGE_KEY2);
504
- if (SUPPORTED_LANGUAGES2.includes(stored)) {
583
+ if (stored !== null && SUPPORTED_LANGUAGES2.includes(stored))
505
584
  return stored;
506
- }
507
585
  }
508
- return FALLBACK_LANGUAGE2;
586
+ return getFallbackLanguage2();
587
+ }
588
+ function messageIn(resources, language, key) {
589
+ if (!Object.hasOwn(resources, language))
590
+ return;
591
+ const message = _.get(resources[language], key);
592
+ return typeof message === "string" ? message : undefined;
509
593
  }
510
594
  function createTranslator2(resources = {}, localeProvider = getLanguage2) {
511
595
  return (key, context = undefined, provideLanguage = localeProvider) => {
512
596
  const language = typeof provideLanguage === "function" ? provideLanguage() : provideLanguage;
513
- let message = _.get(resources[language], key) ?? key;
597
+ let locale = language;
598
+ let found = messageIn(resources, language, key);
599
+ const fallback = getFallbackLanguage2();
600
+ if (found === undefined && fallback !== language) {
601
+ found = messageIn(resources, fallback, key);
602
+ locale = fallback;
603
+ }
604
+ let message = found ?? key;
514
605
  try {
515
- message = new IntlMessageFormat(message, language).format(context);
606
+ message = new IntlMessageFormat(message, locale).format(context);
516
607
  } catch (e) {
517
608
  console.error(e);
518
609
  }
519
610
  return message;
520
611
  };
521
612
  }
522
- var translate2 = createTranslator2(resources2);
523
- export { resources2, LANGUAGE_KEY2, SUPPORTED_LANGUAGES2, FALLBACK_LANGUAGE2, registerLanguageSource2, languageFromSources2, getLanguage2, createTranslator2, translate2 };
613
+ var translate2 = createTranslator2(registeredCatalogues2());
614
+ export { mergeCatalogues2, resources2, registeredCatalogues2, supportedLanguages2, registerCatalogue2, getFallbackLanguage2, setFallbackLanguage2, LANGUAGE_KEY2, SUPPORTED_LANGUAGES2, registerLanguageSource2, languageFromSources2, getLanguage2, createTranslator2, translate2 };
524
615
 
525
- //# debugId=2FA53BAC7C0D362464756E2164756E21
526
- //# sourceMappingURL=index-jscbtbek.js.map
616
+ //# debugId=D6E625C5C81A884864756E2164756E21
617
+ //# sourceMappingURL=index-wabmsewh.js.map
@@ -0,0 +1,17 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/merge.ts", "../src/resources/en/index.ts", "../src/resources/fr/index.ts", "../src/resources/index.ts", "../src/catalogues.ts", "../src/consts.ts", "../src/sources.ts", "../src/i18n.ts"],
4
+ "sourcesContent": [
5
+ "import type { Catalogue } from './types';\n\ntype Branch = Record<string, unknown>;\n\nfunction isBranch(value: unknown): value is Branch {\n\treturn typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * Copies `source` into `target`, which is always an object this module made,\n * never one of the caller's: a branch in `source` is merged into a fresh\n * object, so the result shares nothing with its inputs.\n */\nfunction mergeInto(target: Branch, source: Branch): Branch {\n\tfor (const key of Object.keys(source)) {\n\t\t// A catalogue read from a database is data: `__proto__` must not\n\t\t// reach the result's prototype.\n\t\tif (key === '__proto__') continue;\n\t\tconst value = source[key];\n\t\tif (value === undefined || value === null) continue;\n\t\tif (isBranch(value)) {\n\t\t\tconst current = Object.hasOwn(target, key) ? target[key] : undefined;\n\t\t\ttarget[key] = mergeInto(isBranch(current) ? current : {}, value);\n\t\t} else {\n\t\t\ttarget[key] = value;\n\t\t}\n\t}\n\treturn target;\n}\n\n/**\n * Merges catalogues deeply, the last one winning leaf by leaf:\n * `mergeCatalogues(packageDefaults, appOverrides, databaseOverrides)`.\n *\n * A spread replaces whole branches — `{ ...resources.en, validation }` drops\n * every package key under `validation` the app did not repeat. Here a branch\n * is merged key by key; a leaf (a message) replaces what was there, branch\n * included. `undefined` and `null` are skipped, so an override never erases\n * a message. The inputs are not changed.\n *\n * The result is typed as `base`: an override can add keys, which the type\n * does not list.\n */\nexport function mergeCatalogues<B extends Catalogue>(\n\tbase: B,\n\t...overrides: readonly Catalogue[]\n): B {\n\tconst result: Branch = {};\n\tfor (const catalogue of [base, ...overrides]) {\n\t\tif (!isBranch(catalogue)) {\n\t\t\tthrow new TypeError(\n\t\t\t\t`@nxgt/i18n: mergeCatalogues takes catalogue objects, got ${catalogue === null ? 'null' : typeof catalogue}`,\n\t\t\t);\n\t\t}\n\t\tmergeInto(result, catalogue);\n\t}\n\treturn result as B;\n}\n",
6
+ "import common from './common.json';\nimport errors from './errors.json';\nimport validation from './validation.json';\nimport zod from './zod.json';\n\nexport const en = {\n\t...common,\n\terrors,\n\tvalidation,\n\tzod,\n};\n",
7
+ "import common from './common.json';\nimport errors from './errors.json';\nimport validation from './validation.json';\nimport zod from './zod.json';\n\nexport const fr = {\n\t...common,\n\terrors,\n\tvalidation,\n\tzod,\n};\n",
8
+ "import { en } from './en';\nimport { fr } from './fr';\n\nexport const resources = {\n\ten,\n\tfr,\n};\n",
9
+ "import { mergeCatalogues } from './merge';\nimport { resources } from './resources';\nimport type { Catalogue, Language } from './types';\n\ntype ByLanguage = Record<Language, Catalogue>;\n\n/**\n * The shape every 3.x copy of this package agrees on. It is a contract\n * between versions: a field added here needs a new symbol name.\n */\ntype State = {\n\t/** Each copy's built-in catalogues, merged: a newer copy's keys join. */\n\tbase: ByLanguage;\n\t/** What the app registered, merged per language. */\n\tregistered: ByLanguage;\n\t/** `base` under `registered`, what `translate` reads. */\n\tcatalogues: ByLanguage;\n\tlanguages: Language[];\n\tfallback: Language;\n};\n\n/**\n * One state for the process, on `globalThis`, as the language sources are:\n * an app can hold two copies of this package — one a dependency of\n * `@nxgt/shared-hono`, one its own — and a catalogue registered through\n * either must translate through both. A 2.x copy has no such state and sees\n * none of it.\n */\nconst STATE = Symbol.for('@nxgt/i18n/catalogues@3');\n\n/** No prototype: a language is a key, and `constructor` is a fine name. */\nconst byLanguage = (): ByLanguage => Object.create(null);\n\nfunction refresh(state: State, language: Language): void {\n\tstate.catalogues[language] = mergeCatalogues(\n\t\tstate.base[language] ?? {},\n\t\tstate.registered[language] ?? {},\n\t);\n\tif (!state.languages.includes(language)) state.languages.push(language);\n}\n\nfunction state(): State {\n\tconst global = globalThis as { [STATE]?: State };\n\tglobal[STATE] ??= {\n\t\tbase: byLanguage(),\n\t\tregistered: byLanguage(),\n\t\tcatalogues: byLanguage(),\n\t\tlanguages: [],\n\t\tfallback: 'en',\n\t};\n\treturn global[STATE];\n}\n\n// Each copy adds its built-in catalogues when it loads: keys a newer copy\n// ships reach every copy, and a key both ship keeps the first one's message.\n{\n\tconst shared = state();\n\tfor (const [language, catalogue] of Object.entries(resources)) {\n\t\tshared.base[language] = mergeCatalogues(\n\t\t\tcatalogue,\n\t\t\tshared.base[language] ?? {},\n\t\t);\n\t\trefresh(shared, language);\n\t}\n}\n\n/**\n * The catalogues `translate` reads, by language: this package's `en` and `fr`\n * and whatever was registered on top. The object is live — a registration\n * shows in it — and is read-only to callers.\n */\nexport function registeredCatalogues(): Readonly<ByLanguage> {\n\treturn state().catalogues;\n}\n\n/**\n * The languages a catalogue is registered for. The array is live, the same\n * one `SUPPORTED_LANGUAGES` is.\n */\nexport function supportedLanguages(): readonly Language[] {\n\treturn state().languages;\n}\n\n/**\n * Registers `catalogue` for `language`, merged deeply over what that language\n * already has (`mergeCatalogues`): a key it names wins, a key it leaves out\n * keeps the package's message. A language with no catalogue yet becomes\n * supported — `getLanguage()` accepts it from a source or `localStorage`, and\n * `translate` answers in it.\n */\nexport function registerCatalogue(\n\tlanguage: Language,\n\tcatalogue: Catalogue,\n): void {\n\tif (\n\t\ttypeof language !== 'string' ||\n\t\tlanguage === '' ||\n\t\tlanguage === '__proto__'\n\t) {\n\t\tthrow new TypeError(\n\t\t\t`@nxgt/i18n: registerCatalogue needs a language name, got ${JSON.stringify(language)}`,\n\t\t);\n\t}\n\tconst shared = state();\n\tshared.registered[language] = mergeCatalogues(\n\t\tshared.registered[language] ?? {},\n\t\tcatalogue,\n\t);\n\trefresh(shared, language);\n}\n\n/** The language `getLanguage()` answers when nothing else names one. */\nexport function getFallbackLanguage(): Language {\n\treturn state().fallback;\n}\n\n/**\n * Changes the fallback language, `'en'` until then. It must have a catalogue:\n * register one first for a language this package does not ship.\n */\nexport function setFallbackLanguage(language: Language): void {\n\tif (!state().languages.includes(language)) {\n\t\tthrow new RangeError(\n\t\t\t`@nxgt/i18n: no catalogue is registered for \"${language}\"; call registerCatalogue(\"${language}\", …) first`,\n\t\t);\n\t}\n\tstate().fallback = language;\n}\n",
10
+ "import { supportedLanguages } from './catalogues';\n\nexport const LANGUAGE_KEY = 'language';\n\n/**\n * The languages a catalogue is registered for: `en` and `fr`, then each one\n * `registerCatalogue()` adds. Live — a registration shows in it.\n */\nexport const SUPPORTED_LANGUAGES: readonly string[] = supportedLanguages();\n",
11
+ "import { SUPPORTED_LANGUAGES } from './consts';\nimport type { Language } from './types';\n\n/**\n * Where a language may come from: a server's request context, a framework's\n * state. It answers the request's language, or nothing — `undefined`, `null`,\n * a language no catalogue has — and the next source is asked.\n */\nexport type LanguageSource = () => string | null | undefined;\n\n/**\n * One registry for the process, on `globalThis`: an app can hold two copies\n * of this package — one a dependency of `@nxgt/shared-hono`, one its own —\n * and a source registered through either must answer both.\n */\nconst REGISTRY = Symbol.for('@nxgt/i18n/language-sources');\n\nfunction registry(): LanguageSource[] {\n\tconst global = globalThis as { [REGISTRY]?: LanguageSource[] };\n\tglobal[REGISTRY] ??= [];\n\treturn global[REGISTRY];\n}\n\n/**\n * Adds a source `getLanguage()` asks, after those registered before it.\n * Registering one twice keeps one. Answers the function that removes it.\n *\n * This package knows no server: `@nxgt/shared-hono` registers the Hono\n * request context's `language`, `@alxia/i18n` the alxia request's.\n */\nexport function registerLanguageSource(source: LanguageSource): () => void {\n\tconst sources = registry();\n\tif (!sources.includes(source)) sources.push(source);\n\treturn () => {\n\t\tconst index = sources.indexOf(source);\n\t\tif (index !== -1) sources.splice(index, 1);\n\t};\n}\n\n/**\n * The first supported language a registered source answers. A source that\n * throws is skipped: a language is never worth a failed request.\n */\nexport function languageFromSources(): Language | undefined {\n\tfor (const source of registry()) {\n\t\tlet answer: string | null | undefined;\n\t\ttry {\n\t\t\tanswer = source();\n\t\t} catch {\n\t\t\tcontinue;\n\t\t}\n\t\tif (typeof answer === 'string' && SUPPORTED_LANGUAGES.includes(answer)) {\n\t\t\treturn answer;\n\t\t}\n\t}\n\treturn undefined;\n}\n",
12
+ "import { IntlMessageFormat } from 'intl-messageformat';\nimport _ from 'lodash';\nimport { getFallbackLanguage, registeredCatalogues } from './catalogues';\nimport { LANGUAGE_KEY, SUPPORTED_LANGUAGES } from './consts';\nimport { languageFromSources } from './sources';\nimport type {\n\tLanguage,\n\tLanguageProvider,\n\tLocaleKey,\n\tTranslationContext,\n} from './types';\n\n/**\n * Where the language comes from when the caller does not say, most specific\n * first:\n *\n * 1. a registered source — `registerLanguageSource()`: a server's request\n * context, which `@nxgt/shared-hono` registers for Hono and\n * `@alxia/i18n` for alxia;\n * 2. `localStorage`, when that object exists;\n * 3. the fallback language — `'en'`, or what `setFallbackLanguage()` set.\n *\n * Until 2.0 this read Hono's request context itself, through\n * `hono/context-storage`, which tied every consumer — a browser bundle, an\n * alxia server — to Hono. The Hono part lives in `@nxgt/shared-hono` now.\n *\n * `createTranslator` still takes a provider, so a caller that wants one source\n * and not the others passes it.\n */\nexport function getLanguage(): Language {\n\tconst fromSource = languageFromSources();\n\tif (fromSource !== undefined) return fromSource;\n\tif (typeof localStorage !== 'undefined') {\n\t\tconst stored = localStorage.getItem(LANGUAGE_KEY);\n\t\tif (stored !== null && SUPPORTED_LANGUAGES.includes(stored)) return stored;\n\t}\n\treturn getFallbackLanguage();\n}\n\n/**\n * The message under `key` in `language`'s catalogue, if that language has one\n * of its own (not `constructor` through the prototype) and the key leads to a\n * message, not a branch.\n */\nfunction messageIn(\n\tresources: Record<string, any>,\n\tlanguage: string,\n\tkey: string,\n): string | undefined {\n\tif (!Object.hasOwn(resources, language)) return undefined;\n\tconst message = _.get(resources[language], key);\n\treturn typeof message === 'string' ? message : undefined;\n}\n\n/**\n * A translator over `resources`. A key the language lacks — or a language\n * with no catalogue at all — is looked up in the fallback language\n * (`getFallbackLanguage()`), and only then answered as the key itself.\n */\nexport function createTranslator<K extends string = LocaleKey>(\n\tresources: Record<string, any> = {},\n\tlocaleProvider: LanguageProvider = getLanguage,\n) {\n\treturn (\n\t\tkey: K,\n\t\tcontext: TranslationContext = undefined,\n\t\tprovideLanguage: LanguageProvider = localeProvider,\n\t): string => {\n\t\tconst language =\n\t\t\ttypeof provideLanguage === 'function'\n\t\t\t\t? provideLanguage()\n\t\t\t\t: provideLanguage;\n\t\tlet locale = language;\n\t\tlet found = messageIn(resources, language, key);\n\t\tconst fallback = getFallbackLanguage();\n\t\tif (found === undefined && fallback !== language) {\n\t\t\tfound = messageIn(resources, fallback, key);\n\t\t\tlocale = fallback;\n\t\t}\n\t\tlet message = found ?? key;\n\t\ttry {\n\t\t\tmessage = new IntlMessageFormat(message, locale).format(context);\n\t\t} catch (e) {\n\t\t\tconsole.error(e);\n\t\t}\n\n\t\treturn message;\n\t};\n}\n\n/**\n * Translates with this package's catalogues and every one registered on top\n * (`registerCatalogue`), in `getLanguage()`'s language.\n */\nexport const translate = createTranslator(registeredCatalogues());\n"
13
+ ],
14
+ "mappings": ";AAIA,SAAS,QAAQ,CAAC,OAAiC;AAAA,EAClD,OAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAAA;AAQ3E,SAAS,SAAS,CAAC,QAAgB,QAAwB;AAAA,EAC1D,WAAW,OAAO,OAAO,KAAK,MAAM,GAAG;AAAA,IAGtC,IAAI,QAAQ;AAAA,MAAa;AAAA,IACzB,MAAM,QAAQ,OAAO;AAAA,IACrB,IAAI,UAAU,aAAa,UAAU;AAAA,MAAM;AAAA,IAC3C,IAAI,SAAS,KAAK,GAAG;AAAA,MACpB,MAAM,UAAU,OAAO,OAAO,QAAQ,GAAG,IAAI,OAAO,OAAO;AAAA,MAC3D,OAAO,OAAO,UAAU,SAAS,OAAO,IAAI,UAAU,CAAC,GAAG,KAAK;AAAA,IAChE,EAAO;AAAA,MACN,OAAO,OAAO;AAAA;AAAA,EAEhB;AAAA,EACA,OAAO;AAAA;AAgBD,SAAS,gBAAoC,CACnD,SACG,WACC;AAAA,EACJ,MAAM,SAAiB,CAAC;AAAA,EACxB,WAAW,aAAa,CAAC,MAAM,GAAG,SAAS,GAAG;AAAA,IAC7C,IAAI,CAAC,SAAS,SAAS,GAAG;AAAA,MACzB,MAAM,IAAI,UACT,4DAA4D,cAAc,OAAO,SAAS,OAAO,WAClG;AAAA,IACD;AAAA,IACA,UAAU,QAAQ,SAAS;AAAA,EAC5B;AAAA,EACA,OAAO;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACnDD,IAAM,KAAK;AAAA,KACd;AAAA,EACH;AAAA,EACA;AAAA,EACA;AACD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACLO,IAAM,KAAK;AAAA,KACd;AAAA,EACH;AAAA,EACA;AAAA,EACA;AACD;;;ACPO,IAAM,aAAY;AAAA,EACxB;AAAA,EACA;AACD;;;ACsBA,IAAM,QAAQ,OAAO,IAAI,yBAAyB;AAGlD,IAAM,aAAa,MAAkB,OAAO,OAAO,IAAI;AAEvD,SAAS,OAAO,CAAC,OAAc,UAA0B;AAAA,EACxD,MAAM,WAAW,YAAY,iBAC5B,MAAM,KAAK,aAAa,CAAC,GACzB,MAAM,WAAW,aAAa,CAAC,CAChC;AAAA,EACA,IAAI,CAAC,MAAM,UAAU,SAAS,QAAQ;AAAA,IAAG,MAAM,UAAU,KAAK,QAAQ;AAAA;AAGvE,SAAS,KAAK,GAAU;AAAA,EACvB,MAAM,SAAS;AAAA,EACf,OAAO,WAAW;AAAA,IACjB,MAAM,WAAW;AAAA,IACjB,YAAY,WAAW;AAAA,IACvB,YAAY,WAAW;AAAA,IACvB,WAAW,CAAC;AAAA,IACZ,UAAU;AAAA,EACX;AAAA,EACA,OAAO,OAAO;AAAA;AAKf;AAAA,EACC,MAAM,SAAS,MAAM;AAAA,EACrB,YAAY,UAAU,cAAc,OAAO,QAAQ,UAAS,GAAG;AAAA,IAC9D,OAAO,KAAK,YAAY,iBACvB,WACA,OAAO,KAAK,aAAa,CAAC,CAC3B;AAAA,IACA,QAAQ,QAAQ,QAAQ;AAAA,EACzB;AACD;AAOO,SAAS,qBAAoB,GAAyB;AAAA,EAC5D,OAAO,MAAM,EAAE;AAAA;AAOT,SAAS,mBAAkB,GAAwB;AAAA,EACzD,OAAO,MAAM,EAAE;AAAA;AAUT,SAAS,kBAAiB,CAChC,UACA,WACO;AAAA,EACP,IACC,OAAO,aAAa,YACpB,aAAa,MACb,aAAa,aACZ;AAAA,IACD,MAAM,IAAI,UACT,4DAA4D,KAAK,UAAU,QAAQ,GACpF;AAAA,EACD;AAAA,EACA,MAAM,SAAS,MAAM;AAAA,EACrB,OAAO,WAAW,YAAY,iBAC7B,OAAO,WAAW,aAAa,CAAC,GAChC,SACD;AAAA,EACA,QAAQ,QAAQ,QAAQ;AAAA;AAIlB,SAAS,oBAAmB,GAAa;AAAA,EAC/C,OAAO,MAAM,EAAE;AAAA;AAOT,SAAS,oBAAmB,CAAC,UAA0B;AAAA,EAC7D,IAAI,CAAC,MAAM,EAAE,UAAU,SAAS,QAAQ,GAAG;AAAA,IAC1C,MAAM,IAAI,WACT,+CAA+C,sCAAsC,qBACtF;AAAA,EACD;AAAA,EACA,MAAM,EAAE,WAAW;AAAA;;;AC5Hb,IAAM,gBAAe;AAMrB,IAAM,uBAAyC,oBAAmB;;;ACOzE,IAAM,WAAW,OAAO,IAAI,6BAA6B;AAEzD,SAAS,QAAQ,GAAqB;AAAA,EACrC,MAAM,SAAS;AAAA,EACf,OAAO,cAAc,CAAC;AAAA,EACtB,OAAO,OAAO;AAAA;AAUR,SAAS,uBAAsB,CAAC,QAAoC;AAAA,EAC1E,MAAM,UAAU,SAAS;AAAA,EACzB,IAAI,CAAC,QAAQ,SAAS,MAAM;AAAA,IAAG,QAAQ,KAAK,MAAM;AAAA,EAClD,OAAO,MAAM;AAAA,IACZ,MAAM,QAAQ,QAAQ,QAAQ,MAAM;AAAA,IACpC,IAAI,UAAU;AAAA,MAAI,QAAQ,OAAO,OAAO,CAAC;AAAA;AAAA;AAQpC,SAAS,oBAAmB,GAAyB;AAAA,EAC3D,WAAW,UAAU,SAAS,GAAG;AAAA,IAChC,IAAI;AAAA,IACJ,IAAI;AAAA,MACH,SAAS,OAAO;AAAA,MACf,MAAM;AAAA,MACP;AAAA;AAAA,IAED,IAAI,OAAO,WAAW,YAAY,qBAAoB,SAAS,MAAM,GAAG;AAAA,MACvE,OAAO;AAAA,IACR;AAAA,EACD;AAAA,EACA;AAAA;;;ACvDD;AACA;AA4BO,SAAS,YAAW,GAAa;AAAA,EACvC,MAAM,aAAa,qBAAoB;AAAA,EACvC,IAAI,eAAe;AAAA,IAAW,OAAO;AAAA,EACrC,IAAI,OAAO,iBAAiB,aAAa;AAAA,IACxC,MAAM,SAAS,aAAa,QAAQ,aAAY;AAAA,IAChD,IAAI,WAAW,QAAQ,qBAAoB,SAAS,MAAM;AAAA,MAAG,OAAO;AAAA,EACrE;AAAA,EACA,OAAO,qBAAoB;AAAA;AAQ5B,SAAS,SAAS,CACjB,WACA,UACA,KACqB;AAAA,EACrB,IAAI,CAAC,OAAO,OAAO,WAAW,QAAQ;AAAA,IAAG;AAAA,EACzC,MAAM,UAAU,EAAE,IAAI,UAAU,WAAW,GAAG;AAAA,EAC9C,OAAO,OAAO,YAAY,WAAW,UAAU;AAAA;AAQzC,SAAS,iBAA8C,CAC7D,YAAiC,CAAC,GAClC,iBAAmC,cAClC;AAAA,EACD,OAAO,CACN,KACA,UAA8B,WAC9B,kBAAoC,mBACxB;AAAA,IACZ,MAAM,WACL,OAAO,oBAAoB,aACxB,gBAAgB,IAChB;AAAA,IACJ,IAAI,SAAS;AAAA,IACb,IAAI,QAAQ,UAAU,WAAW,UAAU,GAAG;AAAA,IAC9C,MAAM,WAAW,qBAAoB;AAAA,IACrC,IAAI,UAAU,aAAa,aAAa,UAAU;AAAA,MACjD,QAAQ,UAAU,WAAW,UAAU,GAAG;AAAA,MAC1C,SAAS;AAAA,IACV;AAAA,IACA,IAAI,UAAU,SAAS;AAAA,IACvB,IAAI;AAAA,MACH,UAAU,IAAI,kBAAkB,SAAS,MAAM,EAAE,OAAO,OAAO;AAAA,MAC9D,OAAO,GAAG;AAAA,MACX,QAAQ,MAAM,CAAC;AAAA;AAAA,IAGhB,OAAO;AAAA;AAAA;AAQF,IAAM,aAAY,kBAAiB,sBAAqB,CAAC;",
15
+ "debugId": "D6E625C5C81A884864756E2164756E21",
16
+ "names": []
17
+ }
package/dist/consts.d.ts CHANGED
@@ -1,5 +1,7 @@
1
- import type { Language } from './types';
2
1
  export declare const LANGUAGE_KEY = "language";
3
- export declare const SUPPORTED_LANGUAGES: Language[];
4
- export declare const FALLBACK_LANGUAGE: Language;
2
+ /**
3
+ * The languages a catalogue is registered for: `en` and `fr`, then each one
4
+ * `registerCatalogue()` adds. Live — a registration shows in it.
5
+ */
6
+ export declare const SUPPORTED_LANGUAGES: readonly string[];
5
7
  //# sourceMappingURL=consts.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAExC,eAAO,MAAM,YAAY,aAAa,CAAC;AAEvC,eAAO,MAAM,mBAAmB,EAA6B,QAAQ,EAAE,CAAC;AAExE,eAAO,MAAM,iBAAiB,EAAE,QAAe,CAAC"}
1
+ {"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,YAAY,aAAa,CAAC;AAEvC;;;GAGG;AACH,eAAO,MAAM,mBAAmB,EAAE,SAAS,MAAM,EAAyB,CAAC"}
package/dist/i18n.d.ts CHANGED
@@ -7,7 +7,7 @@ import type { Language, LanguageProvider, LocaleKey, TranslationContext } from '
7
7
  * context, which `@nxgt/shared-hono` registers for Hono and
8
8
  * `@alxia/i18n` for alxia;
9
9
  * 2. `localStorage`, when that object exists;
10
- * 3. the fallback language.
10
+ * 3. the fallback language — `'en'`, or what `setFallbackLanguage()` set.
11
11
  *
12
12
  * Until 2.0 this read Hono's request context itself, through
13
13
  * `hono/context-storage`, which tied every consumer — a browser bundle, an
@@ -17,6 +17,15 @@ import type { Language, LanguageProvider, LocaleKey, TranslationContext } from '
17
17
  * and not the others passes it.
18
18
  */
19
19
  export declare function getLanguage(): Language;
20
+ /**
21
+ * A translator over `resources`. A key the language lacks — or a language
22
+ * with no catalogue at all — is looked up in the fallback language
23
+ * (`getFallbackLanguage()`), and only then answered as the key itself.
24
+ */
20
25
  export declare function createTranslator<K extends string = LocaleKey>(resources?: Record<string, any>, localeProvider?: LanguageProvider): (key: K, context?: TranslationContext, provideLanguage?: LanguageProvider) => string;
26
+ /**
27
+ * Translates with this package's catalogues and every one registered on top
28
+ * (`registerCatalogue`), in `getLanguage()`'s language.
29
+ */
21
30
  export declare const translate: (key: "errors.not-found" | "errors.unauthenticated" | "errors.duplicate-key" | "errors.something-went-wrong" | "errors.insufficient-permissions" | "errors.cast-failed" | "errors.server-connection-failed" | "errors.forbidden" | "errors.optimistic-lock-failed" | "errors.internal-server-error" | "errors.validation-failed" | "errors.address-blocked" | "errors.could-not-use-first-and-last-together" | "errors.invalid-cursor" | "errors.invalid-status-transition" | "errors.service-unavailable" | "errors.graphql-parse-failed" | "errors.graphql-validation-failed" | "errors.persisted-query-not-found" | "errors.persisted-query-not-supported" | "errors.bad-user-input" | "errors.operation-resolution-failure" | "errors.bad-request" | "errors.list-patch-one-operation-per-path" | "validation.errors.time" | "validation.errors.invalid-account-number" | "validation.errors.invalid-big-int" | "validation.errors.invalid-country-code" | "validation.errors.invalid-country-name" | "validation.errors.invalid-cuid" | "validation.errors.invalid-currency" | "validation.errors.invalid-date" | "validation.errors.invalid-date-time" | "validation.errors.invalid-duration" | "validation.errors.invalid-email-address" | "validation.errors.invalid-geo-json" | "validation.errors.invalid-guid" | "validation.errors.invalid-hex-color-code" | "validation.errors.invalid-hexadecimal" | "validation.errors.invalid-hsl" | "validation.errors.invalid-hsla" | "validation.errors.invalid-iban" | "validation.errors.invalid-ip" | "validation.errors.invalid-ipv4" | "validation.errors.invalid-ipv6" | "validation.errors.invalid-isbn" | "validation.errors.invalid-json" | "validation.errors.invalid-json-object" | "validation.errors.invalid-jwt" | "validation.errors.invalid-latitude" | "validation.errors.invalid-locale" | "validation.errors.invalid-longitude" | "validation.errors.invalid-long" | "validation.errors.invalid-mac" | "validation.errors.invalid-negative-float" | "validation.errors.invalid-negative-int" | "validation.errors.invalid-non-empty-string" | "validation.errors.invalid-non-negative-float" | "validation.errors.invalid-non-negative-int" | "validation.errors.invalid-non-positive-float" | "validation.errors.invalid-non-positive-int" | "validation.errors.invalid-object-id" | "validation.errors.invalid-phone-number" | "validation.errors.invalid-port" | "validation.errors.invalid-positive-float" | "validation.errors.invalid-positive-int" | "validation.errors.invalid-postal-code" | "validation.errors.invalid-rgb" | "validation.errors.invalid-rgba" | "validation.errors.invalid-sessn" | "validation.errors.invalid-time" | "validation.errors.invalid-timestamp" | "validation.errors.invalid-url" | "validation.errors.invalid-us-currency" | "validation.errors.invalid-utc-offset" | "validation.errors.invalid-uuid" | "validation.errors.invalid-void" | "validation.errors.min" | "validation.errors.max" | "validation.errors.min-length" | "validation.errors.max-length" | "validation.errors.range" | "validation.errors.size" | "validation.errors.past" | "validation.errors.future" | "validation.errors.email" | "validation.errors.ip" | "validation.errors.ipv4" | "validation.errors.ipv6" | "validation.errors.mac" | "validation.errors.url" | "validation.errors.uuid" | "validation.errors.guid" | "validation.errors.ssn" | "validation.errors.phone" | "validation.errors.street" | "validation.errors.zip-code" | "validation.errors.object-id" | "validation.errors.pattern" | "validation.errors.not-blank" | "validation.errors.not-empty" | "validation.errors.positive" | "validation.errors.negative" | "validation.errors.no-scalars" | "validation.errors.not-null" | "validation.errors.required" | "zod.required" | "zod.invalid_value_single" | "zod.invalid_value_multiple" | "zod.too_big_inclusive" | "zod.too_big_exclusive" | "zod.too_big_no_unit_inclusive" | "zod.too_big_no_unit_exclusive" | "zod.too_small_inclusive" | "zod.too_small_exclusive" | "zod.too_small_no_unit_inclusive" | "zod.too_small_no_unit_exclusive" | "zod.not_multiple_of" | "zod.unrecognized_keys_single" | "zod.unrecognized_keys_multiple" | "zod.invalid_key" | "zod.invalid_union" | "zod.invalid_element" | "zod.default" | "zod.invalid_type.string" | "zod.invalid_type.number" | "zod.invalid_type.bigint" | "zod.invalid_type.boolean" | "zod.invalid_type.symbol" | "zod.invalid_type.undefined" | "zod.invalid_type.object" | "zod.invalid_type.function" | "zod.invalid_type.date" | "zod.invalid_type.default" | "zod.invalid_type.array" | "zod.invalid_type.null" | "zod.invalid_type.enum" | "zod.invalid_format.includes" | "zod.invalid_format.default" | "zod.invalid_format.starts_with" | "zod.invalid_format.ends_with" | "zod.invalid_format.regex" | "zod.nouns.emoji" | "zod.nouns.nanoid" | "zod.nouns.cuid" | "zod.nouns.cuid2" | "zod.nouns.date" | "zod.nouns.time" | "zod.nouns.duration" | "zod.nouns.email" | "zod.nouns.ipv4" | "zod.nouns.ipv6" | "zod.nouns.mac" | "zod.nouns.url" | "zod.nouns.uuid" | "zod.nouns.guid" | "zod.nouns.regex" | "zod.nouns.uuidv4" | "zod.nouns.uuidv6" | "zod.nouns.ulid" | "zod.nouns.xid" | "zod.nouns.ksuid" | "zod.nouns.datetime" | "zod.nouns.cidrv4" | "zod.nouns.cidrv6" | "zod.nouns.base64" | "zod.nouns.base64url" | "zod.nouns.json_string" | "zod.nouns.e164" | "zod.nouns.jwt" | "zod.nouns.template_literal" | "zod.sizing.string.unit" | "zod.sizing.string.verb" | "zod.sizing.array.unit" | "zod.sizing.array.verb" | "zod.sizing.file.unit" | "zod.sizing.file.verb" | "zod.sizing.set.unit" | "zod.sizing.set.verb" | "mail.common.thanks" | "mail.common.greeting" | "mail.common.copyright" | "mail.templates.verification.message" | "mail.templates.verification.verify" | "mail.templates.verification.notes" | "opening-hours.errors.invalid-start-time-format" | "opening-hours.errors.invalid-end-time-format", context?: TranslationContext, provideLanguage?: LanguageProvider) => string;
22
31
  //# sourceMappingURL=i18n.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACX,QAAQ,EACR,gBAAgB,EAChB,SAAS,EACT,kBAAkB,EAClB,MAAM,SAAS,CAAC;AAEjB;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,WAAW,IAAI,QAAQ,CAUtC;AAED,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,MAAM,GAAG,SAAS,EAC5D,SAAS,GAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAM,EACnC,cAAc,GAAE,gBAA8B,IAG7C,KAAK,CAAC,EACN,UAAS,kBAA8B,EACvC,kBAAiB,gBAAiC,KAChD,MAAM,CAcT;AAED,eAAO,MAAM,SAAS,+qLAlBX,kBAAkB,oBACV,gBAAgB,KAC/B,MAgBgD,CAAC"}
1
+ {"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACX,QAAQ,EACR,gBAAgB,EAChB,SAAS,EACT,kBAAkB,EAClB,MAAM,SAAS,CAAC;AAEjB;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,WAAW,IAAI,QAAQ,CAQtC;AAiBD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,MAAM,GAAG,SAAS,EAC5D,SAAS,GAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAM,EACnC,cAAc,GAAE,gBAA8B,IAG7C,KAAK,CAAC,EACN,UAAS,kBAA8B,EACvC,kBAAiB,gBAAiC,KAChD,MAAM,CAqBT;AAED;;;GAGG;AACH,eAAO,MAAM,SAAS,+qLA7BX,kBAAkB,oBACV,gBAAgB,KAC/B,MA2B6D,CAAC"}
@@ -2,8 +2,10 @@
2
2
  * The `browser` condition's entry point. See `src/i18n.browser.ts` for why it
3
3
  * exists: everything else here is identical to `./index`.
4
4
  */
5
+ export * from './catalogues';
5
6
  export * from './consts';
6
7
  export * from './i18n.browser';
8
+ export * from './merge';
7
9
  export * from './resources';
8
10
  export * from './sources';
9
11
  export * from './types';
@@ -1 +1 @@
1
- {"version":3,"file":"index.browser.d.ts","sourceRoot":"","sources":["../src/index.browser.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,cAAc,UAAU,CAAC;AACzB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC"}
1
+ {"version":3,"file":"index.browser.d.ts","sourceRoot":"","sources":["../src/index.browser.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,cAAc,cAAc,CAAC;AAC7B,cAAc,UAAU,CAAC;AACzB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,SAAS,CAAC;AACxB,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC"}
@@ -1,25 +1,35 @@
1
1
  import {
2
+ mergeCatalogues2,
2
3
  resources2,
4
+ registeredCatalogues2,
5
+ supportedLanguages2,
6
+ registerCatalogue2,
7
+ getFallbackLanguage2,
8
+ setFallbackLanguage2,
3
9
  LANGUAGE_KEY2,
4
10
  SUPPORTED_LANGUAGES2,
5
- FALLBACK_LANGUAGE2,
6
11
  registerLanguageSource2,
7
12
  languageFromSources2,
8
13
  getLanguage2,
9
14
  createTranslator2,
10
15
  translate2
11
- } from "./chunks/index-jscbtbek.js";
16
+ } from "./chunks/index-wabmsewh.js";
12
17
  export {
13
- FALLBACK_LANGUAGE2 as FALLBACK_LANGUAGE,
14
18
  LANGUAGE_KEY2 as LANGUAGE_KEY,
15
19
  SUPPORTED_LANGUAGES2 as SUPPORTED_LANGUAGES,
16
20
  createTranslator2 as createTranslator,
21
+ getFallbackLanguage2 as getFallbackLanguage,
17
22
  getLanguage2 as getLanguage,
18
23
  languageFromSources2 as languageFromSources,
24
+ mergeCatalogues2 as mergeCatalogues,
25
+ registerCatalogue2 as registerCatalogue,
19
26
  registerLanguageSource2 as registerLanguageSource,
27
+ registeredCatalogues2 as registeredCatalogues,
20
28
  resources2 as resources,
29
+ setFallbackLanguage2 as setFallbackLanguage,
30
+ supportedLanguages2 as supportedLanguages,
21
31
  translate2 as translate
22
32
  };
23
33
 
24
- //# debugId=56C1E29CC90F7A3164756E2164756E21
34
+ //# debugId=605AF1FB1EE2607F64756E2164756E21
25
35
  //# sourceMappingURL=index.browser.js.map
@@ -4,6 +4,6 @@
4
4
  "sourcesContent": [
5
5
  ],
6
6
  "mappings": "",
7
- "debugId": "56C1E29CC90F7A3164756E2164756E21",
7
+ "debugId": "605AF1FB1EE2607F64756E2164756E21",
8
8
  "names": []
9
9
  }
package/dist/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
+ export * from './catalogues';
1
2
  export * from './consts';
2
3
  export * from './i18n';
4
+ export * from './merge';
3
5
  export * from './resources';
4
6
  export * from './sources';
5
7
  export * from './types';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,QAAQ,CAAC;AACvB,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,UAAU,CAAC;AACzB,cAAc,QAAQ,CAAC;AACvB,cAAc,SAAS,CAAC;AACxB,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC"}
package/dist/index.js CHANGED
@@ -1,25 +1,35 @@
1
1
  import {
2
+ mergeCatalogues2,
2
3
  resources2,
4
+ registeredCatalogues2,
5
+ supportedLanguages2,
6
+ registerCatalogue2,
7
+ getFallbackLanguage2,
8
+ setFallbackLanguage2,
3
9
  LANGUAGE_KEY2,
4
10
  SUPPORTED_LANGUAGES2,
5
- FALLBACK_LANGUAGE2,
6
11
  registerLanguageSource2,
7
12
  languageFromSources2,
8
13
  getLanguage2,
9
14
  createTranslator2,
10
15
  translate2
11
- } from "./chunks/index-jscbtbek.js";
16
+ } from "./chunks/index-wabmsewh.js";
12
17
  export {
13
- FALLBACK_LANGUAGE2 as FALLBACK_LANGUAGE,
14
18
  LANGUAGE_KEY2 as LANGUAGE_KEY,
15
19
  SUPPORTED_LANGUAGES2 as SUPPORTED_LANGUAGES,
16
20
  createTranslator2 as createTranslator,
21
+ getFallbackLanguage2 as getFallbackLanguage,
17
22
  getLanguage2 as getLanguage,
18
23
  languageFromSources2 as languageFromSources,
24
+ mergeCatalogues2 as mergeCatalogues,
25
+ registerCatalogue2 as registerCatalogue,
19
26
  registerLanguageSource2 as registerLanguageSource,
27
+ registeredCatalogues2 as registeredCatalogues,
20
28
  resources2 as resources,
29
+ setFallbackLanguage2 as setFallbackLanguage,
30
+ supportedLanguages2 as supportedLanguages,
21
31
  translate2 as translate
22
32
  };
23
33
 
24
- //# debugId=B6438B8D81267DD164756E2164756E21
34
+ //# debugId=CCCBDD346E5B589364756E2164756E21
25
35
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -4,6 +4,6 @@
4
4
  "sourcesContent": [
5
5
  ],
6
6
  "mappings": "",
7
- "debugId": "B6438B8D81267DD164756E2164756E21",
7
+ "debugId": "CCCBDD346E5B589364756E2164756E21",
8
8
  "names": []
9
9
  }
@@ -0,0 +1,16 @@
1
+ import type { Catalogue } from './types';
2
+ /**
3
+ * Merges catalogues deeply, the last one winning leaf by leaf:
4
+ * `mergeCatalogues(packageDefaults, appOverrides, databaseOverrides)`.
5
+ *
6
+ * A spread replaces whole branches — `{ ...resources.en, validation }` drops
7
+ * every package key under `validation` the app did not repeat. Here a branch
8
+ * is merged key by key; a leaf (a message) replaces what was there, branch
9
+ * included. `undefined` and `null` are skipped, so an override never erases
10
+ * a message. The inputs are not changed.
11
+ *
12
+ * The result is typed as `base`: an override can add keys, which the type
13
+ * does not list.
14
+ */
15
+ export declare function mergeCatalogues<B extends Catalogue>(base: B, ...overrides: readonly Catalogue[]): B;
16
+ //# sourceMappingURL=merge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merge.d.ts","sourceRoot":"","sources":["../src/merge.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AA8BzC;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,SAAS,EAClD,IAAI,EAAE,CAAC,EACP,GAAG,SAAS,EAAE,SAAS,SAAS,EAAE,GAChC,CAAC,CAWH"}
@@ -1,7 +1,18 @@
1
1
  import type { resources } from '../resources';
2
2
  import type { FlatObject } from './path';
3
3
  export type LocaleKey = keyof FlatObject<typeof resources.en, string>;
4
- export type Language = keyof typeof resources;
4
+ /**
5
+ * A language name, as a catalogue is registered under it: `'en'`, `'fr'`,
6
+ * `'pt-BR'`. Since 3.0 any string — a language is data an app registers, not
7
+ * a list this package fixes. `SUPPORTED_LANGUAGES` says which have one.
8
+ */
9
+ export type Language = string;
10
+ /** The languages this package ships catalogues for. */
11
+ export type BuiltInLanguage = keyof typeof resources;
12
+ /** Messages by key, nested: `{ errors: { 'not-found': '…' } }`. */
13
+ export type Catalogue = {
14
+ readonly [key: string]: string | Catalogue;
15
+ };
5
16
  export type TranslationContext = {
6
17
  [k: string]: ((children: any) => any) | string | number | boolean | object | Record<string, any>;
7
18
  } | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../../src/types/i18n.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAEzC,MAAM,MAAM,SAAS,GAAG,MAAM,UAAU,CAAC,OAAO,SAAS,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;AAEtE,MAAM,MAAM,QAAQ,GAAG,MAAM,OAAO,SAAS,CAAC;AAE9C,MAAM,MAAM,kBAAkB,GAC3B;IACA,CAAC,CAAC,EAAE,MAAM,GACP,CAAC,CAAC,QAAQ,EAAE,GAAG,KAAK,GAAG,CAAC,GACxB,MAAM,GACN,MAAM,GACN,OAAO,GACP,MAAM,GACN,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CACtB,GACD,SAAS,CAAC;AAEb,MAAM,MAAM,gBAAgB,GAAG,CAAC,MAAM,QAAQ,CAAC,GAAG,QAAQ,CAAC"}
1
+ {"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../../src/types/i18n.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAEzC,MAAM,MAAM,SAAS,GAAG,MAAM,UAAU,CAAC,OAAO,SAAS,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;AAEtE;;;;GAIG;AACH,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC;AAE9B,uDAAuD;AACvD,MAAM,MAAM,eAAe,GAAG,MAAM,OAAO,SAAS,CAAC;AAErD,mEAAmE;AACnE,MAAM,MAAM,SAAS,GAAG;IAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;CAAE,CAAC;AAEvE,MAAM,MAAM,kBAAkB,GAC3B;IACA,CAAC,CAAC,EAAE,MAAM,GACP,CAAC,CAAC,QAAQ,EAAE,GAAG,KAAK,GAAG,CAAC,GACxB,MAAM,GACN,MAAM,GACN,OAAO,GACP,MAAM,GACN,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CACtB,GACD,SAAS,CAAC;AAEb,MAAM,MAAM,gBAAgB,GAAG,CAAC,MAAM,QAAQ,CAAC,GAAG,QAAQ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxgt/i18n",
3
- "version": "2.0.1",
3
+ "version": "3.0.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -49,6 +49,6 @@
49
49
  "@types/lodash": "^4.17.25"
50
50
  },
51
51
  "peerDependencies": {
52
- "typescript": "^6.0.3"
52
+ "typescript": "^6.0.3 || ^7.0.0"
53
53
  }
54
54
  }
@@ -1,15 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/resources/en/index.ts", "../src/resources/fr/index.ts", "../src/resources/index.ts", "../src/consts.ts", "../src/sources.ts", "../src/i18n.ts"],
4
- "sourcesContent": [
5
- "import common from './common.json';\nimport errors from './errors.json';\nimport validation from './validation.json';\nimport zod from './zod.json';\n\nexport const en = {\n\t...common,\n\terrors,\n\tvalidation,\n\tzod,\n};\n",
6
- "import common from './common.json';\nimport errors from './errors.json';\nimport validation from './validation.json';\nimport zod from './zod.json';\n\nexport const fr = {\n\t...common,\n\terrors,\n\tvalidation,\n\tzod,\n};\n",
7
- "import { en } from './en';\nimport { fr } from './fr';\n\nexport const resources = {\n\ten,\n\tfr,\n};\n",
8
- "import { resources } from './resources';\nimport type { Language } from './types';\n\nexport const LANGUAGE_KEY = 'language';\n\nexport const SUPPORTED_LANGUAGES = Object.keys(resources) as Language[];\n\nexport const FALLBACK_LANGUAGE: Language = 'en';\n",
9
- "import { SUPPORTED_LANGUAGES } from './consts';\nimport type { Language } from './types';\n\n/**\n * Where a language may come from: a server's request context, a framework's\n * state. It answers the request's language, or nothing — `undefined`, `null`,\n * a language no catalogue has — and the next source is asked.\n */\nexport type LanguageSource = () => string | null | undefined;\n\n/**\n * One registry for the process, on `globalThis`: an app can hold two copies\n * of this package — one a dependency of `@nxgt/shared-hono`, one its own —\n * and a source registered through either must answer both.\n */\nconst REGISTRY = Symbol.for('@nxgt/i18n/language-sources');\n\nfunction registry(): LanguageSource[] {\n\tconst global = globalThis as { [REGISTRY]?: LanguageSource[] };\n\tglobal[REGISTRY] ??= [];\n\treturn global[REGISTRY];\n}\n\n/**\n * Adds a source `getLanguage()` asks, after those registered before it.\n * Registering one twice keeps one. Answers the function that removes it.\n *\n * This package knows no server: `@nxgt/shared-hono` registers the Hono\n * request context's `language`, `@alxia/i18n` the alxia request's.\n */\nexport function registerLanguageSource(source: LanguageSource): () => void {\n\tconst sources = registry();\n\tif (!sources.includes(source)) sources.push(source);\n\treturn () => {\n\t\tconst index = sources.indexOf(source);\n\t\tif (index !== -1) sources.splice(index, 1);\n\t};\n}\n\n/**\n * The first supported language a registered source answers. A source that\n * throws is skipped: a language is never worth a failed request.\n */\nexport function languageFromSources(): Language | undefined {\n\tfor (const source of registry()) {\n\t\tlet answer: string | null | undefined;\n\t\ttry {\n\t\t\tanswer = source();\n\t\t} catch {\n\t\t\tcontinue;\n\t\t}\n\t\tif (SUPPORTED_LANGUAGES.includes(answer as Language)) {\n\t\t\treturn answer as Language;\n\t\t}\n\t}\n\treturn undefined;\n}\n",
10
- "import { IntlMessageFormat } from 'intl-messageformat';\nimport _ from 'lodash';\nimport { FALLBACK_LANGUAGE, LANGUAGE_KEY, SUPPORTED_LANGUAGES } from './consts';\nimport { resources } from './resources';\nimport { languageFromSources } from './sources';\nimport type {\n\tLanguage,\n\tLanguageProvider,\n\tLocaleKey,\n\tTranslationContext,\n} from './types';\n\n/**\n * Where the language comes from when the caller does not say, most specific\n * first:\n *\n * 1. a registered source — `registerLanguageSource()`: a server's request\n * context, which `@nxgt/shared-hono` registers for Hono and\n * `@alxia/i18n` for alxia;\n * 2. `localStorage`, when that object exists;\n * 3. the fallback language.\n *\n * Until 2.0 this read Hono's request context itself, through\n * `hono/context-storage`, which tied every consumer — a browser bundle, an\n * alxia server — to Hono. The Hono part lives in `@nxgt/shared-hono` now.\n *\n * `createTranslator` still takes a provider, so a caller that wants one source\n * and not the others passes it.\n */\nexport function getLanguage(): Language {\n\tconst fromSource = languageFromSources();\n\tif (fromSource !== undefined) return fromSource;\n\tif (typeof localStorage !== 'undefined') {\n\t\tconst stored = localStorage.getItem(LANGUAGE_KEY);\n\t\tif (SUPPORTED_LANGUAGES.includes(stored as Language)) {\n\t\t\treturn stored as Language;\n\t\t}\n\t}\n\treturn FALLBACK_LANGUAGE;\n}\n\nexport function createTranslator<K extends string = LocaleKey>(\n\tresources: Record<string, any> = {},\n\tlocaleProvider: LanguageProvider = getLanguage,\n) {\n\treturn (\n\t\tkey: K,\n\t\tcontext: TranslationContext = undefined,\n\t\tprovideLanguage: LanguageProvider = localeProvider,\n\t): string => {\n\t\tconst language =\n\t\t\ttypeof provideLanguage === 'function'\n\t\t\t\t? provideLanguage()\n\t\t\t\t: provideLanguage;\n\t\tlet message: string = _.get(resources[language], key) ?? key;\n\t\ttry {\n\t\t\tmessage = new IntlMessageFormat(message, language).format(context);\n\t\t} catch (e) {\n\t\t\tconsole.error(e);\n\t\t}\n\n\t\treturn message;\n\t};\n}\n\nexport const translate = createTranslator(resources);\n"
11
- ],
12
- "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAKO,IAAM,KAAK;AAAA,KACd;AAAA,EACH;AAAA,EACA;AAAA,EACA;AACD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACLO,IAAM,KAAK;AAAA,KACd;AAAA,EACH;AAAA,EACA;AAAA,EACA;AACD;;;ACPO,IAAM,aAAY;AAAA,EACxB;AAAA,EACA;AACD;;;ACHO,IAAM,gBAAe;AAErB,IAAM,uBAAsB,OAAO,KAAK,UAAS;AAEjD,IAAM,qBAA8B;;;ACQ3C,IAAM,WAAW,OAAO,IAAI,6BAA6B;AAEzD,SAAS,QAAQ,GAAqB;AAAA,EACrC,MAAM,SAAS;AAAA,EACf,OAAO,cAAc,CAAC;AAAA,EACtB,OAAO,OAAO;AAAA;AAUR,SAAS,uBAAsB,CAAC,QAAoC;AAAA,EAC1E,MAAM,UAAU,SAAS;AAAA,EACzB,IAAI,CAAC,QAAQ,SAAS,MAAM;AAAA,IAAG,QAAQ,KAAK,MAAM;AAAA,EAClD,OAAO,MAAM;AAAA,IACZ,MAAM,QAAQ,QAAQ,QAAQ,MAAM;AAAA,IACpC,IAAI,UAAU;AAAA,MAAI,QAAQ,OAAO,OAAO,CAAC;AAAA;AAAA;AAQpC,SAAS,oBAAmB,GAAyB;AAAA,EAC3D,WAAW,UAAU,SAAS,GAAG;AAAA,IAChC,IAAI;AAAA,IACJ,IAAI;AAAA,MACH,SAAS,OAAO;AAAA,MACf,MAAM;AAAA,MACP;AAAA;AAAA,IAED,IAAI,qBAAoB,SAAS,MAAkB,GAAG;AAAA,MACrD,OAAO;AAAA,IACR;AAAA,EACD;AAAA,EACA;AAAA;;;ACvDD;AACA;AA4BO,SAAS,YAAW,GAAa;AAAA,EACvC,MAAM,aAAa,qBAAoB;AAAA,EACvC,IAAI,eAAe;AAAA,IAAW,OAAO;AAAA,EACrC,IAAI,OAAO,iBAAiB,aAAa;AAAA,IACxC,MAAM,SAAS,aAAa,QAAQ,aAAY;AAAA,IAChD,IAAI,qBAAoB,SAAS,MAAkB,GAAG;AAAA,MACrD,OAAO;AAAA,IACR;AAAA,EACD;AAAA,EACA,OAAO;AAAA;AAGD,SAAS,iBAA8C,CAC7D,YAAiC,CAAC,GAClC,iBAAmC,cAClC;AAAA,EACD,OAAO,CACN,KACA,UAA8B,WAC9B,kBAAoC,mBACxB;AAAA,IACZ,MAAM,WACL,OAAO,oBAAoB,aACxB,gBAAgB,IAChB;AAAA,IACJ,IAAI,UAAkB,EAAE,IAAI,UAAU,WAAW,GAAG,KAAK;AAAA,IACzD,IAAI;AAAA,MACH,UAAU,IAAI,kBAAkB,SAAS,QAAQ,EAAE,OAAO,OAAO;AAAA,MAChE,OAAO,GAAG;AAAA,MACX,QAAQ,MAAM,CAAC;AAAA;AAAA,IAGhB,OAAO;AAAA;AAAA;AAIF,IAAM,aAAY,kBAAiB,UAAS;",
13
- "debugId": "2FA53BAC7C0D362464756E2164756E21",
14
- "names": []
15
- }