@xndrjs/i18n 0.6.0-alpha.1 → 0.6.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 +121 -14
- package/dist/index.d.ts +17 -1
- package/dist/index.js +144 -90
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/IcuTranslationProviderMulti.test.ts +54 -0
- package/src/IcuTranslationProviderMulti.ts +22 -0
- package/src/IcuTranslationProviderSingle.test.ts +21 -0
- package/src/IcuTranslationProviderSingle.ts +16 -0
- package/src/codegen/emit/namespace-loaders-file.test.ts +39 -0
- package/src/codegen/emit/namespace-loaders-file.ts +39 -39
- package/src/index.ts +2 -0
- package/src/project-locales.test.ts +66 -0
- package/src/project-locales.ts +46 -0
package/README.md
CHANGED
|
@@ -195,7 +195,7 @@ Variables found across **all locales** of the same key are merged. If parsing fa
|
|
|
195
195
|
|
|
196
196
|
### 3. Generated files
|
|
197
197
|
|
|
198
|
-
- **`i18n-types.generated.ts`** — `I18N_MODE`, `MyProjectParams`, `MyProjectSchema`.
|
|
198
|
+
- **`i18n-types.generated.ts`** — `I18N_MODE`, `MyProjectParams`, `MyProjectSchema`. With `delivery: "custom"`, also `MyProjectDeliveryArea`, `DELIVERY_ARTIFACTS`, `LOCALE_DELIVERY_AREA`, and `MyProjectDeliveryArtifacts`.
|
|
199
199
|
- **`dictionary.generated.ts`** — imports the JSON files and exports `defaultDictionary` (canonical delivery) or `defaultDictionaryFor(locale)` / `defaultDictionaryFor(area)` when eager namespaces exist in split/custom delivery. Omitted when every namespace is lazy in split/custom delivery.
|
|
200
200
|
- **`instance.generated.ts`** — exports `createI18n(dictionary)` (required argument; no default import of the fallback dictionary), typed `projectDictionaryLocales()` (full schema), and in multi mode `projectNamespaceLocales()` (single namespace); with `delivery: "custom"`, also `projectDictionaryForDeliveryArea()` and `projectNamespaceForDeliveryArea()`; `LOCALE_FALLBACK` wired in when configured.
|
|
201
201
|
- **`i18n.ts`** (optional, hand-written) — app-owned singleton if desired.
|
|
@@ -424,7 +424,8 @@ Specify **exactly one** of `dictionary` (single-file) or `namespaces` (multi-fil
|
|
|
424
424
|
| `dictionarySchemaOutput` | Optional path for generated external dictionary validation (`dictionary-schema.generated.ts`). Requires `zod` in the consumer app. |
|
|
425
425
|
| `loadOnInit` | Multi mode only; **canonical delivery only**. Namespaces to include in the initial bundle via static imports. When omitted, all namespaces are eager (default). Not allowed with `split-by-locale` or `custom` — those modes load every namespace through `namespaceLoaders`. |
|
|
426
426
|
| `namespaceLoadersOutput` | Output path for generated `namespaceLoaders` (dynamic `import()` per lazy namespace). Defaults to `{dirname(instanceOutput)}/namespace-loaders.generated.ts`. Required when lazy namespaces exist. |
|
|
427
|
-
| `delivery` | Optional. `"canonical"` (default) — one multilocale JSON per namespace. `"split-by-locale"` — emits `{basename}.{locale}.json` under `{deliveryOutput}/translations/` and per-locale loaders.
|
|
427
|
+
| `delivery` | Optional. `"canonical"` (default) — one multilocale JSON per namespace. `"split-by-locale"` — emits `{basename}.{locale}.json` under `{deliveryOutput}/translations/` and per-locale loaders. `"custom"` — named delivery areas via `deliveryArtifacts`; emits `{basename}.{area}.json`, area-scoped loaders, and typed `DELIVERY_ARTIFACTS` / `LOCALE_DELIVERY_AREA` in `i18n-types.generated.ts`. |
|
|
428
|
+
| `deliveryArtifacts` | Required when `delivery` is `"custom"`. Map of delivery area → locale list (e.g. `{ "eu": ["en", "it"], "amer": ["en-US"] }`). Codegen validates structure and that locales partition the project locale set. Emitted as `DELIVERY_ARTIFACTS` (area → locales) and `LOCALE_DELIVERY_AREA` (locale → area). |
|
|
428
429
|
| `deliveryOutput` | Optional. Directory for compiled and split delivery JSON (files land in `{deliveryOutput}/translations/`). Defaults to `dirname(typesOutput)`. Use e.g. `public/i18n` to ship per-locale JSON from a static host while keeping generated TypeScript under `generated/`. |
|
|
429
430
|
|
|
430
431
|
> Paths are resolved relative to the directory containing `i18n.codegen.json` (e.g. `i18n/` when using `xndrjs-i18n-setup .`).
|
|
@@ -482,6 +483,7 @@ Codegen writes `{deliveryOutput}/translations/{basename}.{locale}.json` (for exa
|
|
|
482
483
|
| `export const defaultDictionary` | `dictionary.generated.ts` is not generated |
|
|
483
484
|
| `namespaceLoaders.billing()` | `namespaceLoaders.billing(locale)` or `namespaceLoaders.billing(area)` |
|
|
484
485
|
| — | `ensureNamespacesLoadedForLocale(i18n, locale)` or `ensureNamespacesLoadedForArea(i18n, area)` in `namespace-loaders.generated.ts` |
|
|
486
|
+
| — | `DELIVERY_ARTIFACTS`, `LOCALE_DELIVERY_AREA` in `i18n-types.generated.ts` (`custom` only) |
|
|
485
487
|
|
|
486
488
|
When `loadOnInit` lists eager namespaces in **canonical** delivery only, `dictionary.generated.ts` still exports `defaultDictionary` with static imports. In split/custom delivery, eager slices use `defaultDictionaryFor(locale)` or `defaultDictionaryFor(area)` when configured.
|
|
487
489
|
|
|
@@ -505,6 +507,55 @@ const i18n = createI18n({});
|
|
|
505
507
|
await ensureNamespacesLoadedForArea(i18n, activeArea);
|
|
506
508
|
```
|
|
507
509
|
|
|
510
|
+
Custom delivery config and typed artifacts (no need to read `deliveryArtifacts` from JSON at runtime):
|
|
511
|
+
|
|
512
|
+
```json
|
|
513
|
+
{
|
|
514
|
+
"delivery": "custom",
|
|
515
|
+
"deliveryArtifacts": {
|
|
516
|
+
"eu": ["en", "it", "fr"],
|
|
517
|
+
"amer": ["en-US", "es-AR"]
|
|
518
|
+
},
|
|
519
|
+
"namespaces": {
|
|
520
|
+
"default": "translations/default.json",
|
|
521
|
+
"billing": "translations/billing.yaml"
|
|
522
|
+
},
|
|
523
|
+
"namespaceLoadersOutput": "generated/namespace-loaders.generated.ts"
|
|
524
|
+
}
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
Codegen emits the partition in `i18n-types.generated.ts`:
|
|
528
|
+
|
|
529
|
+
```ts
|
|
530
|
+
export type MyProjectDeliveryArea = "amer" | "eu";
|
|
531
|
+
|
|
532
|
+
export const DELIVERY_ARTIFACTS = {
|
|
533
|
+
amer: ["en-US", "es-AR"] as const,
|
|
534
|
+
eu: ["en", "fr", "it"] as const,
|
|
535
|
+
} as const satisfies Record<MyProjectDeliveryArea, readonly MyProjectLocale[]>;
|
|
536
|
+
|
|
537
|
+
export type MyProjectDeliveryArtifacts = typeof DELIVERY_ARTIFACTS;
|
|
538
|
+
|
|
539
|
+
export const LOCALE_DELIVERY_AREA = {
|
|
540
|
+
"en-US": "amer",
|
|
541
|
+
"es-AR": "amer",
|
|
542
|
+
en: "eu",
|
|
543
|
+
fr: "eu",
|
|
544
|
+
it: "eu",
|
|
545
|
+
} as const satisfies Record<MyProjectLocale, MyProjectDeliveryArea>;
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
```ts
|
|
549
|
+
import {
|
|
550
|
+
DELIVERY_ARTIFACTS,
|
|
551
|
+
LOCALE_DELIVERY_AREA,
|
|
552
|
+
type MyProjectDeliveryArea,
|
|
553
|
+
} from "./generated/i18n-types.generated.js";
|
|
554
|
+
|
|
555
|
+
const euLocales = DELIVERY_ARTIFACTS.eu; // readonly ["en", "fr", "it"]
|
|
556
|
+
const areaForEnUs: MyProjectDeliveryArea = LOCALE_DELIVERY_AREA["en-US"]; // "amer"
|
|
557
|
+
```
|
|
558
|
+
|
|
508
559
|
Example `dictionary.generated.ts` (multi, canonical delivery, `loadOnInit: ["default"]`):
|
|
509
560
|
|
|
510
561
|
```ts
|
|
@@ -533,6 +584,10 @@ export const namespaceLoaders = {
|
|
|
533
584
|
return import("./translations/billing.it.json").then((m) => m.default);
|
|
534
585
|
case "de-CH":
|
|
535
586
|
return import("./translations/billing.de-CH.json").then((m) => m.default);
|
|
587
|
+
default:
|
|
588
|
+
throw new Error(
|
|
589
|
+
`[i18n] No translation artifact for namespace "billing" and locale "${String(locale)}".`
|
|
590
|
+
);
|
|
536
591
|
}
|
|
537
592
|
},
|
|
538
593
|
};
|
|
@@ -544,7 +599,7 @@ export async function ensureNamespacesLoadedForLocale(
|
|
|
544
599
|
): Promise<void> {
|
|
545
600
|
await Promise.all(
|
|
546
601
|
namespaces.map(async (namespace) => {
|
|
547
|
-
i18n.
|
|
602
|
+
i18n.mergeNamespace(namespace, await namespaceLoaders[namespace](locale));
|
|
548
603
|
})
|
|
549
604
|
);
|
|
550
605
|
}
|
|
@@ -566,12 +621,12 @@ Use split delivery when you want smaller lazy chunks (one locale per dynamic imp
|
|
|
566
621
|
|
|
567
622
|
### Single vs. multi-namespace API
|
|
568
623
|
|
|
569
|
-
| | Single-file
|
|
570
|
-
| ----------- |
|
|
571
|
-
| `I18N_MODE` | `'single'`
|
|
572
|
-
| Provider | `IcuTranslationProviderSingle`
|
|
573
|
-
| `.get()` | `get(key, locale, params?)`
|
|
574
|
-
| Override | `setAll(schema)`
|
|
624
|
+
| | Single-file | Multi-namespace |
|
|
625
|
+
| ----------- | ------------------------------------- | ----------------------------------------------------------------------------- |
|
|
626
|
+
| `I18N_MODE` | `'single'` | `'multi'` |
|
|
627
|
+
| Provider | `IcuTranslationProviderSingle` | `IcuTranslationProviderMulti` |
|
|
628
|
+
| `.get()` | `get(key, locale, params?)` | `get(namespace, key, locale, params?)` |
|
|
629
|
+
| Override | `setAll(schema)` / `mergeAll(schema)` | `setAll(schema)` + `setNamespace(ns, values)` / `mergeNamespace` / `mergeAll` |
|
|
575
630
|
|
|
576
631
|
### Multi-namespace example
|
|
577
632
|
|
|
@@ -611,10 +666,20 @@ i18n.setNamespace("billing", externalBillingPayload);
|
|
|
611
666
|
|
|
612
667
|
### Lazy namespace loading (multi mode)
|
|
613
668
|
|
|
614
|
-
|
|
669
|
+
`.get()` stays synchronous — register lazy namespaces with `setNamespace()` before rendering. The pattern depends on `delivery`:
|
|
670
|
+
|
|
671
|
+
| Delivery | Loading pattern |
|
|
672
|
+
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
673
|
+
| `canonical` | `loadOnInit` for eager namespaces; manual `namespaceLoaders.ns()` for lazy ones. Use `hasNamespace` to skip already-loaded namespaces. |
|
|
674
|
+
| `split-by-locale` / `custom` | Every namespace is lazy. Prefer `ensureNamespacesLoadedForLocale` / `ensureNamespacesLoadedForArea` — they call `mergeAll()` (single) or `mergeNamespace()` (multi) so loading another locale or area accumulates translations instead of replacing prior locales. |
|
|
675
|
+
|
|
676
|
+
#### Canonical delivery — `loadOnInit` and manual loaders
|
|
677
|
+
|
|
678
|
+
Split namespaces across chunks with `loadOnInit` in **canonical** delivery only. Codegen emits typed `namespaceLoaders` with one dynamic `import()` per lazy namespace.
|
|
615
679
|
|
|
616
680
|
```json
|
|
617
681
|
{
|
|
682
|
+
"delivery": "canonical",
|
|
618
683
|
"namespaces": {
|
|
619
684
|
"default": "translations/default.json",
|
|
620
685
|
"billing": "translations/billing.yaml"
|
|
@@ -626,6 +691,8 @@ Split namespaces across chunks with `loadOnInit` in **canonical** delivery only.
|
|
|
626
691
|
|
|
627
692
|
Codegen also emits `LoadOnInitNamespace`, `LazyNamespace`, and `InitialSchema` types. The generated factory accepts a partial `InitialSchema` at init time.
|
|
628
693
|
|
|
694
|
+
Use `hasNamespace` when loading lazily by hand — namespaces loaded once stay in memory:
|
|
695
|
+
|
|
629
696
|
```ts
|
|
630
697
|
import { i18n, namespaceLoaders, type LazyNamespace } from "./i18n";
|
|
631
698
|
|
|
@@ -645,16 +712,54 @@ await Promise.all(
|
|
|
645
712
|
);
|
|
646
713
|
```
|
|
647
714
|
|
|
648
|
-
|
|
715
|
+
#### Split-by-locale / custom — `ensureNamespacesLoaded*`
|
|
716
|
+
|
|
717
|
+
With `split-by-locale` or `custom`, codegen emits `ensureNamespacesLoadedForLocale` / `ensureNamespacesLoadedForArea`. These helpers call `mergeAll()` in single mode or `mergeNamespace()` in multi mode — loading another locale or delivery area **adds** locale entries per key instead of replacing the whole dictionary:
|
|
718
|
+
|
|
719
|
+
```ts
|
|
720
|
+
import { createI18n } from "./generated/instance.generated.js";
|
|
721
|
+
import {
|
|
722
|
+
ensureNamespacesLoadedForLocale,
|
|
723
|
+
ensureNamespacesLoadedForArea,
|
|
724
|
+
} from "./generated/namespace-loaders.generated.js";
|
|
725
|
+
|
|
726
|
+
const i18n = createI18n({});
|
|
727
|
+
|
|
728
|
+
// split-by-locale — en and it coexist after both calls
|
|
729
|
+
await ensureNamespacesLoadedForLocale(i18n, "it");
|
|
730
|
+
i18n.get("billing", "invoice_summary", "it", { count: 3 });
|
|
731
|
+
|
|
732
|
+
await ensureNamespacesLoadedForLocale(i18n, "en");
|
|
733
|
+
i18n.get("billing", "invoice_summary", "en", { count: 3 });
|
|
734
|
+
i18n.get("billing", "invoice_summary", "it", { count: 3 }); // still available
|
|
735
|
+
|
|
736
|
+
// custom delivery
|
|
737
|
+
await ensureNamespacesLoadedForArea(i18n, "eu", ["billing"]);
|
|
738
|
+
```
|
|
739
|
+
|
|
740
|
+
Route-scoped subset:
|
|
741
|
+
|
|
742
|
+
```ts
|
|
743
|
+
await ensureNamespacesLoadedForLocale(i18n, activeLocale, ["billing"]);
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
Manual loader calls (without the helper) should use `mergeAll` (single) or `mergeNamespace` (multi) when accumulating locales on the same instance:
|
|
649
747
|
|
|
650
748
|
```ts
|
|
651
749
|
const locale = "it" as const;
|
|
652
750
|
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
751
|
+
// single
|
|
752
|
+
i18n.mergeAll(await namespaceLoaders.default(locale));
|
|
753
|
+
|
|
754
|
+
// multi
|
|
755
|
+
i18n.mergeNamespace("billing", await namespaceLoaders.billing(locale));
|
|
756
|
+
|
|
757
|
+
// add another locale on the same instance:
|
|
758
|
+
i18n.mergeNamespace("billing", await namespaceLoaders.billing("en"));
|
|
656
759
|
```
|
|
657
760
|
|
|
761
|
+
Generated loaders throw if locale/area does not match a known artifact (no silent `undefined` into `setNamespace`).
|
|
762
|
+
|
|
658
763
|
Optional locale projection before register:
|
|
659
764
|
|
|
660
765
|
```ts
|
|
@@ -728,7 +833,9 @@ Both providers share this behavior:
|
|
|
728
833
|
- **`getAll()`** — returns a deep-frozen snapshot of the current dictionary (not a live reference).
|
|
729
834
|
- **`hasNamespace(ns)`** — (multi only) returns whether a namespace has been loaded (eager init, lazy load, or `setNamespace`).
|
|
730
835
|
- **`setAll(values)`** — replaces the dictionary and clears the entire cache.
|
|
836
|
+
- **`mergeAll(values)`** — merges locale entries per translation key (single: whole schema; multi: each namespace in `values`). Alternative to `setAll` when accumulating locales on the same instance. Used by generated `ensureNamespacesLoaded*` helpers in single-mode split/custom delivery.
|
|
731
837
|
- **`setNamespace(ns, values)`** — (multi only) replaces one namespace and invalidates only its cache entries.
|
|
838
|
+
- **`mergeNamespace(ns, values)`** — (multi only) merges locale entries per translation key into an existing namespace (or registers it when not loaded yet). Used by generated `ensureNamespacesLoaded*` helpers in multi-mode split/custom delivery.
|
|
732
839
|
- **Missing key/locale** — by default throws an error if the template is `undefined` (configurable via `onMissing`, see below). An empty string (`""`) is treated as a valid template.
|
|
733
840
|
- **ICU syntax error** — throws `[i18n ICU Syntax Error] ...`.
|
|
734
841
|
- **Formatting error** (missing/invalid params) — throws `[i18n Formatting Error] ...`.
|
package/dist/index.d.ts
CHANGED
|
@@ -42,6 +42,7 @@ interface TranslationProviderSingle<Schema extends KeyDictionary, Params extends
|
|
|
42
42
|
forLocale<Locale extends RequestLocales>(locale: Locale): TranslationProviderSingleForLocale<Schema, Params, Locale>;
|
|
43
43
|
getAll(): Schema;
|
|
44
44
|
setAll(values: Schema): void;
|
|
45
|
+
mergeAll(values: Partial<Schema>): void;
|
|
45
46
|
}
|
|
46
47
|
declare class IcuTranslationProviderSingleForLocale<Schema extends KeyDictionary, Params extends {
|
|
47
48
|
[K in keyof Schema]: unknown;
|
|
@@ -64,6 +65,7 @@ declare class IcuTranslationProviderSingle<Schema extends KeyDictionary, Params
|
|
|
64
65
|
forLocale<Locale extends RequestLocales>(locale: Locale): IcuTranslationProviderSingleForLocale<Schema, Params, RequestLocales, Locale, Fallback>;
|
|
65
66
|
getAll(): Schema;
|
|
66
67
|
setAll(values: Schema): void;
|
|
68
|
+
mergeAll(values: Partial<Schema>): void;
|
|
67
69
|
}
|
|
68
70
|
|
|
69
71
|
interface TranslationProviderMultiForLocale<Schema extends MultiDictionary, Params extends {
|
|
@@ -84,7 +86,9 @@ interface TranslationProviderMulti<Schema extends MultiDictionary, Params extend
|
|
|
84
86
|
getAll(): Schema;
|
|
85
87
|
hasNamespace<NS extends keyof Schema & string>(namespace: NS): boolean;
|
|
86
88
|
setAll(values: Schema): void;
|
|
89
|
+
mergeAll(values: Partial<Schema>): void;
|
|
87
90
|
setNamespace<NS extends keyof Schema>(namespace: NS, values: Schema[NS]): void;
|
|
91
|
+
mergeNamespace<NS extends keyof Schema>(namespace: NS, values: Schema[NS]): void;
|
|
88
92
|
}
|
|
89
93
|
declare class IcuTranslationProviderMultiForLocale<Schema extends MultiDictionary, Params extends {
|
|
90
94
|
[NS in keyof Schema]: {
|
|
@@ -113,7 +117,9 @@ declare class IcuTranslationProviderMulti<Schema extends MultiDictionary, Params
|
|
|
113
117
|
getAll(): Schema;
|
|
114
118
|
hasNamespace<NS extends keyof Schema & string>(namespace: NS): boolean;
|
|
115
119
|
setAll(values: Schema): void;
|
|
120
|
+
mergeAll(values: Partial<Schema>): void;
|
|
116
121
|
setNamespace<NS extends keyof Schema>(namespace: NS, values: Schema[NS]): void;
|
|
122
|
+
mergeNamespace<NS extends keyof Schema>(namespace: NS, values: Schema[NS]): void;
|
|
117
123
|
}
|
|
118
124
|
|
|
119
125
|
type LocaleFallbackMap = Record<string, string | null>;
|
|
@@ -147,5 +153,15 @@ declare function projectDictionaryForDeliveryAreaCore<T extends MultiDictionary>
|
|
|
147
153
|
* Projects every namespace in a multi-namespace dictionary.
|
|
148
154
|
*/
|
|
149
155
|
declare function projectDictionaryLocalesCore<T extends MultiDictionary>(dictionary: T, locales: readonly string[], localeFallback?: LocaleFallbackMap$1): T;
|
|
156
|
+
/**
|
|
157
|
+
* Merges locale entries per translation key without dropping keys or locales
|
|
158
|
+
* already present in `existing`. Incoming locales overwrite the same locale on conflict.
|
|
159
|
+
*/
|
|
160
|
+
declare function mergeNamespaceLocalesCore<T extends KeyDictionary>(existing: T, incoming: T): T;
|
|
161
|
+
/**
|
|
162
|
+
* Merges locale entries per key in every namespace present in `incoming`.
|
|
163
|
+
* Namespaces and keys not in `incoming` are preserved from `existing`.
|
|
164
|
+
*/
|
|
165
|
+
declare function mergeDictionaryLocalesCore<T extends MultiDictionary>(existing: T, incoming: Partial<T>): T;
|
|
150
166
|
|
|
151
|
-
export { IcuTranslationProviderMulti, type IcuTranslationProviderOptions, IcuTranslationProviderSingle, type KeyDictionary, type LocaleDictionary, type LocaleFallbackMap$1 as LocaleFallbackMap, type LocaleOfMulti, type LocaleOfSingle, type MissingTranslationContext, type MultiDictionary, type OnMissingTranslation, type ResolvedLocaleTemplate, type TranslationProviderMulti, type TranslationProviderMultiForLocale, type TranslationProviderSingle, type TranslationProviderSingleForLocale, formatLocaleFallbackChain, projectDictionaryForDeliveryAreaCore, projectDictionaryLocalesCore, projectNamespaceForDeliveryAreaCore, projectNamespaceLocalesCore, resolveLocaleTemplate, validateLocaleFallback };
|
|
167
|
+
export { IcuTranslationProviderMulti, type IcuTranslationProviderOptions, IcuTranslationProviderSingle, type KeyDictionary, type LocaleDictionary, type LocaleFallbackMap$1 as LocaleFallbackMap, type LocaleOfMulti, type LocaleOfSingle, type MissingTranslationContext, type MultiDictionary, type OnMissingTranslation, type ResolvedLocaleTemplate, type TranslationProviderMulti, type TranslationProviderMultiForLocale, type TranslationProviderSingle, type TranslationProviderSingleForLocale, formatLocaleFallbackChain, mergeDictionaryLocalesCore, mergeNamespaceLocalesCore, projectDictionaryForDeliveryAreaCore, projectDictionaryLocalesCore, projectNamespaceForDeliveryAreaCore, projectNamespaceLocalesCore, resolveLocaleTemplate, validateLocaleFallback };
|
package/dist/index.js
CHANGED
|
@@ -143,6 +143,123 @@ function resolveAndFormat(options) {
|
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
+
// src/project-locales.ts
|
|
147
|
+
function classifyAreaLocales(areaLocales, localeFallback) {
|
|
148
|
+
const areaSet = new Set(areaLocales);
|
|
149
|
+
const full = [];
|
|
150
|
+
const preserve = [];
|
|
151
|
+
for (const locale of areaLocales) {
|
|
152
|
+
const fallback = localeFallback?.[locale];
|
|
153
|
+
if (fallback !== null && fallback !== void 0 && areaSet.has(fallback)) {
|
|
154
|
+
preserve.push(locale);
|
|
155
|
+
} else {
|
|
156
|
+
full.push(locale);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return { full, preserve };
|
|
160
|
+
}
|
|
161
|
+
function projectKeyDictionary(dictionary, locales, localeFallback) {
|
|
162
|
+
const targetLocales = [...new Set(locales)];
|
|
163
|
+
const result = {};
|
|
164
|
+
for (const [key, localesByKey] of Object.entries(dictionary)) {
|
|
165
|
+
const projected = {};
|
|
166
|
+
for (const locale of targetLocales) {
|
|
167
|
+
const resolved = resolveLocaleTemplate(localesByKey, locale, localeFallback);
|
|
168
|
+
if (resolved !== void 0) {
|
|
169
|
+
projected[locale] = resolved.template;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (Object.keys(projected).length > 0) {
|
|
173
|
+
result[key] = projected;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return result;
|
|
177
|
+
}
|
|
178
|
+
function projectNamespaceLocalesCore(dictionary, locales, localeFallback) {
|
|
179
|
+
return projectKeyDictionary(dictionary, locales, localeFallback);
|
|
180
|
+
}
|
|
181
|
+
function projectNamespaceForDeliveryAreaCore(dictionary, areaLocales, localeFallback) {
|
|
182
|
+
const { full, preserve } = classifyAreaLocales(areaLocales, localeFallback);
|
|
183
|
+
const fullProjection = full.length > 0 ? projectKeyDictionary(dictionary, full, localeFallback) : void 0;
|
|
184
|
+
const result = {};
|
|
185
|
+
for (const [key, localesByKey] of Object.entries(dictionary)) {
|
|
186
|
+
const projected = {};
|
|
187
|
+
if (fullProjection !== void 0) {
|
|
188
|
+
const fullEntry = fullProjection[key];
|
|
189
|
+
if (fullEntry !== void 0) {
|
|
190
|
+
Object.assign(projected, fullEntry);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
for (const locale of preserve) {
|
|
194
|
+
const template = localesByKey[locale];
|
|
195
|
+
if (template !== void 0) {
|
|
196
|
+
projected[locale] = template;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (Object.keys(projected).length > 0) {
|
|
200
|
+
result[key] = projected;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return result;
|
|
204
|
+
}
|
|
205
|
+
function projectDictionaryForDeliveryAreaCore(dictionary, areaLocales, localeFallback) {
|
|
206
|
+
const result = {};
|
|
207
|
+
for (const namespace of Object.keys(dictionary)) {
|
|
208
|
+
const namespaceDictionary = dictionary[namespace];
|
|
209
|
+
if (namespaceDictionary === void 0) {
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
result[namespace] = projectNamespaceForDeliveryAreaCore(
|
|
213
|
+
namespaceDictionary,
|
|
214
|
+
areaLocales,
|
|
215
|
+
localeFallback
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
return result;
|
|
219
|
+
}
|
|
220
|
+
function projectDictionaryLocalesCore(dictionary, locales, localeFallback) {
|
|
221
|
+
const result = {};
|
|
222
|
+
for (const namespace of Object.keys(dictionary)) {
|
|
223
|
+
const namespaceDictionary = dictionary[namespace];
|
|
224
|
+
if (namespaceDictionary === void 0) {
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
result[namespace] = projectKeyDictionary(
|
|
228
|
+
namespaceDictionary,
|
|
229
|
+
locales,
|
|
230
|
+
localeFallback
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
return result;
|
|
234
|
+
}
|
|
235
|
+
function mergeNamespaceLocalesCore(existing, incoming) {
|
|
236
|
+
const merged = structuredClone(existing);
|
|
237
|
+
for (const [key, incomingLocales] of Object.entries(incoming)) {
|
|
238
|
+
if (incomingLocales === void 0 || typeof incomingLocales !== "object") {
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
merged[key] = {
|
|
242
|
+
...merged[key] ?? {},
|
|
243
|
+
...incomingLocales
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
return merged;
|
|
247
|
+
}
|
|
248
|
+
function mergeDictionaryLocalesCore(existing, incoming) {
|
|
249
|
+
const merged = structuredClone(existing);
|
|
250
|
+
for (const namespace of Object.keys(incoming)) {
|
|
251
|
+
const incomingNamespace = incoming[namespace];
|
|
252
|
+
if (incomingNamespace === void 0) {
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
merged[namespace] = mergeNamespaceLocalesCore(
|
|
256
|
+
merged[namespace] ?? {},
|
|
257
|
+
incomingNamespace
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
return merged;
|
|
261
|
+
}
|
|
262
|
+
|
|
146
263
|
// src/IcuTranslationProviderSingle.ts
|
|
147
264
|
var IcuTranslationProviderSingleForLocale = class {
|
|
148
265
|
constructor(provider, locale) {
|
|
@@ -202,6 +319,17 @@ var IcuTranslationProviderSingle = class {
|
|
|
202
319
|
this.dictionary = structuredClone(values);
|
|
203
320
|
this.compiledCache = {};
|
|
204
321
|
}
|
|
322
|
+
mergeAll(values) {
|
|
323
|
+
this.dictionary = mergeNamespaceLocalesCore(this.dictionary, values);
|
|
324
|
+
for (const [key, incomingLocales] of Object.entries(values)) {
|
|
325
|
+
if (incomingLocales === void 0 || typeof incomingLocales !== "object") {
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
for (const locale of Object.keys(incomingLocales)) {
|
|
329
|
+
delete this.compiledCache[locale]?.[key];
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
205
333
|
};
|
|
206
334
|
|
|
207
335
|
// src/IcuTranslationProviderMulti.ts
|
|
@@ -279,6 +407,14 @@ var IcuTranslationProviderMulti = class {
|
|
|
279
407
|
this.compiledCache = {};
|
|
280
408
|
this.loadedNamespaces = new Set(Object.keys(values));
|
|
281
409
|
}
|
|
410
|
+
mergeAll(values) {
|
|
411
|
+
for (const namespace of Object.keys(values)) {
|
|
412
|
+
const incoming = values[namespace];
|
|
413
|
+
if (incoming !== void 0) {
|
|
414
|
+
this.mergeNamespace(namespace, incoming);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
282
418
|
setNamespace(namespace, values) {
|
|
283
419
|
this.dictionary = {
|
|
284
420
|
...this.dictionary,
|
|
@@ -289,98 +425,16 @@ var IcuTranslationProviderMulti = class {
|
|
|
289
425
|
delete this.compiledCache[locale]?.[namespace];
|
|
290
426
|
}
|
|
291
427
|
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
const areaSet = new Set(areaLocales);
|
|
297
|
-
const full = [];
|
|
298
|
-
const preserve = [];
|
|
299
|
-
for (const locale of areaLocales) {
|
|
300
|
-
const fallback = localeFallback?.[locale];
|
|
301
|
-
if (fallback !== null && fallback !== void 0 && areaSet.has(fallback)) {
|
|
302
|
-
preserve.push(locale);
|
|
303
|
-
} else {
|
|
304
|
-
full.push(locale);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
return { full, preserve };
|
|
308
|
-
}
|
|
309
|
-
function projectKeyDictionary(dictionary, locales, localeFallback) {
|
|
310
|
-
const targetLocales = [...new Set(locales)];
|
|
311
|
-
const result = {};
|
|
312
|
-
for (const [key, localesByKey] of Object.entries(dictionary)) {
|
|
313
|
-
const projected = {};
|
|
314
|
-
for (const locale of targetLocales) {
|
|
315
|
-
const resolved = resolveLocaleTemplate(localesByKey, locale, localeFallback);
|
|
316
|
-
if (resolved !== void 0) {
|
|
317
|
-
projected[locale] = resolved.template;
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
if (Object.keys(projected).length > 0) {
|
|
321
|
-
result[key] = projected;
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
return result;
|
|
325
|
-
}
|
|
326
|
-
function projectNamespaceLocalesCore(dictionary, locales, localeFallback) {
|
|
327
|
-
return projectKeyDictionary(dictionary, locales, localeFallback);
|
|
328
|
-
}
|
|
329
|
-
function projectNamespaceForDeliveryAreaCore(dictionary, areaLocales, localeFallback) {
|
|
330
|
-
const { full, preserve } = classifyAreaLocales(areaLocales, localeFallback);
|
|
331
|
-
const fullProjection = full.length > 0 ? projectKeyDictionary(dictionary, full, localeFallback) : void 0;
|
|
332
|
-
const result = {};
|
|
333
|
-
for (const [key, localesByKey] of Object.entries(dictionary)) {
|
|
334
|
-
const projected = {};
|
|
335
|
-
if (fullProjection !== void 0) {
|
|
336
|
-
const fullEntry = fullProjection[key];
|
|
337
|
-
if (fullEntry !== void 0) {
|
|
338
|
-
Object.assign(projected, fullEntry);
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
for (const locale of preserve) {
|
|
342
|
-
const template = localesByKey[locale];
|
|
343
|
-
if (template !== void 0) {
|
|
344
|
-
projected[locale] = template;
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
if (Object.keys(projected).length > 0) {
|
|
348
|
-
result[key] = projected;
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
return result;
|
|
352
|
-
}
|
|
353
|
-
function projectDictionaryForDeliveryAreaCore(dictionary, areaLocales, localeFallback) {
|
|
354
|
-
const result = {};
|
|
355
|
-
for (const namespace of Object.keys(dictionary)) {
|
|
356
|
-
const namespaceDictionary = dictionary[namespace];
|
|
357
|
-
if (namespaceDictionary === void 0) {
|
|
358
|
-
continue;
|
|
359
|
-
}
|
|
360
|
-
result[namespace] = projectNamespaceForDeliveryAreaCore(
|
|
361
|
-
namespaceDictionary,
|
|
362
|
-
areaLocales,
|
|
363
|
-
localeFallback
|
|
364
|
-
);
|
|
365
|
-
}
|
|
366
|
-
return result;
|
|
367
|
-
}
|
|
368
|
-
function projectDictionaryLocalesCore(dictionary, locales, localeFallback) {
|
|
369
|
-
const result = {};
|
|
370
|
-
for (const namespace of Object.keys(dictionary)) {
|
|
371
|
-
const namespaceDictionary = dictionary[namespace];
|
|
372
|
-
if (namespaceDictionary === void 0) {
|
|
373
|
-
continue;
|
|
428
|
+
mergeNamespace(namespace, values) {
|
|
429
|
+
if (!this.loadedNamespaces.has(namespace)) {
|
|
430
|
+
this.setNamespace(namespace, values);
|
|
431
|
+
return;
|
|
374
432
|
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
locales,
|
|
378
|
-
localeFallback
|
|
379
|
-
);
|
|
433
|
+
const existing = this.dictionary[namespace];
|
|
434
|
+
this.setNamespace(namespace, mergeNamespaceLocalesCore(existing, values));
|
|
380
435
|
}
|
|
381
|
-
|
|
382
|
-
}
|
|
436
|
+
};
|
|
383
437
|
|
|
384
|
-
export { IcuTranslationProviderMulti, IcuTranslationProviderSingle, formatLocaleFallbackChain, projectDictionaryForDeliveryAreaCore, projectDictionaryLocalesCore, projectNamespaceForDeliveryAreaCore, projectNamespaceLocalesCore, resolveLocaleTemplate, validateLocaleFallback };
|
|
438
|
+
export { IcuTranslationProviderMulti, IcuTranslationProviderSingle, formatLocaleFallbackChain, mergeDictionaryLocalesCore, mergeNamespaceLocalesCore, projectDictionaryForDeliveryAreaCore, projectDictionaryLocalesCore, projectNamespaceForDeliveryAreaCore, projectNamespaceLocalesCore, resolveLocaleTemplate, validateLocaleFallback };
|
|
385
439
|
//# sourceMappingURL=index.js.map
|
|
386
440
|
//# 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/format-core.ts","../src/IcuTranslationProviderSingle.ts","../src/IcuTranslationProviderMulti.ts","../src/project-locales.ts"],"names":[],"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 // Guards against infinite recursion on cyclic structures.\n if (Object.isFrozen(value)) {\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 { formatLocaleFallbackChain, resolveLocaleTemplate } from \"./resolve-locale.js\";\nimport type { LocaleCache, LocaleFallbackMap, OnMissingTranslation } from \"./types.js\";\n\nexport type FormatCoreContext = {\n key: string;\n namespace?: string | undefined;\n locale: string;\n localeFallback?: LocaleFallbackMap | undefined;\n onMissing: OnMissingTranslation;\n};\n\nexport type ResolveAndFormatOptions = {\n localeByKey: Record<string, string | undefined> | undefined;\n locale: string;\n params?: Record<string, unknown> | undefined;\n getCache: (resolvedLocale: string) => LocaleCache;\n context: FormatCoreContext;\n};\n\nfunction formatErrorLabel(context: Pick<FormatCoreContext, \"key\" | \"namespace\">): string {\n if (context.namespace !== undefined) {\n return `namespace \"${context.namespace}\", key \"${context.key}\"`;\n }\n return `key \"${context.key}\"`;\n}\n\nfunction formatMissingError(context: FormatCoreContext): string {\n const chain = formatLocaleFallbackChain(context.locale, context.localeFallback);\n if (context.namespace !== undefined) {\n return `[i18n] Missing key or locale: namespace \"${context.namespace}\", key \"${context.key}\" [${context.locale}] (fallback chain: ${chain})`;\n }\n return `[i18n] Missing key or locale: \"${context.key}\" [${context.locale}] (fallback chain: ${chain})`;\n}\n\nfunction resolveMissing(context: FormatCoreContext): string {\n const chain = formatLocaleFallbackChain(context.locale, context.localeFallback);\n if (context.onMissing === \"key\") {\n if (context.namespace !== undefined) {\n return `${context.namespace}.${context.key}`;\n }\n return context.key;\n }\n if (typeof context.onMissing === \"function\") {\n return context.onMissing({\n ...(context.namespace !== undefined ? { namespace: context.namespace } : {}),\n key: context.key,\n locale: context.locale,\n fallbackChain: chain,\n });\n }\n throw new Error(formatMissingError(context));\n}\n\n/**\n * Shared runtime path for both ICU providers: resolve locale (with fallback) →\n * compile template (cached) → format with params; handles missing keys via `onMissing`.\n */\nexport function resolveAndFormat(options: ResolveAndFormatOptions): string {\n const { localeByKey, locale, params, getCache, context } = options;\n const resolved = resolveLocaleTemplate(localeByKey, locale, context.localeFallback);\n\n if (!resolved) {\n return resolveMissing(context);\n }\n\n const { template, resolvedLocale } = resolved;\n const cache = getCache(resolvedLocale);\n const cacheKey = context.key;\n const label = formatErrorLabel(context);\n\n if (!cache[cacheKey]) {\n try {\n cache[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] Dictionary error for ${label} [${resolvedLocale}]: ${message}`\n );\n }\n }\n\n try {\n return cache[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 ${label} [${resolvedLocale}]: ${message}`\n );\n }\n}\n","import { cloneAndFreeze } from \"./deep-freeze.js\";\nimport { resolveAndFormat } from \"./format-core.js\";\nimport { validateLocaleFallback } from \"./resolve-locale.js\";\nimport type {\n IcuTranslationProviderOptions,\n KeyDictionary,\n LocaleFallbackMap,\n LocaleOfSingle,\n OnMissingTranslation,\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 const params = args[0] as Record<string, unknown> | undefined;\n return this.provider.getWithLocale(String(key), this.locale, params);\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 private readonly onMissing: OnMissingTranslation;\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 this.onMissing = options?.onMissing ?? \"throw\";\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 return this.getWithLocale(String(key), locale, params);\n }\n\n getWithLocale(key: string, locale: string, params?: Record<string, unknown>): string {\n return resolveAndFormat({\n localeByKey: this.dictionary[key],\n locale,\n params,\n getCache: (resolvedLocale) => {\n if (!this.compiledCache[resolvedLocale]) {\n this.compiledCache[resolvedLocale] = {};\n }\n return this.compiledCache[resolvedLocale]!;\n },\n context: {\n key,\n locale,\n localeFallback: this.localeFallback,\n onMissing: this.onMissing,\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 { cloneAndFreeze } from \"./deep-freeze.js\";\nimport { resolveAndFormat } from \"./format-core.js\";\nimport { validateLocaleFallback } from \"./resolve-locale.js\";\nimport type {\n IcuTranslationProviderOptions,\n LocaleFallbackMap,\n LocaleOfMulti,\n MultiCompiledCache,\n MultiDictionary,\n OnMissingTranslation,\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 const params = args[0] as Record<string, unknown> | undefined;\n return this.provider.getWithLocale(String(namespace), String(key), this.locale, params);\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 readonly onMissing: OnMissingTranslation;\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 this.onMissing = options?.onMissing ?? \"throw\";\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 const params = args[0] as Record<string, unknown> | undefined;\n return this.getWithLocale(String(namespace), String(key), locale, params);\n }\n\n getWithLocale(\n namespace: string,\n key: string,\n locale: string,\n params?: Record<string, unknown>\n ): string {\n if (!this.loadedNamespaces.has(namespace)) {\n throw new Error(\n `[i18n] Namespace not loaded: \"${namespace}\". Register it with setNamespace() before calling .get().`\n );\n }\n\n const localeByKey = (\n this.dictionary[namespace] as Record<string, Record<string, string>> | undefined\n )?.[key];\n\n return resolveAndFormat({\n localeByKey,\n locale,\n params,\n getCache: (resolvedLocale) => {\n if (!this.compiledCache[resolvedLocale]) {\n this.compiledCache[resolvedLocale] = {};\n }\n if (!this.compiledCache[resolvedLocale]![namespace]) {\n this.compiledCache[resolvedLocale]![namespace] = {};\n }\n return this.compiledCache[resolvedLocale]![namespace]!;\n },\n context: {\n namespace,\n key,\n locale,\n localeFallback: this.localeFallback,\n onMissing: this.onMissing,\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 classifyAreaLocales(\n areaLocales: readonly string[],\n localeFallback?: LocaleFallbackMap\n): { full: string[]; preserve: string[] } {\n const areaSet = new Set(areaLocales);\n const full: string[] = [];\n const preserve: string[] = [];\n\n for (const locale of areaLocales) {\n const fallback = localeFallback?.[locale];\n if (fallback !== null && fallback !== undefined && areaSet.has(fallback)) {\n preserve.push(locale);\n } else {\n full.push(locale);\n }\n }\n\n return { full, preserve };\n}\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 namespace 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 projectNamespaceLocalesCore<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 a single namespace for a custom delivery area.\n * Full locales resolve through `localeFallback` (e.g. external chains like `it → en-US`);\n * preserve locales copy only their direct canonical entry when present.\n */\nexport function projectNamespaceForDeliveryAreaCore<T extends KeyDictionary>(\n dictionary: T,\n areaLocales: readonly string[],\n localeFallback?: LocaleFallbackMap\n): T {\n const { full, preserve } = classifyAreaLocales(areaLocales, localeFallback);\n const fullProjection =\n full.length > 0 ? projectKeyDictionary(dictionary, full, localeFallback) : undefined;\n const result = {} as T;\n\n for (const [key, localesByKey] of Object.entries(dictionary)) {\n const projected: Record<string, string> = {};\n\n if (fullProjection !== undefined) {\n const fullEntry = fullProjection[key as keyof T];\n if (fullEntry !== undefined) {\n Object.assign(projected, fullEntry);\n }\n }\n\n for (const locale of preserve) {\n const template = localesByKey[locale];\n if (template !== undefined) {\n projected[locale] = 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 * Projects a multi-namespace dictionary for a custom delivery area.\n * Each namespace uses the same delivery-area rules as `projectNamespaceForDeliveryAreaCore`.\n */\nexport function projectDictionaryForDeliveryAreaCore<T extends MultiDictionary>(\n dictionary: T,\n areaLocales: 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] = projectNamespaceForDeliveryAreaCore(\n namespaceDictionary,\n areaLocales,\n localeFallback\n ) as T[typeof namespace];\n }\n\n return result;\n}\n\n/**\n * Projects every namespace in a multi-namespace dictionary.\n */\nexport function projectDictionaryLocalesCore<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
|
+
{"version":3,"sources":["../src/deep-freeze.ts","../src/resolve-locale.ts","../src/format-core.ts","../src/project-locales.ts","../src/IcuTranslationProviderSingle.ts","../src/IcuTranslationProviderMulti.ts"],"names":[],"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 // Guards against infinite recursion on cyclic structures.\n if (Object.isFrozen(value)) {\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 { formatLocaleFallbackChain, resolveLocaleTemplate } from \"./resolve-locale.js\";\nimport type { LocaleCache, LocaleFallbackMap, OnMissingTranslation } from \"./types.js\";\n\nexport type FormatCoreContext = {\n key: string;\n namespace?: string | undefined;\n locale: string;\n localeFallback?: LocaleFallbackMap | undefined;\n onMissing: OnMissingTranslation;\n};\n\nexport type ResolveAndFormatOptions = {\n localeByKey: Record<string, string | undefined> | undefined;\n locale: string;\n params?: Record<string, unknown> | undefined;\n getCache: (resolvedLocale: string) => LocaleCache;\n context: FormatCoreContext;\n};\n\nfunction formatErrorLabel(context: Pick<FormatCoreContext, \"key\" | \"namespace\">): string {\n if (context.namespace !== undefined) {\n return `namespace \"${context.namespace}\", key \"${context.key}\"`;\n }\n return `key \"${context.key}\"`;\n}\n\nfunction formatMissingError(context: FormatCoreContext): string {\n const chain = formatLocaleFallbackChain(context.locale, context.localeFallback);\n if (context.namespace !== undefined) {\n return `[i18n] Missing key or locale: namespace \"${context.namespace}\", key \"${context.key}\" [${context.locale}] (fallback chain: ${chain})`;\n }\n return `[i18n] Missing key or locale: \"${context.key}\" [${context.locale}] (fallback chain: ${chain})`;\n}\n\nfunction resolveMissing(context: FormatCoreContext): string {\n const chain = formatLocaleFallbackChain(context.locale, context.localeFallback);\n if (context.onMissing === \"key\") {\n if (context.namespace !== undefined) {\n return `${context.namespace}.${context.key}`;\n }\n return context.key;\n }\n if (typeof context.onMissing === \"function\") {\n return context.onMissing({\n ...(context.namespace !== undefined ? { namespace: context.namespace } : {}),\n key: context.key,\n locale: context.locale,\n fallbackChain: chain,\n });\n }\n throw new Error(formatMissingError(context));\n}\n\n/**\n * Shared runtime path for both ICU providers: resolve locale (with fallback) →\n * compile template (cached) → format with params; handles missing keys via `onMissing`.\n */\nexport function resolveAndFormat(options: ResolveAndFormatOptions): string {\n const { localeByKey, locale, params, getCache, context } = options;\n const resolved = resolveLocaleTemplate(localeByKey, locale, context.localeFallback);\n\n if (!resolved) {\n return resolveMissing(context);\n }\n\n const { template, resolvedLocale } = resolved;\n const cache = getCache(resolvedLocale);\n const cacheKey = context.key;\n const label = formatErrorLabel(context);\n\n if (!cache[cacheKey]) {\n try {\n cache[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] Dictionary error for ${label} [${resolvedLocale}]: ${message}`\n );\n }\n }\n\n try {\n return cache[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 ${label} [${resolvedLocale}]: ${message}`\n );\n }\n}\n","import { resolveLocaleTemplate } from \"./resolve-locale.js\";\nimport type { KeyDictionary, LocaleFallbackMap, MultiDictionary } from \"./types.js\";\n\nfunction classifyAreaLocales(\n areaLocales: readonly string[],\n localeFallback?: LocaleFallbackMap\n): { full: string[]; preserve: string[] } {\n const areaSet = new Set(areaLocales);\n const full: string[] = [];\n const preserve: string[] = [];\n\n for (const locale of areaLocales) {\n const fallback = localeFallback?.[locale];\n if (fallback !== null && fallback !== undefined && areaSet.has(fallback)) {\n preserve.push(locale);\n } else {\n full.push(locale);\n }\n }\n\n return { full, preserve };\n}\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 namespace 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 projectNamespaceLocalesCore<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 a single namespace for a custom delivery area.\n * Full locales resolve through `localeFallback` (e.g. external chains like `it → en-US`);\n * preserve locales copy only their direct canonical entry when present.\n */\nexport function projectNamespaceForDeliveryAreaCore<T extends KeyDictionary>(\n dictionary: T,\n areaLocales: readonly string[],\n localeFallback?: LocaleFallbackMap\n): T {\n const { full, preserve } = classifyAreaLocales(areaLocales, localeFallback);\n const fullProjection =\n full.length > 0 ? projectKeyDictionary(dictionary, full, localeFallback) : undefined;\n const result = {} as T;\n\n for (const [key, localesByKey] of Object.entries(dictionary)) {\n const projected: Record<string, string> = {};\n\n if (fullProjection !== undefined) {\n const fullEntry = fullProjection[key as keyof T];\n if (fullEntry !== undefined) {\n Object.assign(projected, fullEntry);\n }\n }\n\n for (const locale of preserve) {\n const template = localesByKey[locale];\n if (template !== undefined) {\n projected[locale] = 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 * Projects a multi-namespace dictionary for a custom delivery area.\n * Each namespace uses the same delivery-area rules as `projectNamespaceForDeliveryAreaCore`.\n */\nexport function projectDictionaryForDeliveryAreaCore<T extends MultiDictionary>(\n dictionary: T,\n areaLocales: 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] = projectNamespaceForDeliveryAreaCore(\n namespaceDictionary,\n areaLocales,\n localeFallback\n ) as T[typeof namespace];\n }\n\n return result;\n}\n\n/**\n * Projects every namespace in a multi-namespace dictionary.\n */\nexport function projectDictionaryLocalesCore<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\n/**\n * Merges locale entries per translation key without dropping keys or locales\n * already present in `existing`. Incoming locales overwrite the same locale on conflict.\n */\nexport function mergeNamespaceLocalesCore<T extends KeyDictionary>(existing: T, incoming: T): T {\n const merged = structuredClone(existing);\n\n for (const [key, incomingLocales] of Object.entries(incoming)) {\n if (incomingLocales === undefined || typeof incomingLocales !== \"object\") {\n continue;\n }\n\n merged[key as keyof T] = {\n ...(merged[key as keyof T] ?? {}),\n ...incomingLocales,\n } as T[keyof T];\n }\n\n return merged;\n}\n\n/**\n * Merges locale entries per key in every namespace present in `incoming`.\n * Namespaces and keys not in `incoming` are preserved from `existing`.\n */\nexport function mergeDictionaryLocalesCore<T extends MultiDictionary>(\n existing: T,\n incoming: Partial<T>\n): T {\n const merged = structuredClone(existing) as T;\n\n for (const namespace of Object.keys(incoming) as (keyof T & string)[]) {\n const incomingNamespace = incoming[namespace];\n if (incomingNamespace === undefined) {\n continue;\n }\n\n merged[namespace] = mergeNamespaceLocalesCore(\n (merged[namespace] ?? {}) as T[typeof namespace],\n incomingNamespace as T[typeof namespace]\n ) as T[typeof namespace];\n }\n\n return merged;\n}\n","import { cloneAndFreeze } from \"./deep-freeze.js\";\nimport { resolveAndFormat } from \"./format-core.js\";\nimport { mergeNamespaceLocalesCore } from \"./project-locales.js\";\nimport { validateLocaleFallback } from \"./resolve-locale.js\";\nimport type {\n IcuTranslationProviderOptions,\n KeyDictionary,\n LocaleFallbackMap,\n LocaleOfSingle,\n OnMissingTranslation,\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 mergeAll(values: Partial<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 const params = args[0] as Record<string, unknown> | undefined;\n return this.provider.getWithLocale(String(key), this.locale, params);\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 private readonly onMissing: OnMissingTranslation;\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 this.onMissing = options?.onMissing ?? \"throw\";\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 return this.getWithLocale(String(key), locale, params);\n }\n\n getWithLocale(key: string, locale: string, params?: Record<string, unknown>): string {\n return resolveAndFormat({\n localeByKey: this.dictionary[key],\n locale,\n params,\n getCache: (resolvedLocale) => {\n if (!this.compiledCache[resolvedLocale]) {\n this.compiledCache[resolvedLocale] = {};\n }\n return this.compiledCache[resolvedLocale]!;\n },\n context: {\n key,\n locale,\n localeFallback: this.localeFallback,\n onMissing: this.onMissing,\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 mergeAll(values: Partial<Schema>): void {\n this.dictionary = mergeNamespaceLocalesCore(this.dictionary, values as Schema);\n\n for (const [key, incomingLocales] of Object.entries(values)) {\n if (incomingLocales === undefined || typeof incomingLocales !== \"object\") {\n continue;\n }\n\n for (const locale of Object.keys(incomingLocales)) {\n delete this.compiledCache[locale]?.[key];\n }\n }\n }\n}\n","import { cloneAndFreeze } from \"./deep-freeze.js\";\nimport { resolveAndFormat } from \"./format-core.js\";\nimport { mergeNamespaceLocalesCore } from \"./project-locales.js\";\nimport { validateLocaleFallback } from \"./resolve-locale.js\";\nimport type {\n IcuTranslationProviderOptions,\n LocaleFallbackMap,\n LocaleOfMulti,\n MultiCompiledCache,\n MultiDictionary,\n OnMissingTranslation,\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 mergeAll(values: Partial<Schema>): void;\n setNamespace<NS extends keyof Schema>(namespace: NS, values: Schema[NS]): void;\n mergeNamespace<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 const params = args[0] as Record<string, unknown> | undefined;\n return this.provider.getWithLocale(String(namespace), String(key), this.locale, params);\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 readonly onMissing: OnMissingTranslation;\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 this.onMissing = options?.onMissing ?? \"throw\";\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 const params = args[0] as Record<string, unknown> | undefined;\n return this.getWithLocale(String(namespace), String(key), locale, params);\n }\n\n getWithLocale(\n namespace: string,\n key: string,\n locale: string,\n params?: Record<string, unknown>\n ): string {\n if (!this.loadedNamespaces.has(namespace)) {\n throw new Error(\n `[i18n] Namespace not loaded: \"${namespace}\". Register it with setNamespace() before calling .get().`\n );\n }\n\n const localeByKey = (\n this.dictionary[namespace] as Record<string, Record<string, string>> | undefined\n )?.[key];\n\n return resolveAndFormat({\n localeByKey,\n locale,\n params,\n getCache: (resolvedLocale) => {\n if (!this.compiledCache[resolvedLocale]) {\n this.compiledCache[resolvedLocale] = {};\n }\n if (!this.compiledCache[resolvedLocale]![namespace]) {\n this.compiledCache[resolvedLocale]![namespace] = {};\n }\n return this.compiledCache[resolvedLocale]![namespace]!;\n },\n context: {\n namespace,\n key,\n locale,\n localeFallback: this.localeFallback,\n onMissing: this.onMissing,\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 mergeAll(values: Partial<Schema>): void {\n for (const namespace of Object.keys(values) as (keyof Schema & string)[]) {\n const incoming = values[namespace];\n if (incoming !== undefined) {\n this.mergeNamespace(namespace, incoming as Schema[typeof namespace]);\n }\n }\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 mergeNamespace<NS extends keyof Schema>(namespace: NS, values: Schema[NS]): void {\n if (!this.loadedNamespaces.has(namespace as string)) {\n this.setNamespace(namespace, values);\n return;\n }\n\n const existing = this.dictionary[namespace];\n this.setNamespace(namespace, mergeNamespaceLocalesCore(existing as Schema[NS], values));\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -210,6 +210,31 @@ describe("IcuTranslationProviderMulti", () => {
|
|
|
210
210
|
expect(local.get("default", "login_button", "en")).toBe("Login");
|
|
211
211
|
});
|
|
212
212
|
|
|
213
|
+
it("merges locales per key with mergeNamespace without dropping existing locales", () => {
|
|
214
|
+
const local = new IcuTranslationProviderMulti<TestSchema, TestParams>({
|
|
215
|
+
billing: {
|
|
216
|
+
// @ts-expect-error missing locale
|
|
217
|
+
invoice_summary: {
|
|
218
|
+
en: "You have {count, plural, one {1 invoice} other {{count} invoices}} for {name}",
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
local.mergeNamespace("billing", {
|
|
224
|
+
// @ts-expect-error missing locale
|
|
225
|
+
invoice_summary: {
|
|
226
|
+
it: "Hai {count, plural, one {1 fattura} other {{count} fatture}} per {name}",
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
expect(local.get("billing", "invoice_summary", "en", { count: 2, name: "Bob" })).toBe(
|
|
231
|
+
"You have 2 invoices for Bob"
|
|
232
|
+
);
|
|
233
|
+
expect(local.get("billing", "invoice_summary", "it", { count: 2, name: "Bob" })).toBe(
|
|
234
|
+
"Hai 2 fatture per Bob"
|
|
235
|
+
);
|
|
236
|
+
});
|
|
237
|
+
|
|
213
238
|
it("replaces the full dictionary on setAll", () => {
|
|
214
239
|
const local = new IcuTranslationProviderMulti<TestSchema, TestParams>(dictionary);
|
|
215
240
|
|
|
@@ -224,6 +249,35 @@ describe("IcuTranslationProviderMulti", () => {
|
|
|
224
249
|
expect(local.get("default", "login_button", "en")).toBe("Sign in");
|
|
225
250
|
});
|
|
226
251
|
|
|
252
|
+
it("merges namespaces with mergeAll without dropping existing locales", () => {
|
|
253
|
+
const local = new IcuTranslationProviderMulti<TestSchema, TestParams>({
|
|
254
|
+
billing: {
|
|
255
|
+
// @ts-expect-error missing locale
|
|
256
|
+
invoice_summary: {
|
|
257
|
+
en: "You have {count, plural, one {1 invoice} other {{count} invoices}} for {name}",
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
local.mergeAll({
|
|
263
|
+
billing: {
|
|
264
|
+
// @ts-expect-error missing locale
|
|
265
|
+
invoice_summary: {
|
|
266
|
+
it: "Hai {count, plural, one {1 fattura} other {{count} fatture}} per {name}",
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
default: dictionary.default,
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
expect(local.get("billing", "invoice_summary", "en", { count: 2, name: "Bob" })).toBe(
|
|
273
|
+
"You have 2 invoices for Bob"
|
|
274
|
+
);
|
|
275
|
+
expect(local.get("billing", "invoice_summary", "it", { count: 2, name: "Bob" })).toBe(
|
|
276
|
+
"Hai 2 fatture per Bob"
|
|
277
|
+
);
|
|
278
|
+
expect(local.get("default", "login_button", "en")).toBe("Login");
|
|
279
|
+
});
|
|
280
|
+
|
|
227
281
|
it("returns a deep-frozen snapshot from getAll", () => {
|
|
228
282
|
expect(provider.getAll()).toEqual(dictionary);
|
|
229
283
|
expect(provider.getAll()).not.toBe(dictionary);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { cloneAndFreeze } from "./deep-freeze.js";
|
|
2
2
|
import { resolveAndFormat } from "./format-core.js";
|
|
3
|
+
import { mergeNamespaceLocalesCore } from "./project-locales.js";
|
|
3
4
|
import { validateLocaleFallback } from "./resolve-locale.js";
|
|
4
5
|
import type {
|
|
5
6
|
IcuTranslationProviderOptions,
|
|
@@ -40,7 +41,9 @@ export interface TranslationProviderMulti<
|
|
|
40
41
|
getAll(): Schema;
|
|
41
42
|
hasNamespace<NS extends keyof Schema & string>(namespace: NS): boolean;
|
|
42
43
|
setAll(values: Schema): void;
|
|
44
|
+
mergeAll(values: Partial<Schema>): void;
|
|
43
45
|
setNamespace<NS extends keyof Schema>(namespace: NS, values: Schema[NS]): void;
|
|
46
|
+
mergeNamespace<NS extends keyof Schema>(namespace: NS, values: Schema[NS]): void;
|
|
44
47
|
}
|
|
45
48
|
|
|
46
49
|
export class IcuTranslationProviderMultiForLocale<
|
|
@@ -161,6 +164,15 @@ export class IcuTranslationProviderMulti<
|
|
|
161
164
|
this.loadedNamespaces = new Set(Object.keys(values));
|
|
162
165
|
}
|
|
163
166
|
|
|
167
|
+
mergeAll(values: Partial<Schema>): void {
|
|
168
|
+
for (const namespace of Object.keys(values) as (keyof Schema & string)[]) {
|
|
169
|
+
const incoming = values[namespace];
|
|
170
|
+
if (incoming !== undefined) {
|
|
171
|
+
this.mergeNamespace(namespace, incoming as Schema[typeof namespace]);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
164
176
|
setNamespace<NS extends keyof Schema>(namespace: NS, values: Schema[NS]): void {
|
|
165
177
|
this.dictionary = {
|
|
166
178
|
...this.dictionary,
|
|
@@ -172,4 +184,14 @@ export class IcuTranslationProviderMulti<
|
|
|
172
184
|
delete this.compiledCache[locale]?.[namespace as string];
|
|
173
185
|
}
|
|
174
186
|
}
|
|
187
|
+
|
|
188
|
+
mergeNamespace<NS extends keyof Schema>(namespace: NS, values: Schema[NS]): void {
|
|
189
|
+
if (!this.loadedNamespaces.has(namespace as string)) {
|
|
190
|
+
this.setNamespace(namespace, values);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const existing = this.dictionary[namespace];
|
|
195
|
+
this.setNamespace(namespace, mergeNamespaceLocalesCore(existing as Schema[NS], values));
|
|
196
|
+
}
|
|
175
197
|
}
|
|
@@ -235,6 +235,27 @@ describe("IcuTranslationProviderSingle", () => {
|
|
|
235
235
|
expect(local.get("login_button", "en")).toBe("Sign in");
|
|
236
236
|
});
|
|
237
237
|
|
|
238
|
+
it("merges locales per key with mergeAll without dropping existing locales", () => {
|
|
239
|
+
const local = new IcuTranslationProviderSingle<TestSchema, TestParams>({
|
|
240
|
+
...dictionary,
|
|
241
|
+
// @ts-expect-error missing locale
|
|
242
|
+
welcome: {
|
|
243
|
+
en: "Welcome {name}!",
|
|
244
|
+
},
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
local.mergeAll({
|
|
248
|
+
// @ts-expect-error missing locale
|
|
249
|
+
welcome: {
|
|
250
|
+
it: "Benvenuto {name}!",
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
expect(local.get("welcome", "en", { name: "Ada" })).toBe("Welcome Ada!");
|
|
255
|
+
expect(local.get("welcome", "it", { name: "Ada" })).toBe("Benvenuto Ada!");
|
|
256
|
+
expect(local.get("login_button", "en")).toBe("Login");
|
|
257
|
+
});
|
|
258
|
+
|
|
238
259
|
it("returns a deep-frozen snapshot from getAll", () => {
|
|
239
260
|
expect(provider.getAll()).toEqual(dictionary);
|
|
240
261
|
expect(provider.getAll()).not.toBe(dictionary);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { cloneAndFreeze } from "./deep-freeze.js";
|
|
2
2
|
import { resolveAndFormat } from "./format-core.js";
|
|
3
|
+
import { mergeNamespaceLocalesCore } from "./project-locales.js";
|
|
3
4
|
import { validateLocaleFallback } from "./resolve-locale.js";
|
|
4
5
|
import type {
|
|
5
6
|
IcuTranslationProviderOptions,
|
|
@@ -37,6 +38,7 @@ export interface TranslationProviderSingle<
|
|
|
37
38
|
): TranslationProviderSingleForLocale<Schema, Params, Locale>;
|
|
38
39
|
getAll(): Schema;
|
|
39
40
|
setAll(values: Schema): void;
|
|
41
|
+
mergeAll(values: Partial<Schema>): void;
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
export class IcuTranslationProviderSingleForLocale<
|
|
@@ -128,4 +130,18 @@ export class IcuTranslationProviderSingle<
|
|
|
128
130
|
this.dictionary = structuredClone(values);
|
|
129
131
|
this.compiledCache = {};
|
|
130
132
|
}
|
|
133
|
+
|
|
134
|
+
mergeAll(values: Partial<Schema>): void {
|
|
135
|
+
this.dictionary = mergeNamespaceLocalesCore(this.dictionary, values as Schema);
|
|
136
|
+
|
|
137
|
+
for (const [key, incomingLocales] of Object.entries(values)) {
|
|
138
|
+
if (incomingLocales === undefined || typeof incomingLocales !== "object") {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
for (const locale of Object.keys(incomingLocales)) {
|
|
143
|
+
delete this.compiledCache[locale]?.[key];
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
131
147
|
}
|
|
@@ -100,6 +100,7 @@ describe("formatNamespaceLoadersFile", () => {
|
|
|
100
100
|
expect(output).not.toContain("billing: {");
|
|
101
101
|
expect(output).not.toContain("import('./translations/billing.json')");
|
|
102
102
|
expect(output).toContain("export async function ensureNamespacesLoadedForLocale(");
|
|
103
|
+
expect(output).toContain("i18n.mergeNamespace(namespace,");
|
|
103
104
|
expect(output).toContain("i18n: I18nMultiInstance,");
|
|
104
105
|
expect(output).toContain(
|
|
105
106
|
'namespaces: readonly LazyNamespace[] = ["billing", "default", "user"] as const,'
|
|
@@ -107,6 +108,43 @@ describe("formatNamespaceLoadersFile", () => {
|
|
|
107
108
|
expect(output.match(/from '\.\/i18n-types\.generated'/g)?.length).toBe(1);
|
|
108
109
|
});
|
|
109
110
|
|
|
111
|
+
it("emits mergeAll helper for single mode in split delivery", () => {
|
|
112
|
+
const output = formatNamespaceLoadersFile({
|
|
113
|
+
loadersOutputPath,
|
|
114
|
+
lazyEntries: [
|
|
115
|
+
{
|
|
116
|
+
namespace: "default",
|
|
117
|
+
filePath: "src/i18n/translations/translations.json",
|
|
118
|
+
absolutePath: path.join(projectRoot, "src/i18n/translations/translations.json"),
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
schemaTypeName: "AppSchema",
|
|
122
|
+
paramsTypeName: "AppParams",
|
|
123
|
+
localeTypeName: "AppLocale",
|
|
124
|
+
localeFallbackConstName: "LOCALE_FALLBACK",
|
|
125
|
+
hasLocaleFallback: true,
|
|
126
|
+
typesModule: "i18n-types.generated",
|
|
127
|
+
importExtension: "none",
|
|
128
|
+
projectRoot,
|
|
129
|
+
isSingle: true,
|
|
130
|
+
delivery: "split-by-locale",
|
|
131
|
+
requestLocales: ["en", "it"],
|
|
132
|
+
splitPathsByNamespace: {
|
|
133
|
+
default: {
|
|
134
|
+
en: "src/i18n/generated/translations/translations.en.json",
|
|
135
|
+
it: "src/i18n/generated/translations/translations.it.json",
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
expect(output).toContain("[K in LazyNamespace]: (locale: AppLocale) => Promise<AppSchema>;");
|
|
141
|
+
expect(output).toContain("import type { TranslationProviderSingle } from '@xndrjs/i18n';");
|
|
142
|
+
expect(output).toContain("type I18nSingleInstance = TranslationProviderSingle<");
|
|
143
|
+
expect(output).toContain("export async function ensureNamespacesLoadedForLocale(");
|
|
144
|
+
expect(output).toContain("i18n.mergeAll(await namespaceLoaders[namespace](locale));");
|
|
145
|
+
expect(output).not.toContain("mergeNamespace");
|
|
146
|
+
});
|
|
147
|
+
|
|
110
148
|
it("throws when a split path is missing", () => {
|
|
111
149
|
expect(() =>
|
|
112
150
|
formatNamespaceLoadersFile({
|
|
@@ -190,6 +228,7 @@ describe("formatNamespaceLoadersFile", () => {
|
|
|
190
228
|
);
|
|
191
229
|
expect(output).not.toContain("import('./translations/billing.json')");
|
|
192
230
|
expect(output).toContain("export async function ensureNamespacesLoadedForArea(");
|
|
231
|
+
expect(output).toContain("i18n.mergeNamespace(namespace,");
|
|
193
232
|
expect(output).toContain(
|
|
194
233
|
'namespaces: readonly LazyNamespace[] = ["billing", "default"] as const,'
|
|
195
234
|
);
|
|
@@ -34,76 +34,73 @@ interface PartitionedNamespaceLoadersParams {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
function formatI18nMultiInstanceType(options: NamespaceLoadersFileOptions): string {
|
|
37
|
-
const {
|
|
38
|
-
schemaTypeName,
|
|
39
|
-
paramsTypeName,
|
|
40
|
-
localeTypeName,
|
|
41
|
-
localeFallbackConstName,
|
|
42
|
-
hasLocaleFallback,
|
|
43
|
-
} = options;
|
|
37
|
+
const { schemaTypeName, paramsTypeName, localeTypeName } = options;
|
|
44
38
|
|
|
45
|
-
|
|
46
|
-
? `${schemaTypeName}, ${paramsTypeName}, ${localeTypeName}, typeof ${localeFallbackConstName}`
|
|
47
|
-
: `${schemaTypeName}, ${paramsTypeName}`;
|
|
48
|
-
|
|
49
|
-
return `IcuTranslationProviderMulti<${typeArgs}>`;
|
|
39
|
+
return `TranslationProviderMulti<${schemaTypeName}, ${paramsTypeName}, ${localeTypeName}>`;
|
|
50
40
|
}
|
|
51
41
|
|
|
52
|
-
function
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
.map((namespace) => JSON.stringify(namespace))
|
|
57
|
-
.join(", ");
|
|
42
|
+
function formatI18nSingleInstanceType(options: NamespaceLoadersFileOptions): string {
|
|
43
|
+
const { schemaTypeName, paramsTypeName, localeTypeName } = options;
|
|
44
|
+
|
|
45
|
+
return `TranslationProviderSingle<${schemaTypeName}, ${paramsTypeName}, ${localeTypeName}>`;
|
|
58
46
|
}
|
|
59
47
|
|
|
60
48
|
function formatLoadNamespacesHelper(
|
|
61
49
|
options: NamespaceLoadersFileOptions,
|
|
62
50
|
{ paramName, paramTypeName, loadNamespacesFunctionName }: PartitionedNamespaceLoadersParams
|
|
63
51
|
): string {
|
|
64
|
-
const { lazyEntries } = options;
|
|
52
|
+
const { lazyEntries, isSingle } = options;
|
|
65
53
|
const defaultNamespaces = formatDefaultNamespacesLiteral(lazyEntries);
|
|
54
|
+
const i18nInstanceType = isSingle
|
|
55
|
+
? formatI18nSingleInstanceType(options)
|
|
56
|
+
: formatI18nMultiInstanceType(options);
|
|
57
|
+
const mergeCall = isSingle
|
|
58
|
+
? `i18n.mergeAll(await namespaceLoaders[namespace](${paramName}))`
|
|
59
|
+
: `i18n.mergeNamespace(namespace, await namespaceLoaders[namespace](${paramName}))`;
|
|
60
|
+
const i18nTypeAlias = isSingle ? "I18nSingleInstance" : "I18nMultiInstance";
|
|
66
61
|
|
|
67
62
|
return (
|
|
68
|
-
`\ntype
|
|
63
|
+
`\ntype ${i18nTypeAlias} = ${i18nInstanceType};\n\n` +
|
|
69
64
|
`export async function ${loadNamespacesFunctionName}(\n` +
|
|
70
|
-
` i18n:
|
|
65
|
+
` i18n: ${i18nTypeAlias},\n` +
|
|
71
66
|
` ${paramName}: ${paramTypeName},\n` +
|
|
72
67
|
` namespaces: readonly LazyNamespace[] = [${defaultNamespaces}] as const,\n` +
|
|
73
68
|
`): Promise<void> {\n` +
|
|
74
69
|
` await Promise.all(\n` +
|
|
75
70
|
` namespaces.map(async (namespace) => {\n` +
|
|
76
|
-
`
|
|
71
|
+
` ${mergeCall};\n` +
|
|
77
72
|
` }),\n` +
|
|
78
73
|
` );\n` +
|
|
79
74
|
`}\n`
|
|
80
75
|
);
|
|
81
76
|
}
|
|
82
77
|
|
|
78
|
+
function formatDefaultNamespacesLiteral(lazyEntries: NamespaceEntry[]): string {
|
|
79
|
+
return [...lazyEntries]
|
|
80
|
+
.map((entry) => entry.namespace)
|
|
81
|
+
.sort()
|
|
82
|
+
.map((namespace) => JSON.stringify(namespace))
|
|
83
|
+
.join(", ");
|
|
84
|
+
}
|
|
85
|
+
|
|
83
86
|
function formatPartitionedTypesImport(
|
|
84
87
|
options: NamespaceLoadersFileOptions,
|
|
85
88
|
paramTypeName: string
|
|
86
89
|
): string {
|
|
87
|
-
const {
|
|
88
|
-
|
|
89
|
-
paramsTypeName,
|
|
90
|
-
localeTypeName,
|
|
91
|
-
localeFallbackConstName,
|
|
92
|
-
hasLocaleFallback,
|
|
93
|
-
typesModule,
|
|
94
|
-
importExtension,
|
|
95
|
-
isSingle,
|
|
96
|
-
} = options;
|
|
90
|
+
const { schemaTypeName, paramsTypeName, localeTypeName, typesModule, importExtension, isSingle } =
|
|
91
|
+
options;
|
|
97
92
|
|
|
98
93
|
if (isSingle) {
|
|
99
|
-
return
|
|
94
|
+
return (
|
|
95
|
+
`import type { TranslationProviderSingle } from '@xndrjs/i18n';\n` +
|
|
96
|
+
`import type { ${schemaTypeName}, LazyNamespace, ${paramTypeName}, ${localeTypeName} } from '${toRelativeModuleImport(typesModule, importExtension)}';\n\n`
|
|
97
|
+
);
|
|
100
98
|
}
|
|
101
99
|
|
|
102
|
-
const
|
|
103
|
-
const paramTypeImport = paramTypeName === localeTypeName ? "" : `, type ${paramTypeName}`;
|
|
100
|
+
const paramTypeImport = paramTypeName === localeTypeName ? "" : `, ${paramTypeName}`;
|
|
104
101
|
return (
|
|
105
|
-
`import {
|
|
106
|
-
`import
|
|
102
|
+
`import type { TranslationProviderMulti } from '@xndrjs/i18n';\n` +
|
|
103
|
+
`import type { ${localeTypeName}, ${paramsTypeName}, ${schemaTypeName}, LazyNamespace${paramTypeImport} } from '${toRelativeModuleImport(typesModule, importExtension)}';\n\n`
|
|
107
104
|
);
|
|
108
105
|
}
|
|
109
106
|
|
|
@@ -129,8 +126,11 @@ function formatPartitionedNamespaceLoadersFile(
|
|
|
129
126
|
schemaTypeName,
|
|
130
127
|
projectRoot,
|
|
131
128
|
splitPathsByNamespace = {},
|
|
129
|
+
isSingle = false,
|
|
132
130
|
} = options;
|
|
133
131
|
|
|
132
|
+
const loaderValueType = isSingle ? schemaTypeName : `${schemaTypeName}[K]`;
|
|
133
|
+
|
|
134
134
|
const typesImport = formatPartitionedTypesImport(options, paramTypeName);
|
|
135
135
|
const loaderEntries = lazyEntries
|
|
136
136
|
.map((entry) => {
|
|
@@ -167,9 +167,9 @@ function formatPartitionedNamespaceLoadersFile(
|
|
|
167
167
|
`${GENERATED_FILE_BANNER}` +
|
|
168
168
|
typesImport +
|
|
169
169
|
`export const namespaceLoaders: {\n` +
|
|
170
|
-
` [K in LazyNamespace]: (${paramName}: ${paramTypeName}) => Promise<${
|
|
170
|
+
` [K in LazyNamespace]: (${paramName}: ${paramTypeName}) => Promise<${loaderValueType}>;\n` +
|
|
171
171
|
`} = {\n${loaderEntries}\n};\n` +
|
|
172
|
-
|
|
172
|
+
formatLoadNamespacesHelper(options, params)
|
|
173
173
|
);
|
|
174
174
|
}
|
|
175
175
|
|
package/src/index.ts
CHANGED
|
@@ -30,6 +30,8 @@ export {
|
|
|
30
30
|
export type { ResolvedLocaleTemplate } from "./resolve-locale.js";
|
|
31
31
|
|
|
32
32
|
export {
|
|
33
|
+
mergeDictionaryLocalesCore,
|
|
34
|
+
mergeNamespaceLocalesCore,
|
|
33
35
|
projectDictionaryForDeliveryAreaCore,
|
|
34
36
|
projectDictionaryLocalesCore,
|
|
35
37
|
projectNamespaceForDeliveryAreaCore,
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
2
|
import {
|
|
3
|
+
mergeDictionaryLocalesCore,
|
|
4
|
+
mergeNamespaceLocalesCore,
|
|
3
5
|
projectDictionaryForDeliveryAreaCore,
|
|
4
6
|
projectDictionaryLocalesCore,
|
|
5
7
|
projectNamespaceForDeliveryAreaCore,
|
|
@@ -202,3 +204,67 @@ describe("projectDictionaryLocalesCore", () => {
|
|
|
202
204
|
});
|
|
203
205
|
});
|
|
204
206
|
});
|
|
207
|
+
|
|
208
|
+
describe("mergeNamespaceLocalesCore", () => {
|
|
209
|
+
it("merges locale maps per key without dropping existing locales", () => {
|
|
210
|
+
const existing = {
|
|
211
|
+
welcome: { en: "Welcome {name}!" },
|
|
212
|
+
login_button: { en: "Login", it: "Accedi" },
|
|
213
|
+
};
|
|
214
|
+
const incoming = {
|
|
215
|
+
welcome: { it: "Benvenuto {name}!" },
|
|
216
|
+
invoice_summary: { en: "{count} invoices" },
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
// @ts-expect-error missing key in incoming
|
|
220
|
+
expect(mergeNamespaceLocalesCore(existing, incoming)).toEqual({
|
|
221
|
+
welcome: { en: "Welcome {name}!", it: "Benvenuto {name}!" },
|
|
222
|
+
login_button: { en: "Login", it: "Accedi" },
|
|
223
|
+
invoice_summary: { en: "{count} invoices" },
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it("overwrites the same locale when incoming provides a new template", () => {
|
|
228
|
+
const existing = {
|
|
229
|
+
welcome: { en: "Welcome {name}!" },
|
|
230
|
+
};
|
|
231
|
+
const incoming = {
|
|
232
|
+
welcome: { en: "Hello {name}!" },
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
expect(mergeNamespaceLocalesCore(existing, incoming)).toEqual({
|
|
236
|
+
welcome: { en: "Hello {name}!" },
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
describe("mergeDictionaryLocalesCore", () => {
|
|
242
|
+
it("merges each namespace without dropping existing locales", () => {
|
|
243
|
+
const existing = {
|
|
244
|
+
default: {
|
|
245
|
+
welcome: { en: "Welcome {name}!" },
|
|
246
|
+
},
|
|
247
|
+
billing: {
|
|
248
|
+
invoice_summary: { en: "{count} invoices" },
|
|
249
|
+
},
|
|
250
|
+
};
|
|
251
|
+
const incoming = {
|
|
252
|
+
default: {
|
|
253
|
+
welcome: { it: "Benvenuto {name}!" },
|
|
254
|
+
},
|
|
255
|
+
billing: {
|
|
256
|
+
invoice_summary: { it: "{count} fatture" },
|
|
257
|
+
},
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
// @ts-expect-error missing locale
|
|
261
|
+
expect(mergeDictionaryLocalesCore(existing, incoming)).toEqual({
|
|
262
|
+
default: {
|
|
263
|
+
welcome: { en: "Welcome {name}!", it: "Benvenuto {name}!" },
|
|
264
|
+
},
|
|
265
|
+
billing: {
|
|
266
|
+
invoice_summary: { en: "{count} invoices", it: "{count} fatture" },
|
|
267
|
+
},
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
});
|
package/src/project-locales.ts
CHANGED
|
@@ -152,3 +152,49 @@ export function projectDictionaryLocalesCore<T extends MultiDictionary>(
|
|
|
152
152
|
|
|
153
153
|
return result;
|
|
154
154
|
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Merges locale entries per translation key without dropping keys or locales
|
|
158
|
+
* already present in `existing`. Incoming locales overwrite the same locale on conflict.
|
|
159
|
+
*/
|
|
160
|
+
export function mergeNamespaceLocalesCore<T extends KeyDictionary>(existing: T, incoming: T): T {
|
|
161
|
+
const merged = structuredClone(existing);
|
|
162
|
+
|
|
163
|
+
for (const [key, incomingLocales] of Object.entries(incoming)) {
|
|
164
|
+
if (incomingLocales === undefined || typeof incomingLocales !== "object") {
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
merged[key as keyof T] = {
|
|
169
|
+
...(merged[key as keyof T] ?? {}),
|
|
170
|
+
...incomingLocales,
|
|
171
|
+
} as T[keyof T];
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return merged;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Merges locale entries per key in every namespace present in `incoming`.
|
|
179
|
+
* Namespaces and keys not in `incoming` are preserved from `existing`.
|
|
180
|
+
*/
|
|
181
|
+
export function mergeDictionaryLocalesCore<T extends MultiDictionary>(
|
|
182
|
+
existing: T,
|
|
183
|
+
incoming: Partial<T>
|
|
184
|
+
): T {
|
|
185
|
+
const merged = structuredClone(existing) as T;
|
|
186
|
+
|
|
187
|
+
for (const namespace of Object.keys(incoming) as (keyof T & string)[]) {
|
|
188
|
+
const incomingNamespace = incoming[namespace];
|
|
189
|
+
if (incomingNamespace === undefined) {
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
merged[namespace] = mergeNamespaceLocalesCore(
|
|
194
|
+
(merged[namespace] ?? {}) as T[typeof namespace],
|
|
195
|
+
incomingNamespace as T[typeof namespace]
|
|
196
|
+
) as T[typeof namespace];
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return merged;
|
|
200
|
+
}
|