@xndrjs/i18n 0.3.3-alpha.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,7 +10,7 @@ The core idea: your ICU strings live in local JSON files that act as **type-safe
10
10
  - **ICU MessageFormat** — full support for interpolation, plurals, and select.
11
11
  - **Runtime override** — hydrate translations from an external source via `setAll()` / `setNamespace()` without rebuilding.
12
12
  - **Single-file or multi-namespace** — one flat dictionary, or multiple JSON files each bound to a namespace.
13
- - **Lazy namespace loading** — optional code-splitting via `loadOnInit` and `ensureNamespacesLoaded()` (multi mode).
13
+ - **Lazy namespace loading** — optional code-splitting via `loadOnInit` and generated `namespaceLoaders` (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
16
  - **Translation audit** — `xndrjs-i18n-audit` reports missing locales per key (direct vs effective after fallback); optional CI gate via `--fail-on`.
@@ -21,14 +21,14 @@ The core idea: your ICU strings live in local JSON files that act as **type-safe
21
21
  ### Install
22
22
 
23
23
  ```bash
24
- npm install @xndrjs/i18n tsx
25
- # optional external dictionary validation
26
- npm install zod
24
+ npm install @xndrjs/i18n zod
25
+ npm install -D tsx
27
26
  ```
28
27
 
29
- `tsx` is a **peer dependency** required by `xndrjs-i18n-codegen`: the CLI runs the TypeScript codegen script directly (no precompiled JS bundle). Add it to your app or monorepo root.
28
+ `tsx` and `zod` are **peer dependencies** of `@xndrjs/i18n`:
30
29
 
31
- `zod` is optional only needed when you use `dictionarySchemaOutput` and the generated `validateExternalDictionary()` helpers.
30
+ - **`tsx`**required by `xndrjs-i18n-codegen` and `xndrjs-i18n-audit` (CLIs run TypeScript directly). Install as a **devDependency** — only needed at build/CI time.
31
+ - **`zod`** — required by codegen/audit config validation (`i18n.codegen.json`) and by `@xndrjs/i18n/validation` when using `dictionarySchemaOutput` and `validateExternalDictionary()`. Use a **devDependency** if validation runs only at build time; a regular **dependency** if you validate CMS payloads in production.
32
32
 
33
33
  ### Quick setup (single file)
34
34
 
@@ -75,11 +75,13 @@ npm run i18n:audit
75
75
  ```ts
76
76
  // i18n/index.ts
77
77
  import { createI18n } from "./generated/instance.generated.js";
78
+ import { defaultDictionary } from "./generated/dictionary.generated.js";
78
79
 
79
80
  export * from "./generated/instance.generated.js";
81
+ export * from "./generated/dictionary.generated.js";
80
82
  export * from "./generated/i18n-types.generated.js";
81
83
 
82
- export const i18n = createI18n();
84
+ export const i18n = createI18n(defaultDictionary);
83
85
  ```
84
86
 
85
87
  ```ts
@@ -104,7 +106,7 @@ This creates `i18n/i18n.codegen.json`, starter translation JSON, and `i18n/index
104
106
 
105
107
  Use `namespaces` instead of `dictionary` in `i18n/i18n.codegen.json`. See [Configuration](#configuration-i18ncodegenjson) and the [multi-namespace example](#multi-namespace-example) below.
106
108
 
107
- For lazy loading, add `loadOnInit`, `dictionarySchemaOutput`, and `namespaceLoadersOutput` — see [Lazy namespace loading](#lazy-namespace-loading-multi-mode). Lazy mode requires `zod` (validation runs before a namespace is registered).
109
+ For lazy loading, add `loadOnInit` and `namespaceLoadersOutput` — see [Lazy namespace loading](#lazy-namespace-loading-multi-mode). Add `dictionarySchemaOutput` only when you validate external CMS/API payloads.
108
110
 
109
111
  ## Repository layout
110
112
 
@@ -194,8 +196,8 @@ Variables found across **all locales** of the same key are merged. If parsing fa
194
196
  ### 3. Generated files
195
197
 
196
198
  - **`i18n-types.generated.ts`** — `I18N_MODE`, `MyProjectParams`, `MyProjectSchema`.
197
- - **`dictionary.generated.ts`** — imports the JSON files and assembles the initial dictionary.
198
- - **`instance.generated.ts`** — exports `createI18n()`, typed `projectLocales()` (full schema), and in multi mode `projectNamespaceLocales()` (single namespace); `LOCALE_FALLBACK` wired in when configured.
199
+ - **`dictionary.generated.ts`** — imports the JSON files and exports `defaultDictionary` (the codegen fallback dictionary).
200
+ - **`instance.generated.ts`** — exports `createI18n(dictionary)` (required argument; no default import of the fallback dictionary), typed `projectLocales()` (full schema), and in multi mode `projectNamespaceLocales()` (single namespace); `LOCALE_FALLBACK` wired in when configured.
199
201
  - **`i18n.ts`** (optional, hand-written) — app-owned singleton if desired.
200
202
 
201
203
  Example generated types (multi-namespace):
@@ -227,12 +229,13 @@ export type MyProjectSchema = {
227
229
 
228
230
  ### 4. Runtime provider
229
231
 
230
- Create a provider with the generated factory (no side effects at import time):
232
+ Create a provider with the generated factory (no side effects at import time). Pass the dictionary explicitly — from codegen’s `dictionary.generated.ts`, from lazy loaders, or from an external source — so the factory module does not pull fallback JSON into your bundle:
231
233
 
232
234
  ```ts
233
235
  import { createI18n } from "./i18n";
236
+ import { defaultDictionary } from "./i18n/generated/dictionary.generated.js";
234
237
 
235
- const i18n = createI18n();
238
+ const i18n = createI18n(defaultDictionary);
236
239
  // or with an external dictionary at init:
237
240
  const i18n = createI18n(externalDictionary);
238
241
  ```
@@ -241,7 +244,8 @@ Optionally, wrap it in an app-owned singleton (`i18n.ts`):
241
244
 
242
245
  ```ts
243
246
  import { createI18n } from "./instance.generated.js";
244
- export const i18n = createI18n();
247
+ import { defaultDictionary } from "./dictionary.generated.js";
248
+ export const i18n = createI18n(defaultDictionary);
245
249
  ```
246
250
 
247
251
  ### Locale-bound provider (`forLocale`)
@@ -249,7 +253,7 @@ export const i18n = createI18n();
249
253
  Bind a locale once and omit it on every `.get()`:
250
254
 
251
255
  ```ts
252
- const i18n = createI18n();
256
+ const i18n = createI18n(defaultDictionary);
253
257
  const i18nEn = i18n.forLocale("en");
254
258
 
255
259
  i18nEn.get("login_button"); // single-file
@@ -312,9 +316,10 @@ import {
312
316
  projectLocales,
313
317
  projectNamespaceLocales,
314
318
  } from "./i18n/generated/instance.generated.js";
319
+ import { defaultDictionary } from "./i18n/generated/dictionary.generated.js";
315
320
  import billingDictionary from "./i18n/translations/billing.json";
316
321
 
317
- const i18n = createI18n();
322
+ const i18n = createI18n(defaultDictionary);
318
323
  i18n.setNamespace("billing", projectNamespaceLocales(billingDictionary, [activeLocale]));
319
324
  i18n.setAll(projectLocales(fullDictionary, [activeLocale])); // multi: all namespaces
320
325
  ```
@@ -393,23 +398,23 @@ Specify **exactly one** of `dictionary` (single-file) or `namespaces` (multi-fil
393
398
  }
394
399
  ```
395
400
 
396
- | Field | Description |
397
- | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- |
398
- | `dictionary` | Path to a single dictionary file (`.json`, `.yaml`, or `.yml`) for the flat API. Mutually exclusive with `namespaces`. |
399
- | `namespaces` | Map of `namespace -> dictionary path` (`.json`, `.yaml`, or `.yml`) for the namespaced API. Mutually exclusive with `dictionary`. |
400
- | `defaultNamespace` | Optional. Namespace label used internally in single-file mode (default `"default"`). Not exposed in the flat API. |
401
- | `typesOutput` | Output path for the generated types. |
402
- | `dictionaryOutput` | Output path for the generated dictionary manifest. |
403
- | `instanceOutput` | Output path for the generated factory (`createI18n`). |
404
- | `importExtension` | Optional. Relative import suffix between generated `.ts` modules: `"none"` (default, extensionless), `".ts"`, or `".js"`. |
405
- | `factoryName` | Name of the exported factory function (default `createI18n`). |
406
- | `paramsTypeName` / `schemaTypeName` | Names of the exported types (customizable per project). |
407
- | `localeTypeName` | Name of the exported locale union type (default `MyProjectLocale`). |
408
- | `localeFallback` | Optional map of `locale -> next locale | null` for runtime fallback resolution. |
409
- | `localeFallbackConstName` | Name of the generated fallback constant (default `LOCALE_FALLBACK`). |
410
- | `dictionarySchemaOutput` | Optional path for generated external dictionary validation (`dictionary-schema.generated.ts`). Requires `zod` in the consumer app. |
411
- | `loadOnInit` | Multi mode only. Namespaces to include in the initial bundle via static imports. When omitted, all namespaces are eager (default). |
412
- | `namespaceLoadersOutput` | Output path for generated lazy loaders and `ensureNamespacesLoaded()`. Defaults to `{dirname(instanceOutput)}/namespace-loaders.generated.ts`. Required when lazy namespaces exist. |
401
+ | Field | Description |
402
+ | ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- |
403
+ | `dictionary` | Path to a single dictionary file (`.json`, `.yaml`, or `.yml`) for the flat API. Mutually exclusive with `namespaces`. |
404
+ | `namespaces` | Map of `namespace -> dictionary path` (`.json`, `.yaml`, or `.yml`) for the namespaced API. Mutually exclusive with `dictionary`. |
405
+ | `defaultNamespace` | Optional. Namespace label used internally in single-file mode (default `"default"`). Not exposed in the flat API. |
406
+ | `typesOutput` | Output path for the generated types. |
407
+ | `dictionaryOutput` | Output path for the generated dictionary manifest. |
408
+ | `instanceOutput` | Output path for the generated factory (`createI18n`). |
409
+ | `importExtension` | Optional. Relative import suffix between generated `.ts` modules: `"none"` (default, extensionless), `".ts"`, or `".js"`. |
410
+ | `factoryName` | Name of the exported factory function (default `createI18n`). |
411
+ | `paramsTypeName` / `schemaTypeName` | Names of the exported types (customizable per project). |
412
+ | `localeTypeName` | Name of the exported locale union type (default `MyProjectLocale`). |
413
+ | `localeFallback` | Optional map of `locale -> next locale | null` for runtime fallback resolution. |
414
+ | `localeFallbackConstName` | Name of the generated fallback constant (default `LOCALE_FALLBACK`). |
415
+ | `dictionarySchemaOutput` | Optional path for generated external dictionary validation (`dictionary-schema.generated.ts`). Requires `zod` in the consumer app. |
416
+ | `loadOnInit` | Multi mode only. Namespaces to include in the initial bundle via static imports. When omitted, all namespaces are eager (default). |
417
+ | `namespaceLoadersOutput` | Output path for generated `namespaceLoaders` (dynamic `import()` per lazy namespace). Defaults to `{dirname(instanceOutput)}/namespace-loaders.generated.ts`. Required when lazy namespaces exist. |
413
418
 
414
419
  > Paths are resolved relative to the directory containing `i18n.codegen.json` (e.g. `i18n/` when using `xndrjs-i18n-setup .`).
415
420
 
@@ -453,7 +458,7 @@ Mixed namespaces are supported: some namespaces can stay `.json` while others us
453
458
 
454
459
  ```ts
455
460
  import { i18n } from "./i18n"; // app singleton from i18n.ts
456
- // or: import { createI18n } from './i18n'; const i18n = createI18n();
461
+ // or: import { createI18n, defaultDictionary } from './i18n'; const i18n = createI18n(defaultDictionary);
457
462
 
458
463
  i18n.get("default", "login_button", "it"); // "Accedi"
459
464
  i18n.get("default", "welcome", "en", { name: "Ada" }); // "Welcome Ada!"
@@ -469,7 +474,7 @@ i18n.get("billing", "login_button", "it"); // ✗ key not in namespace
469
474
 
470
475
  ```ts
471
476
  import { i18n } from "./i18n"; // app singleton from i18n.ts
472
- // or: import { createI18n } from './i18n'; const i18n = createI18n();
477
+ // or: import { createI18n, defaultDictionary } from './i18n'; const i18n = createI18n(defaultDictionary);
473
478
 
474
479
  i18n.get("login_button", "it");
475
480
  i18n.get("welcome", "en", { name: "Ada" });
@@ -487,7 +492,7 @@ i18n.setNamespace("billing", externalBillingPayload);
487
492
 
488
493
  ### Lazy namespace loading (multi mode)
489
494
 
490
- Split namespaces across chunks by listing only the namespaces you need at startup in `loadOnInit`. Codegen emits dynamic `import()` loaders and a generated `ensureNamespacesLoaded(i18n, namespaces)` helper. `.get()` stays synchronous — preload lazy namespaces before rendering.
495
+ Split namespaces across chunks by listing only the namespaces you need at startup in `loadOnInit`. Codegen emits typed `namespaceLoaders` one dynamic `import()` per lazy namespace. `.get()` stays synchronous — register lazy namespaces with `setNamespace()` before rendering.
491
496
 
492
497
  ```json
493
498
  {
@@ -496,7 +501,6 @@ Split namespaces across chunks by listing only the namespaces you need at startu
496
501
  "billing": "translations/billing.yaml"
497
502
  },
498
503
  "loadOnInit": ["default"],
499
- "dictionarySchemaOutput": "generated/dictionary-schema.generated.ts",
500
504
  "namespaceLoadersOutput": "generated/namespace-loaders.generated.ts"
501
505
  }
502
506
  ```
@@ -504,23 +508,41 @@ Split namespaces across chunks by listing only the namespaces you need at startu
504
508
  Codegen also emits `LoadOnInitNamespace`, `LazyNamespace`, and `InitialSchema` types. The generated factory accepts a partial `InitialSchema` at init time.
505
509
 
506
510
  ```ts
507
- import { i18n, ensureNamespacesLoaded } from "./i18n";
511
+ import { i18n, namespaceLoaders, type LazyNamespace } from "./i18n";
508
512
 
509
513
  i18n.get("default", "login_button", "en"); // available immediately
510
514
 
511
- await ensureNamespacesLoaded(i18n, ["billing"]);
515
+ if (!i18n.hasNamespace("billing")) {
516
+ i18n.setNamespace("billing", await namespaceLoaders.billing());
517
+ }
512
518
  i18n.get("billing", "invoice_summary", "en", { count: 12 });
513
519
 
514
520
  // batch preload
515
- await ensureNamespacesLoaded(i18n, ["user", "billing"]);
521
+ await Promise.all(
522
+ (["user", "billing"] as const satisfies readonly LazyNamespace[]).map(async (namespace) => {
523
+ if (i18n.hasNamespace(namespace)) return;
524
+ i18n.setNamespace(namespace, await namespaceLoaders[namespace]());
525
+ })
526
+ );
527
+ ```
528
+
529
+ Optional locale projection before register:
530
+
531
+ ```ts
532
+ import { projectNamespaceLocales } from "./i18n/generated/instance.generated.js";
533
+
534
+ const billing = await namespaceLoaders.billing();
535
+ i18n.setNamespace("billing", projectNamespaceLocales(billing, [userLocale]));
516
536
  ```
517
537
 
518
538
  Calling `.get()` on a namespace that is not loaded throws:
519
539
 
520
- `[i18n] Namespace not loaded: "billing". Call ensureNamespacesLoaded(i18n, ["billing"]) first.`
540
+ `[i18n] Namespace not loaded: "billing". Register it with setNamespace() before calling .get().`
521
541
 
522
542
  When `loadOnInit` is omitted, behavior is unchanged: all namespaces are statically imported.
523
543
 
544
+ Fetch/CMS hydration is app-owned: fetch → `validateExternalNamespace()` (requires `dictionarySchemaOutput` + `zod`) → `setNamespace()`.
545
+
524
546
  ### External dictionary validation
525
547
 
526
548
  When translations arrive from an external source (i.e. a CMS), validate the `unknown` input before calling `setAll()` or `setNamespace()`.
@@ -533,7 +555,7 @@ Enable validation by adding `dictionarySchemaOutput` to `i18n/i18n.codegen.json`
533
555
  }
534
556
  ```
535
557
 
536
- Add `zod` as a dependency in the consumer app (optional peer of `@xndrjs/i18n`).
558
+ Add `zod` as a dependency in the consumer app (required peer of `@xndrjs/i18n`).
537
559
 
538
560
  Codegen emits `DICTIONARY_SPEC` and `validateExternalDictionary()`. Validation runs in two phases:
539
561
 
@@ -641,5 +663,5 @@ pnpm run demo:multi # tsx multi/src/index.ts
641
663
  - **TypeScript 6** (`strict`, `resolveJsonModule`, `moduleResolution: "bundler"`)
642
664
  - **[intl-messageformat](https://www.npmjs.com/package/intl-messageformat)** — runtime ICU formatting
643
665
  - **[@formatjs/icu-messageformat-parser](https://www.npmjs.com/package/@formatjs/icu-messageformat-parser)** — build-time AST parsing
644
- - **[zod](https://www.npmjs.com/package/zod)** — optional peer for external dictionary validation
666
+ - **[zod](https://www.npmjs.com/package/zod)** — peer dependency (config validation, external dictionary validation)
645
667
  - **[tsx](https://www.npmjs.com/package/tsx)** — runs TypeScript scripts directly
package/dist/index.d.ts CHANGED
@@ -1,6 +1,3 @@
1
- import { V as ValidationResult } from './types-C1CpXVOJ.js';
2
- import '@formatjs/icu-messageformat-parser';
3
-
4
1
  type LocaleDictionary = Record<string, string>;
5
2
  type KeyDictionary = Record<string, LocaleDictionary>;
6
3
  type MultiDictionary = Record<string, KeyDictionary>;
@@ -122,14 +119,4 @@ declare function projectLocales<T extends KeyDictionary>(dictionary: T, locales:
122
119
  */
123
120
  declare function projectNamespacesLocales<T extends MultiDictionary>(dictionary: T, locales: readonly string[], localeFallback?: LocaleFallbackMap$1): T;
124
121
 
125
- interface EnsureNamespacesLoadedOptions<Schema extends MultiDictionary, NS extends keyof Schema & string> {
126
- provider: Pick<{
127
- hasNamespace(namespace: NS): boolean;
128
- setNamespace(namespace: NS, values: Schema[NS]): void;
129
- }, "hasNamespace" | "setNamespace">;
130
- resolveLoader: (namespace: NS) => () => Promise<unknown>;
131
- validate: (namespace: NS, raw: unknown) => ValidationResult<Schema[NS]>;
132
- }
133
- declare function ensureNamespacesLoadedImpl<Schema extends MultiDictionary, NS extends keyof Schema & string>(options: EnsureNamespacesLoadedOptions<Schema, NS>, namespaces: NS[]): Promise<void>;
134
-
135
- 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, projectNamespacesLocales, resolveLocaleTemplate, validateLocaleFallback };
122
+ export { 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, formatLocaleFallbackChain, projectLocales, projectNamespacesLocales, resolveLocaleTemplate, validateLocaleFallback };
package/dist/index.js CHANGED
@@ -177,7 +177,7 @@ var IcuTranslationProviderMulti = class {
177
177
  get(namespace, key, locale, ...args) {
178
178
  if (!this.loadedNamespaces.has(namespace)) {
179
179
  throw new Error(
180
- `[i18n] Namespace not loaded: "${String(namespace)}". Call ensureNamespacesLoaded(i18n, ["${String(namespace)}"]) first.`
180
+ `[i18n] Namespace not loaded: "${String(namespace)}". Register it with setNamespace() before calling .get().`
181
181
  );
182
182
  }
183
183
  const params = args[0];
@@ -280,78 +280,6 @@ function projectNamespacesLocales(dictionary, locales, localeFallback) {
280
280
  return result;
281
281
  }
282
282
 
283
- // src/validation/errors.ts
284
- function issueMessage(issue) {
285
- switch (issue.kind) {
286
- case "missing_key":
287
- return `Missing required key at ${issue.path.join(".")}`;
288
- case "invalid_input":
289
- case "invalid_locale_value":
290
- case "icu_syntax_error":
291
- case "locale_args_mismatch":
292
- case "variable_mismatch":
293
- case "variable_type_mismatch":
294
- return issue.message;
295
- }
296
- }
297
- function formatIssues(issues) {
298
- return issues.map((issue) => {
299
- if ("path" in issue && issue.path.length > 0) {
300
- return `[${issue.path.join(".")}] ${issue.kind}: ${issueMessage(issue)}`;
301
- }
302
- return `${issue.kind}: ${issueMessage(issue)}`;
303
- }).join("\n");
304
- }
305
- var DictionaryValidationError = class extends Error {
306
- constructor(issues) {
307
- super(formatIssues(issues));
308
- this.issues = issues;
309
- this.name = "DictionaryValidationError";
310
- }
311
- issues;
312
- };
313
-
314
- // src/ensure-namespace.ts
315
- var inFlightByProvider = /* @__PURE__ */ new WeakMap();
316
- async function ensureOneNamespace(options, namespace) {
317
- if (options.provider.hasNamespace(namespace)) {
318
- return;
319
- }
320
- const providerKey = options.provider;
321
- let inFlight = inFlightByProvider.get(providerKey);
322
- if (!inFlight) {
323
- inFlight = /* @__PURE__ */ new Map();
324
- inFlightByProvider.set(providerKey, inFlight);
325
- }
326
- const existing = inFlight.get(namespace);
327
- if (existing) {
328
- return existing;
329
- }
330
- const promise = (async () => {
331
- try {
332
- if (options.provider.hasNamespace(namespace)) {
333
- return;
334
- }
335
- const raw = await options.resolveLoader(namespace)();
336
- const result = options.validate(namespace, raw);
337
- if (!result.ok) {
338
- throw new DictionaryValidationError(result.issues);
339
- }
340
- options.provider.setNamespace(namespace, result.data);
341
- } finally {
342
- inFlight.delete(namespace);
343
- }
344
- })();
345
- inFlight.set(namespace, promise);
346
- return promise;
347
- }
348
- async function ensureNamespacesLoadedImpl(options, namespaces) {
349
- if (namespaces.length === 0) {
350
- return;
351
- }
352
- await Promise.all(namespaces.map((namespace) => ensureOneNamespace(options, namespace)));
353
- }
354
-
355
- export { IcuTranslationProviderMulti, IcuTranslationProviderSingle, ensureNamespacesLoadedImpl, formatLocaleFallbackChain, projectLocales, projectNamespacesLocales, resolveLocaleTemplate, validateLocaleFallback };
283
+ export { IcuTranslationProviderMulti, IcuTranslationProviderSingle, formatLocaleFallbackChain, projectLocales, projectNamespacesLocales, resolveLocaleTemplate, validateLocaleFallback };
356
284
  //# sourceMappingURL=index.js.map
357
285
  //# 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/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, MultiDictionary } from \"./types.js\";\n\nfunction projectKeyDictionary<T extends KeyDictionary>(\n dictionary: T,\n locales: readonly string[],\n localeFallback?: LocaleFallbackMap\n): T {\n const targetLocales = [...new Set(locales)];\n const result = {} as T;\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 as keyof T] = projected as T[keyof T];\n }\n }\n\n return result;\n}\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<T extends KeyDictionary>(\n dictionary: T,\n locales: readonly string[],\n localeFallback?: LocaleFallbackMap\n): T {\n return projectKeyDictionary(dictionary, locales, localeFallback);\n}\n\n/**\n * Projects every namespace in a multi-namespace dictionary.\n */\nexport function projectNamespacesLocales<T extends MultiDictionary>(\n dictionary: T,\n locales: readonly string[],\n localeFallback?: LocaleFallbackMap\n): T {\n const result = {} as T;\n\n for (const namespace of Object.keys(dictionary) as (keyof T & string)[]) {\n const namespaceDictionary = dictionary[namespace];\n if (namespaceDictionary === undefined) {\n continue;\n }\n\n result[namespace] = projectKeyDictionary(\n namespaceDictionary,\n locales,\n localeFallback\n ) as T[typeof namespace];\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"]}
1
+ {"version":3,"sources":["../src/deep-freeze.ts","../src/resolve-locale.ts","../src/IcuTranslationProviderSingle.ts","../src/IcuTranslationProviderMulti.ts","../src/project-locales.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)}\". Register it with setNamespace() before calling .get().`\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, MultiDictionary } from \"./types.js\";\n\nfunction projectKeyDictionary<T extends KeyDictionary>(\n dictionary: T,\n locales: readonly string[],\n localeFallback?: LocaleFallbackMap\n): T {\n const targetLocales = [...new Set(locales)];\n const result = {} as T;\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 as keyof T] = projected as T[keyof T];\n }\n }\n\n return result;\n}\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<T extends KeyDictionary>(\n dictionary: T,\n locales: readonly string[],\n localeFallback?: LocaleFallbackMap\n): T {\n return projectKeyDictionary(dictionary, locales, localeFallback);\n}\n\n/**\n * Projects every namespace in a multi-namespace dictionary.\n */\nexport function projectNamespacesLocales<T extends MultiDictionary>(\n dictionary: T,\n locales: readonly string[],\n localeFallback?: LocaleFallbackMap\n): T {\n const result = {} as T;\n\n for (const namespace of Object.keys(dictionary) as (keyof T & string)[]) {\n const namespaceDictionary = dictionary[namespace];\n if (namespaceDictionary === undefined) {\n continue;\n }\n\n result[namespace] = projectKeyDictionary(\n namespaceDictionary,\n locales,\n localeFallback\n ) as T[typeof namespace];\n }\n\n return result;\n}\n"]}
@@ -1,7 +1,85 @@
1
- import { D as DictionarySpec, V as ValidationResult, N as NormalizedDictionary, a as VariableSpec, b as ValidationIssue, c as VariableMetaSpec } from '../types-C1CpXVOJ.js';
2
- export { d as NormalizedKeyDictionary, e as NormalizedMultiDictionary, P as ParsedKeyEntry, f as ParsedLocaleEntry, g as VariableType, h as extractVariables, m as mergeVariableSpecs, v as variableSpecsEqual } from '../types-C1CpXVOJ.js';
1
+ import { parse } from '@formatjs/icu-messageformat-parser';
3
2
  import { z } from 'zod';
4
- import '@formatjs/icu-messageformat-parser';
3
+
4
+ type VariableType = "string" | "number" | "date";
5
+ type VariableSpec = Record<string, VariableType>;
6
+ type VariableRole = "simple" | "plural" | "selectordinal" | "select" | "number" | "date" | "time";
7
+ type VariableMeta = {
8
+ type: VariableType;
9
+ roles: Set<VariableRole>;
10
+ };
11
+ type VariableMetaSpec = Record<string, VariableMeta>;
12
+ type IcuAst = ReturnType<typeof parse>;
13
+ declare function extractVariables(nodes: IcuAst): VariableSpec;
14
+ declare function mergeVariableSpecs(target: VariableSpec, source: VariableSpec): VariableSpec;
15
+ declare function variableSpecsEqual(a: VariableSpec, b: VariableSpec): boolean;
16
+
17
+ type ParsedLocaleEntry = {
18
+ readonly template: string;
19
+ readonly args: VariableSpec;
20
+ };
21
+ type ParsedKeyEntry = {
22
+ readonly locales: Readonly<Record<string, ParsedLocaleEntry>>;
23
+ readonly mergedArgs: VariableSpec;
24
+ };
25
+ type NormalizedKeyDictionary = Readonly<Record<string, ParsedKeyEntry>>;
26
+ type NormalizedMultiDictionary = Readonly<Record<string, NormalizedKeyDictionary>>;
27
+ type NormalizedDictionary = {
28
+ readonly mode: "single";
29
+ readonly keys: NormalizedKeyDictionary;
30
+ } | {
31
+ readonly mode: "multi";
32
+ readonly namespaces: NormalizedMultiDictionary;
33
+ };
34
+ type DictionarySpec = {
35
+ readonly mode: "single";
36
+ readonly requiredKeys: readonly string[];
37
+ readonly argsByKey: Readonly<Record<string, VariableSpec>>;
38
+ } | {
39
+ readonly mode: "multi";
40
+ readonly requiredKeys: Readonly<Record<string, readonly string[]>>;
41
+ readonly argsByKey: Readonly<Record<string, Readonly<Record<string, VariableSpec>>>>;
42
+ };
43
+ type ValidationIssue = {
44
+ readonly kind: "invalid_input";
45
+ readonly message: string;
46
+ } | {
47
+ readonly kind: "missing_key";
48
+ readonly path: readonly string[];
49
+ } | {
50
+ readonly kind: "invalid_locale_value";
51
+ readonly path: readonly string[];
52
+ readonly message: string;
53
+ } | {
54
+ readonly kind: "icu_syntax_error";
55
+ readonly path: readonly string[];
56
+ readonly message: string;
57
+ } | {
58
+ readonly kind: "locale_args_mismatch";
59
+ readonly path: readonly string[];
60
+ readonly locales: Readonly<Record<string, VariableSpec>>;
61
+ readonly message: string;
62
+ } | {
63
+ readonly kind: "variable_mismatch";
64
+ readonly path: readonly string[];
65
+ readonly expected: VariableSpec;
66
+ readonly found: VariableSpec;
67
+ readonly message: string;
68
+ } | {
69
+ readonly kind: "variable_type_mismatch";
70
+ readonly path: readonly string[];
71
+ readonly variable: string;
72
+ readonly expected: VariableType;
73
+ readonly found: VariableType;
74
+ readonly message: string;
75
+ };
76
+ type ValidationResult<T> = {
77
+ readonly ok: true;
78
+ readonly data: T;
79
+ } | {
80
+ readonly ok: false;
81
+ readonly issues: readonly ValidationIssue[];
82
+ };
5
83
 
6
84
  declare function normalizeDictionary(input: unknown, spec: DictionarySpec): ValidationResult<NormalizedDictionary>;
7
85
 
@@ -42,4 +120,4 @@ declare function parseTemplate(template: string): ParseTemplateResult;
42
120
  declare function validateExternalDictionary<TSchema>(input: unknown, spec: DictionarySpec): ValidationResult<TSchema>;
43
121
  declare function validateExternalNamespace<TNamespaceSchema>(namespace: string, input: unknown, spec: DictionarySpec): ValidationResult<TNamespaceSchema>;
44
122
 
45
- export { DictionarySpec, DictionaryValidationError, NormalizedDictionary, type ParseTemplateResult, ValidationIssue, ValidationResult, VariableSpec, assertValidDictionary, createArgsSchema, createNormalizedDictionarySchema, formatIssues, normalizeDictionary, parseTemplate, toDictionary, toNamespaceDictionary, validateExternalDictionary, validateExternalNamespace, validateNormalizedDictionary };
123
+ export { type DictionarySpec, DictionaryValidationError, type NormalizedDictionary, type NormalizedKeyDictionary, type NormalizedMultiDictionary, type ParseTemplateResult, type ParsedKeyEntry, type ParsedLocaleEntry, type ValidationIssue, type ValidationResult, type VariableSpec, type VariableType, assertValidDictionary, createArgsSchema, createNormalizedDictionarySchema, extractVariables, formatIssues, mergeVariableSpecs, normalizeDictionary, parseTemplate, toDictionary, toNamespaceDictionary, validateExternalDictionary, validateExternalNamespace, validateNormalizedDictionary, variableSpecsEqual };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xndrjs/i18n",
3
- "version": "0.3.3-alpha.0",
3
+ "version": "0.4.0",
4
4
  "description": "Compiler-first, type-safe ICU MessageFormat i18n with runtime dictionary override from external sources.",
5
5
  "private": false,
6
6
  "license": "MIT",
@@ -45,11 +45,6 @@
45
45
  "tsx": ">=4",
46
46
  "zod": "^4.0.0"
47
47
  },
48
- "peerDependenciesMeta": {
49
- "zod": {
50
- "optional": true
51
- }
52
- },
53
48
  "devDependencies": {
54
49
  "tsup": "^8.5.1",
55
50
  "tsx": "^4.22.4",
@@ -161,7 +161,7 @@ describe("IcuTranslationProviderMulti", () => {
161
161
  expect(() =>
162
162
  partial.get("billing", "invoice_summary", "en", { count: 1, name: "Ada" })
163
163
  ).toThrow(
164
- '[i18n] Namespace not loaded: "billing". Call ensureNamespacesLoaded(i18n, ["billing"]) first.'
164
+ '[i18n] Namespace not loaded: "billing". Register it with setNamespace() before calling .get().'
165
165
  );
166
166
  });
167
167
 
@@ -110,7 +110,7 @@ export class IcuTranslationProviderMulti<
110
110
  ): string {
111
111
  if (!this.loadedNamespaces.has(namespace as string)) {
112
112
  throw new Error(
113
- `[i18n] Namespace not loaded: "${String(namespace)}". Call ensureNamespacesLoaded(i18n, ["${String(namespace)}"]) first.`
113
+ `[i18n] Namespace not loaded: "${String(namespace)}". Register it with setNamespace() before calling .get().`
114
114
  );
115
115
  }
116
116
 
@@ -48,7 +48,7 @@ export function formatDictionaryFile(options: DictionaryFileOptions): string {
48
48
  `${GENERATED_FILE_BANNER}` +
49
49
  `import ${importId} from '${importPath}.json';\n` +
50
50
  `import type { ${schemaTypeName} } from '${typesImport}';\n\n` +
51
- `export const dictionary: ${schemaTypeName} = ${importId};\n`
51
+ `export const defaultDictionary: ${schemaTypeName} = ${importId};\n`
52
52
  );
53
53
  }
54
54
 
@@ -72,6 +72,6 @@ export function formatDictionaryFile(options: DictionaryFileOptions): string {
72
72
  `${GENERATED_FILE_BANNER}` +
73
73
  `${imports}\n` +
74
74
  `import type { ${dictionaryTypeName} } from '${typesImport}';\n\n` +
75
- `export const dictionary: ${dictionaryTypeName} = {\n${objectEntries}\n};\n`
75
+ `export const defaultDictionary: ${dictionaryTypeName} = {\n${objectEntries}\n};\n`
76
76
  );
77
77
  }
@@ -5,7 +5,6 @@ export interface InstanceFileOptions {
5
5
  isSingle: boolean;
6
6
  hasLazy: boolean;
7
7
  typesOutputPath: string;
8
- dictionaryOutputPath: string;
9
8
  paramsTypeName: string;
10
9
  schemaTypeName: string;
11
10
  localeTypeName: string;
@@ -22,7 +21,6 @@ export function formatInstanceFile(options: InstanceFileOptions): string {
22
21
  isSingle,
23
22
  hasLazy,
24
23
  typesOutputPath,
25
- dictionaryOutputPath,
26
24
  paramsTypeName,
27
25
  schemaTypeName,
28
26
  localeTypeName,
@@ -36,11 +34,9 @@ export function formatInstanceFile(options: InstanceFileOptions): string {
36
34
 
37
35
  const providerClass = isSingle ? "IcuTranslationProviderSingle" : "IcuTranslationProviderMulti";
38
36
  const typesModule = toModuleBasename(typesOutputPath);
39
- const dictionaryModule = toModuleBasename(dictionaryOutputPath);
40
37
  const typesImport = toRelativeModuleImport(typesModule, importExtension);
41
- const dictionaryImport = toRelativeModuleImport(dictionaryModule, importExtension);
42
- const initialDictionaryType = hasLazy ? "InitialSchema" : schemaTypeName;
43
- const initialDictionaryImport = hasLazy
38
+ const dictionaryParamType = hasLazy ? "InitialSchema" : schemaTypeName;
39
+ const schemaTypesImport = hasLazy
44
40
  ? `import type { ${paramsTypeName}, ${schemaTypeName}, InitialSchema } from '${typesImport}';\n`
45
41
  : `import type { ${paramsTypeName}, ${schemaTypeName} } from '${typesImport}';\n`;
46
42
  const providerTypeArgs = hasLocaleFallback
@@ -77,14 +73,13 @@ export function formatInstanceFile(options: InstanceFileOptions): string {
77
73
  return (
78
74
  `${GENERATED_FILE_BANNER}` +
79
75
  `import { ${providerClass}, ${coreImports} } from '@xndrjs/i18n';\n` +
80
- `import { dictionary } from '${dictionaryImport}';\n` +
81
- initialDictionaryImport +
76
+ schemaTypesImport +
82
77
  typesImportLine +
83
78
  `\n` +
84
79
  `export function ${factoryName}(\n` +
85
- ` initialDictionary: ${initialDictionaryType} = dictionary,\n` +
80
+ ` dictionary: ${dictionaryParamType},\n` +
86
81
  `) {\n` +
87
- ` return new ${providerClass}<${providerTypeArgs}>(initialDictionary${providerOptions});\n` +
82
+ ` return new ${providerClass}<${providerTypeArgs}>(dictionary${providerOptions});\n` +
88
83
  `}\n` +
89
84
  projectLocalesBlock
90
85
  );
@@ -6,14 +6,9 @@ export function formatNamespaceLoadersFile(
6
6
  lazyEntries: (NamespaceEntry & { absolutePath: string })[],
7
7
  schemaTypeName: string,
8
8
  typesModule: string,
9
- dictionarySchemaModule: string,
10
- instanceModule: string,
11
- factoryName: string,
12
9
  importExtension: ImportExtension
13
10
  ): string {
14
- const dictionarySchemaImport = toRelativeModuleImport(dictionarySchemaModule, importExtension);
15
11
  const typesImport = toRelativeModuleImport(typesModule, importExtension);
16
- const instanceImport = toRelativeModuleImport(instanceModule, importExtension);
17
12
  const loaderEntries = lazyEntries
18
13
  .map((entry) => {
19
14
  const importPath = toImportPath(loadersOutputPath, entry.absolutePath);
@@ -23,26 +18,9 @@ export function formatNamespaceLoadersFile(
23
18
 
24
19
  return (
25
20
  `${GENERATED_FILE_BANNER}` +
26
- `import { ensureNamespacesLoadedImpl } from '@xndrjs/i18n';\n` +
27
- `import { validateExternalNamespace } from '${dictionarySchemaImport}';\n` +
28
- `import type { ${schemaTypeName}, LazyNamespace } from '${typesImport}';\n` +
29
- `import type { ${factoryName} } from '${instanceImport}';\n\n` +
30
- `export const namespaceLoaders: Record<\n` +
31
- ` LazyNamespace,\n` +
32
- ` () => Promise<${schemaTypeName}[LazyNamespace]>\n` +
33
- `> = {\n${loaderEntries}\n};\n\n` +
34
- `export async function ensureNamespacesLoaded(\n` +
35
- ` i18n: ReturnType<typeof ${factoryName}>,\n` +
36
- ` namespaces: LazyNamespace[],\n` +
37
- `): Promise<void> {\n` +
38
- ` return ensureNamespacesLoadedImpl<${schemaTypeName}, LazyNamespace>(\n` +
39
- ` {\n` +
40
- ` provider: i18n,\n` +
41
- ` resolveLoader: (namespace) => namespaceLoaders[namespace],\n` +
42
- ` validate: (namespace, raw) => validateExternalNamespace(namespace, raw),\n` +
43
- ` },\n` +
44
- ` namespaces,\n` +
45
- ` );\n` +
46
- `}\n`
21
+ `import type { ${schemaTypeName}, LazyNamespace } from '${typesImport}';\n\n` +
22
+ `export const namespaceLoaders: {\n` +
23
+ ` [K in LazyNamespace]: () => Promise<${schemaTypeName}[K]>;\n` +
24
+ `} = {\n${loaderEntries}\n};\n`
47
25
  );
48
26
  }
@@ -450,12 +450,15 @@ describe("generate-i18n-types", () => {
450
450
  expect(types).toContain("export type LoadOnInitNamespace = 'default'");
451
451
  expect(types).toContain("export type LazyNamespace = 'billing'");
452
452
  expect(types).toContain("export type InitialSchema = Pick<AppSchema, LoadOnInitNamespace>");
453
- expect(dictionary).toContain("export const dictionary: InitialSchema");
453
+ expect(dictionary).toContain("export const defaultDictionary: InitialSchema");
454
454
  expect(dictionary).not.toContain("billingNs");
455
- expect(factory).toContain("initialDictionary: InitialSchema = dictionary");
455
+ expect(factory).toContain("dictionary: InitialSchema,");
456
+ expect(factory).not.toContain("import { dictionary }");
456
457
  expect(loaders).toContain("export const namespaceLoaders");
457
- expect(loaders).toContain("export async function ensureNamespacesLoaded(");
458
- expect(loaders).toContain("namespaces: LazyNamespace[]");
458
+ expect(loaders).toContain("[K in LazyNamespace]: () => Promise<AppSchema[K]>");
459
+ expect(loaders).not.toContain("ensureNamespacesLoaded");
460
+ expect(loaders).not.toContain("ensureNamespacesLoadedImpl");
461
+ expect(loaders).not.toContain("validateExternalNamespace");
459
462
  expect(loaders).toContain("import('./translations/billing.json')");
460
463
  });
461
464
 
@@ -494,9 +497,10 @@ describe("generate-i18n-types", () => {
494
497
  const factory = readFileSync(join(tempDir, "src/i18n/instance.generated.ts"), "utf8");
495
498
 
496
499
  expect(types).not.toContain("LazyNamespace");
497
- expect(dictionary).toContain("export const dictionary: AppSchema");
500
+ expect(dictionary).toContain("export const defaultDictionary: AppSchema");
498
501
  expect(dictionary).toContain("billingNs");
499
- expect(factory).toContain("initialDictionary: AppSchema = dictionary");
502
+ expect(factory).toContain("dictionary: AppSchema,");
503
+ expect(factory).not.toContain("import { dictionary }");
500
504
  expect(result.stdout).not.toContain("namespace-loaders.generated.ts");
501
505
  });
502
506
 
@@ -706,8 +710,9 @@ describe("generate-i18n-types", () => {
706
710
  const dictionary = readFileSync(join(tempDir, "src/i18n/dictionary.generated.ts"), "utf8");
707
711
  const factory = readFileSync(join(tempDir, "src/i18n/instance.generated.ts"), "utf8");
708
712
  expect(dictionary).toContain("from './i18n-types.generated'");
709
- expect(factory).toContain("from './dictionary.generated'");
713
+ expect(factory).not.toContain("from './dictionary.generated'");
710
714
  expect(factory).toContain("from './i18n-types.generated'");
715
+ expect(factory).toContain("dictionary: AppSchema,");
711
716
  expect(dictionary).not.toContain(".generated.js");
712
717
  expect(dictionary).not.toContain(".generated.ts");
713
718
  expect(factory).not.toContain(".generated.js");
@@ -739,7 +744,7 @@ describe("generate-i18n-types", () => {
739
744
  expect(result.status).toBe(0);
740
745
 
741
746
  const factory = readFileSync(join(tempDir, "src/i18n/instance.generated.ts"), "utf8");
742
- expect(factory).toContain("from './dictionary.generated.ts'");
747
+ expect(factory).not.toContain("from './dictionary.generated.ts'");
743
748
  expect(factory).toContain("from './i18n-types.generated.ts'");
744
749
  });
745
750
 
@@ -768,7 +773,7 @@ describe("generate-i18n-types", () => {
768
773
  expect(result.status).toBe(0);
769
774
 
770
775
  const factory = readFileSync(join(tempDir, "src/i18n/instance.generated.ts"), "utf8");
771
- expect(factory).toContain("from './dictionary.generated.js'");
776
+ expect(factory).not.toContain("from './dictionary.generated.js'");
772
777
  expect(factory).toContain("from './i18n-types.generated.js'");
773
778
  });
774
779
 
@@ -46,12 +46,6 @@ function main() {
46
46
  const isSingle = Boolean(config.dictionary);
47
47
  const { loadOnInitSet, lazyEntries, hasLazy } = resolveLoadOnInit(config, entries, isSingle);
48
48
 
49
- if (hasLazy && !config.dictionarySchemaOutput) {
50
- fail(
51
- '[Codegen Error] Lazy namespaces require "dictionarySchemaOutput" for validateExternalNamespace.'
52
- );
53
- }
54
-
55
49
  const typesOutputPath = path.resolve(projectRoot, config.typesOutput);
56
50
  const dictionaryOutputPath = path.resolve(projectRoot, config.dictionaryOutput);
57
51
  const instanceOutputPath = path.resolve(projectRoot, config.instanceOutput);
@@ -124,7 +118,6 @@ function main() {
124
118
  isSingle,
125
119
  hasLazy,
126
120
  typesOutputPath,
127
- dictionaryOutputPath,
128
121
  paramsTypeName,
129
122
  schemaTypeName,
130
123
  localeTypeName,
@@ -173,7 +166,6 @@ function main() {
173
166
  config.namespaceLoadersOutput ??
174
167
  path.join(path.dirname(config.instanceOutput), "namespace-loaders.generated.ts")
175
168
  );
176
- const dictionarySchemaOutputPath = path.resolve(projectRoot, config.dictionarySchemaOutput!);
177
169
  const lazyEntriesWithPaths = lazyEntries.map((entry) => ({
178
170
  ...entry,
179
171
  absolutePath: path.resolve(projectRoot, entry.filePath),
@@ -183,9 +175,6 @@ function main() {
183
175
  lazyEntriesWithPaths,
184
176
  schemaTypeName,
185
177
  typesModule,
186
- toModuleBasename(dictionarySchemaOutputPath),
187
- toModuleBasename(instanceOutputPath),
188
- factoryName,
189
178
  importExtension
190
179
  );
191
180
 
@@ -124,9 +124,10 @@ describe("codegen projectLocales + setNamespace", () => {
124
124
  join(tempDir, "src/hydrate-billing.ts"),
125
125
  [
126
126
  `import { createI18n, projectNamespaceLocales } from "./i18n/instance.generated.js";`,
127
+ `import { defaultDictionary } from "./i18n/dictionary.generated.js";`,
127
128
  `import billingDictionary from "./i18n/translations/billing.json";`,
128
129
  ``,
129
- `const i18n = createI18n();`,
130
+ `const i18n = createI18n(defaultDictionary);`,
130
131
  `i18n.setNamespace("billing", projectNamespaceLocales(billingDictionary, ["en"]));`,
131
132
  ].join("\n")
132
133
  );
@@ -145,16 +146,17 @@ describe("codegen projectLocales + setNamespace", () => {
145
146
  join(tempDir, "src/hydrate-all.ts"),
146
147
  [
147
148
  `import { createI18n, projectLocales } from "./i18n/instance.generated.js";`,
149
+ `import { defaultDictionary } from "./i18n/dictionary.generated.js";`,
148
150
  `import type { AppSchema } from "./i18n/i18n-types.generated.js";`,
149
151
  `import billingDictionary from "./i18n/translations/billing.json";`,
150
- `import defaultDictionary from "./i18n/translations/default.json";`,
152
+ `import defaultNs from "./i18n/translations/default.json";`,
151
153
  ``,
152
154
  `const fullDictionary = {`,
153
- ` default: defaultDictionary,`,
155
+ ` default: defaultNs,`,
154
156
  ` billing: billingDictionary,`,
155
157
  `} satisfies AppSchema;`,
156
158
  ``,
157
- `const i18n = createI18n();`,
159
+ `const i18n = createI18n(defaultDictionary);`,
158
160
  `i18n.setAll(projectLocales(fullDictionary, ["en"]));`,
159
161
  ].join("\n")
160
162
  );
package/src/index.ts CHANGED
@@ -29,6 +29,3 @@ export {
29
29
  export type { ResolvedLocaleTemplate } from "./resolve-locale.js";
30
30
 
31
31
  export { projectLocales, projectNamespacesLocales } from "./project-locales.js";
32
-
33
- export { ensureNamespacesLoadedImpl } from "./ensure-namespace.js";
34
- export type { EnsureNamespacesLoadedOptions } from "./ensure-namespace.js";
@@ -32,11 +32,12 @@ const DEFAULT_STARTER = {
32
32
  const DEFAULT_IMPORT_SUFFIX = importExtensionSuffix(DEFAULT_IMPORT_EXTENSION);
33
33
 
34
34
  const INDEX_TS =
35
- `import { createI18n } from "./generated/instance.generated${DEFAULT_IMPORT_SUFFIX}";\n\n` +
35
+ `import { createI18n } from "./generated/instance.generated${DEFAULT_IMPORT_SUFFIX}";\n` +
36
+ `import { defaultDictionary } from "./generated/dictionary.generated${DEFAULT_IMPORT_SUFFIX}";\n\n` +
36
37
  `export * from "./generated/instance.generated${DEFAULT_IMPORT_SUFFIX}";\n` +
37
38
  `export * from "./generated/dictionary.generated${DEFAULT_IMPORT_SUFFIX}";\n` +
38
39
  `export * from "./generated/i18n-types.generated${DEFAULT_IMPORT_SUFFIX}";\n\n` +
39
- `export const i18n = createI18n();\n`;
40
+ `export const i18n = createI18n(defaultDictionary);\n`;
40
41
 
41
42
  function writeJson(filePath: string, value: unknown): void {
42
43
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
@@ -1,83 +0,0 @@
1
- import { parse } from '@formatjs/icu-messageformat-parser';
2
-
3
- type VariableType = "string" | "number" | "date";
4
- type VariableSpec = Record<string, VariableType>;
5
- type VariableRole = "simple" | "plural" | "selectordinal" | "select" | "number" | "date" | "time";
6
- type VariableMeta = {
7
- type: VariableType;
8
- roles: Set<VariableRole>;
9
- };
10
- type VariableMetaSpec = Record<string, VariableMeta>;
11
- type IcuAst = ReturnType<typeof parse>;
12
- declare function extractVariables(nodes: IcuAst): VariableSpec;
13
- declare function mergeVariableSpecs(target: VariableSpec, source: VariableSpec): VariableSpec;
14
- declare function variableSpecsEqual(a: VariableSpec, b: VariableSpec): boolean;
15
-
16
- type ParsedLocaleEntry = {
17
- readonly template: string;
18
- readonly args: VariableSpec;
19
- };
20
- type ParsedKeyEntry = {
21
- readonly locales: Readonly<Record<string, ParsedLocaleEntry>>;
22
- readonly mergedArgs: VariableSpec;
23
- };
24
- type NormalizedKeyDictionary = Readonly<Record<string, ParsedKeyEntry>>;
25
- type NormalizedMultiDictionary = Readonly<Record<string, NormalizedKeyDictionary>>;
26
- type NormalizedDictionary = {
27
- readonly mode: "single";
28
- readonly keys: NormalizedKeyDictionary;
29
- } | {
30
- readonly mode: "multi";
31
- readonly namespaces: NormalizedMultiDictionary;
32
- };
33
- type DictionarySpec = {
34
- readonly mode: "single";
35
- readonly requiredKeys: readonly string[];
36
- readonly argsByKey: Readonly<Record<string, VariableSpec>>;
37
- } | {
38
- readonly mode: "multi";
39
- readonly requiredKeys: Readonly<Record<string, readonly string[]>>;
40
- readonly argsByKey: Readonly<Record<string, Readonly<Record<string, VariableSpec>>>>;
41
- };
42
- type ValidationIssue = {
43
- readonly kind: "invalid_input";
44
- readonly message: string;
45
- } | {
46
- readonly kind: "missing_key";
47
- readonly path: readonly string[];
48
- } | {
49
- readonly kind: "invalid_locale_value";
50
- readonly path: readonly string[];
51
- readonly message: string;
52
- } | {
53
- readonly kind: "icu_syntax_error";
54
- readonly path: readonly string[];
55
- readonly message: string;
56
- } | {
57
- readonly kind: "locale_args_mismatch";
58
- readonly path: readonly string[];
59
- readonly locales: Readonly<Record<string, VariableSpec>>;
60
- readonly message: string;
61
- } | {
62
- readonly kind: "variable_mismatch";
63
- readonly path: readonly string[];
64
- readonly expected: VariableSpec;
65
- readonly found: VariableSpec;
66
- readonly message: string;
67
- } | {
68
- readonly kind: "variable_type_mismatch";
69
- readonly path: readonly string[];
70
- readonly variable: string;
71
- readonly expected: VariableType;
72
- readonly found: VariableType;
73
- readonly message: string;
74
- };
75
- type ValidationResult<T> = {
76
- readonly ok: true;
77
- readonly data: T;
78
- } | {
79
- readonly ok: false;
80
- readonly issues: readonly ValidationIssue[];
81
- };
82
-
83
- export { type DictionarySpec as D, type NormalizedDictionary as N, type ParsedKeyEntry as P, type ValidationResult as V, type VariableSpec as a, type ValidationIssue as b, type VariableMetaSpec as c, type NormalizedKeyDictionary as d, type NormalizedMultiDictionary as e, type ParsedLocaleEntry as f, type VariableType as g, extractVariables as h, mergeVariableSpecs as m, variableSpecsEqual as v };
@@ -1,66 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { ensureNamespacesLoadedImpl } from "./ensure-namespace.js";
3
- import { IcuTranslationProviderMulti } from "./IcuTranslationProviderMulti.js";
4
-
5
- type TestSchema = {
6
- default: {
7
- login_button: { en: string };
8
- };
9
- user: {
10
- greeting: { en: string };
11
- };
12
- billing: {
13
- invoice_summary: { en: string };
14
- };
15
- };
16
-
17
- type TestParams = {
18
- default: { login_button: never };
19
- user: { greeting: { name: string } };
20
- billing: { invoice_summary: { count: number } };
21
- };
22
-
23
- const namespaces: TestSchema = {
24
- default: { login_button: { en: "Login" } },
25
- user: { greeting: { en: "Hello {name}!" } },
26
- billing: {
27
- invoice_summary: {
28
- en: "You have {count, plural, one {1 invoice} other {{count} invoices}}",
29
- },
30
- },
31
- };
32
-
33
- describe("ensureNamespacesLoadedImpl integration", () => {
34
- it("mirrors the lazy codegen flow: partial init, preload batch, sync get", async () => {
35
- const i18n = new IcuTranslationProviderMulti<TestSchema, TestParams>({
36
- default: namespaces.default,
37
- });
38
-
39
- expect(i18n.hasNamespace("default")).toBe(true);
40
- expect(i18n.hasNamespace("user")).toBe(false);
41
- expect(i18n.hasNamespace("billing")).toBe(false);
42
-
43
- expect(i18n.get("default", "login_button", "en")).toBe("Login");
44
-
45
- expect(() => i18n.get("billing", "invoice_summary", "en", { count: 1 })).toThrow(
46
- 'Namespace not loaded: "billing"'
47
- );
48
-
49
- await ensureNamespacesLoadedImpl(
50
- {
51
- provider: i18n,
52
- resolveLoader: (namespace) => async () => namespaces[namespace],
53
- validate: (_namespace, raw) => ({ ok: true, data: raw as TestSchema["billing"] }),
54
- },
55
- ["user", "billing"]
56
- );
57
-
58
- expect(i18n.get("user", "greeting", "en", { name: "Ada" })).toBe("Hello Ada!");
59
- expect(i18n.get("billing", "invoice_summary", "en", { count: 2 })).toBe("You have 2 invoices");
60
-
61
- i18n.setNamespace("billing", {
62
- invoice_summary: { en: "{count, plural, one {1 bill} other {{count} bills}}" },
63
- });
64
- expect(i18n.get("billing", "invoice_summary", "en", { count: 3 })).toBe("3 bills");
65
- });
66
- });
@@ -1,125 +0,0 @@
1
- import { describe, expect, it, vi } from "vitest";
2
- import { ensureNamespacesLoadedImpl } from "./ensure-namespace.js";
3
- import { IcuTranslationProviderMulti } from "./IcuTranslationProviderMulti.js";
4
- import type { ValidationResult } from "./validation/types.js";
5
-
6
- type TestSchema = {
7
- default: {
8
- hello: { en: string };
9
- };
10
- billing: {
11
- invoice: { en: string };
12
- };
13
- };
14
-
15
- type TestParams = {
16
- default: { hello: never };
17
- billing: { invoice: never };
18
- };
19
-
20
- const defaultNs: TestSchema["default"] = { hello: { en: "Hello" } };
21
- const billingNs: TestSchema["billing"] = { invoice: { en: "Invoice" } };
22
-
23
- function createProvider(initial: Partial<TestSchema> = { default: defaultNs }) {
24
- return new IcuTranslationProviderMulti<TestSchema, TestParams>(initial);
25
- }
26
-
27
- describe("ensureNamespacesLoadedImpl", () => {
28
- it("is a noop for an empty namespaces array", async () => {
29
- const provider = createProvider();
30
- const load = vi.fn();
31
-
32
- await ensureNamespacesLoadedImpl(
33
- {
34
- provider,
35
- resolveLoader: () => load,
36
- validate: () => ({ ok: true, data: billingNs }),
37
- },
38
- []
39
- );
40
-
41
- expect(load).not.toHaveBeenCalled();
42
- });
43
-
44
- it("is a noop when the namespace is already loaded", async () => {
45
- const provider = createProvider({ default: defaultNs, billing: billingNs });
46
- const load = vi.fn();
47
-
48
- await ensureNamespacesLoadedImpl(
49
- {
50
- provider,
51
- resolveLoader: () => load,
52
- validate: () => ({ ok: true, data: billingNs }),
53
- },
54
- ["billing"]
55
- );
56
-
57
- expect(load).not.toHaveBeenCalled();
58
- });
59
-
60
- it("loads multiple namespaces in parallel", async () => {
61
- const provider = createProvider();
62
- const loadBilling = vi.fn(async () => billingNs);
63
- const loadDefault = vi.fn(async () => defaultNs);
64
-
65
- await ensureNamespacesLoadedImpl(
66
- {
67
- provider,
68
- resolveLoader: (namespace) => (namespace === "billing" ? loadBilling : loadDefault),
69
- validate: (_namespace, raw) => ({ ok: true, data: raw as TestSchema["billing"] }),
70
- },
71
- ["billing"]
72
- );
73
-
74
- expect(loadBilling).toHaveBeenCalledTimes(1);
75
- expect(provider.hasNamespace("billing")).toBe(true);
76
- expect(provider.get("billing", "invoice", "en")).toBe("Invoice");
77
- });
78
-
79
- it("dedupes concurrent requests for the same namespace", async () => {
80
- const provider = createProvider();
81
- let resolveLoad!: () => void;
82
- const load = vi.fn(
83
- () =>
84
- new Promise<TestSchema["billing"]>((resolve) => {
85
- resolveLoad = () => resolve(billingNs);
86
- })
87
- );
88
-
89
- const options = {
90
- provider,
91
- resolveLoader: () => load,
92
- validate: () => ({ ok: true, data: billingNs }) as const,
93
- };
94
-
95
- const first = ensureNamespacesLoadedImpl(options, ["billing"]);
96
- const second = ensureNamespacesLoadedImpl(options, ["billing"]);
97
-
98
- resolveLoad();
99
- await Promise.all([first, second]);
100
-
101
- expect(load).toHaveBeenCalledTimes(1);
102
- expect(provider.hasNamespace("billing")).toBe(true);
103
- });
104
-
105
- it("propagates validation failures without marking the namespace loaded", async () => {
106
- const provider = createProvider();
107
- const failed: ValidationResult<TestSchema["billing"]> = {
108
- ok: false,
109
- issues: [{ kind: "invalid_input", message: "bad data" }],
110
- };
111
-
112
- await expect(
113
- ensureNamespacesLoadedImpl(
114
- {
115
- provider,
116
- resolveLoader: () => async () => ({ bad: true }),
117
- validate: () => failed,
118
- },
119
- ["billing"]
120
- )
121
- ).rejects.toThrow("bad data");
122
-
123
- expect(provider.hasNamespace("billing")).toBe(false);
124
- });
125
- });
@@ -1,70 +0,0 @@
1
- import type { MultiDictionary } from "./types.js";
2
- import type { ValidationResult } from "./validation/types.js";
3
- import { DictionaryValidationError } from "./validation/errors.js";
4
-
5
- export interface EnsureNamespacesLoadedOptions<
6
- Schema extends MultiDictionary,
7
- NS extends keyof Schema & string,
8
- > {
9
- provider: Pick<
10
- { hasNamespace(namespace: NS): boolean; setNamespace(namespace: NS, values: Schema[NS]): void },
11
- "hasNamespace" | "setNamespace"
12
- >;
13
- resolveLoader: (namespace: NS) => () => Promise<unknown>;
14
- validate: (namespace: NS, raw: unknown) => ValidationResult<Schema[NS]>;
15
- }
16
-
17
- const inFlightByProvider = new WeakMap<object, Map<string, Promise<void>>>();
18
-
19
- async function ensureOneNamespace<Schema extends MultiDictionary, NS extends keyof Schema & string>(
20
- options: EnsureNamespacesLoadedOptions<Schema, NS>,
21
- namespace: NS
22
- ): Promise<void> {
23
- if (options.provider.hasNamespace(namespace)) {
24
- return;
25
- }
26
-
27
- const providerKey = options.provider as object;
28
- let inFlight = inFlightByProvider.get(providerKey);
29
- if (!inFlight) {
30
- inFlight = new Map();
31
- inFlightByProvider.set(providerKey, inFlight);
32
- }
33
-
34
- const existing = inFlight.get(namespace);
35
- if (existing) {
36
- return existing;
37
- }
38
-
39
- const promise = (async () => {
40
- try {
41
- if (options.provider.hasNamespace(namespace)) {
42
- return;
43
- }
44
-
45
- const raw = await options.resolveLoader(namespace)();
46
- const result = options.validate(namespace, raw);
47
- if (!result.ok) {
48
- throw new DictionaryValidationError(result.issues);
49
- }
50
-
51
- options.provider.setNamespace(namespace, result.data);
52
- } finally {
53
- inFlight!.delete(namespace);
54
- }
55
- })();
56
-
57
- inFlight.set(namespace, promise);
58
- return promise;
59
- }
60
-
61
- export async function ensureNamespacesLoadedImpl<
62
- Schema extends MultiDictionary,
63
- NS extends keyof Schema & string,
64
- >(options: EnsureNamespacesLoadedOptions<Schema, NS>, namespaces: NS[]): Promise<void> {
65
- if (namespaces.length === 0) {
66
- return;
67
- }
68
-
69
- await Promise.all(namespaces.map((namespace) => ensureOneNamespace(options, namespace)));
70
- }