@xndrjs/i18n 0.6.0-alpha.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +107 -7
- package/package.json +1 -1
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
|
};
|
|
@@ -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 always call `setNamespace()` (safe when switching locale or area on a shared `i18n` instance). |
|
|
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,49 @@ 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 **always** reload the requested namespaces — do not guard with `hasNamespace` when switching locale or delivery area on the same instance:
|
|
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
|
|
729
|
+
await ensureNamespacesLoadedForLocale(i18n, "it");
|
|
730
|
+
i18n.get("billing", "invoice_summary", "it", { count: 3 });
|
|
731
|
+
|
|
732
|
+
await ensureNamespacesLoadedForLocale(i18n, "en"); // reloads billing for en
|
|
733
|
+
i18n.get("billing", "invoice_summary", "en", { count: 3 });
|
|
734
|
+
|
|
735
|
+
// custom delivery
|
|
736
|
+
await ensureNamespacesLoadedForArea(i18n, "eu", ["billing"]);
|
|
737
|
+
```
|
|
738
|
+
|
|
739
|
+
Route-scoped subset:
|
|
740
|
+
|
|
741
|
+
```ts
|
|
742
|
+
await ensureNamespacesLoadedForLocale(i18n, activeLocale, ["billing"]);
|
|
743
|
+
```
|
|
744
|
+
|
|
745
|
+
Manual loader calls (without the helper) follow the same rule — call `setNamespace` again when the locale or area changes:
|
|
649
746
|
|
|
650
747
|
```ts
|
|
651
748
|
const locale = "it" as const;
|
|
652
749
|
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
750
|
+
i18n.setNamespace("billing", await namespaceLoaders.billing(locale));
|
|
751
|
+
|
|
752
|
+
// after locale change on the same instance:
|
|
753
|
+
i18n.setNamespace("billing", await namespaceLoaders.billing("en"));
|
|
656
754
|
```
|
|
657
755
|
|
|
756
|
+
Generated loaders throw if locale/area does not match a known artifact (no silent `undefined` into `setNamespace`).
|
|
757
|
+
|
|
658
758
|
Optional locale projection before register:
|
|
659
759
|
|
|
660
760
|
```ts
|
package/package.json
CHANGED