@xndrjs/i18n 0.5.0 → 0.6.0-alpha.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 +67 -35
- package/dist/codegen/index.d.ts +5 -3
- package/dist/codegen/index.js +15 -2
- package/dist/codegen/index.js.map +1 -1
- package/package.json +1 -1
- package/src/codegen/codegen-config-schema.ts +27 -1
- package/src/codegen/config.test.ts +79 -1
- package/src/codegen/config.ts +10 -1
- package/src/codegen/delivery-artifacts.test.ts +24 -0
- package/src/codegen/delivery-artifacts.ts +29 -0
- package/src/codegen/emit/dictionary-file.test.ts +32 -0
- package/src/codegen/emit/dictionary-file.ts +6 -2
- package/src/codegen/emit/dictionary-schema-file.ts +2 -2
- package/src/codegen/emit/namespace-loaders-file.test.ts +53 -1
- package/src/codegen/emit/namespace-loaders-file.ts +95 -7
- package/src/codegen/emit/types-file.test.ts +5 -0
- package/src/codegen/emit/types-file.ts +19 -6
- package/src/codegen/generate-i18n-types.test.ts +82 -56
- package/src/codegen/generate-i18n-types.ts +15 -4
- package/src/codegen-config/index.ts +1 -0
package/README.md
CHANGED
|
@@ -196,7 +196,7 @@ Variables found across **all locales** of the same key are merged. If parsing fa
|
|
|
196
196
|
### 3. Generated files
|
|
197
197
|
|
|
198
198
|
- **`i18n-types.generated.ts`** — `I18N_MODE`, `MyProjectParams`, `MyProjectSchema`.
|
|
199
|
-
- **`dictionary.generated.ts`** — imports the JSON files and exports `defaultDictionary` (canonical delivery) or `defaultDictionaryFor(locale)` (split
|
|
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.
|
|
202
202
|
|
|
@@ -407,25 +407,25 @@ Specify **exactly one** of `dictionary` (single-file) or `namespaces` (multi-fil
|
|
|
407
407
|
}
|
|
408
408
|
```
|
|
409
409
|
|
|
410
|
-
| Field | Description
|
|
411
|
-
| ----------------------------------- |
|
|
412
|
-
| `dictionary` | Path to a single dictionary file (`.json`, `.yaml`, or `.yml`) for the flat API. Mutually exclusive with `namespaces`.
|
|
413
|
-
| `namespaces` | Map of `namespace -> dictionary path` (`.json`, `.yaml`, or `.yml`) for the namespaced API. Mutually exclusive with `dictionary`.
|
|
414
|
-
| `defaultNamespace` | Optional. Namespace label used internally in single-file mode (default `"default"`). Not exposed in the flat API.
|
|
415
|
-
| `typesOutput` | Output path for the generated types.
|
|
416
|
-
| `dictionaryOutput` | Output path for the
|
|
417
|
-
| `instanceOutput` | Output path for the generated factory (`createI18n`).
|
|
418
|
-
| `importExtension` | Optional. Relative import suffix between generated `.ts` modules: `"none"` (default, extensionless), `".ts"`, or `".js"`.
|
|
419
|
-
| `factoryName` | Name of the exported factory function (default `createI18n`).
|
|
420
|
-
| `paramsTypeName` / `schemaTypeName` | Names of the exported types (customizable per project).
|
|
421
|
-
| `localeTypeName` | Name of the exported locale union type (default `MyProjectLocale`).
|
|
422
|
-
| `localeFallback` | Optional map of `locale -> next locale
|
|
423
|
-
| `localeFallbackConstName` | Name of the generated fallback constant (default `LOCALE_FALLBACK`).
|
|
424
|
-
| `dictionarySchemaOutput` | Optional path for generated external dictionary validation (`dictionary-schema.generated.ts`). Requires `zod` in the consumer app.
|
|
425
|
-
| `loadOnInit` | Multi mode only
|
|
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.
|
|
428
|
-
| `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/`.
|
|
410
|
+
| Field | Description |
|
|
411
|
+
| ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- |
|
|
412
|
+
| `dictionary` | Path to a single dictionary file (`.json`, `.yaml`, or `.yml`) for the flat API. Mutually exclusive with `namespaces`. |
|
|
413
|
+
| `namespaces` | Map of `namespace -> dictionary path` (`.json`, `.yaml`, or `.yml`) for the namespaced API. Mutually exclusive with `dictionary`. |
|
|
414
|
+
| `defaultNamespace` | Optional. Namespace label used internally in single-file mode (default `"default"`). Not exposed in the flat API. |
|
|
415
|
+
| `typesOutput` | Output path for the generated types. |
|
|
416
|
+
| `dictionaryOutput` | Optional. Output path for `dictionary.generated.ts` when codegen emits it (canonical delivery, or split/custom with eager namespaces). Omit in split/custom when every namespace is lazy — codegen does not write the file; if a stale manifest exists at the default `{dirname(typesOutput)}/dictionary.generated.ts`, it is removed. Explicit `dictionaryOutput` still controls which path is cleaned up. |
|
|
417
|
+
| `instanceOutput` | Output path for the generated factory (`createI18n`). |
|
|
418
|
+
| `importExtension` | Optional. Relative import suffix between generated `.ts` modules: `"none"` (default, extensionless), `".ts"`, or `".js"`. |
|
|
419
|
+
| `factoryName` | Name of the exported factory function (default `createI18n`). |
|
|
420
|
+
| `paramsTypeName` / `schemaTypeName` | Names of the exported types (customizable per project). |
|
|
421
|
+
| `localeTypeName` | Name of the exported locale union type (default `MyProjectLocale`). |
|
|
422
|
+
| `localeFallback` | Optional map of `locale -> next locale | null` for runtime fallback resolution. |
|
|
423
|
+
| `localeFallbackConstName` | Name of the generated fallback constant (default `LOCALE_FALLBACK`). |
|
|
424
|
+
| `dictionarySchemaOutput` | Optional path for generated external dictionary validation (`dictionary-schema.generated.ts`). Requires `zod` in the consumer app. |
|
|
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
|
+
| `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. |
|
|
428
|
+
| `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
429
|
|
|
430
430
|
> Paths are resolved relative to the directory containing `i18n.codegen.json` (e.g. `i18n/` when using `xndrjs-i18n-setup .`).
|
|
431
431
|
|
|
@@ -458,7 +458,7 @@ Mixed namespaces are supported: some namespaces can stay `.json` while others us
|
|
|
458
458
|
|
|
459
459
|
By default (`delivery: "canonical"`), codegen keeps one multilocale JSON per namespace — either your source file in place (`.json`) or a compiled YAML → JSON under `{deliveryOutput}/translations/` (default: `generated/translations/`).
|
|
460
460
|
|
|
461
|
-
Set `"delivery": "split-by-locale"` to emit **one JSON file per locale** instead:
|
|
461
|
+
Set `"delivery": "split-by-locale"` to emit **one JSON file per locale** instead. All namespaces are lazy in this mode — register them with `namespaceLoaders` before rendering:
|
|
462
462
|
|
|
463
463
|
```json
|
|
464
464
|
{
|
|
@@ -467,7 +467,7 @@ Set `"delivery": "split-by-locale"` to emit **one JSON file per locale** instead
|
|
|
467
467
|
"default": "translations/default.json",
|
|
468
468
|
"billing": "translations/billing.yaml"
|
|
469
469
|
},
|
|
470
|
-
"
|
|
470
|
+
"namespaceLoadersOutput": "generated/namespace-loaders.generated.ts"
|
|
471
471
|
}
|
|
472
472
|
```
|
|
473
473
|
|
|
@@ -477,12 +477,35 @@ Codegen writes `{deliveryOutput}/translations/{basename}.{locale}.json` (for exa
|
|
|
477
477
|
|
|
478
478
|
**Generated API changes (opt-in):**
|
|
479
479
|
|
|
480
|
-
| Canonical | Split-by-locale
|
|
481
|
-
| -------------------------------- |
|
|
482
|
-
| `export const defaultDictionary` | `
|
|
483
|
-
| `namespaceLoaders.billing()` | `namespaceLoaders.billing(locale)`
|
|
480
|
+
| Canonical | Split-by-locale / custom (all lazy) |
|
|
481
|
+
| -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
|
|
482
|
+
| `export const defaultDictionary` | `dictionary.generated.ts` is not generated |
|
|
483
|
+
| `namespaceLoaders.billing()` | `namespaceLoaders.billing(locale)` or `namespaceLoaders.billing(area)` |
|
|
484
|
+
| — | `ensureNamespacesLoadedForLocale(i18n, locale)` or `ensureNamespacesLoadedForArea(i18n, area)` in `namespace-loaders.generated.ts` |
|
|
485
|
+
|
|
486
|
+
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
|
+
|
|
488
|
+
Example init (multi, split-by-locale, all namespaces lazy):
|
|
489
|
+
|
|
490
|
+
```ts
|
|
491
|
+
import { createI18n } from "./generated/instance.generated.js";
|
|
492
|
+
import { ensureNamespacesLoadedForLocale } from "./generated/namespace-loaders.generated.js";
|
|
493
|
+
|
|
494
|
+
const i18n = createI18n({});
|
|
495
|
+
await ensureNamespacesLoadedForLocale(i18n, activeLocale);
|
|
496
|
+
```
|
|
484
497
|
|
|
485
|
-
Example
|
|
498
|
+
Example init (multi, custom delivery, all namespaces lazy):
|
|
499
|
+
|
|
500
|
+
```ts
|
|
501
|
+
import { createI18n } from "./generated/instance.generated.js";
|
|
502
|
+
import { ensureNamespacesLoadedForArea } from "./generated/namespace-loaders.generated.js";
|
|
503
|
+
|
|
504
|
+
const i18n = createI18n({});
|
|
505
|
+
await ensureNamespacesLoadedForArea(i18n, activeArea);
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
Example `dictionary.generated.ts` (multi, canonical delivery, `loadOnInit: ["default"]`):
|
|
486
509
|
|
|
487
510
|
```ts
|
|
488
511
|
import defaultEn from "./translations/default.en.json";
|
|
@@ -513,19 +536,28 @@ export const namespaceLoaders = {
|
|
|
513
536
|
}
|
|
514
537
|
},
|
|
515
538
|
};
|
|
539
|
+
|
|
540
|
+
export async function ensureNamespacesLoadedForLocale(
|
|
541
|
+
i18n: I18nMultiInstance,
|
|
542
|
+
locale: MyProjectLocale,
|
|
543
|
+
namespaces: readonly LazyNamespace[] = ["billing", "default"] as const
|
|
544
|
+
): Promise<void> {
|
|
545
|
+
await Promise.all(
|
|
546
|
+
namespaces.map(async (namespace) => {
|
|
547
|
+
i18n.setNamespace(namespace, await namespaceLoaders[namespace](locale));
|
|
548
|
+
})
|
|
549
|
+
);
|
|
550
|
+
}
|
|
516
551
|
```
|
|
517
552
|
|
|
518
|
-
|
|
553
|
+
Load only the namespaces you need:
|
|
519
554
|
|
|
520
555
|
```ts
|
|
521
556
|
import { createI18n } from "./generated/instance.generated.js";
|
|
522
|
-
import {
|
|
523
|
-
|
|
524
|
-
const i18n = createI18n(defaultDictionaryFor(activeLocale));
|
|
557
|
+
import { ensureNamespacesLoadedForLocale } from "./generated/namespace-loaders.generated.js";
|
|
525
558
|
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
}
|
|
559
|
+
const i18n = createI18n({});
|
|
560
|
+
await ensureNamespacesLoadedForLocale(i18n, activeLocale, ["billing"]);
|
|
529
561
|
```
|
|
530
562
|
|
|
531
563
|
Use split delivery when you want smaller lazy chunks (one locale per dynamic import) or when serving per-locale JSON from `public/` without runtime `projectNamespaceLocales`. Runtime `projectDictionaryLocales` / `projectNamespaceLocales` remain available for external CMS/API payloads.
|
|
@@ -579,7 +611,7 @@ i18n.setNamespace("billing", externalBillingPayload);
|
|
|
579
611
|
|
|
580
612
|
### Lazy namespace loading (multi mode)
|
|
581
613
|
|
|
582
|
-
Split namespaces across chunks
|
|
614
|
+
Split namespaces across chunks with `loadOnInit` in **canonical** delivery only. With `split-by-locale` or `custom`, every namespace is lazy and codegen emits typed `namespaceLoaders` — one dynamic `import()` per lazy namespace (and per locale or delivery area). `.get()` stays synchronous — register lazy namespaces with `setNamespace()` before rendering.
|
|
583
615
|
|
|
584
616
|
```json
|
|
585
617
|
{
|
package/dist/codegen/index.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ declare const codegenConfigShape: {
|
|
|
7
7
|
namespaces: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
8
8
|
defaultNamespace: z.ZodOptional<z.ZodString>;
|
|
9
9
|
typesOutput: z.ZodString;
|
|
10
|
-
dictionaryOutput: z.ZodString
|
|
10
|
+
dictionaryOutput: z.ZodOptional<z.ZodString>;
|
|
11
11
|
instanceOutput: z.ZodString;
|
|
12
12
|
dictionarySchemaOutput: z.ZodOptional<z.ZodString>;
|
|
13
13
|
loadOnInit: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
@@ -37,7 +37,7 @@ declare const codegenConfigSchema: z.ZodObject<{
|
|
|
37
37
|
namespaces: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
38
38
|
defaultNamespace: z.ZodOptional<z.ZodString>;
|
|
39
39
|
typesOutput: z.ZodString;
|
|
40
|
-
dictionaryOutput: z.ZodString
|
|
40
|
+
dictionaryOutput: z.ZodOptional<z.ZodString>;
|
|
41
41
|
instanceOutput: z.ZodString;
|
|
42
42
|
dictionarySchemaOutput: z.ZodOptional<z.ZodString>;
|
|
43
43
|
loadOnInit: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
@@ -64,6 +64,8 @@ declare const codegenConfigSchema: z.ZodObject<{
|
|
|
64
64
|
type CodegenConfigInput = z.input<typeof codegenConfigSchema>;
|
|
65
65
|
type CodegenConfig = z.infer<typeof codegenConfigSchema>;
|
|
66
66
|
declare function resolveDeliveryOutputDir(config: Pick<CodegenConfig, "typesOutput" | "deliveryOutput">): string;
|
|
67
|
+
/** Resolved path for dictionary.generated.ts (explicit config or default next to typesOutput). */
|
|
68
|
+
declare function resolveDictionaryOutputPath(config: Pick<CodegenConfig, "typesOutput" | "dictionaryOutput">): string;
|
|
67
69
|
|
|
68
70
|
type DeliveryArtifactsMap = Record<string, readonly string[]>;
|
|
69
71
|
|
|
@@ -82,4 +84,4 @@ declare function typeNamesForProject(project: string): {
|
|
|
82
84
|
|
|
83
85
|
declare function writeCodegenConfig(configPath: string, config: CodegenConfigInput): void;
|
|
84
86
|
|
|
85
|
-
export { type CodegenConfig, type CodegenConfigInput, DELIVERY_MODES, type DeliveryArtifactsMap, type DeliveryMode, SUPPORTED_IMPORT_EXTENSIONS, type SetupMode, type SupportedImportExtension, buildCodegenConfig, codegenConfigKeys, inferProjectName, resolveDeliveryOutputDir, typeNamesForProject, writeCodegenConfig };
|
|
87
|
+
export { type CodegenConfig, type CodegenConfigInput, DELIVERY_MODES, type DeliveryArtifactsMap, type DeliveryMode, SUPPORTED_IMPORT_EXTENSIONS, type SetupMode, type SupportedImportExtension, buildCodegenConfig, codegenConfigKeys, inferProjectName, resolveDeliveryOutputDir, resolveDictionaryOutputPath, typeNamesForProject, writeCodegenConfig };
|
package/dist/codegen/index.js
CHANGED
|
@@ -53,7 +53,9 @@ var codegenConfigShape = {
|
|
|
53
53
|
namespaces: z.record(z.string(), z.string().min(1)).optional(),
|
|
54
54
|
defaultNamespace: z.string().min(1).optional(),
|
|
55
55
|
typesOutput: z.string().min(1),
|
|
56
|
-
dictionaryOutput: z.string().min(1)
|
|
56
|
+
dictionaryOutput: z.string().min(1).optional().describe(
|
|
57
|
+
'Output path for dictionary.generated.ts when emitted (canonical, or split/custom with eager namespaces). Omitted in split/custom when every namespace is lazy \u2014 defaults to "{dirname(typesOutput)}/dictionary.generated.ts" for stale-file cleanup only.'
|
|
58
|
+
),
|
|
57
59
|
instanceOutput: z.string().min(1),
|
|
58
60
|
dictionarySchemaOutput: z.string().min(1).optional(),
|
|
59
61
|
loadOnInit: z.array(z.string().min(1)).optional(),
|
|
@@ -122,10 +124,21 @@ z.object(codegenConfigShape).strict().superRefine((config, ctx) => {
|
|
|
122
124
|
message: 'deliveryArtifacts is only allowed when delivery is "custom".'
|
|
123
125
|
});
|
|
124
126
|
}
|
|
127
|
+
if (config.loadOnInit !== void 0 && config.delivery !== "canonical") {
|
|
128
|
+
ctx.addIssue({
|
|
129
|
+
code: "custom",
|
|
130
|
+
path: ["loadOnInit"],
|
|
131
|
+
message: 'loadOnInit is only allowed when delivery is "canonical".'
|
|
132
|
+
});
|
|
133
|
+
}
|
|
125
134
|
});
|
|
126
135
|
function resolveDeliveryOutputDir(config) {
|
|
127
136
|
return config.deliveryOutput ?? path.dirname(config.typesOutput);
|
|
128
137
|
}
|
|
138
|
+
var DEFAULT_DICTIONARY_BASENAME = "dictionary.generated.ts";
|
|
139
|
+
function resolveDictionaryOutputPath(config) {
|
|
140
|
+
return config.dictionaryOutput ?? path.join(path.dirname(config.typesOutput), DEFAULT_DICTIONARY_BASENAME);
|
|
141
|
+
}
|
|
129
142
|
|
|
130
143
|
// src/codegen-config/type-names.ts
|
|
131
144
|
function inferProjectName(dirName) {
|
|
@@ -176,6 +189,6 @@ function writeCodegenConfig(configPath, config) {
|
|
|
176
189
|
`);
|
|
177
190
|
}
|
|
178
191
|
|
|
179
|
-
export { DELIVERY_MODES, SUPPORTED_IMPORT_EXTENSIONS, buildCodegenConfig, codegenConfigKeys, inferProjectName, resolveDeliveryOutputDir, typeNamesForProject, writeCodegenConfig };
|
|
192
|
+
export { DELIVERY_MODES, SUPPORTED_IMPORT_EXTENSIONS, buildCodegenConfig, codegenConfigKeys, inferProjectName, resolveDeliveryOutputDir, resolveDictionaryOutputPath, typeNamesForProject, writeCodegenConfig };
|
|
180
193
|
//# sourceMappingURL=index.js.map
|
|
181
194
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/codegen/constants.ts","../../src/codegen/delivery-artifacts.ts","../../src/codegen/codegen-config-schema.ts","../../src/codegen-config/type-names.ts","../../src/codegen-config/build-config.ts","../../src/codegen-config/write-config.ts"],"names":["path"],"mappings":"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","file":"index.js","sourcesContent":["export const SUPPORTED_IMPORT_EXTENSIONS = [\"none\", \".ts\", \".js\"] as const;\nexport type SupportedImportExtension = (typeof SUPPORTED_IMPORT_EXTENSIONS)[number];\n\n// Translation keys and namespace names become TypeScript identifiers and\n// template-literal type segments in the generated code, so they must be valid\n// identifiers (e.g. \"app.title\" would emit the broken type `\"app.title:...\"`).\nexport const IDENTIFIER_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;\nexport const IDENTIFIER_NAME_REQUIREMENT =\n \"allowed: letters, digits, underscore; must not start with a digit\";\n","/** Valid TypeScript identifier / filename segment for a delivery area id. */\nexport const DELIVERY_AREA_NAME_PATTERN = /^[a-zA-Z][a-zA-Z0-9_-]*$/;\n\nexport type DeliveryArtifactsMap = Record<string, readonly string[]>;\n\nexport type DeliveryArtifactsIssue = {\n path: (string | number)[];\n message: string;\n};\n\nexport function getDeliveryArtifactsStructureIssues(\n deliveryArtifacts: DeliveryArtifactsMap\n): DeliveryArtifactsIssue[] {\n const issues: DeliveryArtifactsIssue[] = [];\n\n for (const [area, locales] of Object.entries(deliveryArtifacts)) {\n if (!DELIVERY_AREA_NAME_PATTERN.test(area)) {\n issues.push({\n path: [\"deliveryArtifacts\", area],\n message: `Invalid delivery area name \"${area}\": must match ${DELIVERY_AREA_NAME_PATTERN.source}`,\n });\n }\n\n if (locales.length === 0) {\n issues.push({\n path: [\"deliveryArtifacts\", area],\n message: `Delivery area \"${area}\" must include at least one locale.`,\n });\n }\n }\n\n const localeToArea = new Map<string, string>();\n for (const [area, locales] of Object.entries(deliveryArtifacts)) {\n for (const locale of locales) {\n const previousArea = localeToArea.get(locale);\n if (previousArea) {\n issues.push({\n path: [\"deliveryArtifacts\", area],\n message: `Locale \"${locale}\" appears in both \"${previousArea}\" and \"${area}\".`,\n });\n } else {\n localeToArea.set(locale, area);\n }\n }\n }\n\n return issues;\n}\n\nexport function getDeliveryArtifactsPartitionIssues(\n deliveryArtifacts: DeliveryArtifactsMap,\n requestLocales: ReadonlySet<string>\n): DeliveryArtifactsIssue[] {\n const issues: DeliveryArtifactsIssue[] = [];\n const artifactLocales = new Set<string>();\n\n for (const locales of Object.values(deliveryArtifacts)) {\n for (const locale of locales) {\n artifactLocales.add(locale);\n }\n }\n\n const missing = [...requestLocales].filter((locale) => !artifactLocales.has(locale)).sort();\n if (missing.length > 0) {\n issues.push({\n path: [\"deliveryArtifacts\"],\n message: `deliveryArtifacts is missing locales required by dictionaries and localeFallback: ${missing.join(\", \")}`,\n });\n }\n\n const excess = [...artifactLocales].filter((locale) => !requestLocales.has(locale)).sort();\n if (excess.length > 0) {\n issues.push({\n path: [\"deliveryArtifacts\"],\n message: `deliveryArtifacts includes locales not required by dictionaries and localeFallback: ${excess.join(\", \")}`,\n });\n }\n\n return issues;\n}\n\n/** Pre-emit validation gate for `deliveryArtifacts` in custom delivery mode. */\nexport function getDeliveryArtifactsIssues(\n deliveryArtifacts: DeliveryArtifactsMap,\n requestLocales: ReadonlySet<string>\n): DeliveryArtifactsIssue[] {\n return [\n ...getDeliveryArtifactsStructureIssues(deliveryArtifacts),\n ...getDeliveryArtifactsPartitionIssues(deliveryArtifacts, requestLocales),\n ];\n}\n\nexport function getDeliveryAreaNames(deliveryArtifacts: DeliveryArtifactsMap): string[] {\n return Object.keys(deliveryArtifacts).sort();\n}\n\nexport function getLocaleDeliveryAreaMap(\n deliveryArtifacts: DeliveryArtifactsMap\n): Record<string, string> {\n const localeToArea: Record<string, string> = {};\n\n for (const [area, locales] of Object.entries(deliveryArtifacts)) {\n for (const locale of locales) {\n localeToArea[locale] = area;\n }\n }\n\n return localeToArea;\n}\n\nexport function formatLocaleDeliveryAreaBlock(\n deliveryArtifacts: DeliveryArtifactsMap,\n constName: string,\n localeTypeName: string,\n deliveryAreaTypeName: string\n): string {\n const localeToArea = getLocaleDeliveryAreaMap(deliveryArtifacts);\n const lines = Object.keys(localeToArea)\n .sort()\n .map((locale) => ` ${JSON.stringify(locale)}: ${JSON.stringify(localeToArea[locale])},`)\n .join(\"\\n\");\n\n return `export const ${constName} = {\\n${lines}\\n} as const satisfies Record<${localeTypeName}, ${deliveryAreaTypeName}>;\\n\\n`;\n}\n","import { z } from \"zod\";\nimport path from \"node:path\";\nimport {\n IDENTIFIER_NAME_PATTERN,\n IDENTIFIER_NAME_REQUIREMENT,\n SUPPORTED_IMPORT_EXTENSIONS,\n} from \"./constants.js\";\nimport { getDeliveryArtifactsStructureIssues } from \"./delivery-artifacts.js\";\n\nconst localeFallbackSchema = z.record(z.string(), z.union([z.string(), z.null()]));\nconst deliveryArtifactsSchema = z.record(z.string(), z.array(z.string().min(1)));\n\nexport const DELIVERY_MODES = [\"canonical\", \"split-by-locale\", \"custom\"] as const;\nexport type DeliveryMode = (typeof DELIVERY_MODES)[number];\n\nconst codegenConfigShape = {\n dictionary: z.string().min(1).optional(),\n namespaces: z.record(z.string(), z.string().min(1)).optional(),\n defaultNamespace: z.string().min(1).optional(),\n typesOutput: z.string().min(1),\n dictionaryOutput: z.string().min(1),\n instanceOutput: z.string().min(1),\n dictionarySchemaOutput: z.string().min(1).optional(),\n loadOnInit: z.array(z.string().min(1)).optional(),\n namespaceLoadersOutput: z.string().min(1).optional(),\n importExtension: z.enum(SUPPORTED_IMPORT_EXTENSIONS).optional(),\n paramsTypeName: z.string().min(1),\n schemaTypeName: z.string().min(1),\n localeTypeName: z.string().min(1).optional(),\n localeFallbackConstName: z.string().min(1).optional(),\n localeFallback: localeFallbackSchema.optional(),\n factoryName: z.string().min(1).optional(),\n delivery: z.enum(DELIVERY_MODES).optional().default(\"canonical\"),\n deliveryArtifacts: deliveryArtifactsSchema.optional(),\n deliveryOutput: z.string().min(1).optional(),\n};\n\nexport const codegenConfigKeys = Object.keys(\n codegenConfigShape\n) as (keyof typeof codegenConfigShape)[];\n\nexport const codegenConfigSchema = z\n .object(codegenConfigShape)\n .strict()\n .superRefine((config, ctx) => {\n const hasDictionary = config.dictionary !== undefined;\n const hasNamespaces = config.namespaces !== undefined;\n\n if (hasDictionary === hasNamespaces) {\n ctx.addIssue({\n code: \"custom\",\n message: 'Specify exactly one of \"dictionary\" or \"namespaces\".',\n });\n }\n\n if (config.namespaces) {\n for (const namespace of Object.keys(config.namespaces)) {\n if (!IDENTIFIER_NAME_PATTERN.test(namespace)) {\n ctx.addIssue({\n code: \"custom\",\n path: [\"namespaces\", namespace],\n message: `Invalid namespace name \"${namespace}\" (${IDENTIFIER_NAME_REQUIREMENT}).`,\n });\n }\n }\n }\n\n if (\n config.defaultNamespace !== undefined &&\n !IDENTIFIER_NAME_PATTERN.test(config.defaultNamespace)\n ) {\n ctx.addIssue({\n code: \"custom\",\n path: [\"defaultNamespace\"],\n message: `Invalid namespace name \"${config.defaultNamespace}\" (${IDENTIFIER_NAME_REQUIREMENT}).`,\n });\n }\n\n if (config.delivery === \"custom\") {\n if (!config.deliveryArtifacts) {\n ctx.addIssue({\n code: \"custom\",\n path: [\"deliveryArtifacts\"],\n message: 'deliveryArtifacts is required when delivery is \"custom\".',\n });\n } else {\n for (const issue of getDeliveryArtifactsStructureIssues(config.deliveryArtifacts)) {\n ctx.addIssue({\n code: \"custom\",\n path: issue.path,\n message: issue.message,\n });\n }\n }\n } else if (config.deliveryArtifacts !== undefined) {\n ctx.addIssue({\n code: \"custom\",\n path: [\"deliveryArtifacts\"],\n message: 'deliveryArtifacts is only allowed when delivery is \"custom\".',\n });\n }\n });\n\nexport type CodegenConfigInput = z.input<typeof codegenConfigSchema>;\nexport type CodegenConfig = z.infer<typeof codegenConfigSchema>;\n\nexport function resolveDeliveryOutputDir(\n config: Pick<CodegenConfig, \"typesOutput\" | \"deliveryOutput\">\n): string {\n return config.deliveryOutput ?? path.dirname(config.typesOutput);\n}\n\nexport function formatCodegenConfigIssues(error: z.ZodError): string {\n const issueLines = error.issues.map((issue) => {\n const path = issue.path.length > 0 ? issue.path.join(\".\") : \"(root)\";\n return ` - ${path}: ${issue.message}`;\n });\n\n return [\n \"[Codegen Error] Invalid i18n.codegen.json:\",\n ...issueLines,\n \"\",\n `Allowed keys: ${codegenConfigKeys.join(\", \")}`,\n ].join(\"\\n\");\n}\n","export function inferProjectName(dirName: string): string {\n const parts = dirName.split(/[-_]+/).filter(Boolean);\n if (parts.length === 0) {\n return \"App\";\n }\n\n return parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(\"\");\n}\n\nexport function typeNamesForProject(project: string): {\n paramsTypeName: string;\n schemaTypeName: string;\n localeTypeName: string;\n} {\n return {\n paramsTypeName: `${project}Params`,\n schemaTypeName: `${project}Schema`,\n localeTypeName: `${project}Locale`,\n };\n}\n","import type { CodegenConfigInput } from \"../codegen/codegen-config-schema.js\";\nimport { typeNamesForProject } from \"./type-names.js\";\n\nexport type SetupMode = \"single\" | \"multi\";\n\nconst GENERATED_DIR = \"generated\";\nconst TRANSLATIONS_DIR = \"translations\";\n\nexport function buildCodegenConfig(mode: SetupMode, project: string): CodegenConfigInput {\n const typeNames = typeNamesForProject(project);\n const base = {\n typesOutput: `${GENERATED_DIR}/i18n-types.generated.ts`,\n dictionaryOutput: `${GENERATED_DIR}/dictionary.generated.ts`,\n instanceOutput: `${GENERATED_DIR}/instance.generated.ts`,\n paramsTypeName: typeNames.paramsTypeName,\n schemaTypeName: typeNames.schemaTypeName,\n localeTypeName: typeNames.localeTypeName,\n factoryName: \"createI18n\",\n } satisfies CodegenConfigInput;\n\n if (mode === \"single\") {\n return {\n dictionary: `${TRANSLATIONS_DIR}/translations.json`,\n ...base,\n };\n }\n\n return {\n namespaces: {\n default: `${TRANSLATIONS_DIR}/default.json`,\n },\n ...base,\n };\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport type { CodegenConfigInput } from \"../codegen/codegen-config-schema.js\";\n\nexport function writeCodegenConfig(configPath: string, config: CodegenConfigInput): void {\n fs.mkdirSync(path.dirname(configPath), { recursive: true });\n fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\\n`);\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/codegen/constants.ts","../../src/codegen/delivery-artifacts.ts","../../src/codegen/codegen-config-schema.ts","../../src/codegen-config/type-names.ts","../../src/codegen-config/build-config.ts","../../src/codegen-config/write-config.ts"],"names":["path"],"mappings":"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","file":"index.js","sourcesContent":["export const SUPPORTED_IMPORT_EXTENSIONS = [\"none\", \".ts\", \".js\"] as const;\nexport type SupportedImportExtension = (typeof SUPPORTED_IMPORT_EXTENSIONS)[number];\n\n// Translation keys and namespace names become TypeScript identifiers and\n// template-literal type segments in the generated code, so they must be valid\n// identifiers (e.g. \"app.title\" would emit the broken type `\"app.title:...\"`).\nexport const IDENTIFIER_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;\nexport const IDENTIFIER_NAME_REQUIREMENT =\n \"allowed: letters, digits, underscore; must not start with a digit\";\n","/** Valid TypeScript identifier / filename segment for a delivery area id. */\nexport const DELIVERY_AREA_NAME_PATTERN = /^[a-zA-Z][a-zA-Z0-9_-]*$/;\n\nexport type DeliveryArtifactsMap = Record<string, readonly string[]>;\n\nexport type DeliveryArtifactsIssue = {\n path: (string | number)[];\n message: string;\n};\n\nexport function getDeliveryArtifactsStructureIssues(\n deliveryArtifacts: DeliveryArtifactsMap\n): DeliveryArtifactsIssue[] {\n const issues: DeliveryArtifactsIssue[] = [];\n\n for (const [area, locales] of Object.entries(deliveryArtifacts)) {\n if (!DELIVERY_AREA_NAME_PATTERN.test(area)) {\n issues.push({\n path: [\"deliveryArtifacts\", area],\n message: `Invalid delivery area name \"${area}\": must match ${DELIVERY_AREA_NAME_PATTERN.source}`,\n });\n }\n\n if (locales.length === 0) {\n issues.push({\n path: [\"deliveryArtifacts\", area],\n message: `Delivery area \"${area}\" must include at least one locale.`,\n });\n }\n }\n\n const localeToArea = new Map<string, string>();\n for (const [area, locales] of Object.entries(deliveryArtifacts)) {\n for (const locale of locales) {\n const previousArea = localeToArea.get(locale);\n if (previousArea) {\n issues.push({\n path: [\"deliveryArtifacts\", area],\n message: `Locale \"${locale}\" appears in both \"${previousArea}\" and \"${area}\".`,\n });\n } else {\n localeToArea.set(locale, area);\n }\n }\n }\n\n return issues;\n}\n\nexport function getDeliveryArtifactsPartitionIssues(\n deliveryArtifacts: DeliveryArtifactsMap,\n requestLocales: ReadonlySet<string>\n): DeliveryArtifactsIssue[] {\n const issues: DeliveryArtifactsIssue[] = [];\n const artifactLocales = new Set<string>();\n\n for (const locales of Object.values(deliveryArtifacts)) {\n for (const locale of locales) {\n artifactLocales.add(locale);\n }\n }\n\n const missing = [...requestLocales].filter((locale) => !artifactLocales.has(locale)).sort();\n if (missing.length > 0) {\n issues.push({\n path: [\"deliveryArtifacts\"],\n message: `deliveryArtifacts is missing locales required by dictionaries and localeFallback: ${missing.join(\", \")}`,\n });\n }\n\n const excess = [...artifactLocales].filter((locale) => !requestLocales.has(locale)).sort();\n if (excess.length > 0) {\n issues.push({\n path: [\"deliveryArtifacts\"],\n message: `deliveryArtifacts includes locales not required by dictionaries and localeFallback: ${excess.join(\", \")}`,\n });\n }\n\n return issues;\n}\n\n/** Pre-emit validation gate for `deliveryArtifacts` in custom delivery mode. */\nexport function getDeliveryArtifactsIssues(\n deliveryArtifacts: DeliveryArtifactsMap,\n requestLocales: ReadonlySet<string>\n): DeliveryArtifactsIssue[] {\n return [\n ...getDeliveryArtifactsStructureIssues(deliveryArtifacts),\n ...getDeliveryArtifactsPartitionIssues(deliveryArtifacts, requestLocales),\n ];\n}\n\nexport function getDeliveryAreaNames(deliveryArtifacts: DeliveryArtifactsMap): string[] {\n return Object.keys(deliveryArtifacts).sort();\n}\n\nexport function getLocaleDeliveryAreaMap(\n deliveryArtifacts: DeliveryArtifactsMap\n): Record<string, string> {\n const localeToArea: Record<string, string> = {};\n\n for (const [area, locales] of Object.entries(deliveryArtifacts)) {\n for (const locale of locales) {\n localeToArea[locale] = area;\n }\n }\n\n return localeToArea;\n}\n\nexport function formatLocaleDeliveryAreaBlock(\n deliveryArtifacts: DeliveryArtifactsMap,\n constName: string,\n localeTypeName: string,\n deliveryAreaTypeName: string\n): string {\n const localeToArea = getLocaleDeliveryAreaMap(deliveryArtifacts);\n const lines = Object.keys(localeToArea)\n .sort()\n .map((locale) => ` ${JSON.stringify(locale)}: ${JSON.stringify(localeToArea[locale])},`)\n .join(\"\\n\");\n\n return `export const ${constName} = {\\n${lines}\\n} as const satisfies Record<${localeTypeName}, ${deliveryAreaTypeName}>;\\n\\n`;\n}\n\nexport function getDeliveryArtifactsTypeName(deliveryAreaTypeName: string): string {\n return deliveryAreaTypeName.endsWith(\"DeliveryArea\")\n ? deliveryAreaTypeName.replace(/DeliveryArea$/, \"DeliveryArtifacts\")\n : `${deliveryAreaTypeName}Artifacts`;\n}\n\nexport function formatDeliveryArtifactsBlock(\n deliveryArtifacts: DeliveryArtifactsMap,\n constName: string,\n localeTypeName: string,\n deliveryAreaTypeName: string\n): string {\n const deliveryArtifactsTypeName = getDeliveryArtifactsTypeName(deliveryAreaTypeName);\n const areaEntries = getDeliveryAreaNames(deliveryArtifacts)\n .map((area) => {\n const locales = [...deliveryArtifacts[area]!]\n .sort()\n .map((locale) => JSON.stringify(locale))\n .join(\", \");\n return ` ${JSON.stringify(area)}: [${locales}] as const,`;\n })\n .join(\"\\n\");\n\n return (\n `export const ${constName} = {\\n${areaEntries}\\n} as const satisfies Record<${deliveryAreaTypeName}, readonly ${localeTypeName}[]>;\\n\\n` +\n `export type ${deliveryArtifactsTypeName} = typeof ${constName};\\n\\n`\n );\n}\n","import { z } from \"zod\";\nimport path from \"node:path\";\nimport {\n IDENTIFIER_NAME_PATTERN,\n IDENTIFIER_NAME_REQUIREMENT,\n SUPPORTED_IMPORT_EXTENSIONS,\n} from \"./constants.js\";\nimport { getDeliveryArtifactsStructureIssues } from \"./delivery-artifacts.js\";\n\nconst localeFallbackSchema = z.record(z.string(), z.union([z.string(), z.null()]));\nconst deliveryArtifactsSchema = z.record(z.string(), z.array(z.string().min(1)));\n\nexport const DELIVERY_MODES = [\"canonical\", \"split-by-locale\", \"custom\"] as const;\nexport type DeliveryMode = (typeof DELIVERY_MODES)[number];\n\nconst codegenConfigShape = {\n dictionary: z.string().min(1).optional(),\n namespaces: z.record(z.string(), z.string().min(1)).optional(),\n defaultNamespace: z.string().min(1).optional(),\n typesOutput: z.string().min(1),\n dictionaryOutput: z\n .string()\n .min(1)\n .optional()\n .describe(\n 'Output path for dictionary.generated.ts when emitted (canonical, or split/custom with eager namespaces). Omitted in split/custom when every namespace is lazy — defaults to \"{dirname(typesOutput)}/dictionary.generated.ts\" for stale-file cleanup only.'\n ),\n instanceOutput: z.string().min(1),\n dictionarySchemaOutput: z.string().min(1).optional(),\n loadOnInit: z.array(z.string().min(1)).optional(),\n namespaceLoadersOutput: z.string().min(1).optional(),\n importExtension: z.enum(SUPPORTED_IMPORT_EXTENSIONS).optional(),\n paramsTypeName: z.string().min(1),\n schemaTypeName: z.string().min(1),\n localeTypeName: z.string().min(1).optional(),\n localeFallbackConstName: z.string().min(1).optional(),\n localeFallback: localeFallbackSchema.optional(),\n factoryName: z.string().min(1).optional(),\n delivery: z.enum(DELIVERY_MODES).optional().default(\"canonical\"),\n deliveryArtifacts: deliveryArtifactsSchema.optional(),\n deliveryOutput: z.string().min(1).optional(),\n};\n\nexport const codegenConfigKeys = Object.keys(\n codegenConfigShape\n) as (keyof typeof codegenConfigShape)[];\n\nexport const codegenConfigSchema = z\n .object(codegenConfigShape)\n .strict()\n .superRefine((config, ctx) => {\n const hasDictionary = config.dictionary !== undefined;\n const hasNamespaces = config.namespaces !== undefined;\n\n if (hasDictionary === hasNamespaces) {\n ctx.addIssue({\n code: \"custom\",\n message: 'Specify exactly one of \"dictionary\" or \"namespaces\".',\n });\n }\n\n if (config.namespaces) {\n for (const namespace of Object.keys(config.namespaces)) {\n if (!IDENTIFIER_NAME_PATTERN.test(namespace)) {\n ctx.addIssue({\n code: \"custom\",\n path: [\"namespaces\", namespace],\n message: `Invalid namespace name \"${namespace}\" (${IDENTIFIER_NAME_REQUIREMENT}).`,\n });\n }\n }\n }\n\n if (\n config.defaultNamespace !== undefined &&\n !IDENTIFIER_NAME_PATTERN.test(config.defaultNamespace)\n ) {\n ctx.addIssue({\n code: \"custom\",\n path: [\"defaultNamespace\"],\n message: `Invalid namespace name \"${config.defaultNamespace}\" (${IDENTIFIER_NAME_REQUIREMENT}).`,\n });\n }\n\n if (config.delivery === \"custom\") {\n if (!config.deliveryArtifacts) {\n ctx.addIssue({\n code: \"custom\",\n path: [\"deliveryArtifacts\"],\n message: 'deliveryArtifacts is required when delivery is \"custom\".',\n });\n } else {\n for (const issue of getDeliveryArtifactsStructureIssues(config.deliveryArtifacts)) {\n ctx.addIssue({\n code: \"custom\",\n path: issue.path,\n message: issue.message,\n });\n }\n }\n } else if (config.deliveryArtifacts !== undefined) {\n ctx.addIssue({\n code: \"custom\",\n path: [\"deliveryArtifacts\"],\n message: 'deliveryArtifacts is only allowed when delivery is \"custom\".',\n });\n }\n\n if (config.loadOnInit !== undefined && config.delivery !== \"canonical\") {\n ctx.addIssue({\n code: \"custom\",\n path: [\"loadOnInit\"],\n message: 'loadOnInit is only allowed when delivery is \"canonical\".',\n });\n }\n });\n\nexport type CodegenConfigInput = z.input<typeof codegenConfigSchema>;\nexport type CodegenConfig = z.infer<typeof codegenConfigSchema>;\n\nexport function resolveDeliveryOutputDir(\n config: Pick<CodegenConfig, \"typesOutput\" | \"deliveryOutput\">\n): string {\n return config.deliveryOutput ?? path.dirname(config.typesOutput);\n}\n\nconst DEFAULT_DICTIONARY_BASENAME = \"dictionary.generated.ts\";\n\n/** Resolved path for dictionary.generated.ts (explicit config or default next to typesOutput). */\nexport function resolveDictionaryOutputPath(\n config: Pick<CodegenConfig, \"typesOutput\" | \"dictionaryOutput\">\n): string {\n return (\n config.dictionaryOutput ??\n path.join(path.dirname(config.typesOutput), DEFAULT_DICTIONARY_BASENAME)\n );\n}\n\nexport function formatCodegenConfigIssues(error: z.ZodError): string {\n const issueLines = error.issues.map((issue) => {\n const path = issue.path.length > 0 ? issue.path.join(\".\") : \"(root)\";\n return ` - ${path}: ${issue.message}`;\n });\n\n return [\n \"[Codegen Error] Invalid i18n.codegen.json:\",\n ...issueLines,\n \"\",\n `Allowed keys: ${codegenConfigKeys.join(\", \")}`,\n ].join(\"\\n\");\n}\n","export function inferProjectName(dirName: string): string {\n const parts = dirName.split(/[-_]+/).filter(Boolean);\n if (parts.length === 0) {\n return \"App\";\n }\n\n return parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(\"\");\n}\n\nexport function typeNamesForProject(project: string): {\n paramsTypeName: string;\n schemaTypeName: string;\n localeTypeName: string;\n} {\n return {\n paramsTypeName: `${project}Params`,\n schemaTypeName: `${project}Schema`,\n localeTypeName: `${project}Locale`,\n };\n}\n","import type { CodegenConfigInput } from \"../codegen/codegen-config-schema.js\";\nimport { typeNamesForProject } from \"./type-names.js\";\n\nexport type SetupMode = \"single\" | \"multi\";\n\nconst GENERATED_DIR = \"generated\";\nconst TRANSLATIONS_DIR = \"translations\";\n\nexport function buildCodegenConfig(mode: SetupMode, project: string): CodegenConfigInput {\n const typeNames = typeNamesForProject(project);\n const base = {\n typesOutput: `${GENERATED_DIR}/i18n-types.generated.ts`,\n dictionaryOutput: `${GENERATED_DIR}/dictionary.generated.ts`,\n instanceOutput: `${GENERATED_DIR}/instance.generated.ts`,\n paramsTypeName: typeNames.paramsTypeName,\n schemaTypeName: typeNames.schemaTypeName,\n localeTypeName: typeNames.localeTypeName,\n factoryName: \"createI18n\",\n } satisfies CodegenConfigInput;\n\n if (mode === \"single\") {\n return {\n dictionary: `${TRANSLATIONS_DIR}/translations.json`,\n ...base,\n };\n }\n\n return {\n namespaces: {\n default: `${TRANSLATIONS_DIR}/default.json`,\n },\n ...base,\n };\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport type { CodegenConfigInput } from \"../codegen/codegen-config-schema.js\";\n\nexport function writeCodegenConfig(configPath: string, config: CodegenConfigInput): void {\n fs.mkdirSync(path.dirname(configPath), { recursive: true });\n fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\\n`);\n}\n"]}
|
package/package.json
CHANGED
|
@@ -18,7 +18,13 @@ const codegenConfigShape = {
|
|
|
18
18
|
namespaces: z.record(z.string(), z.string().min(1)).optional(),
|
|
19
19
|
defaultNamespace: z.string().min(1).optional(),
|
|
20
20
|
typesOutput: z.string().min(1),
|
|
21
|
-
dictionaryOutput: z
|
|
21
|
+
dictionaryOutput: z
|
|
22
|
+
.string()
|
|
23
|
+
.min(1)
|
|
24
|
+
.optional()
|
|
25
|
+
.describe(
|
|
26
|
+
'Output path for dictionary.generated.ts when emitted (canonical, or split/custom with eager namespaces). Omitted in split/custom when every namespace is lazy — defaults to "{dirname(typesOutput)}/dictionary.generated.ts" for stale-file cleanup only.'
|
|
27
|
+
),
|
|
22
28
|
instanceOutput: z.string().min(1),
|
|
23
29
|
dictionarySchemaOutput: z.string().min(1).optional(),
|
|
24
30
|
loadOnInit: z.array(z.string().min(1)).optional(),
|
|
@@ -99,6 +105,14 @@ export const codegenConfigSchema = z
|
|
|
99
105
|
message: 'deliveryArtifacts is only allowed when delivery is "custom".',
|
|
100
106
|
});
|
|
101
107
|
}
|
|
108
|
+
|
|
109
|
+
if (config.loadOnInit !== undefined && config.delivery !== "canonical") {
|
|
110
|
+
ctx.addIssue({
|
|
111
|
+
code: "custom",
|
|
112
|
+
path: ["loadOnInit"],
|
|
113
|
+
message: 'loadOnInit is only allowed when delivery is "canonical".',
|
|
114
|
+
});
|
|
115
|
+
}
|
|
102
116
|
});
|
|
103
117
|
|
|
104
118
|
export type CodegenConfigInput = z.input<typeof codegenConfigSchema>;
|
|
@@ -110,6 +124,18 @@ export function resolveDeliveryOutputDir(
|
|
|
110
124
|
return config.deliveryOutput ?? path.dirname(config.typesOutput);
|
|
111
125
|
}
|
|
112
126
|
|
|
127
|
+
const DEFAULT_DICTIONARY_BASENAME = "dictionary.generated.ts";
|
|
128
|
+
|
|
129
|
+
/** Resolved path for dictionary.generated.ts (explicit config or default next to typesOutput). */
|
|
130
|
+
export function resolveDictionaryOutputPath(
|
|
131
|
+
config: Pick<CodegenConfig, "typesOutput" | "dictionaryOutput">
|
|
132
|
+
): string {
|
|
133
|
+
return (
|
|
134
|
+
config.dictionaryOutput ??
|
|
135
|
+
path.join(path.dirname(config.typesOutput), DEFAULT_DICTIONARY_BASENAME)
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
113
139
|
export function formatCodegenConfigIssues(error: z.ZodError): string {
|
|
114
140
|
const issueLines = error.issues.map((issue) => {
|
|
115
141
|
const path = issue.path.length > 0 ? issue.path.join(".") : "(root)";
|
|
@@ -2,7 +2,7 @@ import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
|
2
2
|
import { tmpdir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { afterEach, describe, expect, it } from "vitest";
|
|
5
|
-
import { codegenConfigKeys } from "./codegen-config-schema.js";
|
|
5
|
+
import { codegenConfigKeys, resolveDictionaryOutputPath } from "./codegen-config-schema.js";
|
|
6
6
|
import { loadConfig, resolveDeliveryOutputDir, resolveLoadOnInit } from "./config.js";
|
|
7
7
|
import { resolveImportExtension } from "./paths.js";
|
|
8
8
|
|
|
@@ -42,6 +42,35 @@ describe("loadConfig", () => {
|
|
|
42
42
|
});
|
|
43
43
|
});
|
|
44
44
|
|
|
45
|
+
it("accepts split-by-locale config without dictionaryOutput", () => {
|
|
46
|
+
tempDir = mkdtempSync(join(tmpdir(), "xndrjs-i18n-config-"));
|
|
47
|
+
const { dictionaryOutput: _dictionaryOutput, ...configWithoutDictionaryOutput } =
|
|
48
|
+
validMultiConfig;
|
|
49
|
+
const configPath = writeConfig(tempDir, {
|
|
50
|
+
...configWithoutDictionaryOutput,
|
|
51
|
+
delivery: "split-by-locale",
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
expect(loadConfig(configPath)).toEqual({
|
|
55
|
+
...configWithoutDictionaryOutput,
|
|
56
|
+
delivery: "split-by-locale",
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("resolves dictionaryOutput next to typesOutput when omitted", () => {
|
|
61
|
+
expect(
|
|
62
|
+
resolveDictionaryOutputPath({
|
|
63
|
+
typesOutput: "generated/i18n-types.generated.ts",
|
|
64
|
+
})
|
|
65
|
+
).toBe("generated/dictionary.generated.ts");
|
|
66
|
+
expect(
|
|
67
|
+
resolveDictionaryOutputPath({
|
|
68
|
+
typesOutput: "generated/i18n-types.generated.ts",
|
|
69
|
+
dictionaryOutput: "custom/dictionary.generated.ts",
|
|
70
|
+
})
|
|
71
|
+
).toBe("custom/dictionary.generated.ts");
|
|
72
|
+
});
|
|
73
|
+
|
|
45
74
|
it("throws on invalid config JSON", () => {
|
|
46
75
|
tempDir = mkdtempSync(join(tmpdir(), "xndrjs-i18n-config-"));
|
|
47
76
|
const configPath = join(tempDir, "i18n.codegen.json");
|
|
@@ -207,6 +236,35 @@ describe("loadConfig", () => {
|
|
|
207
236
|
|
|
208
237
|
expect(() => loadConfig(configPath)).toThrow('Locale "fr" appears in both "eu" and "us"');
|
|
209
238
|
});
|
|
239
|
+
|
|
240
|
+
it("throws when loadOnInit is set for split-by-locale delivery", () => {
|
|
241
|
+
tempDir = mkdtempSync(join(tmpdir(), "xndrjs-i18n-config-"));
|
|
242
|
+
const configPath = writeConfig(tempDir, {
|
|
243
|
+
...validMultiConfig,
|
|
244
|
+
delivery: "split-by-locale",
|
|
245
|
+
loadOnInit: ["default"],
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
expect(() => loadConfig(configPath)).toThrow(
|
|
249
|
+
'loadOnInit is only allowed when delivery is "canonical".'
|
|
250
|
+
);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it("throws when loadOnInit is set for custom delivery", () => {
|
|
254
|
+
tempDir = mkdtempSync(join(tmpdir(), "xndrjs-i18n-config-"));
|
|
255
|
+
const configPath = writeConfig(tempDir, {
|
|
256
|
+
...validMultiConfig,
|
|
257
|
+
delivery: "custom",
|
|
258
|
+
deliveryArtifacts: {
|
|
259
|
+
eu: ["it"],
|
|
260
|
+
},
|
|
261
|
+
loadOnInit: ["default"],
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
expect(() => loadConfig(configPath)).toThrow(
|
|
265
|
+
'loadOnInit is only allowed when delivery is "canonical".'
|
|
266
|
+
);
|
|
267
|
+
});
|
|
210
268
|
});
|
|
211
269
|
|
|
212
270
|
describe("resolveLoadOnInit", () => {
|
|
@@ -230,6 +288,26 @@ describe("resolveLoadOnInit", () => {
|
|
|
230
288
|
'[Codegen Error] loadOnInit: namespace "missing" is not defined in namespaces config.'
|
|
231
289
|
);
|
|
232
290
|
});
|
|
291
|
+
|
|
292
|
+
it("treats all namespaces as lazy when delivery is split-by-locale", () => {
|
|
293
|
+
const config = { ...validMultiConfig, delivery: "split-by-locale" as const };
|
|
294
|
+
|
|
295
|
+
expect(resolveLoadOnInit(config, entries, false)).toEqual({
|
|
296
|
+
loadOnInitSet: new Set(),
|
|
297
|
+
lazyEntries: entries,
|
|
298
|
+
hasLazy: true,
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it("treats all namespaces as lazy when delivery is custom", () => {
|
|
303
|
+
const config = { ...validMultiConfig, delivery: "custom" as const };
|
|
304
|
+
|
|
305
|
+
expect(resolveLoadOnInit(config, entries, false)).toEqual({
|
|
306
|
+
loadOnInitSet: new Set(),
|
|
307
|
+
lazyEntries: entries,
|
|
308
|
+
hasLazy: true,
|
|
309
|
+
});
|
|
310
|
+
});
|
|
233
311
|
});
|
|
234
312
|
|
|
235
313
|
describe("resolveImportExtension", () => {
|
package/src/codegen/config.ts
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
formatCodegenConfigIssues,
|
|
7
7
|
} from "./codegen-config-schema.js";
|
|
8
8
|
|
|
9
|
-
export { resolveDeliveryOutputDir } from "./codegen-config-schema.js";
|
|
9
|
+
export { resolveDeliveryOutputDir, resolveDictionaryOutputPath } from "./codegen-config-schema.js";
|
|
10
10
|
|
|
11
11
|
/** Parses and validates `i18n.codegen.json`; first step of the codegen pipeline. */
|
|
12
12
|
export function loadConfig(configPath: string): CodegenConfig {
|
|
@@ -72,6 +72,15 @@ export function resolveLoadOnInit(
|
|
|
72
72
|
return { loadOnInitSet: all, lazyEntries: [], hasLazy: false };
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
const delivery = config.delivery ?? "canonical";
|
|
76
|
+
if (delivery !== "canonical") {
|
|
77
|
+
return {
|
|
78
|
+
loadOnInitSet: new Set(),
|
|
79
|
+
lazyEntries: entries,
|
|
80
|
+
hasLazy: entries.length > 0,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
75
84
|
const allNamespaces = new Set(entries.map((entry) => entry.namespace));
|
|
76
85
|
const loadOnInitSet = config.loadOnInit
|
|
77
86
|
? new Set(config.loadOnInit)
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
2
|
import {
|
|
3
|
+
formatDeliveryArtifactsBlock,
|
|
3
4
|
formatLocaleDeliveryAreaBlock,
|
|
4
5
|
getDeliveryAreaNames,
|
|
5
6
|
getDeliveryArtifactsIssues,
|
|
6
7
|
getDeliveryArtifactsPartitionIssues,
|
|
7
8
|
getDeliveryArtifactsStructureIssues,
|
|
9
|
+
getDeliveryArtifactsTypeName,
|
|
8
10
|
getLocaleDeliveryAreaMap,
|
|
9
11
|
} from "./delivery-artifacts.js";
|
|
10
12
|
|
|
@@ -139,4 +141,26 @@ describe("delivery-artifacts", () => {
|
|
|
139
141
|
"} as const satisfies Record<AppLocale, AppDeliveryArea>;\n\n"
|
|
140
142
|
);
|
|
141
143
|
});
|
|
144
|
+
|
|
145
|
+
it("formats delivery artifacts block for generated types", () => {
|
|
146
|
+
expect(getDeliveryArtifactsTypeName("AppDeliveryArea")).toBe("AppDeliveryArtifacts");
|
|
147
|
+
|
|
148
|
+
expect(
|
|
149
|
+
formatDeliveryArtifactsBlock(
|
|
150
|
+
{
|
|
151
|
+
eu: ["it", "fr"],
|
|
152
|
+
us: ["en-US"],
|
|
153
|
+
},
|
|
154
|
+
"DELIVERY_ARTIFACTS",
|
|
155
|
+
"AppLocale",
|
|
156
|
+
"AppDeliveryArea"
|
|
157
|
+
)
|
|
158
|
+
).toBe(
|
|
159
|
+
"export const DELIVERY_ARTIFACTS = {\n" +
|
|
160
|
+
' "eu": ["fr", "it"] as const,\n' +
|
|
161
|
+
' "us": ["en-US"] as const,\n' +
|
|
162
|
+
"} as const satisfies Record<AppDeliveryArea, readonly AppLocale[]>;\n\n" +
|
|
163
|
+
"export type AppDeliveryArtifacts = typeof DELIVERY_ARTIFACTS;\n\n"
|
|
164
|
+
);
|
|
165
|
+
});
|
|
142
166
|
});
|
|
@@ -122,3 +122,32 @@ export function formatLocaleDeliveryAreaBlock(
|
|
|
122
122
|
|
|
123
123
|
return `export const ${constName} = {\n${lines}\n} as const satisfies Record<${localeTypeName}, ${deliveryAreaTypeName}>;\n\n`;
|
|
124
124
|
}
|
|
125
|
+
|
|
126
|
+
export function getDeliveryArtifactsTypeName(deliveryAreaTypeName: string): string {
|
|
127
|
+
return deliveryAreaTypeName.endsWith("DeliveryArea")
|
|
128
|
+
? deliveryAreaTypeName.replace(/DeliveryArea$/, "DeliveryArtifacts")
|
|
129
|
+
: `${deliveryAreaTypeName}Artifacts`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function formatDeliveryArtifactsBlock(
|
|
133
|
+
deliveryArtifacts: DeliveryArtifactsMap,
|
|
134
|
+
constName: string,
|
|
135
|
+
localeTypeName: string,
|
|
136
|
+
deliveryAreaTypeName: string
|
|
137
|
+
): string {
|
|
138
|
+
const deliveryArtifactsTypeName = getDeliveryArtifactsTypeName(deliveryAreaTypeName);
|
|
139
|
+
const areaEntries = getDeliveryAreaNames(deliveryArtifacts)
|
|
140
|
+
.map((area) => {
|
|
141
|
+
const locales = [...deliveryArtifacts[area]!]
|
|
142
|
+
.sort()
|
|
143
|
+
.map((locale) => JSON.stringify(locale))
|
|
144
|
+
.join(", ");
|
|
145
|
+
return ` ${JSON.stringify(area)}: [${locales}] as const,`;
|
|
146
|
+
})
|
|
147
|
+
.join("\n");
|
|
148
|
+
|
|
149
|
+
return (
|
|
150
|
+
`export const ${constName} = {\n${areaEntries}\n} as const satisfies Record<${deliveryAreaTypeName}, readonly ${localeTypeName}[]>;\n\n` +
|
|
151
|
+
`export type ${deliveryArtifactsTypeName} = typeof ${constName};\n\n`
|
|
152
|
+
);
|
|
153
|
+
}
|