@xndrjs/i18n 0.3.0 → 0.3.1
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 +23 -4
- package/dist/index.d.ts +8 -1
- package/dist/index.js +20 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/codegen/emit/instance-file.ts +17 -4
- package/src/codegen/generate-i18n-types.test.ts +6 -0
- package/src/codegen/generate-i18n-types.ts +1 -0
- package/src/index.ts +2 -0
- package/src/project-locales.test.ts +67 -0
- package/src/project-locales.ts +33 -0
package/README.md
CHANGED
|
@@ -13,6 +13,7 @@ The core idea: your ICU strings live in local JSON files that act as **type-safe
|
|
|
13
13
|
- **Lazy namespace loading** — optional code-splitting via `loadOnInit` and `ensureNamespacesLoaded()` (multi mode).
|
|
14
14
|
- **Hot compilation cache** — compiled `IntlMessageFormat` instances are cached and invalidated on override.
|
|
15
15
|
- **Explicit runtime errors** — malformed ICU (e.g. a corrupt remote payload) or missing parameters throw descriptive errors.
|
|
16
|
+
- **Translation audit** — `xndrjs-i18n-audit` reports missing locales per key (direct vs effective after fallback); optional CI gate via `--fail-on`.
|
|
16
17
|
- **Publishable library** — the runtime and codegen live in a standalone package (`@xndrjs/i18n`) that carries no project-specific types.
|
|
17
18
|
|
|
18
19
|
## Getting started
|
|
@@ -58,15 +59,17 @@ npm install zod
|
|
|
58
59
|
```json
|
|
59
60
|
{
|
|
60
61
|
"scripts": {
|
|
61
|
-
"i18n:codegen": "xndrjs-i18n-codegen --config i18n/i18n.codegen.json"
|
|
62
|
+
"i18n:codegen": "xndrjs-i18n-codegen --config i18n/i18n.codegen.json",
|
|
63
|
+
"i18n:audit": "xndrjs-i18n-audit --config i18n/i18n.codegen.json"
|
|
62
64
|
}
|
|
63
65
|
}
|
|
64
66
|
```
|
|
65
67
|
|
|
66
|
-
**4. Generate and
|
|
68
|
+
**4. Generate and audit:**
|
|
67
69
|
|
|
68
70
|
```bash
|
|
69
71
|
npm run i18n:codegen
|
|
72
|
+
npm run i18n:audit
|
|
70
73
|
```
|
|
71
74
|
|
|
72
75
|
```ts
|
|
@@ -192,7 +195,7 @@ Variables found across **all locales** of the same key are merged. If parsing fa
|
|
|
192
195
|
|
|
193
196
|
- **`i18n-types.generated.ts`** — `I18N_MODE`, `MyProjectParams`, `MyProjectSchema`.
|
|
194
197
|
- **`dictionary.generated.ts`** — imports the JSON files and assembles the initial dictionary.
|
|
195
|
-
- **`instance.generated.ts`** — exports `createI18n()
|
|
198
|
+
- **`instance.generated.ts`** — exports `createI18n()` and typed `projectLocales()` (with `LOCALE_FALLBACK` wired in when configured).
|
|
196
199
|
- **`i18n.ts`** (optional, hand-written) — app-owned singleton if desired.
|
|
197
200
|
|
|
198
201
|
Example generated types (multi-namespace):
|
|
@@ -299,6 +302,20 @@ const localeFallback = {
|
|
|
299
302
|
const i18n = new IcuTranslationProviderMulti(schema, { localeFallback });
|
|
300
303
|
```
|
|
301
304
|
|
|
305
|
+
#### `projectLocales`
|
|
306
|
+
|
|
307
|
+
Namespaces split the dictionary by domain; `projectLocales` splits it by **locale**. Use it before `setAll()` / `setNamespace()` when you only need one locale (or a small regional group) in memory — not every language on every loaded namespace.
|
|
308
|
+
|
|
309
|
+
```ts
|
|
310
|
+
import { createI18n, projectLocales } from "./i18n/generated/instance.generated.js";
|
|
311
|
+
import billingDictionary from "./i18n/translations/billing.json";
|
|
312
|
+
|
|
313
|
+
const i18n = createI18n();
|
|
314
|
+
i18n.setNamespace("billing", projectLocales(billingDictionary, [activeLocale]));
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
Codegen emits a typed `projectLocales` in `instance.generated.ts` (wires `LOCALE_FALLBACK` like `createI18n`). The low-level `@xndrjs/i18n` export remains for tooling without codegen.
|
|
318
|
+
|
|
302
319
|
### 6. Translation audit (`xndrjs-i18n-audit`)
|
|
303
320
|
|
|
304
321
|
Report missing translations as JSON — useful for translator backlogs and optional CI gates.
|
|
@@ -395,7 +412,9 @@ Specify **exactly one** of `dictionary` (single-file) or `namespaces` (multi-fil
|
|
|
395
412
|
|
|
396
413
|
Dictionary paths may use `.yaml` or `.yml` instead of `.json`. **YAML is an authoring format, not a runtime format** — codegen compiles YAML to JSON under the generated output directory (for example `translations/billing.yaml` → `{dirname(typesOutput)}/translations/billing.json`); generated TypeScript imports the compiled JSON at runtime. Edit only the YAML source — the compiled JSON is overwritten on each codegen run.
|
|
397
414
|
|
|
398
|
-
YAML
|
|
415
|
+
Use YAML when ICU is hard to read on one line: multiple parameters, or plural/select branches. It is not aimed at long legal copy or styled pages — those usually belong in a separate content template with localized fragments inside.
|
|
416
|
+
|
|
417
|
+
YAML block scalars keep complex ICU readable in source while preserving (or intentionally folding) line breaks.
|
|
399
418
|
|
|
400
419
|
```yaml
|
|
401
420
|
# translations/billing.yaml
|
package/dist/index.d.ts
CHANGED
|
@@ -111,6 +111,13 @@ declare function validateLocaleFallback(map: LocaleFallbackMap): void;
|
|
|
111
111
|
declare function resolveLocaleTemplate(localeByKey: Record<string, string | undefined> | undefined, requestedLocale: string, fallbackMap?: LocaleFallbackMap): ResolvedLocaleTemplate | undefined;
|
|
112
112
|
declare function formatLocaleFallbackChain(requestedLocale: string, fallbackMap?: LocaleFallbackMap): string;
|
|
113
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Builds a dictionary that contains only the requested locales.
|
|
116
|
+
* For each key and target locale, copies the direct template when present;
|
|
117
|
+
* otherwise resolves through `localeFallback` (same rules as `.get()` at runtime).
|
|
118
|
+
*/
|
|
119
|
+
declare function projectLocales(dictionary: KeyDictionary, locales: readonly string[], localeFallback?: LocaleFallbackMap$1): KeyDictionary;
|
|
120
|
+
|
|
114
121
|
interface EnsureNamespacesLoadedOptions<Schema extends MultiDictionary, NS extends keyof Schema & string> {
|
|
115
122
|
provider: Pick<{
|
|
116
123
|
hasNamespace(namespace: NS): boolean;
|
|
@@ -121,4 +128,4 @@ interface EnsureNamespacesLoadedOptions<Schema extends MultiDictionary, NS exten
|
|
|
121
128
|
}
|
|
122
129
|
declare function ensureNamespacesLoadedImpl<Schema extends MultiDictionary, NS extends keyof Schema & string>(options: EnsureNamespacesLoadedOptions<Schema, NS>, namespaces: NS[]): Promise<void>;
|
|
123
130
|
|
|
124
|
-
export { type EnsureNamespacesLoadedOptions, IcuTranslationProviderMulti, type IcuTranslationProviderOptions, IcuTranslationProviderSingle, type KeyDictionary, type LocaleDictionary, type LocaleFallbackMap$1 as LocaleFallbackMap, type LocaleOfMulti, type LocaleOfSingle, type MultiDictionary, type RequestLocale, type ResolvedLocaleTemplate, type TranslationProviderMulti, type TranslationProviderMultiForLocale, type TranslationProviderSingle, type TranslationProviderSingleForLocale, ensureNamespacesLoadedImpl, formatLocaleFallbackChain, resolveLocaleTemplate, validateLocaleFallback };
|
|
131
|
+
export { type EnsureNamespacesLoadedOptions, IcuTranslationProviderMulti, type IcuTranslationProviderOptions, IcuTranslationProviderSingle, type KeyDictionary, type LocaleDictionary, type LocaleFallbackMap$1 as LocaleFallbackMap, type LocaleOfMulti, type LocaleOfSingle, type MultiDictionary, type RequestLocale, type ResolvedLocaleTemplate, type TranslationProviderMulti, type TranslationProviderMultiForLocale, type TranslationProviderSingle, type TranslationProviderSingleForLocale, ensureNamespacesLoadedImpl, formatLocaleFallbackChain, projectLocales, resolveLocaleTemplate, validateLocaleFallback };
|
package/dist/index.js
CHANGED
|
@@ -243,6 +243,25 @@ var IcuTranslationProviderMulti = class {
|
|
|
243
243
|
}
|
|
244
244
|
};
|
|
245
245
|
|
|
246
|
+
// src/project-locales.ts
|
|
247
|
+
function projectLocales(dictionary, locales, localeFallback) {
|
|
248
|
+
const targetLocales = [...new Set(locales)];
|
|
249
|
+
const result = {};
|
|
250
|
+
for (const [key, localesByKey] of Object.entries(dictionary)) {
|
|
251
|
+
const projected = {};
|
|
252
|
+
for (const locale of targetLocales) {
|
|
253
|
+
const resolved = resolveLocaleTemplate(localesByKey, locale, localeFallback);
|
|
254
|
+
if (resolved !== void 0) {
|
|
255
|
+
projected[locale] = resolved.template;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (Object.keys(projected).length > 0) {
|
|
259
|
+
result[key] = projected;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return result;
|
|
263
|
+
}
|
|
264
|
+
|
|
246
265
|
// src/validation/errors.ts
|
|
247
266
|
function issueMessage(issue) {
|
|
248
267
|
switch (issue.kind) {
|
|
@@ -315,6 +334,6 @@ async function ensureNamespacesLoadedImpl(options, namespaces) {
|
|
|
315
334
|
await Promise.all(namespaces.map((namespace) => ensureOneNamespace(options, namespace)));
|
|
316
335
|
}
|
|
317
336
|
|
|
318
|
-
export { IcuTranslationProviderMulti, IcuTranslationProviderSingle, ensureNamespacesLoadedImpl, formatLocaleFallbackChain, resolveLocaleTemplate, validateLocaleFallback };
|
|
337
|
+
export { IcuTranslationProviderMulti, IcuTranslationProviderSingle, ensureNamespacesLoadedImpl, formatLocaleFallbackChain, projectLocales, resolveLocaleTemplate, validateLocaleFallback };
|
|
319
338
|
//# sourceMappingURL=index.js.map
|
|
320
339
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/deep-freeze.ts","../src/resolve-locale.ts","../src/IcuTranslationProviderSingle.ts","../src/IcuTranslationProviderMulti.ts","../src/validation/errors.ts","../src/ensure-namespace.ts"],"names":["IntlMessageFormat"],"mappings":";;;;;AAAO,SAAS,WAAc,KAAA,EAAa;AACzC,EAAA,IAAI,KAAA,KAAU,IAAA,IAAQ,OAAO,KAAA,KAAU,QAAA,EAAU;AAC/C,IAAA,OAAO,KAAA;AAAA,EACT;AAEA,EAAA,MAAA,CAAO,OAAO,KAAK,CAAA;AAEnB,EAAA,KAAA,MAAW,KAAA,IAAS,MAAA,CAAO,MAAA,CAAO,KAAK,CAAA,EAAG;AACxC,IAAA,UAAA,CAAW,KAAK,CAAA;AAAA,EAClB;AAEA,EAAA,OAAO,KAAA;AACT;AAEO,SAAS,eAAkB,KAAA,EAAa;AAC7C,EAAA,OAAO,UAAA,CAAW,eAAA,CAAgB,KAAK,CAAC,CAAA;AAC1C;;;ACRO,SAAS,uBAAuB,GAAA,EAA8B;AACnE,EAAA,KAAA,MAAW,WAAA,IAAe,MAAA,CAAO,IAAA,CAAK,GAAG,CAAA,EAAG;AAC1C,IAAA,MAAM,OAAA,uBAAc,GAAA,EAAY;AAChC,IAAA,IAAI,OAAA,GAA8B,WAAA;AAElC,IAAA,OAAO,YAAY,MAAA,EAAW;AAC5B,MAAA,IAAI,OAAA,CAAQ,GAAA,CAAI,OAAO,CAAA,EAAG;AACxB,QAAA,MAAM,IAAI,KAAA,CAAM,CAAA,oDAAA,EAAuD,OAAO,CAAA,CAAA,CAAG,CAAA;AAAA,MACnF;AACA,MAAA,OAAA,CAAQ,IAAI,OAAO,CAAA;AAEnB,MAAA,MAAM,IAAA,GAAkC,IAAI,OAAO,CAAA;AACnD,MAAA,IAAI,IAAA,KAAS,MAAA,IAAa,IAAA,KAAS,IAAA,EAAM;AACvC,QAAA;AAAA,MACF;AACA,MAAA,OAAA,GAAU,IAAA;AAAA,IACZ;AAAA,EACF;AACF;AAEO,SAAS,qBAAA,CACd,WAAA,EACA,eAAA,EACA,WAAA,EACoC;AACpC,EAAA,MAAM,KAAA,GAAkB,CAAC,eAAe,CAAA;AACxC,EAAA,IAAI,aAAA,GAAgB,eAAA;AAEpB,EAAA,OAAO,IAAA,EAAM;AACX,IAAA,MAAM,QAAA,GAAW,cAAc,aAAa,CAAA;AAC5C,IAAA,IAAI,aAAa,MAAA,EAAW;AAC1B,MAAA,OAAO,EAAE,QAAA,EAAU,cAAA,EAAgB,aAAA,EAAe,eAAe,KAAA,EAAM;AAAA,IACzE;AAEA,IAAA,IAAI,CAAC,WAAA,EAAa;AAChB,MAAA,OAAO,MAAA;AAAA,IACT;AAEA,IAAA,MAAM,QAAA,GAAW,YAAY,aAAa,CAAA;AAC1C,IAAA,IAAI,QAAA,KAAa,MAAA,IAAa,QAAA,KAAa,IAAA,EAAM;AAC/C,MAAA,OAAO,MAAA;AAAA,IACT;AAEA,IAAA,IAAI,KAAA,CAAM,QAAA,CAAS,QAAQ,CAAA,EAAG;AAC5B,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,0CAAA,EAA6C,CAAC,GAAG,KAAA,EAAO,QAAQ,CAAA,CAAE,IAAA,CAAK,UAAK,CAAC,CAAA;AAAA,OAC/E;AAAA,IACF;AAEA,IAAA,KAAA,CAAM,KAAK,QAAQ,CAAA;AACnB,IAAA,aAAA,GAAgB,QAAA;AAAA,EAClB;AACF;AAEO,SAAS,yBAAA,CACd,iBACA,WAAA,EACQ;AACR,EAAA,IAAI,CAAC,WAAA,EAAa;AAChB,IAAA,OAAO,eAAA;AAAA,EACT;AAEA,EAAA,MAAM,KAAA,GAAkB,CAAC,eAAe,CAAA;AACxC,EAAA,IAAI,OAAA,GAAU,eAAA;AAEd,EAAA,OAAO,IAAA,EAAM;AACX,IAAA,MAAM,QAAA,GAAW,YAAY,OAAO,CAAA;AACpC,IAAA,IAAI,QAAA,KAAa,MAAA,IAAa,QAAA,KAAa,IAAA,EAAM;AAC/C,MAAA;AAAA,IACF;AACA,IAAA,IAAI,KAAA,CAAM,QAAA,CAAS,QAAQ,CAAA,EAAG;AAC5B,MAAA,KAAA,CAAM,KAAK,QAAQ,CAAA;AACnB,MAAA;AAAA,IACF;AACA,IAAA,KAAA,CAAM,KAAK,QAAQ,CAAA;AACnB,IAAA,OAAA,GAAU,QAAA;AAAA,EACZ;AAEA,EAAA,OAAO,KAAA,CAAM,KAAK,UAAK,CAAA;AACzB;;;AC3CO,IAAM,wCAAN,MAMiE;AAAA,EACtE,WAAA,CACmB,UAMR,MAAA,EACT;AAPiB,IAAA,IAAA,CAAA,QAAA,GAAA,QAAA;AAMR,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AAAA,EACR;AAAA,EAPgB,QAAA;AAAA,EAMR,MAAA;AAAA,EAGX,GAAA,CACE,QACG,IAAA,EACK;AACR,IAAA,OACE,KAAK,QAAA,CAAS,GAAA,CAKd,KAAK,IAAA,CAAK,MAAA,EAAQ,GAAG,IAAI,CAAA;AAAA,EAC7B;AACF,CAAA;AAEO,IAAM,+BAAN,MAKgE;AAAA,EAC7D,UAAA;AAAA,EACA,gBAAqC,EAAC;AAAA,EAC7B,cAAA;AAAA,EAEjB,WAAA,CAAY,YAAoB,OAAA,EAAmD;AACjF,IAAA,IAAA,CAAK,UAAA,GAAa,gBAAgB,UAAU,CAAA;AAC5C,IAAA,IAAI,SAAS,cAAA,EAAgB;AAC3B,MAAA,sBAAA,CAAuB,QAAQ,cAAc,CAAA;AAC7C,MAAA,IAAA,CAAK,iBAAiB,OAAA,CAAQ,cAAA;AAAA,IAChC;AAAA,EACF;AAAA,EAEA,GAAA,CACE,GAAA,EACA,MAAA,EAAA,GACG,IAAA,EACK;AACR,IAAA,MAAM,MAAA,GAAS,KAAK,CAAC,CAAA;AACrB,IAAA,MAAM,QAAA,GAAW,sBAAsB,IAAA,CAAK,UAAA,CAAW,GAAG,CAAA,EAAG,MAAA,EAAQ,KAAK,cAAc,CAAA;AAExF,IAAA,IAAI,CAAC,QAAA,EAAU;AACb,MAAA,MAAM,KAAA,GAAQ,yBAAA,CAA0B,MAAA,EAAQ,IAAA,CAAK,cAAc,CAAA;AACnE,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,kCAAkC,MAAA,CAAO,GAAG,CAAC,CAAA,GAAA,EAAM,MAAM,sBAAsB,KAAK,CAAA,CAAA;AAAA,OACtF;AAAA,IACF;AAEA,IAAA,MAAM,EAAE,QAAA,EAAU,cAAA,EAAe,GAAI,QAAA;AAErC,IAAA,IAAI,CAAC,IAAA,CAAK,aAAA,CAAc,cAAc,CAAA,EAAG;AACvC,MAAA,IAAA,CAAK,aAAA,CAAc,cAAc,CAAA,GAAI,EAAC;AAAA,IACxC;AAEA,IAAA,MAAM,QAAA,GAAW,OAAO,GAAG,CAAA;AAE3B,IAAA,IAAI,CAAC,IAAA,CAAK,aAAA,CAAc,cAAc,CAAA,CAAE,QAAQ,CAAA,EAAG;AACjD,MAAA,IAAI;AACF,QAAA,IAAA,CAAK,aAAA,CAAc,cAAc,CAAA,CAAE,QAAQ,IAAI,IAAI,iBAAA;AAAA,UACjD,QAAA;AAAA,UACA;AAAA,SACF;AAAA,MACF,SAAS,GAAA,EAAK;AACZ,QAAA,MAAM,UAAU,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU,OAAO,GAAG,CAAA;AAC/D,QAAA,MAAM,IAAI,KAAA;AAAA,UACR,qDAAqD,MAAA,CAAO,GAAG,CAAC,CAAA,GAAA,EAAM,cAAc,MAAM,OAAO,CAAA;AAAA,SACnG;AAAA,MACF;AAAA,IACF;AAEA,IAAA,IAAI;AACF,MAAA,OAAO,KAAK,aAAA,CAAc,cAAc,EAAE,QAAQ,CAAA,CAAE,OAAO,MAAM,CAAA;AAAA,IACnE,SAAS,GAAA,EAAK;AACZ,MAAA,MAAM,UAAU,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU,OAAO,GAAG,CAAA;AAC/D,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,kEAAkE,MAAA,CAAO,GAAG,CAAC,CAAA,GAAA,EAAM,cAAc,MAAM,OAAO,CAAA;AAAA,OAChH;AAAA,IACF;AAAA,EACF;AAAA,EAEA,UACE,MAAA,EACyF;AACzF,IAAA,OAAO,IAAI,qCAAA,CAAsC,IAAA,EAAM,MAAM,CAAA;AAAA,EAC/D;AAAA,EAEA,MAAA,GAAiB;AACf,IAAA,OAAO,cAAA,CAAe,KAAK,UAAU,CAAA;AAAA,EACvC;AAAA,EAEA,OAAO,MAAA,EAAsB;AAC3B,IAAA,IAAA,CAAK,UAAA,GAAa,gBAAgB,MAAM,CAAA;AACxC,IAAA,IAAA,CAAK,gBAAgB,EAAC;AAAA,EACxB;AACF;AC1GO,IAAM,uCAAN,MAMgE;AAAA,EACrE,WAAA,CACmB,UAMR,MAAA,EACT;AAPiB,IAAA,IAAA,CAAA,QAAA,GAAA,QAAA;AAMR,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AAAA,EACR;AAAA,EAPgB,QAAA;AAAA,EAMR,MAAA;AAAA,EAGX,GAAA,CACE,SAAA,EACA,GAAA,EAAA,GACG,IAAA,EACK;AACR,IAAA,OACE,IAAA,CAAK,SAAS,GAAA,CASd,SAAA,EAAW,KAAK,IAAA,CAAK,MAAA,EAAQ,GAAG,IAAI,CAAA;AAAA,EACxC;AACF,CAAA;AAEO,IAAM,8BAAN,MAK+D;AAAA,EAC5D,UAAA;AAAA,EACA,gBAAoC,EAAC;AAAA,EAC5B,cAAA;AAAA,EACT,gBAAA;AAAA,EAER,WAAA,CAAY,YAA6B,OAAA,EAAmD;AAC1F,IAAA,IAAA,CAAK,UAAA,GAAa,gBAAgB,UAAU,CAAA;AAC5C,IAAA,IAAA,CAAK,mBAAmB,IAAI,GAAA,CAAI,MAAA,CAAO,IAAA,CAAK,UAAU,CAAC,CAAA;AACvD,IAAA,IAAI,SAAS,cAAA,EAAgB;AAC3B,MAAA,sBAAA,CAAuB,QAAQ,cAAc,CAAA;AAC7C,MAAA,IAAA,CAAK,iBAAiB,OAAA,CAAQ,cAAA;AAAA,IAChC;AAAA,EACF;AAAA,EAEA,GAAA,CACE,SAAA,EACA,GAAA,EACA,MAAA,EAAA,GACG,IAAA,EACK;AACR,IAAA,IAAI,CAAC,IAAA,CAAK,gBAAA,CAAiB,GAAA,CAAI,SAAmB,CAAA,EAAG;AACnD,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,iCAAiC,MAAA,CAAO,SAAS,CAAC,CAAA,uCAAA,EAA0C,MAAA,CAAO,SAAS,CAAC,CAAA,UAAA;AAAA,OAC/G;AAAA,IACF;AAEA,IAAA,MAAM,MAAA,GAAS,KAAK,CAAC,CAAA;AACrB,IAAA,MAAM,WAAA,GACJ,IAAA,CAAK,UAAA,CAAW,SAAmB,IACjC,GAAG,CAAA;AACP,IAAA,MAAM,QAAA,GAAW,qBAAA,CAAsB,WAAA,EAAa,MAAA,EAAQ,KAAK,cAAc,CAAA;AAE/E,IAAA,IAAI,CAAC,QAAA,EAAU;AACb,MAAA,MAAM,KAAA,GAAQ,yBAAA,CAA0B,MAAA,EAAQ,IAAA,CAAK,cAAc,CAAA;AACnE,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,yCAAA,EAA4C,MAAA,CAAO,SAAS,CAAC,CAAA,QAAA,EAAW,MAAA,CAAO,GAAG,CAAC,CAAA,GAAA,EAAM,MAAM,CAAA,mBAAA,EAAsB,KAAK,CAAA,CAAA;AAAA,OAC5H;AAAA,IACF;AAEA,IAAA,MAAM,EAAE,QAAA,EAAU,cAAA,EAAe,GAAI,QAAA;AAErC,IAAA,IAAI,CAAC,IAAA,CAAK,aAAA,CAAc,cAAc,CAAA,EAAG;AACvC,MAAA,IAAA,CAAK,aAAA,CAAc,cAAc,CAAA,GAAI,EAAC;AAAA,IACxC;AAEA,IAAA,IAAI,CAAC,IAAA,CAAK,aAAA,CAAc,cAAc,CAAA,CAAE,SAAmB,CAAA,EAAG;AAC5D,MAAA,IAAA,CAAK,aAAA,CAAc,cAAc,CAAA,CAAE,SAAmB,IAAI,EAAC;AAAA,IAC7D;AAEA,IAAA,MAAM,cAAA,GAAiB,IAAA,CAAK,aAAA,CAAc,cAAc,EAAE,SAAmB,CAAA;AAC7E,IAAA,MAAM,QAAA,GAAW,OAAO,GAAG,CAAA;AAE3B,IAAA,IAAI,CAAC,cAAA,CAAe,QAAQ,CAAA,EAAG;AAC7B,MAAA,IAAI;AACF,QAAA,cAAA,CAAe,QAAQ,CAAA,GAAI,IAAIA,iBAAAA,CAAkB,UAAU,cAAc,CAAA;AAAA,MAC3E,SAAS,GAAA,EAAK;AACZ,QAAA,MAAM,UAAU,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU,OAAO,GAAG,CAAA;AAC/D,QAAA,MAAM,IAAI,KAAA;AAAA,UACR,CAAA,mCAAA,EAAsC,MAAA,CAAO,SAAS,CAAC,CAAA,QAAA,EAAW,MAAA,CAAO,GAAG,CAAC,CAAA,GAAA,EAAM,cAAc,CAAA,GAAA,EAAM,OAAO,CAAA;AAAA,SAChH;AAAA,MACF;AAAA,IACF;AAEA,IAAA,IAAI;AACF,MAAA,OAAO,cAAA,CAAe,QAAQ,CAAA,CAAE,MAAA,CAAO,MAAM,CAAA;AAAA,IAC/C,SAAS,GAAA,EAAK;AACZ,MAAA,MAAM,UAAU,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU,OAAO,GAAG,CAAA;AAC/D,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,mCAAA,EAAsC,MAAA,CAAO,SAAS,CAAC,CAAA,QAAA,EAAW,MAAA,CAAO,GAAG,CAAC,CAAA,GAAA,EAAM,cAAc,CAAA,GAAA,EAAM,OAAO,CAAA;AAAA,OAChH;AAAA,IACF;AAAA,EACF;AAAA,EAEA,UACE,MAAA,EACwF;AACxF,IAAA,OAAO,IAAI,oCAAA,CAAqC,IAAA,EAAM,MAAM,CAAA;AAAA,EAC9D;AAAA,EAEA,MAAA,GAAiB;AACf,IAAA,OAAO,cAAA,CAAe,KAAK,UAAU,CAAA;AAAA,EACvC;AAAA,EAEA,aAA+C,SAAA,EAAwB;AACrE,IAAA,OAAO,IAAA,CAAK,gBAAA,CAAiB,GAAA,CAAI,SAAS,CAAA;AAAA,EAC5C;AAAA,EAEA,OAAO,MAAA,EAAsB;AAC3B,IAAA,IAAA,CAAK,UAAA,GAAa,gBAAgB,MAAM,CAAA;AACxC,IAAA,IAAA,CAAK,gBAAgB,EAAC;AACtB,IAAA,IAAA,CAAK,mBAAmB,IAAI,GAAA,CAAI,MAAA,CAAO,IAAA,CAAK,MAAM,CAAC,CAAA;AAAA,EACrD;AAAA,EAEA,YAAA,CAAsC,WAAe,MAAA,EAA0B;AAC7E,IAAA,IAAA,CAAK,UAAA,GAAa;AAAA,MAChB,GAAG,IAAA,CAAK,UAAA;AAAA,MACR,CAAC,SAAS,GAAG,eAAA,CAAgB,MAAM;AAAA,KACrC;AACA,IAAA,IAAA,CAAK,gBAAA,CAAiB,IAAI,SAAmB,CAAA;AAE7C,IAAA,KAAA,MAAW,MAAA,IAAU,MAAA,CAAO,IAAA,CAAK,IAAA,CAAK,aAAa,CAAA,EAAG;AACpD,MAAA,OAAO,IAAA,CAAK,aAAA,CAAc,MAAM,CAAA,GAAI,SAAmB,CAAA;AAAA,IACzD;AAAA,EACF;AACF;;;AChMA,SAAS,aAAa,KAAA,EAAgC;AACpD,EAAA,QAAQ,MAAM,IAAA;AAAM,IAClB,KAAK,aAAA;AACH,MAAA,OAAO,CAAA,wBAAA,EAA2B,KAAA,CAAM,IAAA,CAAK,IAAA,CAAK,GAAG,CAAC,CAAA,CAAA;AAAA,IACxD,KAAK,eAAA;AAAA,IACL,KAAK,sBAAA;AAAA,IACL,KAAK,kBAAA;AAAA,IACL,KAAK,sBAAA;AAAA,IACL,KAAK,mBAAA;AAAA,IACL,KAAK,wBAAA;AACH,MAAA,OAAO,KAAA,CAAM,OAAA;AAAA;AAEnB;AAEO,SAAS,aAAa,MAAA,EAA4C;AACvE,EAAA,OAAO,MAAA,CACJ,GAAA,CAAI,CAAC,KAAA,KAAU;AACd,IAAA,IAAI,MAAA,IAAU,KAAA,IAAS,KAAA,CAAM,IAAA,CAAK,SAAS,CAAA,EAAG;AAC5C,MAAA,OAAO,CAAA,CAAA,EAAI,KAAA,CAAM,IAAA,CAAK,IAAA,CAAK,GAAG,CAAC,CAAA,EAAA,EAAK,KAAA,CAAM,IAAI,CAAA,EAAA,EAAK,YAAA,CAAa,KAAK,CAAC,CAAA,CAAA;AAAA,IACxE;AACA,IAAA,OAAO,GAAG,KAAA,CAAM,IAAI,CAAA,EAAA,EAAK,YAAA,CAAa,KAAK,CAAC,CAAA,CAAA;AAAA,EAC9C,CAAC,CAAA,CACA,IAAA,CAAK,IAAI,CAAA;AACd;AAEO,IAAM,yBAAA,GAAN,cAAwC,KAAA,CAAM;AAAA,EACnD,YAA4B,MAAA,EAAoC;AAC9D,IAAA,KAAA,CAAM,YAAA,CAAa,MAAM,CAAC,CAAA;AADA,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AAE1B,IAAA,IAAA,CAAK,IAAA,GAAO,2BAAA;AAAA,EACd;AAAA,EAH4B,MAAA;AAI9B,CAAA;;;AChBA,IAAM,kBAAA,uBAAyB,OAAA,EAA4C;AAE3E,eAAe,kBAAA,CACb,SACA,SAAA,EACe;AACf,EAAA,IAAI,OAAA,CAAQ,QAAA,CAAS,YAAA,CAAa,SAAS,CAAA,EAAG;AAC5C,IAAA;AAAA,EACF;AAEA,EAAA,MAAM,cAAc,OAAA,CAAQ,QAAA;AAC5B,EAAA,IAAI,QAAA,GAAW,kBAAA,CAAmB,GAAA,CAAI,WAAW,CAAA;AACjD,EAAA,IAAI,CAAC,QAAA,EAAU;AACb,IAAA,QAAA,uBAAe,GAAA,EAAI;AACnB,IAAA,kBAAA,CAAmB,GAAA,CAAI,aAAa,QAAQ,CAAA;AAAA,EAC9C;AAEA,EAAA,MAAM,QAAA,GAAW,QAAA,CAAS,GAAA,CAAI,SAAS,CAAA;AACvC,EAAA,IAAI,QAAA,EAAU;AACZ,IAAA,OAAO,QAAA;AAAA,EACT;AAEA,EAAA,MAAM,WAAW,YAAY;AAC3B,IAAA,IAAI;AACF,MAAA,IAAI,OAAA,CAAQ,QAAA,CAAS,YAAA,CAAa,SAAS,CAAA,EAAG;AAC5C,QAAA;AAAA,MACF;AAEA,MAAA,MAAM,GAAA,GAAM,MAAM,OAAA,CAAQ,aAAA,CAAc,SAAS,CAAA,EAAE;AACnD,MAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,QAAA,CAAS,SAAA,EAAW,GAAG,CAAA;AAC9C,MAAA,IAAI,CAAC,OAAO,EAAA,EAAI;AACd,QAAA,MAAM,IAAI,yBAAA,CAA0B,MAAA,CAAO,MAAM,CAAA;AAAA,MACnD;AAEA,MAAA,OAAA,CAAQ,QAAA,CAAS,YAAA,CAAa,SAAA,EAAW,MAAA,CAAO,IAAI,CAAA;AAAA,IACtD,CAAA,SAAE;AACA,MAAA,QAAA,CAAU,OAAO,SAAS,CAAA;AAAA,IAC5B;AAAA,EACF,CAAA,GAAG;AAEH,EAAA,QAAA,CAAS,GAAA,CAAI,WAAW,OAAO,CAAA;AAC/B,EAAA,OAAO,OAAA;AACT;AAEA,eAAsB,0BAAA,CAGpB,SAAoD,UAAA,EAAiC;AACrF,EAAA,IAAI,UAAA,CAAW,WAAW,CAAA,EAAG;AAC3B,IAAA;AAAA,EACF;AAEA,EAAA,MAAM,OAAA,CAAQ,GAAA,CAAI,UAAA,CAAW,GAAA,CAAI,CAAC,cAAc,kBAAA,CAAmB,OAAA,EAAS,SAAS,CAAC,CAAC,CAAA;AACzF","file":"index.js","sourcesContent":["export function deepFreeze<T>(value: T): T {\n if (value === null || typeof value !== \"object\") {\n return value;\n }\n\n Object.freeze(value);\n\n for (const child of Object.values(value)) {\n deepFreeze(child);\n }\n\n return value;\n}\n\nexport function cloneAndFreeze<T>(value: T): T {\n return deepFreeze(structuredClone(value));\n}\n","export type LocaleFallbackMap = Record<string, string | null>;\n\nexport interface ResolvedLocaleTemplate {\n template: string;\n resolvedLocale: string;\n fallbackChain: readonly string[];\n}\n\nexport function validateLocaleFallback(map: LocaleFallbackMap): void {\n for (const startLocale of Object.keys(map)) {\n const visited = new Set<string>();\n let current: string | undefined = startLocale;\n\n while (current !== undefined) {\n if (visited.has(current)) {\n throw new Error(`[i18n] Circular locale fallback detected involving \"${current}\"`);\n }\n visited.add(current);\n\n const next: string | null | undefined = map[current];\n if (next === undefined || next === null) {\n break;\n }\n current = next;\n }\n }\n}\n\nexport function resolveLocaleTemplate(\n localeByKey: Record<string, string | undefined> | undefined,\n requestedLocale: string,\n fallbackMap?: LocaleFallbackMap\n): ResolvedLocaleTemplate | undefined {\n const chain: string[] = [requestedLocale];\n let currentLocale = requestedLocale;\n\n while (true) {\n const template = localeByKey?.[currentLocale];\n if (template !== undefined) {\n return { template, resolvedLocale: currentLocale, fallbackChain: chain };\n }\n\n if (!fallbackMap) {\n return undefined;\n }\n\n const fallback = fallbackMap[currentLocale];\n if (fallback === undefined || fallback === null) {\n return undefined;\n }\n\n if (chain.includes(fallback)) {\n throw new Error(\n `[i18n] Circular locale fallback detected: ${[...chain, fallback].join(\" → \")}`\n );\n }\n\n chain.push(fallback);\n currentLocale = fallback;\n }\n}\n\nexport function formatLocaleFallbackChain(\n requestedLocale: string,\n fallbackMap?: LocaleFallbackMap\n): string {\n if (!fallbackMap) {\n return requestedLocale;\n }\n\n const chain: string[] = [requestedLocale];\n let current = requestedLocale;\n\n while (true) {\n const fallback = fallbackMap[current];\n if (fallback === undefined || fallback === null) {\n break;\n }\n if (chain.includes(fallback)) {\n chain.push(fallback);\n break;\n }\n chain.push(fallback);\n current = fallback;\n }\n\n return chain.join(\" → \");\n}\n","import { IntlMessageFormat } from \"intl-messageformat\";\nimport { cloneAndFreeze } from \"./deep-freeze.js\";\nimport {\n formatLocaleFallbackChain,\n resolveLocaleTemplate,\n validateLocaleFallback,\n} from \"./resolve-locale.js\";\nimport type {\n IcuTranslationProviderOptions,\n KeyDictionary,\n LocaleFallbackMap,\n LocaleOfSingle,\n SingleCompiledCache,\n} from \"./types.js\";\n\nexport interface TranslationProviderSingleForLocale<\n Schema extends KeyDictionary,\n Params extends { [K in keyof Schema]: unknown },\n Locale extends string,\n> {\n readonly locale: Locale;\n get<K extends keyof Schema & string>(\n key: K,\n ...params: Params[K] extends never ? [] : [params: Params[K]]\n ): string;\n}\n\nexport interface TranslationProviderSingle<\n Schema extends KeyDictionary,\n Params extends { [K in keyof Schema]: unknown },\n RequestLocales extends string = LocaleOfSingle<Schema>,\n> {\n get<K extends keyof Schema & string>(\n key: K,\n locale: RequestLocales,\n ...params: Params[K] extends never ? [] : [params: Params[K]]\n ): string;\n forLocale<Locale extends RequestLocales>(\n locale: Locale\n ): TranslationProviderSingleForLocale<Schema, Params, Locale>;\n getAll(): Schema;\n setAll(values: Schema): void;\n}\n\nexport class IcuTranslationProviderSingleForLocale<\n Schema extends KeyDictionary,\n Params extends { [K in keyof Schema]: unknown },\n RequestLocales extends string,\n Locale extends RequestLocales,\n Fallback extends LocaleFallbackMap | undefined,\n> implements TranslationProviderSingleForLocale<Schema, Params, Locale> {\n constructor(\n private readonly provider: IcuTranslationProviderSingle<\n Schema,\n Params,\n RequestLocales,\n Fallback\n >,\n readonly locale: Locale\n ) {}\n\n get<K extends keyof Schema & string>(\n key: K,\n ...args: Params[K] extends never ? [] : [params: Params[K]]\n ): string {\n return (\n this.provider.get as (\n key: K,\n locale: Locale,\n ...params: Params[K] extends never ? [] : [params: Params[K]]\n ) => string\n )(key, this.locale, ...args);\n }\n}\n\nexport class IcuTranslationProviderSingle<\n Schema extends KeyDictionary,\n Params extends { [K in keyof Schema]: unknown },\n RequestLocales extends string = LocaleOfSingle<Schema>,\n Fallback extends LocaleFallbackMap | undefined = undefined,\n> implements TranslationProviderSingle<Schema, Params, RequestLocales> {\n private dictionary: Schema;\n private compiledCache: SingleCompiledCache = {};\n private readonly localeFallback?: Fallback;\n\n constructor(dictionary: Schema, options?: IcuTranslationProviderOptions<Fallback>) {\n this.dictionary = structuredClone(dictionary);\n if (options?.localeFallback) {\n validateLocaleFallback(options.localeFallback);\n this.localeFallback = options.localeFallback;\n }\n }\n\n get<K extends keyof Schema & string>(\n key: K,\n locale: RequestLocales,\n ...args: Params[K] extends never ? [] : [params: Params[K]]\n ): string {\n const params = args[0] as Record<string, unknown> | undefined;\n const resolved = resolveLocaleTemplate(this.dictionary[key], locale, this.localeFallback);\n\n if (!resolved) {\n const chain = formatLocaleFallbackChain(locale, this.localeFallback);\n throw new Error(\n `[i18n] Missing key or locale: \"${String(key)}\" [${locale}] (fallback chain: ${chain})`\n );\n }\n\n const { template, resolvedLocale } = resolved;\n\n if (!this.compiledCache[resolvedLocale]) {\n this.compiledCache[resolvedLocale] = {};\n }\n\n const cacheKey = String(key);\n\n if (!this.compiledCache[resolvedLocale][cacheKey]) {\n try {\n this.compiledCache[resolvedLocale][cacheKey] = new IntlMessageFormat(\n template,\n resolvedLocale\n );\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new Error(\n `[i18n ICU Syntax Error] Dictionary error for key \"${String(key)}\" [${resolvedLocale}]: ${message}`\n );\n }\n }\n\n try {\n return this.compiledCache[resolvedLocale][cacheKey].format(params) as string;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new Error(\n `[i18n Formatting Error] Invalid or missing parameters for key \"${String(key)}\" [${resolvedLocale}]: ${message}`\n );\n }\n }\n\n forLocale<Locale extends RequestLocales>(\n locale: Locale\n ): IcuTranslationProviderSingleForLocale<Schema, Params, RequestLocales, Locale, Fallback> {\n return new IcuTranslationProviderSingleForLocale(this, locale);\n }\n\n getAll(): Schema {\n return cloneAndFreeze(this.dictionary);\n }\n\n setAll(values: Schema): void {\n this.dictionary = structuredClone(values);\n this.compiledCache = {};\n }\n}\n","import { IntlMessageFormat } from \"intl-messageformat\";\nimport { cloneAndFreeze } from \"./deep-freeze.js\";\nimport {\n formatLocaleFallbackChain,\n resolveLocaleTemplate,\n validateLocaleFallback,\n} from \"./resolve-locale.js\";\nimport type {\n IcuTranslationProviderOptions,\n LocaleFallbackMap,\n LocaleOfMulti,\n MultiCompiledCache,\n MultiDictionary,\n} from \"./types.js\";\n\nexport interface TranslationProviderMultiForLocale<\n Schema extends MultiDictionary,\n Params extends { [NS in keyof Schema]: { [K in keyof Schema[NS]]: unknown } },\n Locale extends string,\n> {\n readonly locale: Locale;\n get<NS extends keyof Schema, K extends keyof Schema[NS] & keyof Params[NS] & string>(\n namespace: NS,\n key: K,\n ...params: Params[NS][K] extends never ? [] : [params: Params[NS][K]]\n ): string;\n}\n\nexport interface TranslationProviderMulti<\n Schema extends MultiDictionary,\n Params extends { [NS in keyof Schema]: { [K in keyof Schema[NS]]: unknown } },\n RequestLocales extends string = LocaleOfMulti<Schema>,\n> {\n get<NS extends keyof Schema, K extends keyof Schema[NS] & keyof Params[NS] & string>(\n namespace: NS,\n key: K,\n locale: RequestLocales,\n ...params: Params[NS][K] extends never ? [] : [params: Params[NS][K]]\n ): string;\n forLocale<Locale extends RequestLocales>(\n locale: Locale\n ): TranslationProviderMultiForLocale<Schema, Params, Locale>;\n getAll(): Schema;\n hasNamespace<NS extends keyof Schema & string>(namespace: NS): boolean;\n setAll(values: Schema): void;\n setNamespace<NS extends keyof Schema>(namespace: NS, values: Schema[NS]): void;\n}\n\nexport class IcuTranslationProviderMultiForLocale<\n Schema extends MultiDictionary,\n Params extends { [NS in keyof Schema]: { [K in keyof Schema[NS]]: unknown } },\n RequestLocales extends string,\n Locale extends RequestLocales,\n Fallback extends LocaleFallbackMap | undefined,\n> implements TranslationProviderMultiForLocale<Schema, Params, Locale> {\n constructor(\n private readonly provider: IcuTranslationProviderMulti<\n Schema,\n Params,\n RequestLocales,\n Fallback\n >,\n readonly locale: Locale\n ) {}\n\n get<NS extends keyof Schema, K extends keyof Schema[NS] & keyof Params[NS] & string>(\n namespace: NS,\n key: K,\n ...args: Params[NS][K] extends never ? [] : [params: Params[NS][K]]\n ): string {\n return (\n this.provider.get as <\n NS extends keyof Schema,\n K extends keyof Schema[NS] & keyof Params[NS] & string,\n >(\n namespace: NS,\n key: K,\n locale: Locale,\n ...params: Params[NS][K] extends never ? [] : [params: Params[NS][K]]\n ) => string\n )(namespace, key, this.locale, ...args);\n }\n}\n\nexport class IcuTranslationProviderMulti<\n Schema extends MultiDictionary,\n Params extends { [NS in keyof Schema]: { [K in keyof Schema[NS]]: unknown } },\n RequestLocales extends string = LocaleOfMulti<Schema>,\n Fallback extends LocaleFallbackMap | undefined = undefined,\n> implements TranslationProviderMulti<Schema, Params, RequestLocales> {\n private dictionary: Schema;\n private compiledCache: MultiCompiledCache = {};\n private readonly localeFallback?: Fallback;\n private loadedNamespaces: Set<string>;\n\n constructor(dictionary: Partial<Schema>, options?: IcuTranslationProviderOptions<Fallback>) {\n this.dictionary = structuredClone(dictionary) as Schema;\n this.loadedNamespaces = new Set(Object.keys(dictionary));\n if (options?.localeFallback) {\n validateLocaleFallback(options.localeFallback);\n this.localeFallback = options.localeFallback;\n }\n }\n\n get<NS extends keyof Schema, K extends keyof Schema[NS] & keyof Params[NS] & string>(\n namespace: NS,\n key: K,\n locale: RequestLocales,\n ...args: Params[NS][K] extends never ? [] : [params: Params[NS][K]]\n ): string {\n if (!this.loadedNamespaces.has(namespace as string)) {\n throw new Error(\n `[i18n] Namespace not loaded: \"${String(namespace)}\". Call ensureNamespacesLoaded(i18n, [\"${String(namespace)}\"]) first.`\n );\n }\n\n const params = args[0] as Record<string, unknown> | undefined;\n const localeByKey = (\n this.dictionary[namespace as string] as Record<string, Record<string, string>>\n )?.[key];\n const resolved = resolveLocaleTemplate(localeByKey, locale, this.localeFallback);\n\n if (!resolved) {\n const chain = formatLocaleFallbackChain(locale, this.localeFallback);\n throw new Error(\n `[i18n] Missing key or locale: namespace \"${String(namespace)}\", key \"${String(key)}\" [${locale}] (fallback chain: ${chain})`\n );\n }\n\n const { template, resolvedLocale } = resolved;\n\n if (!this.compiledCache[resolvedLocale]) {\n this.compiledCache[resolvedLocale] = {};\n }\n\n if (!this.compiledCache[resolvedLocale][namespace as string]) {\n this.compiledCache[resolvedLocale][namespace as string] = {};\n }\n\n const namespaceCache = this.compiledCache[resolvedLocale][namespace as string]!;\n const cacheKey = String(key);\n\n if (!namespaceCache[cacheKey]) {\n try {\n namespaceCache[cacheKey] = new IntlMessageFormat(template, resolvedLocale);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new Error(\n `[i18n ICU Syntax Error] namespace \"${String(namespace)}\", key \"${String(key)}\" [${resolvedLocale}]: ${message}`\n );\n }\n }\n\n try {\n return namespaceCache[cacheKey].format(params) as string;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new Error(\n `[i18n Formatting Error] namespace \"${String(namespace)}\", key \"${String(key)}\" [${resolvedLocale}]: ${message}`\n );\n }\n }\n\n forLocale<Locale extends RequestLocales>(\n locale: Locale\n ): IcuTranslationProviderMultiForLocale<Schema, Params, RequestLocales, Locale, Fallback> {\n return new IcuTranslationProviderMultiForLocale(this, locale);\n }\n\n getAll(): Schema {\n return cloneAndFreeze(this.dictionary);\n }\n\n hasNamespace<NS extends keyof Schema & string>(namespace: NS): boolean {\n return this.loadedNamespaces.has(namespace);\n }\n\n setAll(values: Schema): void {\n this.dictionary = structuredClone(values);\n this.compiledCache = {};\n this.loadedNamespaces = new Set(Object.keys(values));\n }\n\n setNamespace<NS extends keyof Schema>(namespace: NS, values: Schema[NS]): void {\n this.dictionary = {\n ...this.dictionary,\n [namespace]: structuredClone(values),\n };\n this.loadedNamespaces.add(namespace as string);\n\n for (const locale of Object.keys(this.compiledCache)) {\n delete this.compiledCache[locale]?.[namespace as string];\n }\n }\n}\n","import type { ValidationIssue, ValidationResult } from \"./types.js\";\n\nfunction issueMessage(issue: ValidationIssue): string {\n switch (issue.kind) {\n case \"missing_key\":\n return `Missing required key at ${issue.path.join(\".\")}`;\n case \"invalid_input\":\n case \"invalid_locale_value\":\n case \"icu_syntax_error\":\n case \"locale_args_mismatch\":\n case \"variable_mismatch\":\n case \"variable_type_mismatch\":\n return issue.message;\n }\n}\n\nexport function formatIssues(issues: readonly ValidationIssue[]): string {\n return issues\n .map((issue) => {\n if (\"path\" in issue && issue.path.length > 0) {\n return `[${issue.path.join(\".\")}] ${issue.kind}: ${issueMessage(issue)}`;\n }\n return `${issue.kind}: ${issueMessage(issue)}`;\n })\n .join(\"\\n\");\n}\n\nexport class DictionaryValidationError extends Error {\n constructor(public readonly issues: readonly ValidationIssue[]) {\n super(formatIssues(issues));\n this.name = \"DictionaryValidationError\";\n }\n}\n\nexport function assertValidDictionary<T>(\n result: ValidationResult<T>\n): asserts result is { ok: true; data: T } {\n if (!result.ok) {\n throw new DictionaryValidationError(result.issues);\n }\n}\n","import type { MultiDictionary } from \"./types.js\";\nimport type { ValidationResult } from \"./validation/types.js\";\nimport { DictionaryValidationError } from \"./validation/errors.js\";\n\nexport interface EnsureNamespacesLoadedOptions<\n Schema extends MultiDictionary,\n NS extends keyof Schema & string,\n> {\n provider: Pick<\n { hasNamespace(namespace: NS): boolean; setNamespace(namespace: NS, values: Schema[NS]): void },\n \"hasNamespace\" | \"setNamespace\"\n >;\n resolveLoader: (namespace: NS) => () => Promise<unknown>;\n validate: (namespace: NS, raw: unknown) => ValidationResult<Schema[NS]>;\n}\n\nconst inFlightByProvider = new WeakMap<object, Map<string, Promise<void>>>();\n\nasync function ensureOneNamespace<Schema extends MultiDictionary, NS extends keyof Schema & string>(\n options: EnsureNamespacesLoadedOptions<Schema, NS>,\n namespace: NS\n): Promise<void> {\n if (options.provider.hasNamespace(namespace)) {\n return;\n }\n\n const providerKey = options.provider as object;\n let inFlight = inFlightByProvider.get(providerKey);\n if (!inFlight) {\n inFlight = new Map();\n inFlightByProvider.set(providerKey, inFlight);\n }\n\n const existing = inFlight.get(namespace);\n if (existing) {\n return existing;\n }\n\n const promise = (async () => {\n try {\n if (options.provider.hasNamespace(namespace)) {\n return;\n }\n\n const raw = await options.resolveLoader(namespace)();\n const result = options.validate(namespace, raw);\n if (!result.ok) {\n throw new DictionaryValidationError(result.issues);\n }\n\n options.provider.setNamespace(namespace, result.data);\n } finally {\n inFlight!.delete(namespace);\n }\n })();\n\n inFlight.set(namespace, promise);\n return promise;\n}\n\nexport async function ensureNamespacesLoadedImpl<\n Schema extends MultiDictionary,\n NS extends keyof Schema & string,\n>(options: EnsureNamespacesLoadedOptions<Schema, NS>, namespaces: NS[]): Promise<void> {\n if (namespaces.length === 0) {\n return;\n }\n\n await Promise.all(namespaces.map((namespace) => ensureOneNamespace(options, namespace)));\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/deep-freeze.ts","../src/resolve-locale.ts","../src/IcuTranslationProviderSingle.ts","../src/IcuTranslationProviderMulti.ts","../src/project-locales.ts","../src/validation/errors.ts","../src/ensure-namespace.ts"],"names":["IntlMessageFormat"],"mappings":"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","file":"index.js","sourcesContent":["export function deepFreeze<T>(value: T): T {\n if (value === null || typeof value !== \"object\") {\n return value;\n }\n\n Object.freeze(value);\n\n for (const child of Object.values(value)) {\n deepFreeze(child);\n }\n\n return value;\n}\n\nexport function cloneAndFreeze<T>(value: T): T {\n return deepFreeze(structuredClone(value));\n}\n","export type LocaleFallbackMap = Record<string, string | null>;\n\nexport interface ResolvedLocaleTemplate {\n template: string;\n resolvedLocale: string;\n fallbackChain: readonly string[];\n}\n\nexport function validateLocaleFallback(map: LocaleFallbackMap): void {\n for (const startLocale of Object.keys(map)) {\n const visited = new Set<string>();\n let current: string | undefined = startLocale;\n\n while (current !== undefined) {\n if (visited.has(current)) {\n throw new Error(`[i18n] Circular locale fallback detected involving \"${current}\"`);\n }\n visited.add(current);\n\n const next: string | null | undefined = map[current];\n if (next === undefined || next === null) {\n break;\n }\n current = next;\n }\n }\n}\n\nexport function resolveLocaleTemplate(\n localeByKey: Record<string, string | undefined> | undefined,\n requestedLocale: string,\n fallbackMap?: LocaleFallbackMap\n): ResolvedLocaleTemplate | undefined {\n const chain: string[] = [requestedLocale];\n let currentLocale = requestedLocale;\n\n while (true) {\n const template = localeByKey?.[currentLocale];\n if (template !== undefined) {\n return { template, resolvedLocale: currentLocale, fallbackChain: chain };\n }\n\n if (!fallbackMap) {\n return undefined;\n }\n\n const fallback = fallbackMap[currentLocale];\n if (fallback === undefined || fallback === null) {\n return undefined;\n }\n\n if (chain.includes(fallback)) {\n throw new Error(\n `[i18n] Circular locale fallback detected: ${[...chain, fallback].join(\" → \")}`\n );\n }\n\n chain.push(fallback);\n currentLocale = fallback;\n }\n}\n\nexport function formatLocaleFallbackChain(\n requestedLocale: string,\n fallbackMap?: LocaleFallbackMap\n): string {\n if (!fallbackMap) {\n return requestedLocale;\n }\n\n const chain: string[] = [requestedLocale];\n let current = requestedLocale;\n\n while (true) {\n const fallback = fallbackMap[current];\n if (fallback === undefined || fallback === null) {\n break;\n }\n if (chain.includes(fallback)) {\n chain.push(fallback);\n break;\n }\n chain.push(fallback);\n current = fallback;\n }\n\n return chain.join(\" → \");\n}\n","import { IntlMessageFormat } from \"intl-messageformat\";\nimport { cloneAndFreeze } from \"./deep-freeze.js\";\nimport {\n formatLocaleFallbackChain,\n resolveLocaleTemplate,\n validateLocaleFallback,\n} from \"./resolve-locale.js\";\nimport type {\n IcuTranslationProviderOptions,\n KeyDictionary,\n LocaleFallbackMap,\n LocaleOfSingle,\n SingleCompiledCache,\n} from \"./types.js\";\n\nexport interface TranslationProviderSingleForLocale<\n Schema extends KeyDictionary,\n Params extends { [K in keyof Schema]: unknown },\n Locale extends string,\n> {\n readonly locale: Locale;\n get<K extends keyof Schema & string>(\n key: K,\n ...params: Params[K] extends never ? [] : [params: Params[K]]\n ): string;\n}\n\nexport interface TranslationProviderSingle<\n Schema extends KeyDictionary,\n Params extends { [K in keyof Schema]: unknown },\n RequestLocales extends string = LocaleOfSingle<Schema>,\n> {\n get<K extends keyof Schema & string>(\n key: K,\n locale: RequestLocales,\n ...params: Params[K] extends never ? [] : [params: Params[K]]\n ): string;\n forLocale<Locale extends RequestLocales>(\n locale: Locale\n ): TranslationProviderSingleForLocale<Schema, Params, Locale>;\n getAll(): Schema;\n setAll(values: Schema): void;\n}\n\nexport class IcuTranslationProviderSingleForLocale<\n Schema extends KeyDictionary,\n Params extends { [K in keyof Schema]: unknown },\n RequestLocales extends string,\n Locale extends RequestLocales,\n Fallback extends LocaleFallbackMap | undefined,\n> implements TranslationProviderSingleForLocale<Schema, Params, Locale> {\n constructor(\n private readonly provider: IcuTranslationProviderSingle<\n Schema,\n Params,\n RequestLocales,\n Fallback\n >,\n readonly locale: Locale\n ) {}\n\n get<K extends keyof Schema & string>(\n key: K,\n ...args: Params[K] extends never ? [] : [params: Params[K]]\n ): string {\n return (\n this.provider.get as (\n key: K,\n locale: Locale,\n ...params: Params[K] extends never ? [] : [params: Params[K]]\n ) => string\n )(key, this.locale, ...args);\n }\n}\n\nexport class IcuTranslationProviderSingle<\n Schema extends KeyDictionary,\n Params extends { [K in keyof Schema]: unknown },\n RequestLocales extends string = LocaleOfSingle<Schema>,\n Fallback extends LocaleFallbackMap | undefined = undefined,\n> implements TranslationProviderSingle<Schema, Params, RequestLocales> {\n private dictionary: Schema;\n private compiledCache: SingleCompiledCache = {};\n private readonly localeFallback?: Fallback;\n\n constructor(dictionary: Schema, options?: IcuTranslationProviderOptions<Fallback>) {\n this.dictionary = structuredClone(dictionary);\n if (options?.localeFallback) {\n validateLocaleFallback(options.localeFallback);\n this.localeFallback = options.localeFallback;\n }\n }\n\n get<K extends keyof Schema & string>(\n key: K,\n locale: RequestLocales,\n ...args: Params[K] extends never ? [] : [params: Params[K]]\n ): string {\n const params = args[0] as Record<string, unknown> | undefined;\n const resolved = resolveLocaleTemplate(this.dictionary[key], locale, this.localeFallback);\n\n if (!resolved) {\n const chain = formatLocaleFallbackChain(locale, this.localeFallback);\n throw new Error(\n `[i18n] Missing key or locale: \"${String(key)}\" [${locale}] (fallback chain: ${chain})`\n );\n }\n\n const { template, resolvedLocale } = resolved;\n\n if (!this.compiledCache[resolvedLocale]) {\n this.compiledCache[resolvedLocale] = {};\n }\n\n const cacheKey = String(key);\n\n if (!this.compiledCache[resolvedLocale][cacheKey]) {\n try {\n this.compiledCache[resolvedLocale][cacheKey] = new IntlMessageFormat(\n template,\n resolvedLocale\n );\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new Error(\n `[i18n ICU Syntax Error] Dictionary error for key \"${String(key)}\" [${resolvedLocale}]: ${message}`\n );\n }\n }\n\n try {\n return this.compiledCache[resolvedLocale][cacheKey].format(params) as string;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new Error(\n `[i18n Formatting Error] Invalid or missing parameters for key \"${String(key)}\" [${resolvedLocale}]: ${message}`\n );\n }\n }\n\n forLocale<Locale extends RequestLocales>(\n locale: Locale\n ): IcuTranslationProviderSingleForLocale<Schema, Params, RequestLocales, Locale, Fallback> {\n return new IcuTranslationProviderSingleForLocale(this, locale);\n }\n\n getAll(): Schema {\n return cloneAndFreeze(this.dictionary);\n }\n\n setAll(values: Schema): void {\n this.dictionary = structuredClone(values);\n this.compiledCache = {};\n }\n}\n","import { IntlMessageFormat } from \"intl-messageformat\";\nimport { cloneAndFreeze } from \"./deep-freeze.js\";\nimport {\n formatLocaleFallbackChain,\n resolveLocaleTemplate,\n validateLocaleFallback,\n} from \"./resolve-locale.js\";\nimport type {\n IcuTranslationProviderOptions,\n LocaleFallbackMap,\n LocaleOfMulti,\n MultiCompiledCache,\n MultiDictionary,\n} from \"./types.js\";\n\nexport interface TranslationProviderMultiForLocale<\n Schema extends MultiDictionary,\n Params extends { [NS in keyof Schema]: { [K in keyof Schema[NS]]: unknown } },\n Locale extends string,\n> {\n readonly locale: Locale;\n get<NS extends keyof Schema, K extends keyof Schema[NS] & keyof Params[NS] & string>(\n namespace: NS,\n key: K,\n ...params: Params[NS][K] extends never ? [] : [params: Params[NS][K]]\n ): string;\n}\n\nexport interface TranslationProviderMulti<\n Schema extends MultiDictionary,\n Params extends { [NS in keyof Schema]: { [K in keyof Schema[NS]]: unknown } },\n RequestLocales extends string = LocaleOfMulti<Schema>,\n> {\n get<NS extends keyof Schema, K extends keyof Schema[NS] & keyof Params[NS] & string>(\n namespace: NS,\n key: K,\n locale: RequestLocales,\n ...params: Params[NS][K] extends never ? [] : [params: Params[NS][K]]\n ): string;\n forLocale<Locale extends RequestLocales>(\n locale: Locale\n ): TranslationProviderMultiForLocale<Schema, Params, Locale>;\n getAll(): Schema;\n hasNamespace<NS extends keyof Schema & string>(namespace: NS): boolean;\n setAll(values: Schema): void;\n setNamespace<NS extends keyof Schema>(namespace: NS, values: Schema[NS]): void;\n}\n\nexport class IcuTranslationProviderMultiForLocale<\n Schema extends MultiDictionary,\n Params extends { [NS in keyof Schema]: { [K in keyof Schema[NS]]: unknown } },\n RequestLocales extends string,\n Locale extends RequestLocales,\n Fallback extends LocaleFallbackMap | undefined,\n> implements TranslationProviderMultiForLocale<Schema, Params, Locale> {\n constructor(\n private readonly provider: IcuTranslationProviderMulti<\n Schema,\n Params,\n RequestLocales,\n Fallback\n >,\n readonly locale: Locale\n ) {}\n\n get<NS extends keyof Schema, K extends keyof Schema[NS] & keyof Params[NS] & string>(\n namespace: NS,\n key: K,\n ...args: Params[NS][K] extends never ? [] : [params: Params[NS][K]]\n ): string {\n return (\n this.provider.get as <\n NS extends keyof Schema,\n K extends keyof Schema[NS] & keyof Params[NS] & string,\n >(\n namespace: NS,\n key: K,\n locale: Locale,\n ...params: Params[NS][K] extends never ? [] : [params: Params[NS][K]]\n ) => string\n )(namespace, key, this.locale, ...args);\n }\n}\n\nexport class IcuTranslationProviderMulti<\n Schema extends MultiDictionary,\n Params extends { [NS in keyof Schema]: { [K in keyof Schema[NS]]: unknown } },\n RequestLocales extends string = LocaleOfMulti<Schema>,\n Fallback extends LocaleFallbackMap | undefined = undefined,\n> implements TranslationProviderMulti<Schema, Params, RequestLocales> {\n private dictionary: Schema;\n private compiledCache: MultiCompiledCache = {};\n private readonly localeFallback?: Fallback;\n private loadedNamespaces: Set<string>;\n\n constructor(dictionary: Partial<Schema>, options?: IcuTranslationProviderOptions<Fallback>) {\n this.dictionary = structuredClone(dictionary) as Schema;\n this.loadedNamespaces = new Set(Object.keys(dictionary));\n if (options?.localeFallback) {\n validateLocaleFallback(options.localeFallback);\n this.localeFallback = options.localeFallback;\n }\n }\n\n get<NS extends keyof Schema, K extends keyof Schema[NS] & keyof Params[NS] & string>(\n namespace: NS,\n key: K,\n locale: RequestLocales,\n ...args: Params[NS][K] extends never ? [] : [params: Params[NS][K]]\n ): string {\n if (!this.loadedNamespaces.has(namespace as string)) {\n throw new Error(\n `[i18n] Namespace not loaded: \"${String(namespace)}\". Call ensureNamespacesLoaded(i18n, [\"${String(namespace)}\"]) first.`\n );\n }\n\n const params = args[0] as Record<string, unknown> | undefined;\n const localeByKey = (\n this.dictionary[namespace as string] as Record<string, Record<string, string>>\n )?.[key];\n const resolved = resolveLocaleTemplate(localeByKey, locale, this.localeFallback);\n\n if (!resolved) {\n const chain = formatLocaleFallbackChain(locale, this.localeFallback);\n throw new Error(\n `[i18n] Missing key or locale: namespace \"${String(namespace)}\", key \"${String(key)}\" [${locale}] (fallback chain: ${chain})`\n );\n }\n\n const { template, resolvedLocale } = resolved;\n\n if (!this.compiledCache[resolvedLocale]) {\n this.compiledCache[resolvedLocale] = {};\n }\n\n if (!this.compiledCache[resolvedLocale][namespace as string]) {\n this.compiledCache[resolvedLocale][namespace as string] = {};\n }\n\n const namespaceCache = this.compiledCache[resolvedLocale][namespace as string]!;\n const cacheKey = String(key);\n\n if (!namespaceCache[cacheKey]) {\n try {\n namespaceCache[cacheKey] = new IntlMessageFormat(template, resolvedLocale);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new Error(\n `[i18n ICU Syntax Error] namespace \"${String(namespace)}\", key \"${String(key)}\" [${resolvedLocale}]: ${message}`\n );\n }\n }\n\n try {\n return namespaceCache[cacheKey].format(params) as string;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new Error(\n `[i18n Formatting Error] namespace \"${String(namespace)}\", key \"${String(key)}\" [${resolvedLocale}]: ${message}`\n );\n }\n }\n\n forLocale<Locale extends RequestLocales>(\n locale: Locale\n ): IcuTranslationProviderMultiForLocale<Schema, Params, RequestLocales, Locale, Fallback> {\n return new IcuTranslationProviderMultiForLocale(this, locale);\n }\n\n getAll(): Schema {\n return cloneAndFreeze(this.dictionary);\n }\n\n hasNamespace<NS extends keyof Schema & string>(namespace: NS): boolean {\n return this.loadedNamespaces.has(namespace);\n }\n\n setAll(values: Schema): void {\n this.dictionary = structuredClone(values);\n this.compiledCache = {};\n this.loadedNamespaces = new Set(Object.keys(values));\n }\n\n setNamespace<NS extends keyof Schema>(namespace: NS, values: Schema[NS]): void {\n this.dictionary = {\n ...this.dictionary,\n [namespace]: structuredClone(values),\n };\n this.loadedNamespaces.add(namespace as string);\n\n for (const locale of Object.keys(this.compiledCache)) {\n delete this.compiledCache[locale]?.[namespace as string];\n }\n }\n}\n","import { resolveLocaleTemplate } from \"./resolve-locale.js\";\nimport type { KeyDictionary, LocaleFallbackMap } from \"./types.js\";\n\n/**\n * Builds a dictionary that contains only the requested locales.\n * For each key and target locale, copies the direct template when present;\n * otherwise resolves through `localeFallback` (same rules as `.get()` at runtime).\n */\nexport function projectLocales(\n dictionary: KeyDictionary,\n locales: readonly string[],\n localeFallback?: LocaleFallbackMap\n): KeyDictionary {\n const targetLocales = [...new Set(locales)];\n const result: KeyDictionary = {};\n\n for (const [key, localesByKey] of Object.entries(dictionary)) {\n const projected: Record<string, string> = {};\n\n for (const locale of targetLocales) {\n const resolved = resolveLocaleTemplate(localesByKey, locale, localeFallback);\n if (resolved !== undefined) {\n projected[locale] = resolved.template;\n }\n }\n\n if (Object.keys(projected).length > 0) {\n result[key] = projected;\n }\n }\n\n return result;\n}\n","import type { ValidationIssue, ValidationResult } from \"./types.js\";\n\nfunction issueMessage(issue: ValidationIssue): string {\n switch (issue.kind) {\n case \"missing_key\":\n return `Missing required key at ${issue.path.join(\".\")}`;\n case \"invalid_input\":\n case \"invalid_locale_value\":\n case \"icu_syntax_error\":\n case \"locale_args_mismatch\":\n case \"variable_mismatch\":\n case \"variable_type_mismatch\":\n return issue.message;\n }\n}\n\nexport function formatIssues(issues: readonly ValidationIssue[]): string {\n return issues\n .map((issue) => {\n if (\"path\" in issue && issue.path.length > 0) {\n return `[${issue.path.join(\".\")}] ${issue.kind}: ${issueMessage(issue)}`;\n }\n return `${issue.kind}: ${issueMessage(issue)}`;\n })\n .join(\"\\n\");\n}\n\nexport class DictionaryValidationError extends Error {\n constructor(public readonly issues: readonly ValidationIssue[]) {\n super(formatIssues(issues));\n this.name = \"DictionaryValidationError\";\n }\n}\n\nexport function assertValidDictionary<T>(\n result: ValidationResult<T>\n): asserts result is { ok: true; data: T } {\n if (!result.ok) {\n throw new DictionaryValidationError(result.issues);\n }\n}\n","import type { MultiDictionary } from \"./types.js\";\nimport type { ValidationResult } from \"./validation/types.js\";\nimport { DictionaryValidationError } from \"./validation/errors.js\";\n\nexport interface EnsureNamespacesLoadedOptions<\n Schema extends MultiDictionary,\n NS extends keyof Schema & string,\n> {\n provider: Pick<\n { hasNamespace(namespace: NS): boolean; setNamespace(namespace: NS, values: Schema[NS]): void },\n \"hasNamespace\" | \"setNamespace\"\n >;\n resolveLoader: (namespace: NS) => () => Promise<unknown>;\n validate: (namespace: NS, raw: unknown) => ValidationResult<Schema[NS]>;\n}\n\nconst inFlightByProvider = new WeakMap<object, Map<string, Promise<void>>>();\n\nasync function ensureOneNamespace<Schema extends MultiDictionary, NS extends keyof Schema & string>(\n options: EnsureNamespacesLoadedOptions<Schema, NS>,\n namespace: NS\n): Promise<void> {\n if (options.provider.hasNamespace(namespace)) {\n return;\n }\n\n const providerKey = options.provider as object;\n let inFlight = inFlightByProvider.get(providerKey);\n if (!inFlight) {\n inFlight = new Map();\n inFlightByProvider.set(providerKey, inFlight);\n }\n\n const existing = inFlight.get(namespace);\n if (existing) {\n return existing;\n }\n\n const promise = (async () => {\n try {\n if (options.provider.hasNamespace(namespace)) {\n return;\n }\n\n const raw = await options.resolveLoader(namespace)();\n const result = options.validate(namespace, raw);\n if (!result.ok) {\n throw new DictionaryValidationError(result.issues);\n }\n\n options.provider.setNamespace(namespace, result.data);\n } finally {\n inFlight!.delete(namespace);\n }\n })();\n\n inFlight.set(namespace, promise);\n return promise;\n}\n\nexport async function ensureNamespacesLoadedImpl<\n Schema extends MultiDictionary,\n NS extends keyof Schema & string,\n>(options: EnsureNamespacesLoadedOptions<Schema, NS>, namespaces: NS[]): Promise<void> {\n if (namespaces.length === 0) {\n return;\n }\n\n await Promise.all(namespaces.map((namespace) => ensureOneNamespace(options, namespace)));\n}\n"]}
|
package/package.json
CHANGED
|
@@ -12,6 +12,7 @@ export interface InstanceFileOptions {
|
|
|
12
12
|
localeFallbackConstName: string;
|
|
13
13
|
factoryName: string;
|
|
14
14
|
hasLocaleFallback: boolean;
|
|
15
|
+
hasLocaleType: boolean;
|
|
15
16
|
importExtension: ImportExtension;
|
|
16
17
|
}
|
|
17
18
|
|
|
@@ -27,6 +28,7 @@ export function formatInstanceFile(options: InstanceFileOptions): string {
|
|
|
27
28
|
localeFallbackConstName,
|
|
28
29
|
factoryName,
|
|
29
30
|
hasLocaleFallback,
|
|
31
|
+
hasLocaleType,
|
|
30
32
|
importExtension,
|
|
31
33
|
} = options;
|
|
32
34
|
|
|
@@ -45,21 +47,32 @@ export function formatInstanceFile(options: InstanceFileOptions): string {
|
|
|
45
47
|
const providerOptions = hasLocaleFallback
|
|
46
48
|
? `, {\n localeFallback: ${localeFallbackConstName},\n }`
|
|
47
49
|
: "";
|
|
48
|
-
const
|
|
49
|
-
?
|
|
50
|
+
const typesImportLine = hasLocaleType
|
|
51
|
+
? hasLocaleFallback
|
|
52
|
+
? `import { ${localeFallbackConstName}, type ${localeTypeName} } from '${typesImport}';\n`
|
|
53
|
+
: `import type { ${localeTypeName} } from '${typesImport}';\n`
|
|
50
54
|
: "";
|
|
55
|
+
const localesParamType = hasLocaleType ? `readonly ${localeTypeName}[]` : "readonly string[]";
|
|
56
|
+
const projectLocalesFallbackArg = hasLocaleFallback ? `, ${localeFallbackConstName}` : "";
|
|
51
57
|
|
|
52
58
|
return (
|
|
53
59
|
`${GENERATED_FILE_BANNER}` +
|
|
54
|
-
`import { ${providerClass} } from '@xndrjs/i18n';\n` +
|
|
60
|
+
`import { ${providerClass}, projectLocales as projectLocalesCore, type KeyDictionary } from '@xndrjs/i18n';\n` +
|
|
55
61
|
`import { dictionary } from '${dictionaryImport}';\n` +
|
|
56
62
|
initialDictionaryImport +
|
|
57
|
-
|
|
63
|
+
typesImportLine +
|
|
58
64
|
`\n` +
|
|
59
65
|
`export function ${factoryName}(\n` +
|
|
60
66
|
` initialDictionary: ${initialDictionaryType} = dictionary,\n` +
|
|
61
67
|
`) {\n` +
|
|
62
68
|
` return new ${providerClass}<${providerTypeArgs}>(initialDictionary${providerOptions});\n` +
|
|
69
|
+
`}\n` +
|
|
70
|
+
`\n` +
|
|
71
|
+
`export function projectLocales(\n` +
|
|
72
|
+
` dictionary: KeyDictionary,\n` +
|
|
73
|
+
` locales: ${localesParamType},\n` +
|
|
74
|
+
`): KeyDictionary {\n` +
|
|
75
|
+
` return projectLocalesCore(dictionary, locales${projectLocalesFallbackArg});\n` +
|
|
63
76
|
`}\n`
|
|
64
77
|
);
|
|
65
78
|
}
|
|
@@ -73,6 +73,9 @@ describe("generate-i18n-types", () => {
|
|
|
73
73
|
expect(types).toContain("export const I18N_MODE = 'multi' as const");
|
|
74
74
|
expect(types).toContain("export type AppLocale = 'en'");
|
|
75
75
|
expect(factory).toContain("export function createI18n(");
|
|
76
|
+
expect(factory).toContain("export function projectLocales(");
|
|
77
|
+
expect(factory).toContain("locales: readonly AppLocale[]");
|
|
78
|
+
expect(factory).toContain("projectLocalesCore(dictionary, locales)");
|
|
76
79
|
expect(factory).toContain("IcuTranslationProviderMulti");
|
|
77
80
|
expect(types).toContain("login_button: never");
|
|
78
81
|
expect(types).toContain("welcome: { name: string }");
|
|
@@ -289,6 +292,7 @@ describe("generate-i18n-types", () => {
|
|
|
289
292
|
expect(types).toContain('"de-CH": "en"');
|
|
290
293
|
expect(types).toContain("export type AppLocale = 'de-CH' | 'en'");
|
|
291
294
|
expect(factory).toContain("localeFallback: LOCALE_FALLBACK");
|
|
295
|
+
expect(factory).toContain("projectLocalesCore(dictionary, locales, LOCALE_FALLBACK)");
|
|
292
296
|
expect(factory).toContain(
|
|
293
297
|
"IcuTranslationProviderSingle<AppSchema, AppParams, AppLocale, typeof LOCALE_FALLBACK>"
|
|
294
298
|
);
|
|
@@ -358,6 +362,8 @@ describe("generate-i18n-types", () => {
|
|
|
358
362
|
const factory = readFileSync(join(tempDir, "src/i18n/instance.generated.ts"), "utf8");
|
|
359
363
|
expect(types).not.toContain("export const LOCALE_FALLBACK");
|
|
360
364
|
expect(factory).not.toContain("localeFallback:");
|
|
365
|
+
expect(factory).toContain("projectLocalesCore(dictionary, locales)");
|
|
366
|
+
expect(factory).not.toContain("LOCALE_FALLBACK");
|
|
361
367
|
expect(types).toContain("export type AppLocale = 'en' | 'it'");
|
|
362
368
|
});
|
|
363
369
|
|
package/src/index.ts
CHANGED
|
@@ -28,5 +28,7 @@ export {
|
|
|
28
28
|
} from "./resolve-locale.js";
|
|
29
29
|
export type { ResolvedLocaleTemplate } from "./resolve-locale.js";
|
|
30
30
|
|
|
31
|
+
export { projectLocales } from "./project-locales.js";
|
|
32
|
+
|
|
31
33
|
export { ensureNamespacesLoadedImpl } from "./ensure-namespace.js";
|
|
32
34
|
export type { EnsureNamespacesLoadedOptions } from "./ensure-namespace.js";
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { projectLocales } from "./project-locales.js";
|
|
3
|
+
|
|
4
|
+
describe("projectLocales", () => {
|
|
5
|
+
const dictionary = {
|
|
6
|
+
login_button: { en: "Login", it: "Accedi" },
|
|
7
|
+
welcome: { en: "Welcome {name}!", it: "Benvenuto {name}!" },
|
|
8
|
+
empty_label: { en: "" },
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const fallback = {
|
|
12
|
+
en: null,
|
|
13
|
+
"de-DE": "en",
|
|
14
|
+
"de-CH": "de-DE",
|
|
15
|
+
it: "en",
|
|
16
|
+
} as const;
|
|
17
|
+
|
|
18
|
+
it("keeps only the requested locales with direct templates", () => {
|
|
19
|
+
expect(projectLocales(dictionary, ["en", "it"])).toEqual({
|
|
20
|
+
login_button: { en: "Login", it: "Accedi" },
|
|
21
|
+
welcome: { en: "Welcome {name}!", it: "Benvenuto {name}!" },
|
|
22
|
+
empty_label: { en: "" },
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("drops locales not listed in the projection", () => {
|
|
27
|
+
expect(projectLocales(dictionary, ["en"])).toEqual({
|
|
28
|
+
login_button: { en: "Login" },
|
|
29
|
+
welcome: { en: "Welcome {name}!" },
|
|
30
|
+
empty_label: { en: "" },
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("resolves fallback templates for missing direct locale entries", () => {
|
|
35
|
+
expect(projectLocales(dictionary, ["de-CH"], fallback)).toEqual({
|
|
36
|
+
login_button: { "de-CH": "Login" },
|
|
37
|
+
welcome: { "de-CH": "Welcome {name}!" },
|
|
38
|
+
empty_label: { "de-CH": "" },
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("projects multiple locales with mixed direct and fallback resolution", () => {
|
|
43
|
+
expect(projectLocales(dictionary, ["it", "de-CH"], fallback)).toEqual({
|
|
44
|
+
login_button: { it: "Accedi", "de-CH": "Login" },
|
|
45
|
+
welcome: { it: "Benvenuto {name}!", "de-CH": "Welcome {name}!" },
|
|
46
|
+
empty_label: { it: "", "de-CH": "" },
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("omits a target locale when fallback cannot resolve a template", () => {
|
|
51
|
+
const partial = {
|
|
52
|
+
login_button: { it: "Accedi" },
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
expect(projectLocales(partial, ["en", "it"], fallback)).toEqual({
|
|
56
|
+
login_button: { it: "Accedi" },
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("deduplicates locales in the projection list", () => {
|
|
61
|
+
expect(projectLocales(dictionary, ["en", "en"])).toEqual({
|
|
62
|
+
login_button: { en: "Login" },
|
|
63
|
+
welcome: { en: "Welcome {name}!" },
|
|
64
|
+
empty_label: { en: "" },
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { resolveLocaleTemplate } from "./resolve-locale.js";
|
|
2
|
+
import type { KeyDictionary, LocaleFallbackMap } from "./types.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Builds a dictionary that contains only the requested locales.
|
|
6
|
+
* For each key and target locale, copies the direct template when present;
|
|
7
|
+
* otherwise resolves through `localeFallback` (same rules as `.get()` at runtime).
|
|
8
|
+
*/
|
|
9
|
+
export function projectLocales(
|
|
10
|
+
dictionary: KeyDictionary,
|
|
11
|
+
locales: readonly string[],
|
|
12
|
+
localeFallback?: LocaleFallbackMap
|
|
13
|
+
): KeyDictionary {
|
|
14
|
+
const targetLocales = [...new Set(locales)];
|
|
15
|
+
const result: KeyDictionary = {};
|
|
16
|
+
|
|
17
|
+
for (const [key, localesByKey] of Object.entries(dictionary)) {
|
|
18
|
+
const projected: Record<string, string> = {};
|
|
19
|
+
|
|
20
|
+
for (const locale of targetLocales) {
|
|
21
|
+
const resolved = resolveLocaleTemplate(localesByKey, locale, localeFallback);
|
|
22
|
+
if (resolved !== undefined) {
|
|
23
|
+
projected[locale] = resolved.template;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (Object.keys(projected).length > 0) {
|
|
28
|
+
result[key] = projected;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return result;
|
|
33
|
+
}
|