@nxgt/i18n 2.1.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -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,122 @@ 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
+ - `registerCatalogue(language, catalogue, { replace: true })` drops what was
73
+ registered for that language before, then registers `catalogue`. A reload
74
+ from a database uses it, so a key retired since the last load disappears.
75
+ It drops every earlier registration for that language, whichever module
76
+ made it. The package's own messages stay underneath either way. The options
77
+ are typed `RegisterCatalogueOptions`.
78
+ - `setFallbackLanguage(language)` changes what `getLanguage()` answers when
79
+ nothing names a language (`'en'` until then). The language must have a
80
+ catalogue; otherwise it throws a `RangeError`, so register a new language
81
+ first. `getFallbackLanguage()` reads it.
82
+ - `registeredCatalogues()` answers the catalogues `translate` reads, by
83
+ language.
84
+
85
+ The state lives on `globalThis`, as the language sources do: two 3.x copies of
86
+ this package in one app share it, and the keys a newer copy ships reach both.
87
+ A 2.x copy shares the language sources but not the catalogues: it refuses a
88
+ language it does not ship and answers in English. Every `@nxgt/*` package an
89
+ app uses has to resolve `@nxgt/i18n` 3 before its registrations reach it.
90
+
91
+ A language registered with only the app's keys has none of the package's
92
+ `errors`, `validation` and `zod` messages, and `translate` answers those in the
93
+ fallback language. Register them in that language too to have them translated.
94
+
95
+ ### `mergeCatalogues`
96
+
97
+ `mergeCatalogues(base, ...overrides)` is the merge `registerCatalogue` uses,
98
+ for an app that builds its catalogues itself:
99
+
100
+ ```ts
101
+ import { mergeCatalogues, resources } from '@nxgt/i18n';
102
+
103
+ export const en = mergeCatalogues(resources.en, appEn, overridesFromTheDatabase.en);
104
+ ```
105
+
106
+ A spread replaces whole branches: `{ ...resources.en, validation }` drops every
107
+ package key under `validation` that the app did not repeat. `mergeCatalogues`
108
+ merges a branch key by key, and the last override wins leaf by leaf. A leaf
109
+ replaces a branch, and the other way round. `undefined` and `null` are
110
+ skipped, so an override never erases a message. The inputs are not changed,
111
+ and a `__proto__` key is ignored. The result is typed as `base`; keys an
112
+ override adds are not in that type.
113
+
114
+ ### Checking a message: `parseMessage`
115
+
116
+ `parseMessage(message)` parses an ICU message the way `translate` formats it,
117
+ and answers the arguments it uses, with their kinds:
118
+
119
+ ```ts
120
+ import { MessageSyntaxError, parseMessage } from '@nxgt/i18n';
121
+
122
+ parseMessage('{n, plural, one {# file by {who}} other {# files}}');
123
+ // [{ name: 'n', kind: 'plural' }, { name: 'who', kind: 'argument' }]
124
+
125
+ try {
126
+ parseMessage('Hello {name');
127
+ } catch (error) {
128
+ if (error instanceof MessageSyntaxError) {
129
+ error.code; // 'EXPECT_ARGUMENT_CLOSING_BRACE'
130
+ error.line; error.column; // 1, 7
131
+ }
132
+ }
133
+ ```
134
+
135
+ - Each `{ name, kind }` pair is listed once, in the order it first appears,
136
+ including inside plural and select branches and tags. A name used two ways,
137
+ such as `{n}` and `{n, number}`, appears twice, which is how a check finds it.
138
+ The result is a `MessageArgument[]`, and `kind` is an `ArgumentKind`.
139
+ - The kinds are `argument`, `number`, `date`, `time`, `select`, `plural`,
140
+ `selectordinal` and `tag`. A tag such as `<b>…</b>` is formatted by a
141
+ function the caller passes under that name.
142
+ - A message that does not parse throws a `MessageSyntaxError`, a
143
+ `SyntaxError` carrying the parser's `code`, the `source`, and the `offset`,
144
+ `line` and `column` where parsing failed. A plural or select without an
145
+ `other` branch is refused, as the formatter refuses it. A skeleton the
146
+ parser locates keeps its own code (`INVALID_NUMBER_SKELETON`,
147
+ `EXPECT_DATE_TIME_SKELETON`). One it refuses without saying where, such as
148
+ `{d, date, ::YYYY}`, has the code `INVALID_SKELETON` and no position. `cause`
149
+ is the parser's own error.
150
+
151
+ Use it to check a catalogue loaded from a database, at load time or in a spec:
152
+ every message parses, keeps to the ICU subset the app allows, and uses each
153
+ argument with the same kind in every language. `translate` itself never
154
+ throws: a message that does not parse is logged and returned as it is.
49
155
 
50
156
  ## Where the language comes from
51
157
 
@@ -53,7 +159,7 @@ and `errors.service-unavailable`.
53
159
 
54
160
  1. every registered **language source**, in the order registered
55
161
  2. `localStorage.language`, when that object exists
56
- 3. `FALLBACK_LANGUAGE` (`'en'`)
162
+ 3. the fallback language: `'en'`, or what `setFallbackLanguage()` set
57
163
 
58
164
  A source is a function answering the current request's language, or nothing.
59
165
  This package knows no server; the server says where its request keeps the
@@ -76,13 +182,25 @@ skipped. Registering one twice keeps one. The registry is on `globalThis`, so
76
182
  two copies of this package in one app — one a dependency of
77
183
  `@nxgt/shared-hono`, one the app's own — share it.
78
184
 
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.
185
+ A server knows the language from its request, and a browser keeps it in
186
+ `localStorage`. Asking both serves either without a caller changing anything.
187
+ Pass a provider (a function or a language name) to `createTranslator` if you
188
+ want one source and not the other.
189
+
190
+ `SUPPORTED_LANGUAGES` is derived from the registered catalogues, not typed out,
191
+ and it is live: a registration shows in it.
192
+
193
+ ### Since 3.0
84
194
 
85
- `SUPPORTED_LANGUAGES` is derived from the catalogue keys, not typed out.
195
+ - `Language` is `string`, no longer `'en' | 'fr'`; that union is
196
+ `BuiltInLanguage`. Code that narrowed `getLanguage()` to the union now
197
+ narrows it itself, for example with `SUPPORTED_LANGUAGES.includes()`.
198
+ - `FALLBACK_LANGUAGE` is removed. Read `getFallbackLanguage()`, and change it
199
+ with `setFallbackLanguage()`.
200
+ - `SUPPORTED_LANGUAGES` is a `readonly string[]` that grows when a language is
201
+ registered.
202
+ - A key the language lacks is looked up in the fallback language before the
203
+ key itself is answered. Until 3.0, the key itself was answered right away.
86
204
 
87
205
  ### Since 2.0
88
206
 
@@ -112,3 +230,8 @@ a source as a server does, or relies on `localStorage`.
112
230
  - **ICU formatting failures are swallowed** (`console.error`) and the
113
231
  unformatted message is returned. A bad `{name}` in a catalogue is a log
114
232
  line, not a 500.
233
+ - **Registering throws on bad input, at startup rather than per request.**
234
+ `registerCatalogue('', …)`, `registerCatalogue('__proto__', …)` and a
235
+ catalogue that is not an object throw a `TypeError`, and so does
236
+ `mergeCatalogues(null, …)`. `setFallbackLanguage()` for a language with no
237
+ catalogue throws a `RangeError`.
@@ -0,0 +1,39 @@
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
+ export type RegisterCatalogueOptions = {
15
+ /**
16
+ * Replace what was registered for the language instead of merging over
17
+ * it: a reload drops the keys it no longer has. The package's own
18
+ * catalogue stays underneath either way.
19
+ */
20
+ replace?: boolean;
21
+ };
22
+ /**
23
+ * Registers `catalogue` for `language`, merged deeply over what that language
24
+ * already has (`mergeCatalogues`): a key it names wins, a key it leaves out
25
+ * keeps the package's message. A language with no catalogue yet becomes
26
+ * supported — `getLanguage()` accepts it from a source or `localStorage`, and
27
+ * `translate` answers in it. With `{ replace: true }`, earlier registrations
28
+ * for the language are dropped first.
29
+ */
30
+ export declare function registerCatalogue(language: Language, catalogue: Catalogue, { replace }?: RegisterCatalogueOptions): void;
31
+ /** The language `getLanguage()` answers when nothing else names one. */
32
+ export declare function getFallbackLanguage(): Language;
33
+ /**
34
+ * Changes the fallback language, `'en'` until then. It must have a catalogue:
35
+ * register one first for a language this package does not ship.
36
+ */
37
+ export declare function setFallbackLanguage(language: Language): void;
38
+ export {};
39
+ //# 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,MAAM,MAAM,wBAAwB,GAAG;IACtC;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAChC,QAAQ,EAAE,QAAQ,EAClB,SAAS,EAAE,SAAS,EACpB,EAAE,OAAe,EAAE,GAAE,wBAA6B,GAChD,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: {
@@ -111,12 +141,12 @@ var validation_default = {
111
141
  size: "The length of the field ''{path}'' must be between {min} and {max}.",
112
142
  past: "The value for the field ''{path}'' must be a valid date before {limit}.",
113
143
  future: "The value for the field ''{path}'' must be a valid date after {limit}.",
114
- email: "The value for the field ''{path}'' must be {numberOfDomains, plural, =0 {a valid email address} =1 {an email address within the domain ''{domains}''} other {an email address within one of the following domains: [{domains}]}.",
144
+ email: "The value for the field ''{path}'' must be {numberOfDomains, plural, =0 {a valid email address} =1 {an email address within the domain ''{domains}''} other {an email address within one of the following domains: [{domains}]}}.",
115
145
  ip: "The value for the field ''{path}'' must be a valid IP address.",
116
146
  ipv4: "The value for the field ''{path}'' must be a valid IPv4 address.",
117
147
  ipv6: "The value for the field ''{path}'' must be a valid IPv6 address.",
118
148
  mac: "The value for the field ''{path}'' must be a valid MAC address.",
119
- url: "The value for the field ''{path}'' must be {numberOfDomains, plural, =0 {a valid URL} =1 {a URL within the domain ''{domains}''} other {a URL within one of the following domains: [{domains}]}.",
149
+ url: "The value for the field ''{path}'' must be {numberOfDomains, plural, =0 {a valid URL} =1 {a URL within the domain ''{domains}''} other {a URL within one of the following domains: [{domains}]}}.",
120
150
  uuid: "The value for the field ''{path}'' must be a valid UUID.",
121
151
  guid: "The value for the field ''{path}'' must be a valid GUID.",
122
152
  ssn: "The value for the field ''{path}'' must be a valid social security number(SSN).",
@@ -130,7 +160,7 @@ var validation_default = {
130
160
  positive: "The value for the field ''{path}'' must be a positive number.",
131
161
  negative: "The value for the field ''{path}'' must be a negative number.",
132
162
  "no-scalars": "Invalid type: ''{type}'' is not a scalar type.",
133
- "not-null": "The value for {length, plural, =0 {the field} other {each item of the field} ''{path}'' should not be null.",
163
+ "not-null": "The value for {length, plural, =0 {the field} other {each item of the field}} ''{path}'' should not be null.",
134
164
  required: "The value for the field ''{path}'' is required.",
135
165
  time: "The value for the field ''{path}'' must be a valid time in the format HH:mm."
136
166
  }
@@ -336,12 +366,12 @@ var validation_default2 = {
336
366
  size: "La longueur du champ ''{path}'' doit être comprise entre {min} et {max}.",
337
367
  past: "La valeur pour le champ ''{path}'' doit être une date valide antérieure à {limit}.",
338
368
  future: "La valeur pour le champ ''{path}'' doit être une date valide postérieure à {limit}.",
339
- email: "La valeur pour le champ ''{path}'' doit être {numberOfDomains, plural, =0 {une adresse électronique valide} =1 {une adresse électronique dans le domaine ''{domains}'' } other {une adresse électronique dans l'un des domaines suivants : [{domains}]}.",
369
+ email: "La valeur pour le champ ''{path}'' doit être {numberOfDomains, plural, =0 {une adresse électronique valide} =1 {une adresse électronique dans le domaine ''{domains}'' } other {une adresse électronique dans l'un des domaines suivants : [{domains}]}}.",
340
370
  ip: "La valeur pour le champ ''{path}'' doit être une adresse IP valide.",
341
371
  ipv4: "La valeur pour le champ ''{path}'' doit être une adresse IPv4 valide.",
342
372
  ipv6: "La valeur pour le champ ''{path}'' doit être une adresse IPv6 valide.",
343
373
  mac: "La valeur pour le champ ''{path}'' doit être une adresse MAC valide.",
344
- url: "La valeur pour le champ ''{path}'' doit être {numberOfDomains, plural, =0 {une URL valide} =1 {une URL dans le domaine ''{domains}'' } other {une URL dans l'un des domaines suivants : [{domains}]}.",
374
+ url: "La valeur pour le champ ''{path}'' doit être {numberOfDomains, plural, =0 {une URL valide} =1 {une URL dans le domaine ''{domains}'' } other {une URL dans l'un des domaines suivants : [{domains}]}}.",
345
375
  uuid: "La valeur pour le champ ''{path}'' doit être un UUID valide.",
346
376
  guid: "La valeur pour le champ ''{path}'' doit être un GUID valide.",
347
377
  ssn: "La valeur pour le champ ''{path}'' doit être un numéro de sécurité social valide.",
@@ -350,8 +380,8 @@ var validation_default2 = {
350
380
  "zip-code": "La valeur pour le champ ''{path}'' doit être un code postal valide.",
351
381
  "object-id": "La valeur pour le champ ''{path}'' doit être une chaîne hexadécimale de 24 caractères.",
352
382
  pattern: "Valeur invalide pour le champ ''{path}'' . La valeur doit correspondre au motif : ''{pattern}'' .",
353
- "not-blank": "{length, plural, =0 {Le champ} other {Chaque élément du champ} ''{path}'' ne doit pas être vide.",
354
- "not-empty": "{length, plural, =0 {Le champ} other {Chaque élément du champ} ''{path}'' ne doit pas être vide.",
383
+ "not-blank": "Le champ ''{path}'' ne doit pas être vide.",
384
+ "not-empty": "Le champ ''{path}'' ne doit pas être vide.",
355
385
  positive: "La valeur pour le champ ''{path}'' doit être un nombre positif.",
356
386
  negative: "La valeur pour le champ ''{path}'' doit être un nombre négatif.",
357
387
  "no-scalars": "Type invalide : ''{type}'' n'est pas un type scalaire.",
@@ -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, { replace = false } = {}) {
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(replace ? {} : 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,114 @@ 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
+
615
+ // src/parse.ts
616
+ import {
617
+ parse,
618
+ TYPE
619
+ } from "@formatjs/icu-messageformat-parser";
620
+
621
+ class MessageSyntaxError2 extends SyntaxError {
622
+ code;
623
+ source;
624
+ name = "MessageSyntaxError";
625
+ offset;
626
+ line;
627
+ column;
628
+ constructor(code, source, position, cause) {
629
+ const where = position ? ` at ${position.line}:${position.column}` : "";
630
+ super(`@nxgt/i18n: ${code}${where} in ${JSON.stringify(source)}`, {
631
+ cause
632
+ });
633
+ this.code = code;
634
+ this.source = source;
635
+ this.offset = position?.offset;
636
+ this.line = position?.line;
637
+ this.column = position?.column;
638
+ }
639
+ }
640
+ function kindOf(element) {
641
+ switch (element.type) {
642
+ case TYPE.argument:
643
+ return "argument";
644
+ case TYPE.number:
645
+ return "number";
646
+ case TYPE.date:
647
+ return "date";
648
+ case TYPE.time:
649
+ return "time";
650
+ case TYPE.select:
651
+ return "select";
652
+ case TYPE.plural:
653
+ return element.pluralType === "ordinal" ? "selectordinal" : "plural";
654
+ case TYPE.tag:
655
+ return "tag";
656
+ default:
657
+ return;
658
+ }
659
+ }
660
+ function collect(elements, found) {
661
+ for (const element of elements) {
662
+ const kind = kindOf(element);
663
+ if (kind !== undefined && "value" in element) {
664
+ const name = element.value;
665
+ if (!found.some((seen) => seen.name === name && seen.kind === kind)) {
666
+ found.push({ name, kind });
667
+ }
668
+ }
669
+ if (element.type === TYPE.select || element.type === TYPE.plural) {
670
+ for (const option of Object.values(element.options)) {
671
+ collect(option.value, found);
672
+ }
673
+ } else if (element.type === TYPE.tag) {
674
+ collect(element.children, found);
675
+ }
676
+ }
677
+ }
678
+ function parseMessage2(message) {
679
+ let elements;
680
+ try {
681
+ elements = parse(message);
682
+ } catch (error) {
683
+ const { location } = error;
684
+ throw location ? new MessageSyntaxError2(error.message, message, location.start, error) : new MessageSyntaxError2("INVALID_SKELETON", message, undefined, error);
685
+ }
686
+ const found = [];
687
+ collect(elements, found);
688
+ return found;
689
+ }
690
+ export { mergeCatalogues2, resources2, registeredCatalogues2, supportedLanguages2, registerCatalogue2, getFallbackLanguage2, setFallbackLanguage2, LANGUAGE_KEY2, SUPPORTED_LANGUAGES2, registerLanguageSource2, languageFromSources2, getLanguage2, createTranslator2, translate2, MessageSyntaxError2, parseMessage2 };
524
691
 
525
- //# debugId=2FA53BAC7C0D362464756E2164756E21
526
- //# sourceMappingURL=index-jscbtbek.js.map
692
+ //# debugId=08677851F59036F664756E2164756E21
693
+ //# sourceMappingURL=index-g7dn8z2a.js.map
@@ -0,0 +1,18 @@
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", "../src/parse.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\nexport type RegisterCatalogueOptions = {\n\t/**\n\t * Replace what was registered for the language instead of merging over\n\t * it: a reload drops the keys it no longer has. The package's own\n\t * catalogue stays underneath either way.\n\t */\n\treplace?: boolean;\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. With `{ replace: true }`, earlier registrations\n * for the language are dropped first.\n */\nexport function registerCatalogue(\n\tlanguage: Language,\n\tcatalogue: Catalogue,\n\t{ replace = false }: RegisterCatalogueOptions = {},\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\treplace ? {} : (shared.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
+ "import {\n\ttype MessageFormatElement,\n\tparse,\n\tTYPE,\n} from '@formatjs/icu-messageformat-parser';\n\n/**\n * How a message uses an argument, as the ICU syntax names it: `{name}` is an\n * `argument`, `{n, number}` a `number`, `{n, plural, …}` a `plural`,\n * `{n, selectordinal, …}` a `selectordinal`, `<b>…</b>` a `tag` (formatted\n * by a function the caller passes under that name).\n */\nexport type ArgumentKind =\n\t| 'argument'\n\t| 'number'\n\t| 'date'\n\t| 'time'\n\t| 'select'\n\t| 'plural'\n\t| 'selectordinal'\n\t| 'tag';\n\n/** One argument a message uses, and how. */\nexport type MessageArgument = { name: string; kind: ArgumentKind };\n\ntype Position = { offset: number; line: number; column: number };\n\n/**\n * A message that is not valid ICU. `code` is the parser's\n * (`EXPECT_ARGUMENT_CLOSING_BRACE`, …), and `offset`, `line` and `column`\n * point at where it went wrong in `source`. A skeleton the parser locates\n * keeps its own code (`INVALID_NUMBER_SKELETON`, `EXPECT_DATE_TIME_SKELETON`);\n * one it refuses without saying where (`{d, date, ::YYYY}`) has the code\n * `INVALID_SKELETON` and no position. `cause` is the parser's own error.\n */\nexport class MessageSyntaxError extends SyntaxError {\n\toverride readonly name = 'MessageSyntaxError';\n\treadonly offset: number | undefined;\n\treadonly line: number | undefined;\n\treadonly column: number | undefined;\n\n\tconstructor(\n\t\treadonly code: string,\n\t\treadonly source: string,\n\t\tposition: Position | undefined,\n\t\tcause: unknown,\n\t) {\n\t\tconst where = position ? ` at ${position.line}:${position.column}` : '';\n\t\tsuper(`@nxgt/i18n: ${code}${where} in ${JSON.stringify(source)}`, {\n\t\t\tcause,\n\t\t});\n\t\tthis.offset = position?.offset;\n\t\tthis.line = position?.line;\n\t\tthis.column = position?.column;\n\t}\n}\n\nfunction kindOf(element: MessageFormatElement): ArgumentKind | undefined {\n\tswitch (element.type) {\n\t\tcase TYPE.argument:\n\t\t\treturn 'argument';\n\t\tcase TYPE.number:\n\t\t\treturn 'number';\n\t\tcase TYPE.date:\n\t\t\treturn 'date';\n\t\tcase TYPE.time:\n\t\t\treturn 'time';\n\t\tcase TYPE.select:\n\t\t\treturn 'select';\n\t\tcase TYPE.plural:\n\t\t\treturn element.pluralType === 'ordinal' ? 'selectordinal' : 'plural';\n\t\tcase TYPE.tag:\n\t\t\treturn 'tag';\n\t\tdefault:\n\t\t\treturn undefined;\n\t}\n}\n\nfunction collect(\n\telements: MessageFormatElement[],\n\tfound: MessageArgument[],\n): void {\n\tfor (const element of elements) {\n\t\tconst kind = kindOf(element);\n\t\tif (kind !== undefined && 'value' in element) {\n\t\t\tconst name = element.value;\n\t\t\tif (!found.some((seen) => seen.name === name && seen.kind === kind)) {\n\t\t\t\tfound.push({ name, kind });\n\t\t\t}\n\t\t}\n\t\tif (element.type === TYPE.select || element.type === TYPE.plural) {\n\t\t\tfor (const option of Object.values(element.options)) {\n\t\t\t\tcollect(option.value, found);\n\t\t\t}\n\t\t} else if (element.type === TYPE.tag) {\n\t\t\tcollect(element.children, found);\n\t\t}\n\t}\n}\n\n/**\n * Parses an ICU message the way `translate` formats it, and answers the\n * arguments it uses, each `{ name, kind }` pair once, in the order they first\n * appear — inside plural and select branches and tags too. A name used two\n * ways appears twice, which is how a check finds it. Throws a\n * `MessageSyntaxError` for a message that does not parse.\n *\n * For a catalogue loaded from a database: check every message at load time\n * or in a spec, keep to the ICU subset the app allows, and compare the kinds\n * of one key across languages.\n */\nexport function parseMessage(message: string): MessageArgument[] {\n\tlet elements: MessageFormatElement[];\n\ttry {\n\t\telements = parse(message);\n\t} catch (error) {\n\t\t// A syntax error carries its location; a refused skeleton is a plain\n\t\t// RangeError with a sentence for a message and no location.\n\t\tconst { location } = error as { location?: { start: Position } };\n\t\tthrow location\n\t\t\t? new MessageSyntaxError(\n\t\t\t\t\t(error as Error).message,\n\t\t\t\t\tmessage,\n\t\t\t\t\tlocation.start,\n\t\t\t\t\terror,\n\t\t\t\t)\n\t\t\t: new MessageSyntaxError('INVALID_SKELETON', message, undefined, error);\n\t}\n\tconst found: MessageArgument[] = [];\n\tcollect(elements, found);\n\treturn found;\n}\n"
14
+ ],
15
+ "mappings": "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",
16
+ "debugId": "08677851F59036F664756E2164756E21",
17
+ "names": []
18
+ }
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,11 @@
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';
9
+ export * from './parse';
7
10
  export * from './resources';
8
11
  export * from './sources';
9
12
  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,SAAS,CAAC;AACxB,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC"}
@@ -1,25 +1,39 @@
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
- translate2
11
- } from "./chunks/index-jscbtbek.js";
15
+ translate2,
16
+ MessageSyntaxError2,
17
+ parseMessage2
18
+ } from "./chunks/index-g7dn8z2a.js";
12
19
  export {
13
- FALLBACK_LANGUAGE2 as FALLBACK_LANGUAGE,
14
20
  LANGUAGE_KEY2 as LANGUAGE_KEY,
21
+ MessageSyntaxError2 as MessageSyntaxError,
15
22
  SUPPORTED_LANGUAGES2 as SUPPORTED_LANGUAGES,
16
23
  createTranslator2 as createTranslator,
24
+ getFallbackLanguage2 as getFallbackLanguage,
17
25
  getLanguage2 as getLanguage,
18
26
  languageFromSources2 as languageFromSources,
27
+ mergeCatalogues2 as mergeCatalogues,
28
+ parseMessage2 as parseMessage,
29
+ registerCatalogue2 as registerCatalogue,
19
30
  registerLanguageSource2 as registerLanguageSource,
31
+ registeredCatalogues2 as registeredCatalogues,
20
32
  resources2 as resources,
33
+ setFallbackLanguage2 as setFallbackLanguage,
34
+ supportedLanguages2 as supportedLanguages,
21
35
  translate2 as translate
22
36
  };
23
37
 
24
- //# debugId=56C1E29CC90F7A3164756E2164756E21
38
+ //# debugId=640BCA462E74B52664756E2164756E21
25
39
  //# sourceMappingURL=index.browser.js.map
@@ -4,6 +4,6 @@
4
4
  "sourcesContent": [
5
5
  ],
6
6
  "mappings": "",
7
- "debugId": "56C1E29CC90F7A3164756E2164756E21",
7
+ "debugId": "640BCA462E74B52664756E2164756E21",
8
8
  "names": []
9
9
  }
package/dist/index.d.ts CHANGED
@@ -1,5 +1,8 @@
1
+ export * from './catalogues';
1
2
  export * from './consts';
2
3
  export * from './i18n';
4
+ export * from './merge';
5
+ export * from './parse';
3
6
  export * from './resources';
4
7
  export * from './sources';
5
8
  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,SAAS,CAAC;AACxB,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC"}
package/dist/index.js CHANGED
@@ -1,25 +1,39 @@
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
- translate2
11
- } from "./chunks/index-jscbtbek.js";
15
+ translate2,
16
+ MessageSyntaxError2,
17
+ parseMessage2
18
+ } from "./chunks/index-g7dn8z2a.js";
12
19
  export {
13
- FALLBACK_LANGUAGE2 as FALLBACK_LANGUAGE,
14
20
  LANGUAGE_KEY2 as LANGUAGE_KEY,
21
+ MessageSyntaxError2 as MessageSyntaxError,
15
22
  SUPPORTED_LANGUAGES2 as SUPPORTED_LANGUAGES,
16
23
  createTranslator2 as createTranslator,
24
+ getFallbackLanguage2 as getFallbackLanguage,
17
25
  getLanguage2 as getLanguage,
18
26
  languageFromSources2 as languageFromSources,
27
+ mergeCatalogues2 as mergeCatalogues,
28
+ parseMessage2 as parseMessage,
29
+ registerCatalogue2 as registerCatalogue,
19
30
  registerLanguageSource2 as registerLanguageSource,
31
+ registeredCatalogues2 as registeredCatalogues,
20
32
  resources2 as resources,
33
+ setFallbackLanguage2 as setFallbackLanguage,
34
+ supportedLanguages2 as supportedLanguages,
21
35
  translate2 as translate
22
36
  };
23
37
 
24
- //# debugId=B6438B8D81267DD164756E2164756E21
38
+ //# debugId=F6F4B5663EA2D86364756E2164756E21
25
39
  //# 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": "F6F4B5663EA2D86364756E2164756E21",
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"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * How a message uses an argument, as the ICU syntax names it: `{name}` is an
3
+ * `argument`, `{n, number}` a `number`, `{n, plural, …}` a `plural`,
4
+ * `{n, selectordinal, …}` a `selectordinal`, `<b>…</b>` a `tag` (formatted
5
+ * by a function the caller passes under that name).
6
+ */
7
+ export type ArgumentKind = 'argument' | 'number' | 'date' | 'time' | 'select' | 'plural' | 'selectordinal' | 'tag';
8
+ /** One argument a message uses, and how. */
9
+ export type MessageArgument = {
10
+ name: string;
11
+ kind: ArgumentKind;
12
+ };
13
+ type Position = {
14
+ offset: number;
15
+ line: number;
16
+ column: number;
17
+ };
18
+ /**
19
+ * A message that is not valid ICU. `code` is the parser's
20
+ * (`EXPECT_ARGUMENT_CLOSING_BRACE`, …), and `offset`, `line` and `column`
21
+ * point at where it went wrong in `source`. A skeleton the parser locates
22
+ * keeps its own code (`INVALID_NUMBER_SKELETON`, `EXPECT_DATE_TIME_SKELETON`);
23
+ * one it refuses without saying where (`{d, date, ::YYYY}`) has the code
24
+ * `INVALID_SKELETON` and no position. `cause` is the parser's own error.
25
+ */
26
+ export declare class MessageSyntaxError extends SyntaxError {
27
+ readonly code: string;
28
+ readonly source: string;
29
+ readonly name = "MessageSyntaxError";
30
+ readonly offset: number | undefined;
31
+ readonly line: number | undefined;
32
+ readonly column: number | undefined;
33
+ constructor(code: string, source: string, position: Position | undefined, cause: unknown);
34
+ }
35
+ /**
36
+ * Parses an ICU message the way `translate` formats it, and answers the
37
+ * arguments it uses, each `{ name, kind }` pair once, in the order they first
38
+ * appear — inside plural and select branches and tags too. A name used two
39
+ * ways appears twice, which is how a check finds it. Throws a
40
+ * `MessageSyntaxError` for a message that does not parse.
41
+ *
42
+ * For a catalogue loaded from a database: check every message at load time
43
+ * or in a spec, keep to the ICU subset the app allows, and compare the kinds
44
+ * of one key across languages.
45
+ */
46
+ export declare function parseMessage(message: string): MessageArgument[];
47
+ export {};
48
+ //# sourceMappingURL=parse.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../src/parse.ts"],"names":[],"mappings":"AAMA;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GACrB,UAAU,GACV,QAAQ,GACR,MAAM,GACN,MAAM,GACN,QAAQ,GACR,QAAQ,GACR,eAAe,GACf,KAAK,CAAC;AAET,4CAA4C;AAC5C,MAAM,MAAM,eAAe,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,YAAY,CAAA;CAAE,CAAC;AAEnE,KAAK,QAAQ,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjE;;;;;;;GAOG;AACH,qBAAa,kBAAmB,SAAQ,WAAW;IAOjD,QAAQ,CAAC,IAAI,EAAE,MAAM;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM;IAPxB,SAAkB,IAAI,wBAAwB;IAC9C,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;gBAG1B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACvB,QAAQ,EAAE,QAAQ,GAAG,SAAS,EAC9B,KAAK,EAAE,OAAO;CAUf;AA6CD;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,eAAe,EAAE,CAoB/D"}
@@ -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.1.0",
3
+ "version": "3.1.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -41,6 +41,7 @@
41
41
  ]
42
42
  },
43
43
  "dependencies": {
44
+ "@formatjs/icu-messageformat-parser": "^3.5.20",
44
45
  "intl-messageformat": "^12.1.2",
45
46
  "lodash": "^4.18.1"
46
47
  },
@@ -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
- }