@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 +94 -19
- package/dist/catalogues.d.ts +30 -0
- package/dist/catalogues.d.ts.map +1 -0
- package/dist/chunks/{index-jscbtbek.js → index-wabmsewh.js} +103 -12
- package/dist/chunks/index-wabmsewh.js.map +17 -0
- package/dist/consts.d.ts +5 -3
- package/dist/consts.d.ts.map +1 -1
- package/dist/i18n.d.ts +10 -1
- package/dist/i18n.d.ts.map +1 -1
- package/dist/index.browser.d.ts +2 -0
- package/dist/index.browser.d.ts.map +1 -1
- package/dist/index.browser.js +14 -4
- package/dist/index.browser.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -4
- package/dist/index.js.map +1 -1
- package/dist/merge.d.ts +16 -0
- package/dist/merge.d.ts.map +1 -0
- package/dist/types/i18n.d.ts +12 -1
- package/dist/types/i18n.d.ts.map +1 -1
- package/package.json +2 -2
- package/dist/chunks/index-jscbtbek.js.map +0 -15
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
|
|
18
|
-
`^6.0.3
|
|
19
|
-
|
|
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
|
-
|
|
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`
|
|
40
|
-
`
|
|
41
|
-
`createTranslator` when a service has
|
|
42
|
-
|
|
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 `
|
|
46
|
-
`
|
|
47
|
-
`
|
|
48
|
-
|
|
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.
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
`
|
|
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 =
|
|
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
|
|
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
|
|
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,
|
|
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(
|
|
523
|
-
export { resources2, LANGUAGE_KEY2, SUPPORTED_LANGUAGES2,
|
|
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=
|
|
526
|
-
//# sourceMappingURL=index-
|
|
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
|
-
|
|
4
|
-
|
|
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
|
package/dist/consts.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"
|
|
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
|
package/dist/i18n.d.ts.map
CHANGED
|
@@ -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,
|
|
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"}
|
package/dist/index.browser.d.ts
CHANGED
|
@@ -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"}
|
package/dist/index.browser.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-
|
|
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=
|
|
34
|
+
//# debugId=605AF1FB1EE2607F64756E2164756E21
|
|
25
35
|
//# sourceMappingURL=index.browser.js.map
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -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-
|
|
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=
|
|
34
|
+
//# debugId=CCCBDD346E5B589364756E2164756E21
|
|
25
35
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
package/dist/merge.d.ts
ADDED
|
@@ -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"}
|
package/dist/types/i18n.d.ts
CHANGED
|
@@ -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
|
-
|
|
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;
|
package/dist/types/i18n.d.ts.map
CHANGED
|
@@ -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;
|
|
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": "
|
|
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
|
-
}
|