@xndrjs/i18n 0.7.0 → 0.8.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.
Files changed (69) hide show
  1. package/README.md +67 -872
  2. package/dist/codegen/index.d.ts +117 -51
  3. package/dist/codegen/index.js +1585 -93
  4. package/dist/codegen/index.js.map +1 -1
  5. package/dist/index.d.ts +100 -234
  6. package/dist/index.js +74 -638
  7. package/dist/index.js.map +1 -1
  8. package/dist/validation/index.d.ts +5 -0
  9. package/dist/validation/index.js.map +1 -1
  10. package/package.json +2 -2
  11. package/src/IcuTranslationProviderMulti.test.ts +18 -40
  12. package/src/IcuTranslationProviderMulti.ts +27 -37
  13. package/src/audit/audit-dictionaries.ts +1 -1
  14. package/src/audit/run-audit.test.ts +6 -7
  15. package/src/builder-load-registry.ts +26 -5
  16. package/src/builder-loaders.ts +8 -11
  17. package/src/builder-types.test.ts +11 -42
  18. package/src/builder.test.ts +82 -243
  19. package/src/builder.ts +8 -110
  20. package/src/codegen/codegen-config-schema.ts +82 -77
  21. package/src/codegen/config.test.ts +55 -143
  22. package/src/codegen/config.ts +10 -67
  23. package/src/codegen/dictionary-spec-contract.test.ts +28 -0
  24. package/src/codegen/dictionary-spec-contract.ts +138 -0
  25. package/src/codegen/emit/dictionary-schema-file.ts +7 -43
  26. package/src/codegen/emit/instance-file.test.ts +35 -52
  27. package/src/codegen/emit/instance-file.ts +170 -268
  28. package/src/codegen/emit/namespace-loaders-file.test.ts +72 -74
  29. package/src/codegen/emit/namespace-loaders-file.ts +147 -90
  30. package/src/codegen/emit/types-file.test.ts +20 -41
  31. package/src/codegen/emit/types-file.ts +48 -74
  32. package/src/codegen/generate-i18n-types.test.ts +163 -492
  33. package/src/codegen/generate-i18n-types.ts +7 -269
  34. package/src/codegen/paths.ts +0 -14
  35. package/src/codegen/project-locales-set-namespace.test.ts +44 -86
  36. package/src/codegen/read-dictionary.test.ts +27 -9
  37. package/src/codegen/read-dictionary.ts +18 -40
  38. package/src/codegen/regenerate-namespaces.ts +180 -0
  39. package/src/codegen/run-codegen.test.ts +230 -0
  40. package/src/codegen/run-codegen.ts +252 -0
  41. package/src/codegen/types.ts +0 -6
  42. package/src/codegen-config/build-config.ts +4 -23
  43. package/src/codegen-config/codegen-config.test.ts +10 -6
  44. package/src/codegen-config/index.ts +18 -3
  45. package/src/engine.ts +3 -27
  46. package/src/fetch-artifact.ts +16 -0
  47. package/src/i18n-handle.ts +202 -0
  48. package/src/index.ts +15 -34
  49. package/src/project-locales.ts +2 -2
  50. package/src/scope-multi.ts +1 -20
  51. package/src/scope-types.ts +14 -6
  52. package/src/scope.test.ts +3 -312
  53. package/src/serialized-state.test.ts +114 -0
  54. package/src/serialized-state.ts +28 -0
  55. package/src/setup/setup-i18n.test.ts +22 -33
  56. package/src/setup/setup-i18n.ts +22 -27
  57. package/src/types.ts +3 -3
  58. package/src/validation/index.ts +4 -0
  59. package/src/IcuTranslationProviderSingle.test.ts +0 -177
  60. package/src/IcuTranslationProviderSingle.ts +0 -114
  61. package/src/builder-multi.ts +0 -481
  62. package/src/codegen/emit/dictionary-file.test.ts +0 -215
  63. package/src/codegen/emit/dictionary-file.ts +0 -244
  64. package/src/deep-freeze.test.ts +0 -40
  65. package/src/deep-freeze.ts +0 -22
  66. package/src/patch-key.test.ts +0 -186
  67. package/src/patch-key.ts +0 -140
  68. package/src/scope-single.ts +0 -85
  69. package/src/single-builder.ts +0 -153
@@ -1,6 +1,8 @@
1
1
  import { z } from 'zod';
2
- import path from 'path';
3
- import fs from 'fs';
2
+ import path8 from 'path';
3
+ import fs3 from 'fs';
4
+ import { parse } from '@formatjs/icu-messageformat-parser';
5
+ import { parse as parse$1 } from 'yaml';
4
6
 
5
7
  // src/codegen/codegen-config-schema.ts
6
8
 
@@ -43,64 +45,135 @@ function getDeliveryArtifactsStructureIssues(deliveryArtifacts) {
43
45
  }
44
46
  return issues;
45
47
  }
48
+ function getDeliveryArtifactsPartitionIssues(deliveryArtifacts, requestLocales) {
49
+ const issues = [];
50
+ const artifactLocales = /* @__PURE__ */ new Set();
51
+ for (const locales of Object.values(deliveryArtifacts)) {
52
+ for (const locale of locales) {
53
+ artifactLocales.add(locale);
54
+ }
55
+ }
56
+ const missing = [...requestLocales].filter((locale) => !artifactLocales.has(locale)).sort();
57
+ if (missing.length > 0) {
58
+ issues.push({
59
+ path: ["deliveryArtifacts"],
60
+ message: `deliveryArtifacts is missing locales required by dictionaries and localeFallback: ${missing.join(", ")}`
61
+ });
62
+ }
63
+ const excess = [...artifactLocales].filter((locale) => !requestLocales.has(locale)).sort();
64
+ if (excess.length > 0) {
65
+ issues.push({
66
+ path: ["deliveryArtifacts"],
67
+ message: `deliveryArtifacts includes locales not required by dictionaries and localeFallback: ${excess.join(", ")}`
68
+ });
69
+ }
70
+ return issues;
71
+ }
72
+ function getDeliveryArtifactsIssues(deliveryArtifacts, requestLocales) {
73
+ return [
74
+ ...getDeliveryArtifactsStructureIssues(deliveryArtifacts),
75
+ ...getDeliveryArtifactsPartitionIssues(deliveryArtifacts, requestLocales)
76
+ ];
77
+ }
78
+ function getDeliveryAreaNames(deliveryArtifacts) {
79
+ return Object.keys(deliveryArtifacts).sort();
80
+ }
81
+ function getLocaleDeliveryAreaMap(deliveryArtifacts) {
82
+ const localeToArea = {};
83
+ for (const [area, locales] of Object.entries(deliveryArtifacts)) {
84
+ for (const locale of locales) {
85
+ localeToArea[locale] = area;
86
+ }
87
+ }
88
+ return localeToArea;
89
+ }
90
+ function formatLocaleDeliveryAreaBlock(deliveryArtifacts, constName, localeTypeName, deliveryAreaTypeName) {
91
+ const localeToArea = getLocaleDeliveryAreaMap(deliveryArtifacts);
92
+ const lines = Object.keys(localeToArea).sort().map((locale) => ` ${JSON.stringify(locale)}: ${JSON.stringify(localeToArea[locale])},`).join("\n");
93
+ return `export const ${constName} = {
94
+ ${lines}
95
+ } as const satisfies Record<${localeTypeName}, ${deliveryAreaTypeName}>;
96
+
97
+ `;
98
+ }
99
+ function getDeliveryArtifactsTypeName(deliveryAreaTypeName) {
100
+ return deliveryAreaTypeName.endsWith("DeliveryArea") ? deliveryAreaTypeName.replace(/DeliveryArea$/, "DeliveryArtifacts") : `${deliveryAreaTypeName}Artifacts`;
101
+ }
102
+ function formatDeliveryArtifactsBlock(deliveryArtifacts, constName, localeTypeName, deliveryAreaTypeName) {
103
+ const deliveryArtifactsTypeName = getDeliveryArtifactsTypeName(deliveryAreaTypeName);
104
+ const areaEntries = getDeliveryAreaNames(deliveryArtifacts).map((area) => {
105
+ const locales = [...deliveryArtifacts[area]].sort().map((locale) => JSON.stringify(locale)).join(", ");
106
+ return ` ${JSON.stringify(area)}: [${locales}] as const,`;
107
+ }).join("\n");
108
+ return `export const ${constName} = {
109
+ ${areaEntries}
110
+ } as const satisfies Record<${deliveryAreaTypeName}, readonly ${localeTypeName}[]>;
111
+
112
+ export type ${deliveryArtifactsTypeName} = typeof ${constName};
113
+
114
+ export type LocalesForDeliveryArea<A extends ${deliveryAreaTypeName}> = ${deliveryArtifactsTypeName}[A][number];
115
+
116
+ `;
117
+ }
118
+
119
+ // src/codegen-config/type-names.ts
120
+ function inferProjectName(dirName) {
121
+ const parts = dirName.split(/[-_]+/).filter(Boolean);
122
+ if (parts.length === 0) {
123
+ return "App";
124
+ }
125
+ return parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
126
+ }
127
+ function typeNamesForProject(project) {
128
+ return {
129
+ paramsTypeName: `${project}Params`,
130
+ schemaTypeName: `${project}Schema`,
131
+ localeTypeName: `${project}Locale`
132
+ };
133
+ }
46
134
 
47
135
  // src/codegen/codegen-config-schema.ts
48
136
  var localeFallbackSchema = z.record(z.string(), z.union([z.string(), z.null()]));
49
137
  var deliveryArtifactsSchema = z.record(z.string(), z.array(z.string().min(1)));
50
- var DELIVERY_MODES = ["canonical", "split-by-locale", "custom"];
138
+ var DELIVERY_MODES = ["split-by-locale", "custom"];
139
+ var LOADER_STRATEGIES = ["import", "fetch"];
140
+ var PROJECT_NAME_PATTERN = /^[A-Z][a-zA-Z0-9]*$/;
51
141
  var codegenConfigShape = {
52
- dictionary: z.string().min(1).optional(),
53
- namespaces: z.record(z.string(), z.string().min(1)).optional(),
54
- defaultNamespace: z.string().min(1).optional(),
55
- typesOutput: 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
- ),
59
- instanceOutput: z.string().min(1),
60
- dictionarySchemaOutput: z.string().min(1).optional(),
61
- loadOnInit: z.array(z.string().min(1)).optional(),
62
- namespaceLoadersOutput: z.string().min(1).optional(),
63
- importExtension: z.enum(SUPPORTED_IMPORT_EXTENSIONS).optional(),
64
- paramsTypeName: z.string().min(1),
65
- schemaTypeName: z.string().min(1),
66
- localeTypeName: z.string().min(1).optional(),
67
- localeFallbackConstName: z.string().min(1).optional(),
142
+ /** PascalCase project id — generates `{project}Params`, `{project}Schema`, `{project}Locale`. */
143
+ projectName: z.string().min(1).regex(PROJECT_NAME_PATTERN, 'projectName must be PascalCase (e.g. "MyApp")'),
144
+ namespaces: z.record(z.string(), z.string().min(1)),
145
+ /**
146
+ * Directory for generated TypeScript modules:
147
+ * `i18n-types.generated.ts`, `instance.generated.ts`,
148
+ * `namespace-loaders.generated.ts`, `dictionary-schema.generated.ts`.
149
+ */
150
+ codegenPath: z.string().min(1),
68
151
  localeFallback: localeFallbackSchema.optional(),
69
- factoryName: z.string().min(1).optional(),
70
- delivery: z.enum(DELIVERY_MODES).optional().default("canonical"),
152
+ delivery: z.enum(DELIVERY_MODES).optional().default("split-by-locale"),
71
153
  deliveryArtifacts: deliveryArtifactsSchema.optional(),
72
- deliveryOutput: z.string().min(1).optional()
154
+ /** Directory for delivery JSON (`translations/`). Defaults to {@link codegenPath}. */
155
+ artifactsPath: z.string().min(1).optional(),
156
+ /**
157
+ * How generated `namespaceLoaders` resolve artifacts.
158
+ * - `import` (default): dynamic `import()` — JSON is bundled; content updates need a rebuild.
159
+ * - `fetch`: runtime `fetchImpl({ locale, namespace, area? })` via required `createI18n({ fetchImpl })`.
160
+ * Codegen does not know URLs — mapping id → transport is an application concern.
161
+ */
162
+ loaderStrategy: z.enum(LOADER_STRATEGIES).optional().default("import")
73
163
  };
74
164
  var codegenConfigKeys = Object.keys(
75
165
  codegenConfigShape
76
166
  );
77
- z.object(codegenConfigShape).strict().superRefine((config, ctx) => {
78
- const hasDictionary = config.dictionary !== void 0;
79
- const hasNamespaces = config.namespaces !== void 0;
80
- if (hasDictionary === hasNamespaces) {
81
- ctx.addIssue({
82
- code: "custom",
83
- message: 'Specify exactly one of "dictionary" or "namespaces".'
84
- });
85
- }
86
- if (config.namespaces) {
87
- for (const namespace of Object.keys(config.namespaces)) {
88
- if (!IDENTIFIER_NAME_PATTERN.test(namespace)) {
89
- ctx.addIssue({
90
- code: "custom",
91
- path: ["namespaces", namespace],
92
- message: `Invalid namespace name "${namespace}" (${IDENTIFIER_NAME_REQUIREMENT}).`
93
- });
94
- }
167
+ var codegenConfigSchema = z.object(codegenConfigShape).strict().superRefine((config, ctx) => {
168
+ for (const namespace of Object.keys(config.namespaces)) {
169
+ if (!IDENTIFIER_NAME_PATTERN.test(namespace)) {
170
+ ctx.addIssue({
171
+ code: "custom",
172
+ path: ["namespaces", namespace],
173
+ message: `Invalid namespace name "${namespace}" (${IDENTIFIER_NAME_REQUIREMENT}).`
174
+ });
95
175
  }
96
176
  }
97
- if (config.defaultNamespace !== void 0 && !IDENTIFIER_NAME_PATTERN.test(config.defaultNamespace)) {
98
- ctx.addIssue({
99
- code: "custom",
100
- path: ["defaultNamespace"],
101
- message: `Invalid namespace name "${config.defaultNamespace}" (${IDENTIFIER_NAME_REQUIREMENT}).`
102
- });
103
- }
104
177
  if (config.delivery === "custom") {
105
178
  if (!config.deliveryArtifacts) {
106
179
  ctx.addIssue({
@@ -124,71 +197,1490 @@ z.object(codegenConfigShape).strict().superRefine((config, ctx) => {
124
197
  message: 'deliveryArtifacts is only allowed when delivery is "custom".'
125
198
  });
126
199
  }
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
- }
134
200
  });
135
- function resolveDeliveryOutputDir(config) {
136
- return config.deliveryOutput ?? path.dirname(config.typesOutput);
201
+ var GENERATED_BASENAMES = {
202
+ types: "i18n-types.generated.ts",
203
+ instance: "instance.generated.ts",
204
+ namespaceLoaders: "namespace-loaders.generated.ts",
205
+ dictionarySchema: "dictionary-schema.generated.ts"
206
+ };
207
+ var DEFAULT_FACTORY_NAME = "createI18n";
208
+ var DEFAULT_LOCALE_FALLBACK_CONST_NAME = "LOCALE_FALLBACK";
209
+ function resolveCodegenPaths(config) {
210
+ const typeNames = typeNamesForProject(config.projectName);
211
+ const codegenPath = config.codegenPath;
212
+ return {
213
+ codegenPath,
214
+ typesOutput: path8.join(codegenPath, GENERATED_BASENAMES.types),
215
+ instanceOutput: path8.join(codegenPath, GENERATED_BASENAMES.instance),
216
+ namespaceLoadersOutput: path8.join(codegenPath, GENERATED_BASENAMES.namespaceLoaders),
217
+ dictionarySchemaOutput: path8.join(codegenPath, GENERATED_BASENAMES.dictionarySchema),
218
+ artifactsPath: config.artifactsPath ?? codegenPath,
219
+ paramsTypeName: typeNames.paramsTypeName,
220
+ schemaTypeName: typeNames.schemaTypeName,
221
+ localeTypeName: typeNames.localeTypeName,
222
+ localeFallbackConstName: DEFAULT_LOCALE_FALLBACK_CONST_NAME,
223
+ factoryName: DEFAULT_FACTORY_NAME,
224
+ deliveryAreaTypeName: `${config.projectName}DeliveryArea`
225
+ };
137
226
  }
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);
227
+ function resolveArtifactsPath(config) {
228
+ return config.artifactsPath ?? config.codegenPath;
141
229
  }
142
-
143
- // src/codegen-config/type-names.ts
144
- function inferProjectName(dirName) {
145
- const parts = dirName.split(/[-_]+/).filter(Boolean);
146
- if (parts.length === 0) {
147
- return "App";
230
+ function formatCodegenConfigIssues(error) {
231
+ const issueLines = error.issues.map((issue) => {
232
+ const path10 = issue.path.length > 0 ? issue.path.join(".") : "(root)";
233
+ return ` - ${path10}: ${issue.message}`;
234
+ });
235
+ return [
236
+ "[Codegen Error] Invalid i18n.codegen.json:",
237
+ ...issueLines,
238
+ "",
239
+ `Allowed keys: ${codegenConfigKeys.join(", ")}`
240
+ ].join("\n");
241
+ }
242
+ function loadConfig(configPath) {
243
+ let raw;
244
+ try {
245
+ raw = JSON.parse(fs3.readFileSync(configPath, "utf8"));
246
+ } catch (error) {
247
+ const message = error instanceof Error ? error.message : String(error);
248
+ throw new Error(`[Codegen Error] Failed to parse config JSON (${configPath}): ${message}`);
148
249
  }
149
- return parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
250
+ const result = codegenConfigSchema.safeParse(raw);
251
+ if (!result.success) {
252
+ throw new Error(formatCodegenConfigIssues(result.error));
253
+ }
254
+ return result.data;
150
255
  }
151
- function typeNamesForProject(project) {
152
- return {
153
- paramsTypeName: `${project}Params`,
154
- schemaTypeName: `${project}Schema`,
155
- localeTypeName: `${project}Locale`
156
- };
256
+ function resolveNamespaces(config) {
257
+ return Object.entries(config.namespaces).map(([namespace, filePath]) => ({
258
+ namespace,
259
+ filePath
260
+ }));
157
261
  }
158
262
 
159
263
  // src/codegen-config/build-config.ts
160
264
  var GENERATED_DIR = "generated";
161
265
  var TRANSLATIONS_DIR = "translations";
162
- function buildCodegenConfig(mode, project) {
163
- const typeNames = typeNamesForProject(project);
164
- const base = {
165
- typesOutput: `${GENERATED_DIR}/i18n-types.generated.ts`,
166
- dictionaryOutput: `${GENERATED_DIR}/dictionary.generated.ts`,
167
- instanceOutput: `${GENERATED_DIR}/instance.generated.ts`,
168
- paramsTypeName: typeNames.paramsTypeName,
169
- schemaTypeName: typeNames.schemaTypeName,
170
- localeTypeName: typeNames.localeTypeName,
171
- factoryName: "createI18n"
172
- };
173
- if (mode === "single") {
174
- return {
175
- dictionary: `${TRANSLATIONS_DIR}/translations.json`,
176
- ...base
177
- };
178
- }
266
+ function buildCodegenConfig(project) {
179
267
  return {
268
+ projectName: project,
180
269
  namespaces: {
181
270
  default: `${TRANSLATIONS_DIR}/default.json`
182
271
  },
183
- ...base
272
+ delivery: "split-by-locale",
273
+ codegenPath: GENERATED_DIR
184
274
  };
185
275
  }
186
276
  function writeCodegenConfig(configPath, config) {
187
- fs.mkdirSync(path.dirname(configPath), { recursive: true });
188
- fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}
277
+ fs3.mkdirSync(path8.dirname(configPath), { recursive: true });
278
+ fs3.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}
279
+ `);
280
+ }
281
+ var GENERATED_FILE_BANNER = "// Automatically generated code. Do not edit manually.\n";
282
+ var DEFAULT_IMPORT_EXTENSION = "none";
283
+ function reportCodegenIssues(issues) {
284
+ for (const issue of issues) {
285
+ console.error(`[Codegen Error] ${issue.message}`);
286
+ }
287
+ }
288
+ function toImportPath(fromFile, toFile) {
289
+ const relative = path8.relative(path8.dirname(fromFile), toFile).replace(/\\/g, "/");
290
+ const withoutExt = relative.replace(/\.(json|ya?ml)$/i, "");
291
+ return withoutExt.startsWith(".") ? withoutExt : `./${withoutExt}`;
292
+ }
293
+ function toModuleBasename(filePath) {
294
+ return path8.basename(filePath).replace(/\.ts$/, "");
295
+ }
296
+ function importExtensionSuffix(importExtension) {
297
+ return importExtension === "none" ? "" : importExtension;
298
+ }
299
+ function toRelativeModuleImport(moduleBasename, importExtension) {
300
+ return `./${moduleBasename}${importExtensionSuffix(importExtension)}`;
301
+ }
302
+
303
+ // src/codegen/emit/dictionary-schema-file.ts
304
+ function formatVariableSpecObject(spec) {
305
+ const entries = Object.entries(spec);
306
+ if (entries.length === 0) {
307
+ return "{}";
308
+ }
309
+ const lines = entries.map(
310
+ ([name, type]) => ` ${JSON.stringify(name)}: ${JSON.stringify(type)},`
311
+ );
312
+ return `{
313
+ ${lines.join("\n")}
314
+ }`;
315
+ }
316
+ function formatDictionarySpecBlock(entries, argsSpecByNamespace) {
317
+ const requiredKeysLines = entries.map((entry) => {
318
+ const keys = Object.keys(argsSpecByNamespace[entry.namespace] ?? {}).map((key) => JSON.stringify(key)).join(", ");
319
+ return ` ${JSON.stringify(entry.namespace)}: [${keys}] as const,`;
320
+ }).join("\n");
321
+ const argsByKeyLines = entries.map((entry) => {
322
+ const argsByKey = argsSpecByNamespace[entry.namespace] ?? {};
323
+ const keyLines = Object.entries(argsByKey).map(([key, spec]) => ` ${JSON.stringify(key)}: ${formatVariableSpecObject(spec)},`).join("\n");
324
+ return ` ${JSON.stringify(entry.namespace)}: {
325
+ ${keyLines}
326
+ },`;
327
+ }).join("\n");
328
+ return `export const DICTIONARY_SPEC = {
329
+ mode: 'multi' as const,
330
+ requiredKeys: {
331
+ ${requiredKeysLines}
332
+ },
333
+ argsByKey: {
334
+ ${argsByKeyLines}
335
+ },
336
+ } satisfies DictionarySpec;
337
+ `;
338
+ }
339
+ function formatDictionarySchemaFile(schemaTypeName, typesModule, dictionarySpecBlock, importExtension) {
340
+ const typesImport = toRelativeModuleImport(typesModule, importExtension);
341
+ const partialImport = ` validateExternalDictionaryPartial as validateExternalDictionaryPartialCore,
342
+ validateExternalNamespacePartial as validateExternalNamespacePartialCore,
343
+ validateExternalKey as validateExternalKeyCore,
344
+ `;
345
+ const multiValidators = `export function validateExternalNamespacePartial<NS extends keyof ${schemaTypeName}>(
346
+ namespace: NS,
347
+ input: unknown,
348
+ ): ValidationResult<Partial<${schemaTypeName}[NS]>> {
349
+ return validateExternalNamespacePartialCore<Partial<${schemaTypeName}[NS]>>(
350
+ namespace as string,
351
+ input,
352
+ DICTIONARY_SPEC,
353
+ );
354
+ }
355
+
356
+ export function validateExternalKey<
357
+ NS extends keyof ${schemaTypeName},
358
+ K extends keyof ${schemaTypeName}[NS],
359
+ >(
360
+ namespace: NS,
361
+ key: K,
362
+ input: unknown,
363
+ ): ValidationResult<Pick<${schemaTypeName}[NS], K>> {
364
+ return validateExternalKeyCore<Pick<${schemaTypeName}[NS], K>>(
365
+ namespace as string,
366
+ key as string,
367
+ input,
368
+ DICTIONARY_SPEC,
369
+ );
370
+ }
371
+
372
+ `;
373
+ return `${GENERATED_FILE_BANNER}import {
374
+ ` + partialImport + ` type DictionarySpec,
375
+ type ValidationResult,
376
+ } from '@xndrjs/i18n/validation';
377
+ import type { ${schemaTypeName} } from '${typesImport}';
378
+
379
+ ${dictionarySpecBlock}
380
+ export function validateExternalDictionaryPartial(
381
+ input: unknown,
382
+ ): ValidationResult<Partial<${schemaTypeName}>> {
383
+ return validateExternalDictionaryPartialCore<Partial<${schemaTypeName}>>(
384
+ input,
385
+ DICTIONARY_SPEC,
386
+ );
387
+ }
388
+
389
+ ` + multiValidators;
390
+ }
391
+
392
+ // src/codegen/emit/instance-file.ts
393
+ var STATE_TYPE = `{ dictionary: InitialSchema; resources?: readonly (readonly [string, string])[] }`;
394
+ function formatInstanceFile(options) {
395
+ const {
396
+ typesOutputPath,
397
+ namespaceLoadersOutputPath,
398
+ paramsTypeName,
399
+ schemaTypeName,
400
+ localeTypeName,
401
+ localeFallbackConstName,
402
+ factoryName,
403
+ hasLocaleFallback,
404
+ hasLocaleType,
405
+ importExtension,
406
+ delivery,
407
+ localeDeliveryAreaConstName = "LOCALE_DELIVERY_AREA",
408
+ loaderStrategy = "import"
409
+ } = options;
410
+ const providerClass = "IcuTranslationProviderMulti";
411
+ const typesModule = toModuleBasename(typesOutputPath);
412
+ const typesImport = toRelativeModuleImport(typesModule, importExtension);
413
+ const loadersModule = toModuleBasename(namespaceLoadersOutputPath);
414
+ const loadersImport = toRelativeModuleImport(loadersModule, importExtension);
415
+ const schemaTypesImport = `import type { ${paramsTypeName}, ${schemaTypeName}, InitialSchema } from '${typesImport}';
416
+ `;
417
+ const typesImportLine = formatTypesValueImport({
418
+ hasLocaleType,
419
+ hasLocaleFallback,
420
+ localeTypeName,
421
+ localeFallbackConstName,
422
+ localeDeliveryAreaConstName,
423
+ delivery,
424
+ typesImport
425
+ });
426
+ const packageImports = formatPackageImports(providerClass, loaderStrategy);
427
+ const loadersImportLine = loaderStrategy === "fetch" ? `import { createNamespaceLoaders } from '${loadersImport}';
428
+ ` : `import { namespaceLoaders } from '${loadersImport}';
429
+ `;
430
+ const createFactoryBlock = formatCreateI18nFactory({
431
+ providerClass,
432
+ schemaTypeName,
433
+ paramsTypeName,
434
+ localeTypeName,
435
+ localeFallbackConstName,
436
+ factoryName,
437
+ hasLocaleFallback,
438
+ delivery,
439
+ localeDeliveryAreaConstName,
440
+ loaderStrategy
441
+ });
442
+ return `${GENERATED_FILE_BANNER}import {
443
+ ${packageImports},
444
+ } from '@xndrjs/i18n';
445
+ ` + schemaTypesImport + typesImportLine + loadersImportLine + `
446
+ ` + createFactoryBlock;
447
+ }
448
+ function formatTypesValueImport(options) {
449
+ const {
450
+ hasLocaleType,
451
+ hasLocaleFallback,
452
+ localeTypeName,
453
+ localeFallbackConstName,
454
+ localeDeliveryAreaConstName,
455
+ delivery,
456
+ typesImport
457
+ } = options;
458
+ if (!hasLocaleType && delivery !== "custom") {
459
+ return "";
460
+ }
461
+ const valueNames = [];
462
+ if (hasLocaleFallback) {
463
+ valueNames.push(localeFallbackConstName);
464
+ }
465
+ if (delivery === "custom") {
466
+ valueNames.push(localeDeliveryAreaConstName);
467
+ }
468
+ if (valueNames.length > 0 && hasLocaleType) {
469
+ return `import { ${valueNames.join(", ")}, type ${localeTypeName} } from '${typesImport}';
470
+ `;
471
+ }
472
+ if (valueNames.length > 0) {
473
+ return `import { ${valueNames.join(", ")} } from '${typesImport}';
474
+ `;
475
+ }
476
+ if (hasLocaleType) {
477
+ return `import type { ${localeTypeName} } from '${typesImport}';
478
+ `;
479
+ }
480
+ return "";
481
+ }
482
+ function formatCreateI18nFactory(options) {
483
+ const {
484
+ providerClass,
485
+ schemaTypeName,
486
+ paramsTypeName,
487
+ localeTypeName,
488
+ localeFallbackConstName,
489
+ factoryName,
490
+ hasLocaleFallback,
491
+ delivery,
492
+ localeDeliveryAreaConstName,
493
+ loaderStrategy
494
+ } = options;
495
+ const providerTypeArgs = hasLocaleFallback ? `${schemaTypeName}, ${paramsTypeName}, ${localeTypeName}, typeof ${localeFallbackConstName}` : `${schemaTypeName}, ${paramsTypeName}`;
496
+ const handleReturnType = `I18nHandle<${schemaTypeName}, ${paramsTypeName}, ${localeTypeName}>`;
497
+ const handleTypeArgs = `<${schemaTypeName}, ${paramsTypeName}, ${localeTypeName}>`;
498
+ const partitionForLocale = delivery === "custom" ? ` partitionForLocale: (locale) => ${localeDeliveryAreaConstName}[locale],
499
+ ` : ` partitionForLocale: (locale) => locale,
500
+ `;
501
+ const optionsType = loaderStrategy === "fetch" ? `{ fetchImpl: FetchArtifact; state?: ${STATE_TYPE}; onMissing?: OnMissingTranslation }` : `{ state?: ${STATE_TYPE}; onMissing?: OnMissingTranslation }`;
502
+ const optionsParam = loaderStrategy === "fetch" ? ` options: ${optionsType},
503
+ ` : ` options?: ${optionsType},
504
+ `;
505
+ const namespaceLoadersLine = loaderStrategy === "fetch" ? ` namespaceLoaders: createNamespaceLoaders(fetchImpl),
506
+ ` : ` namespaceLoaders,
507
+ `;
508
+ return `export function ${factoryName}(
509
+ ` + optionsParam + `): ${handleReturnType} {
510
+ ` + formatBody({
511
+ providerClass,
512
+ providerTypeArgs,
513
+ hasLocaleFallback,
514
+ localeFallbackConstName,
515
+ loaderStrategy
516
+ }) + ` engine.seedBuilderResources(normalized.resources);
517
+ return createI18nHandle${handleTypeArgs}(engine, {
518
+ ` + namespaceLoadersLine + partitionForLocale + ` } as I18nHandleOptions<${schemaTypeName}, ${localeTypeName}>);
519
+ }
520
+ `;
521
+ }
522
+ function formatBody(options) {
523
+ const {
524
+ providerClass,
525
+ providerTypeArgs,
526
+ hasLocaleFallback,
527
+ localeFallbackConstName,
528
+ loaderStrategy
529
+ } = options;
530
+ if (loaderStrategy === "fetch") {
531
+ const engineBlock2 = hasLocaleFallback ? ` const engine = new ${providerClass}<${providerTypeArgs}>(normalized.dictionary, {
532
+ localeFallback: ${localeFallbackConstName},
533
+ ...providerOptions,
534
+ });
535
+ ` : ` const engine = new ${providerClass}<${providerTypeArgs}>(normalized.dictionary, providerOptions);
536
+ `;
537
+ return ` const { fetchImpl, state, ...providerOptions } = options;
538
+ const normalized = normalizeI18nCreateInput(state);
539
+ ` + engineBlock2;
540
+ }
541
+ const engineBlock = hasLocaleFallback ? ` const engine = new ${providerClass}<${providerTypeArgs}>(normalized.dictionary, {
542
+ localeFallback: ${localeFallbackConstName},
543
+ ...providerOptions,
544
+ });
545
+ ` : ` const engine = new ${providerClass}<${providerTypeArgs}>(normalized.dictionary, providerOptions);
546
+ `;
547
+ return ` const { state, ...providerOptions } = options ?? {};
548
+ const normalized = normalizeI18nCreateInput(state);
549
+ ` + engineBlock;
550
+ }
551
+ function formatPackageImports(providerClass, loaderStrategy) {
552
+ const names = [
553
+ providerClass,
554
+ "createI18nHandle",
555
+ "normalizeI18nCreateInput",
556
+ "type I18nHandle",
557
+ "type I18nHandleOptions",
558
+ "type OnMissingTranslation"
559
+ ];
560
+ if (loaderStrategy === "fetch") {
561
+ names.push("type FetchArtifact");
562
+ }
563
+ return names.join(",\n ");
564
+ }
565
+ function formatDefaultNamespacesLiteral(lazyEntries) {
566
+ return [...lazyEntries].map((entry) => entry.namespace).sort().map((namespace) => JSON.stringify(namespace)).join(", ");
567
+ }
568
+ function formatPartitionedTypesImport(options, paramTypeName) {
569
+ const { schemaTypeName, typesModule, importExtension } = options;
570
+ return `import type { ${schemaTypeName}, LazyNamespace, ${paramTypeName} } from '${toRelativeModuleImport(typesModule, importExtension)}';
571
+
572
+ `;
573
+ }
574
+ function formatImportCase(loadersOutputPath, projectRoot, splitRelativePath) {
575
+ const importPath = toImportPath(loadersOutputPath, path8.resolve(projectRoot, splitRelativePath));
576
+ return `return import('${importPath}.json').then((m) => m.default);`;
577
+ }
578
+ function indentBlock(text, spaces) {
579
+ const pad = " ".repeat(spaces);
580
+ return text.split("\n").map((line) => line.length === 0 ? line : `${pad}${line}`).join("\n");
581
+ }
582
+ function formatImportNamespaceLoaderEntry(options) {
583
+ const {
584
+ namespace,
585
+ paramName,
586
+ partitionKeys,
587
+ splitPathsByNamespace,
588
+ loadersOutputPath,
589
+ projectRoot
590
+ } = options;
591
+ const caseBodies = partitionKeys.map((partitionKey) => {
592
+ const splitRelativePath = splitPathsByNamespace[namespace]?.[partitionKey];
593
+ if (!splitRelativePath) {
594
+ throw new Error(
595
+ `[Codegen Error] Missing split path for namespace "${namespace}", ${paramName} "${partitionKey}".`
596
+ );
597
+ }
598
+ const body = formatImportCase(loadersOutputPath, projectRoot, splitRelativePath);
599
+ return [`case ${JSON.stringify(partitionKey)}:`, ` ${body}`].join("\n");
600
+ });
601
+ const switchBody = [
602
+ ...caseBodies,
603
+ `default:`,
604
+ ` throw new Error(\`[i18n] No translation artifact for namespace ${JSON.stringify(namespace)} and ${paramName} "\${String(${paramName})}".\`);`
605
+ ].join("\n");
606
+ const entry = [
607
+ `${namespace}: (${paramName}) => {`,
608
+ ` switch (${paramName}) {`,
609
+ indentBlock(switchBody, 4),
610
+ ` }`,
611
+ `},`
612
+ ].join("\n");
613
+ return indentBlock(entry, 2);
614
+ }
615
+ function formatFetchNamespaceLoaderEntry(options) {
616
+ const { namespace, paramName, schemaTypeName, delivery } = options;
617
+ const nsLiteral = JSON.stringify(namespace);
618
+ const resourceId = delivery === "custom" ? `{ locale, namespace: ${nsLiteral}, area }` : `{ locale, namespace: ${nsLiteral} }`;
619
+ const signature = delivery === "custom" ? `(${paramName}, { locale })` : `(${paramName})`;
620
+ return indentBlock(
621
+ `${namespace}: ${signature} =>
622
+ fetchImpl(${resourceId}) as Promise<${schemaTypeName}[${nsLiteral}]>,`,
623
+ 4
624
+ );
625
+ }
626
+ function formatPartitionedNamespaceLoadersFile(options, params) {
627
+ const { partitionKeys, paramName, paramTypeName } = params;
628
+ const {
629
+ loadersOutputPath,
630
+ lazyEntries,
631
+ schemaTypeName,
632
+ projectRoot,
633
+ delivery,
634
+ splitPathsByNamespace = {}
635
+ } = options;
636
+ const loaderStrategy = options.loaderStrategy ?? "import";
637
+ const loaderValueType = `${schemaTypeName}[K]`;
638
+ const typesImport = formatPartitionedTypesImport(options, paramTypeName);
639
+ if (loaderStrategy === "fetch") {
640
+ const fetchLoadersType = `{
641
+ [K in LazyNamespace]: (${delivery === "custom" ? `${paramName}: ${paramTypeName}, context: { locale: string }` : `${paramName}: ${paramTypeName}`}) => Promise<${loaderValueType}>;
642
+ }`;
643
+ const loaderEntries2 = lazyEntries.map(
644
+ (entry) => formatFetchNamespaceLoaderEntry({
645
+ namespace: entry.namespace,
646
+ paramName,
647
+ schemaTypeName,
648
+ delivery
649
+ })
650
+ ).join("\n");
651
+ return `${GENERATED_FILE_BANNER}import type { FetchArtifact } from '@xndrjs/i18n';
652
+ ` + typesImport + `export type NamespaceLoaders = ${fetchLoadersType};
653
+
654
+ /** Build loaders that resolve artifacts via the injected {@link FetchArtifact} (resource id only). */
655
+ export function createNamespaceLoaders(fetchImpl: FetchArtifact): NamespaceLoaders {
656
+ return {
657
+ ${loaderEntries2}
658
+ };
659
+ }
660
+
661
+ export const defaultLazyNamespaces = [${formatDefaultNamespacesLiteral(lazyEntries)}] as const;
662
+ `;
663
+ }
664
+ const loaderEntries = lazyEntries.map(
665
+ (entry) => formatImportNamespaceLoaderEntry({
666
+ namespace: entry.namespace,
667
+ paramName,
668
+ partitionKeys,
669
+ splitPathsByNamespace,
670
+ loadersOutputPath,
671
+ projectRoot
672
+ })
673
+ ).join("\n");
674
+ const importLoadersType = `{
675
+ [K in LazyNamespace]: (${paramName}: ${paramTypeName}) => Promise<${loaderValueType}>;
676
+ }`;
677
+ return `${GENERATED_FILE_BANNER}` + typesImport + `export const namespaceLoaders: ${importLoadersType} = {
678
+ ${loaderEntries}
679
+ };
680
+
681
+ export const defaultLazyNamespaces = [${formatDefaultNamespacesLiteral(lazyEntries)}] as const;
682
+ `;
683
+ }
684
+ function formatNamespaceLoadersFile(options) {
685
+ if (options.delivery === "split-by-locale") {
686
+ return formatPartitionedNamespaceLoadersFile(options, {
687
+ partitionKeys: options.requestLocales ?? [],
688
+ paramName: "locale",
689
+ paramTypeName: options.localeTypeName
690
+ });
691
+ }
692
+ if (!options.deliveryAreaTypeName) {
693
+ throw new Error("[Codegen Error] deliveryAreaTypeName is required for custom delivery.");
694
+ }
695
+ return formatPartitionedNamespaceLoadersFile(options, {
696
+ partitionKeys: options.deliveryAreaNames ?? [],
697
+ paramName: "area",
698
+ paramTypeName: options.deliveryAreaTypeName
699
+ });
700
+ }
701
+
702
+ // src/resolve-locale.ts
703
+ function validateLocaleFallback(map) {
704
+ for (const startLocale of Object.keys(map)) {
705
+ const visited = /* @__PURE__ */ new Set();
706
+ let current = startLocale;
707
+ while (current !== void 0) {
708
+ if (visited.has(current)) {
709
+ throw new Error(`[i18n] Circular locale fallback detected involving "${current}"`);
710
+ }
711
+ visited.add(current);
712
+ const next = map[current];
713
+ if (next === void 0 || next === null) {
714
+ break;
715
+ }
716
+ current = next;
717
+ }
718
+ }
719
+ }
720
+ function resolveLocaleTemplate(localeByKey, requestedLocale, fallbackMap) {
721
+ const chain = [requestedLocale];
722
+ let currentLocale = requestedLocale;
723
+ while (true) {
724
+ const template = localeByKey?.[currentLocale];
725
+ if (template !== void 0) {
726
+ return { template, resolvedLocale: currentLocale, fallbackChain: chain };
727
+ }
728
+ if (!fallbackMap) {
729
+ return void 0;
730
+ }
731
+ const fallback = fallbackMap[currentLocale];
732
+ if (fallback === void 0 || fallback === null) {
733
+ return void 0;
734
+ }
735
+ if (chain.includes(fallback)) {
736
+ throw new Error(
737
+ `[i18n] Circular locale fallback detected: ${[...chain, fallback].join(" \u2192 ")}`
738
+ );
739
+ }
740
+ chain.push(fallback);
741
+ currentLocale = fallback;
742
+ }
743
+ }
744
+
745
+ // src/codegen/locale-fallback.ts
746
+ function collectRequestLocales(dictionaryLocales, fallback) {
747
+ const all = new Set(dictionaryLocales);
748
+ if (!fallback) {
749
+ return all;
750
+ }
751
+ for (const [locale, target] of Object.entries(fallback)) {
752
+ all.add(locale);
753
+ if (target !== null) {
754
+ all.add(target);
755
+ }
756
+ }
757
+ return all;
758
+ }
759
+ function getCodegenLocaleFallbackIssues(fallback, dictionaryLocales) {
760
+ const issues = [];
761
+ try {
762
+ validateLocaleFallback(fallback);
763
+ } catch (error) {
764
+ const message = error instanceof Error ? error.message : String(error);
765
+ issues.push({ path: ["localeFallback"], message });
766
+ }
767
+ for (const [locale, target] of Object.entries(fallback)) {
768
+ if (target !== null && !(target in fallback) && !dictionaryLocales.has(target)) {
769
+ issues.push({
770
+ path: ["localeFallback", locale],
771
+ message: `localeFallback: "${locale}" points to "${target}" which is not defined in the fallback map or dictionary locales`
772
+ });
773
+ }
774
+ }
775
+ return issues;
776
+ }
777
+ function formatLocaleFallbackBlock(fallback, constName, typeName) {
778
+ const lines = Object.entries(fallback).map(
779
+ ([locale, target]) => ` ${JSON.stringify(locale)}: ${target === null ? "null" : JSON.stringify(target)},`
780
+ ).join("\n");
781
+ return `export const ${constName} = {
782
+ ${lines}
783
+ } as const satisfies Record<string, string | null>;
784
+
785
+ export type ${typeName} = typeof ${constName};
786
+
787
+ `;
788
+ }
789
+
790
+ // src/codegen/emit/types-file.ts
791
+ function formatLazyTypesBlock(lazyEntries) {
792
+ const lazyUnion = lazyEntries.length > 0 ? lazyEntries.map((entry) => `'${entry.namespace}'`).join(" | ") : "never";
793
+ return `export type LazyNamespace = ${lazyUnion};
794
+ /** Empty cold-start schema \u2014 namespaces arrive via \`namespaceLoaders\`. */
795
+ export type InitialSchema = Record<string, never>;
796
+
797
+ `;
798
+ }
799
+ function formatLocaleTemplateType(localeTypeName, hasLocaleUnion) {
800
+ if (!hasLocaleUnion) {
801
+ return "Partial<Record<string, string>>";
802
+ }
803
+ return `Partial<Record<${localeTypeName}, string>>`;
804
+ }
805
+ function formatTypesFile(options) {
806
+ const {
807
+ entries,
808
+ paramsTypeName,
809
+ schemaTypeName,
810
+ localeTypeName,
811
+ localeFallbackConstName,
812
+ localeFallbackTypeName,
813
+ localeFallback,
814
+ paramsByNamespace,
815
+ requestLocales,
816
+ deliveryAreaTypeName,
817
+ deliveryAreaNames,
818
+ deliveryArtifacts,
819
+ localeDeliveryAreaConstName = "LOCALE_DELIVERY_AREA",
820
+ lazyEntries
821
+ } = options;
822
+ const hasLocaleUnion = requestLocales.length > 0;
823
+ const localeTemplateType = formatLocaleTemplateType(localeTypeName, hasLocaleUnion);
824
+ const localesConstName = `${localeTypeName}s`;
825
+ const localeBlock = hasLocaleUnion ? `${localeFallback ? formatLocaleFallbackBlock(localeFallback, localeFallbackConstName, localeFallbackTypeName) : ""}export const ${localesConstName} = [${requestLocales.map((locale) => JSON.stringify(locale)).join(", ")}] as const;
826
+ export type ${localeTypeName} = (typeof ${localesConstName})[number];
827
+
828
+ ` : "";
829
+ const deliveryAreasConstName = deliveryAreaTypeName ? `${deliveryAreaTypeName}s` : void 0;
830
+ const deliveryAreaBlock = deliveryAreaTypeName && deliveryAreasConstName && deliveryAreaNames && deliveryAreaNames.length > 0 ? `export const ${deliveryAreasConstName} = [${deliveryAreaNames.map((area) => JSON.stringify(area)).join(", ")}] as const;
831
+ export type ${deliveryAreaTypeName} = (typeof ${deliveryAreasConstName})[number];
832
+
833
+ ` + (deliveryArtifacts ? formatDeliveryArtifactsBlock(
834
+ deliveryArtifacts,
835
+ "DELIVERY_ARTIFACTS",
836
+ localeTypeName,
837
+ deliveryAreaTypeName
838
+ ) + formatLocaleDeliveryAreaBlock(
839
+ deliveryArtifacts,
840
+ localeDeliveryAreaConstName,
841
+ localeTypeName,
842
+ deliveryAreaTypeName
843
+ ) : "") : "";
844
+ const namespaceBlocks = entries.map((entry) => {
845
+ const keyTypes = paramsByNamespace[entry.namespace] ?? {};
846
+ const lines = Object.entries(keyTypes).map(([key, type]) => ` ${key}: ${type};`).join("\n");
847
+ return ` ${entry.namespace}: {
848
+ ${lines}
849
+ };`;
850
+ }).join("\n");
851
+ const paramsBlock = `export type ${paramsTypeName} = {
852
+ ${namespaceBlocks}
853
+ };`;
854
+ const schemaLines = entries.map((entry) => {
855
+ const keyTypes = paramsByNamespace[entry.namespace] ?? {};
856
+ const lines = Object.keys(keyTypes).map((key) => ` ${key}: ${localeTemplateType};`).join("\n");
857
+ return ` ${entry.namespace}: {
858
+ ${lines}
859
+ };`;
860
+ }).join("\n");
861
+ const schemaBlock = `export type ${schemaTypeName} = {
862
+ ${schemaLines}
863
+ };`;
864
+ const lazyTypesBlock = formatLazyTypesBlock(lazyEntries);
865
+ return `${GENERATED_FILE_BANNER}export const I18N_MODE = 'multi' as const;
866
+
867
+ ${localeBlock}${deliveryAreaBlock}${paramsBlock}
868
+
869
+ ${schemaBlock}
870
+ ${lazyTypesBlock}`;
871
+ }
872
+
873
+ // src/icu/extract-variables.ts
874
+ function isPluralOrOrdinalNode(node) {
875
+ return node.type === 6 && "pluralType" in node && Boolean(node.pluralType);
876
+ }
877
+ function hasVariableName(node) {
878
+ return "value" in node && typeof node.value === "string";
879
+ }
880
+ function addVariableMeta(variables, name, type, role) {
881
+ const existing = variables[name];
882
+ if (!existing) {
883
+ variables[name] = { type, roles: /* @__PURE__ */ new Set([role]) };
884
+ return;
885
+ }
886
+ existing.roles.add(role);
887
+ existing.type = mergeVariableTypes(existing.type, type);
888
+ }
889
+ function mergeVariableTypes(left, right) {
890
+ if (left === right) {
891
+ return left;
892
+ }
893
+ if (left === "string" && right === "number" || left === "number" && right === "string") {
894
+ return "number";
895
+ }
896
+ return right;
897
+ }
898
+ function extractVariableMeta(nodes) {
899
+ const variables = {};
900
+ const walk = (walkNodes) => {
901
+ for (const node of walkNodes) {
902
+ if (node.type === 1 && hasVariableName(node)) {
903
+ addVariableMeta(variables, node.value, "string", "simple");
904
+ } else if (node.type === 2 && hasVariableName(node)) {
905
+ addVariableMeta(variables, node.value, "number", "number");
906
+ } else if (isPluralOrOrdinalNode(node) && hasVariableName(node)) {
907
+ const role = node.pluralType === "ordinal" ? "selectordinal" : "plural";
908
+ addVariableMeta(variables, node.value, "number", role);
909
+ } else if (node.type === 3 && hasVariableName(node)) {
910
+ addVariableMeta(variables, node.value, "date", "date");
911
+ } else if (node.type === 4 && hasVariableName(node)) {
912
+ addVariableMeta(variables, node.value, "date", "time");
913
+ } else if (node.type === 5 && hasVariableName(node)) {
914
+ addVariableMeta(variables, node.value, "string", "select");
915
+ }
916
+ if ("options" in node && node.options) {
917
+ for (const option of Object.values(node.options)) {
918
+ walk(option.value);
919
+ }
920
+ }
921
+ if ("children" in node && Array.isArray(node.children)) {
922
+ walk(node.children);
923
+ }
924
+ }
925
+ };
926
+ walk(nodes);
927
+ return variables;
928
+ }
929
+ function inferMergedVariableType(roles) {
930
+ const hasPlural = roles.has("plural");
931
+ const hasSelectordinal = roles.has("selectordinal");
932
+ const hasSelect = roles.has("select");
933
+ const hasSimple = roles.has("simple");
934
+ const hasNumber = roles.has("number");
935
+ const hasDate = roles.has("date");
936
+ const hasTime = roles.has("time");
937
+ if (hasPlural && hasSelectordinal) {
938
+ return "CONFLICT";
939
+ }
940
+ if (hasSelect && (hasPlural || hasSelectordinal || hasNumber)) {
941
+ return "CONFLICT";
942
+ }
943
+ if (hasPlural && hasSelect) {
944
+ return "CONFLICT";
945
+ }
946
+ if (hasSelectordinal && hasSelect) {
947
+ return "CONFLICT";
948
+ }
949
+ if (hasDate || hasTime) {
950
+ if (hasPlural || hasSelect || hasSelectordinal || hasSimple || hasNumber) {
951
+ return "CONFLICT";
952
+ }
953
+ return "date";
954
+ }
955
+ if (hasPlural || hasSelectordinal || hasNumber) {
956
+ return "number";
957
+ }
958
+ if (hasSelect || hasSimple) {
959
+ return "string";
960
+ }
961
+ return "string";
962
+ }
963
+ function mergeVariableMetaAcrossLocales(localeMetas) {
964
+ const keySignatures = localeMetas.map((meta) => Object.keys(meta).sort().join(","));
965
+ if (new Set(keySignatures).size > 1) {
966
+ return {
967
+ ok: false,
968
+ message: `Inconsistent ICU variable names across locales (keys: ${keySignatures.join(" vs ")})`
969
+ };
970
+ }
971
+ const merged = {};
972
+ const variableNames = new Set(localeMetas.flatMap((meta) => Object.keys(meta)));
973
+ for (const variableName of variableNames) {
974
+ const combinedRoles = /* @__PURE__ */ new Set();
975
+ for (const meta of localeMetas) {
976
+ for (const role of meta[variableName]?.roles ?? []) {
977
+ combinedRoles.add(role);
978
+ }
979
+ }
980
+ const mergedType = inferMergedVariableType(combinedRoles);
981
+ if (mergedType === "CONFLICT") {
982
+ const rolesByLocale = localeMetas.map((meta) => {
983
+ const entry = meta[variableName];
984
+ if (!entry) {
985
+ return null;
986
+ }
987
+ return [...entry.roles].sort().join("|");
988
+ }).filter((value) => value !== null);
989
+ return {
990
+ ok: false,
991
+ message: `Incompatible ICU variable "${variableName}" across locales (roles: ${rolesByLocale.join(" vs ")})`
992
+ };
993
+ }
994
+ merged[variableName] = mergedType;
995
+ }
996
+ return { ok: true, merged };
997
+ }
998
+
999
+ // src/project-locales.ts
1000
+ function classifyAreaLocales(areaLocales, localeFallback) {
1001
+ const areaSet = new Set(areaLocales);
1002
+ const full = [];
1003
+ const preserve = [];
1004
+ for (const locale of areaLocales) {
1005
+ const fallback = localeFallback?.[locale];
1006
+ if (fallback !== null && fallback !== void 0 && areaSet.has(fallback)) {
1007
+ preserve.push(locale);
1008
+ } else {
1009
+ full.push(locale);
1010
+ }
1011
+ }
1012
+ return { full, preserve };
1013
+ }
1014
+ function projectKeyDictionary(dictionary, locales, localeFallback) {
1015
+ const targetLocales = [...new Set(locales)];
1016
+ const result = {};
1017
+ for (const [key, localesByKey] of Object.entries(dictionary)) {
1018
+ const projected = {};
1019
+ for (const locale of targetLocales) {
1020
+ const resolved = resolveLocaleTemplate(localesByKey, locale, localeFallback);
1021
+ if (resolved !== void 0) {
1022
+ projected[locale] = resolved.template;
1023
+ }
1024
+ }
1025
+ if (Object.keys(projected).length > 0) {
1026
+ result[key] = projected;
1027
+ }
1028
+ }
1029
+ return result;
1030
+ }
1031
+ function projectNamespaceLocalesCore(dictionary, locales, localeFallback) {
1032
+ return projectKeyDictionary(dictionary, locales, localeFallback);
1033
+ }
1034
+ function projectNamespaceForDeliveryAreaCore(dictionary, areaLocales, localeFallback) {
1035
+ const { full, preserve } = classifyAreaLocales(areaLocales, localeFallback);
1036
+ const fullProjection = full.length > 0 ? projectKeyDictionary(dictionary, full, localeFallback) : void 0;
1037
+ const result = {};
1038
+ for (const [key, localesByKey] of Object.entries(dictionary)) {
1039
+ const projected = {};
1040
+ if (fullProjection !== void 0) {
1041
+ const fullEntry = fullProjection[key];
1042
+ if (fullEntry !== void 0) {
1043
+ Object.assign(projected, fullEntry);
1044
+ }
1045
+ }
1046
+ for (const locale of preserve) {
1047
+ const template = localesByKey[locale];
1048
+ if (template !== void 0) {
1049
+ projected[locale] = template;
1050
+ }
1051
+ }
1052
+ if (Object.keys(projected).length > 0) {
1053
+ result[key] = projected;
1054
+ }
1055
+ }
1056
+ return result;
1057
+ }
1058
+ function writeFileIfChanged(absolutePath, content) {
1059
+ if (fs3.existsSync(absolutePath)) {
1060
+ const currentContent = fs3.readFileSync(absolutePath, "utf8");
1061
+ if (currentContent === content) {
1062
+ return false;
1063
+ }
1064
+ }
1065
+ fs3.mkdirSync(path8.dirname(absolutePath), { recursive: true });
1066
+ fs3.writeFileSync(absolutePath, content);
1067
+ return true;
1068
+ }
1069
+
1070
+ // src/codegen/read-dictionary.ts
1071
+ function getDictionaryFormat(filePath) {
1072
+ const extension = path8.extname(filePath).toLowerCase();
1073
+ if (extension === ".json") {
1074
+ return "json";
1075
+ }
1076
+ if (extension === ".yaml" || extension === ".yml") {
1077
+ return "yaml";
1078
+ }
1079
+ return null;
1080
+ }
1081
+ function resolveSplitJsonPath(sourcePath, locale, generatedDirRelative) {
1082
+ const baseName = path8.basename(sourcePath, path8.extname(sourcePath));
1083
+ return path8.join(generatedDirRelative, "translations", `${baseName}.${locale}.json`).replace(/\\/g, "/");
1084
+ }
1085
+ function resolveAreaJsonPath(sourcePath, area, generatedDirRelative) {
1086
+ const baseName = path8.basename(sourcePath, path8.extname(sourcePath));
1087
+ return path8.join(generatedDirRelative, "translations", `${baseName}.${area}.json`).replace(/\\/g, "/");
1088
+ }
1089
+ function splitDictionaryByLocale(dictionary, locales, localeFallback) {
1090
+ const byLocale = {};
1091
+ for (const locale of locales) {
1092
+ byLocale[locale] = projectNamespaceLocalesCore(dictionary, [locale], localeFallback);
1093
+ }
1094
+ return byLocale;
1095
+ }
1096
+ function splitDictionaryByDeliveryArea(dictionary, deliveryArtifacts, localeFallback) {
1097
+ const byArea = {};
1098
+ for (const [area, areaLocales] of Object.entries(deliveryArtifacts)) {
1099
+ byArea[area] = projectNamespaceForDeliveryAreaCore(dictionary, areaLocales, localeFallback);
1100
+ }
1101
+ return byArea;
1102
+ }
1103
+ function assertDictionaryShape(value, context) {
1104
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
1105
+ throw new Error(`[Codegen Error] ${context} must be a plain object.`);
1106
+ }
1107
+ for (const [key, localesByKey] of Object.entries(value)) {
1108
+ if (!IDENTIFIER_NAME_PATTERN.test(key)) {
1109
+ throw new Error(
1110
+ `[Codegen Error] ${context}: invalid key "${key}" (${IDENTIFIER_NAME_REQUIREMENT}).`
1111
+ );
1112
+ }
1113
+ if (localesByKey === null || typeof localesByKey !== "object" || Array.isArray(localesByKey)) {
1114
+ throw new Error(`[Codegen Error] ${context}: key "${key}" must map locales to strings.`);
1115
+ }
1116
+ for (const [locale, template] of Object.entries(localesByKey)) {
1117
+ if (typeof template !== "string") {
1118
+ throw new Error(
1119
+ `[Codegen Error] ${context}: key "${key}", locale "${locale}" must be a string.`
1120
+ );
1121
+ }
1122
+ }
1123
+ }
1124
+ }
1125
+ function readDictionaryFile(absolutePath) {
1126
+ const format = getDictionaryFormat(absolutePath);
1127
+ if (!format) {
1128
+ throw new Error(
1129
+ `[Codegen Error] Unsupported dictionary format "${path8.extname(absolutePath)}" for ${absolutePath}. Use .json, .yaml, or .yml.`
1130
+ );
1131
+ }
1132
+ const source = fs3.readFileSync(absolutePath, "utf8");
1133
+ let parsed;
1134
+ try {
1135
+ parsed = format === "json" ? JSON.parse(source) : parse$1(source);
1136
+ } catch (error) {
1137
+ const message = error instanceof Error ? error.message : String(error);
1138
+ throw new Error(`[Codegen Error] Failed to parse dictionary ${absolutePath}: ${message}`);
1139
+ }
1140
+ assertDictionaryShape(parsed, `Dictionary file ${absolutePath}`);
1141
+ return parsed;
1142
+ }
1143
+ function writeCompiledJson(absoluteJsonPath, dictionary) {
1144
+ return writeFileIfChanged(absoluteJsonPath, `${JSON.stringify(dictionary, null, 2)}
189
1145
  `);
190
1146
  }
1147
+ function prepareDictionaryEntries(projectRoot, entries, generatedDirRelative, options) {
1148
+ const delivery = options.delivery;
1149
+ const { dictionariesByNamespace, localeFallback, requestLocales, deliveryArtifacts } = options;
1150
+ const resolvedEntries = [];
1151
+ const splitPathsByNamespace = {};
1152
+ const compiledFiles = [];
1153
+ if (delivery === "split-by-locale" && (!requestLocales || requestLocales.length === 0)) {
1154
+ throw new Error(
1155
+ "[Codegen Error] split-by-locale delivery requires at least one request locale."
1156
+ );
1157
+ }
1158
+ if (delivery === "custom" && (!deliveryArtifacts || Object.keys(deliveryArtifacts).length === 0)) {
1159
+ throw new Error("[Codegen Error] custom delivery requires deliveryArtifacts.");
1160
+ }
1161
+ for (const entry of entries) {
1162
+ const sourceAbsolutePath = path8.resolve(projectRoot, entry.filePath);
1163
+ const format = getDictionaryFormat(entry.filePath);
1164
+ if (!format) {
1165
+ throw new Error(
1166
+ `[Codegen Error] Namespace "${entry.namespace}" uses unsupported dictionary extension in "${entry.filePath}". Use .json, .yaml, or .yml.`
1167
+ );
1168
+ }
1169
+ const dictionary = dictionariesByNamespace[entry.namespace];
1170
+ if (!dictionary) {
1171
+ throw new Error(
1172
+ `[Codegen Error] Missing parsed dictionary for namespace "${entry.namespace}".`
1173
+ );
1174
+ }
1175
+ if (delivery === "split-by-locale") {
1176
+ const splitPaths2 = {};
1177
+ const dictionariesByLocale = splitDictionaryByLocale(
1178
+ dictionary,
1179
+ requestLocales,
1180
+ localeFallback
1181
+ );
1182
+ for (const locale of requestLocales) {
1183
+ const splitRelativePath = resolveSplitJsonPath(
1184
+ entry.filePath,
1185
+ locale,
1186
+ generatedDirRelative
1187
+ );
1188
+ const splitAbsolutePath = path8.resolve(projectRoot, splitRelativePath);
1189
+ const wroteFile = writeCompiledJson(splitAbsolutePath, dictionariesByLocale[locale]);
1190
+ if (wroteFile) {
1191
+ compiledFiles.push(
1192
+ `${path8.relative(projectRoot, sourceAbsolutePath)} \u2192 ${splitRelativePath}`
1193
+ );
1194
+ }
1195
+ splitPaths2[locale] = splitRelativePath;
1196
+ }
1197
+ splitPathsByNamespace[entry.namespace] = splitPaths2;
1198
+ resolvedEntries.push(entry);
1199
+ continue;
1200
+ }
1201
+ const splitPaths = {};
1202
+ const dictionariesByArea = splitDictionaryByDeliveryArea(
1203
+ dictionary,
1204
+ deliveryArtifacts,
1205
+ localeFallback
1206
+ );
1207
+ for (const area of Object.keys(deliveryArtifacts).sort()) {
1208
+ const areaRelativePath = resolveAreaJsonPath(entry.filePath, area, generatedDirRelative);
1209
+ const areaAbsolutePath = path8.resolve(projectRoot, areaRelativePath);
1210
+ const wroteFile = writeCompiledJson(areaAbsolutePath, dictionariesByArea[area]);
1211
+ if (wroteFile) {
1212
+ compiledFiles.push(
1213
+ `${path8.relative(projectRoot, sourceAbsolutePath)} \u2192 ${areaRelativePath}`
1214
+ );
1215
+ }
1216
+ splitPaths[area] = areaRelativePath;
1217
+ }
1218
+ splitPathsByNamespace[entry.namespace] = splitPaths;
1219
+ resolvedEntries.push(entry);
1220
+ }
1221
+ return { resolvedEntries, splitPathsByNamespace, compiledFiles };
1222
+ }
1223
+
1224
+ // src/codegen/icu-analysis.ts
1225
+ function paramsTypeForVariables(variables) {
1226
+ const keys = Object.keys(variables);
1227
+ if (keys.length === 0) {
1228
+ return "never";
1229
+ }
1230
+ const props = keys.map((key) => {
1231
+ const type = variables[key] === "date" ? "Date | number" : variables[key];
1232
+ return `${key}: ${type}`;
1233
+ });
1234
+ return `{ ${props.join("; ")} }`;
1235
+ }
1236
+ function analyzeDictionaries(projectRoot, entries) {
1237
+ const paramsByNamespace = {};
1238
+ const argsSpecByNamespace = {};
1239
+ const locales = /* @__PURE__ */ new Set();
1240
+ const dictionariesByNamespace = {};
1241
+ let hasErrors = false;
1242
+ for (const entry of entries) {
1243
+ const absolutePath = path8.resolve(projectRoot, entry.filePath);
1244
+ if (!fs3.existsSync(absolutePath)) {
1245
+ console.error(
1246
+ `[Codegen Error] Dictionary file not found for namespace "${entry.namespace}": ${absolutePath}`
1247
+ );
1248
+ hasErrors = true;
1249
+ continue;
1250
+ }
1251
+ const dictionary = readDictionaryFile(absolutePath);
1252
+ dictionariesByNamespace[entry.namespace] = dictionary;
1253
+ paramsByNamespace[entry.namespace] = {};
1254
+ argsSpecByNamespace[entry.namespace] = {};
1255
+ for (const [key, localesByKey] of Object.entries(dictionary)) {
1256
+ const localeMetas = [];
1257
+ for (const locale of Object.keys(localesByKey)) {
1258
+ locales.add(locale);
1259
+ }
1260
+ for (const [locale, template] of Object.entries(localesByKey)) {
1261
+ try {
1262
+ const ast = parse(template);
1263
+ localeMetas.push(extractVariableMeta(ast));
1264
+ } catch (error) {
1265
+ hasErrors = true;
1266
+ const message = error instanceof Error ? error.message : String(error);
1267
+ console.error(
1268
+ `[Codegen Error] ICU syntax error \u2014 namespace "${entry.namespace}", key "${key}", locale "${locale}": ${message}`
1269
+ );
1270
+ }
1271
+ }
1272
+ if (localeMetas.length === 0) {
1273
+ continue;
1274
+ }
1275
+ const mergedVariables = mergeVariableMetaAcrossLocales(localeMetas);
1276
+ if (!mergedVariables.ok) {
1277
+ hasErrors = true;
1278
+ console.error(
1279
+ `[Codegen Error] ${mergedVariables.message} \u2014 namespace "${entry.namespace}", key "${key}"`
1280
+ );
1281
+ continue;
1282
+ }
1283
+ const variables = mergedVariables.merged;
1284
+ paramsByNamespace[entry.namespace][key] = paramsTypeForVariables(variables);
1285
+ argsSpecByNamespace[entry.namespace][key] = variables;
1286
+ }
1287
+ }
1288
+ if (hasErrors) {
1289
+ return { ok: false };
1290
+ }
1291
+ return {
1292
+ ok: true,
1293
+ analysis: { paramsByNamespace, argsSpecByNamespace, locales, dictionariesByNamespace }
1294
+ };
1295
+ }
1296
+
1297
+ // src/codegen/locale-policy.ts
1298
+ function buildRequiredLocales(dictionaryLocales, configFallback) {
1299
+ return [...collectRequestLocales(dictionaryLocales, configFallback)].sort();
1300
+ }
1301
+ function enrichLocaleFallback(dictionaryLocales, configFallback) {
1302
+ const enriched = { ...configFallback };
1303
+ for (const locale of buildRequiredLocales(dictionaryLocales, configFallback)) {
1304
+ if (!(locale in enriched)) {
1305
+ enriched[locale] = null;
1306
+ }
1307
+ }
1308
+ const sortedEntries = Object.entries(enriched).sort(
1309
+ ([left], [right]) => left.localeCompare(right)
1310
+ );
1311
+ return Object.fromEntries(sortedEntries);
1312
+ }
1313
+
1314
+ // src/codegen/run-codegen.ts
1315
+ function resolveRunCodegenInput(input) {
1316
+ if (typeof input === "string" || input === void 0) {
1317
+ const configPath2 = path8.resolve(
1318
+ process.cwd(),
1319
+ typeof input === "string" ? input : "i18n/i18n.codegen.json"
1320
+ );
1321
+ if (!fs3.existsSync(configPath2)) {
1322
+ throw new Error(`[Codegen Error] Config file not found: ${configPath2}`);
1323
+ }
1324
+ return {
1325
+ projectRoot: path8.dirname(configPath2),
1326
+ config: loadConfig(configPath2),
1327
+ log: true
1328
+ };
1329
+ }
1330
+ const log = input.log !== false;
1331
+ if (input.config !== void 0) {
1332
+ const projectRoot = path8.resolve(input.projectRoot ?? process.cwd());
1333
+ return { projectRoot, config: input.config, log };
1334
+ }
1335
+ const configPath = path8.resolve(process.cwd(), input.configPath ?? "i18n/i18n.codegen.json");
1336
+ if (!fs3.existsSync(configPath)) {
1337
+ throw new Error(`[Codegen Error] Config file not found: ${configPath}`);
1338
+ }
1339
+ return {
1340
+ projectRoot: path8.dirname(configPath),
1341
+ config: loadConfig(configPath),
1342
+ log
1343
+ };
1344
+ }
1345
+ function runCodegen(input) {
1346
+ const { projectRoot, config, log } = resolveRunCodegenInput(input);
1347
+ const sourceEntries = resolveNamespaces(config);
1348
+ const artifactsPathRelative = resolveArtifactsPath(config);
1349
+ const delivery = config.delivery ?? "split-by-locale";
1350
+ const loaderStrategy = config.loaderStrategy ?? "import";
1351
+ const analysisResult = analyzeDictionaries(projectRoot, sourceEntries);
1352
+ if (!analysisResult.ok) {
1353
+ throw new Error("[Codegen Error] Dictionary ICU analysis failed. See messages above.");
1354
+ }
1355
+ const { paramsByNamespace, argsSpecByNamespace, locales, dictionariesByNamespace } = analysisResult.analysis;
1356
+ if (config.localeFallback) {
1357
+ const localeFallbackIssues = getCodegenLocaleFallbackIssues(config.localeFallback, locales);
1358
+ if (localeFallbackIssues.length > 0) {
1359
+ reportCodegenIssues(localeFallbackIssues);
1360
+ throw new Error("[Codegen Error] Invalid localeFallback configuration.");
1361
+ }
1362
+ }
1363
+ const paths = resolveCodegenPaths(config);
1364
+ const {
1365
+ paramsTypeName,
1366
+ schemaTypeName,
1367
+ localeTypeName,
1368
+ localeFallbackConstName,
1369
+ factoryName,
1370
+ deliveryAreaTypeName: resolvedDeliveryAreaTypeName
1371
+ } = paths;
1372
+ const localeFallbackTypeName = `${localeTypeName}Fallback`;
1373
+ const importExtension = DEFAULT_IMPORT_EXTENSION;
1374
+ const requestLocales = collectRequestLocales(locales, config.localeFallback);
1375
+ const requestLocalesList = [...requestLocales].sort();
1376
+ if (delivery === "custom" && config.deliveryArtifacts) {
1377
+ const deliveryArtifactsIssues = getDeliveryArtifactsIssues(
1378
+ config.deliveryArtifacts,
1379
+ requestLocales
1380
+ );
1381
+ if (deliveryArtifactsIssues.length > 0) {
1382
+ reportCodegenIssues(deliveryArtifactsIssues);
1383
+ throw new Error("[Codegen Error] Invalid deliveryArtifacts configuration.");
1384
+ }
1385
+ }
1386
+ const {
1387
+ resolvedEntries: entries,
1388
+ splitPathsByNamespace,
1389
+ compiledFiles
1390
+ } = prepareDictionaryEntries(projectRoot, sourceEntries, artifactsPathRelative, {
1391
+ dictionariesByNamespace,
1392
+ delivery,
1393
+ localeFallback: config.localeFallback,
1394
+ requestLocales: delivery === "split-by-locale" ? requestLocalesList : void 0,
1395
+ deliveryArtifacts: delivery === "custom" ? config.deliveryArtifacts : void 0
1396
+ });
1397
+ const typesOutputPath = path8.resolve(projectRoot, paths.typesOutput);
1398
+ const instanceOutputPath = path8.resolve(projectRoot, paths.instanceOutput);
1399
+ const namespaceLoadersOutputPath = path8.resolve(projectRoot, paths.namespaceLoadersOutput);
1400
+ const dictionarySchemaOutputPath = path8.resolve(projectRoot, paths.dictionarySchemaOutput);
1401
+ const typesModule = toModuleBasename(typesOutputPath);
1402
+ const deliveryAreaNames = delivery === "custom" && config.deliveryArtifacts ? Object.keys(config.deliveryArtifacts).sort() : void 0;
1403
+ const deliveryAreaTypeName = delivery === "custom" ? resolvedDeliveryAreaTypeName : void 0;
1404
+ const localeFallbackForEmit = config.localeFallback ? enrichLocaleFallback(locales, config.localeFallback) : void 0;
1405
+ const typesContent = formatTypesFile({
1406
+ entries,
1407
+ paramsTypeName,
1408
+ schemaTypeName,
1409
+ localeTypeName,
1410
+ localeFallbackConstName,
1411
+ localeFallbackTypeName,
1412
+ localeFallback: localeFallbackForEmit,
1413
+ paramsByNamespace,
1414
+ requestLocales: requestLocalesList,
1415
+ ...deliveryAreaTypeName && deliveryAreaNames ? {
1416
+ deliveryAreaTypeName,
1417
+ deliveryAreaNames,
1418
+ ...delivery === "custom" && config.deliveryArtifacts ? { deliveryArtifacts: config.deliveryArtifacts } : {}
1419
+ } : {},
1420
+ lazyEntries: entries
1421
+ });
1422
+ const instanceContent = formatInstanceFile({
1423
+ typesOutputPath,
1424
+ namespaceLoadersOutputPath,
1425
+ paramsTypeName,
1426
+ schemaTypeName,
1427
+ localeTypeName,
1428
+ localeFallbackConstName,
1429
+ factoryName,
1430
+ hasLocaleFallback: Boolean(config.localeFallback),
1431
+ hasLocaleType: requestLocalesList.length > 0,
1432
+ importExtension,
1433
+ delivery,
1434
+ loaderStrategy,
1435
+ ...delivery === "custom" ? { localeDeliveryAreaConstName: "LOCALE_DELIVERY_AREA" } : {}
1436
+ });
1437
+ writeFileIfChanged(typesOutputPath, typesContent);
1438
+ const generatedFiles = [
1439
+ path8.relative(projectRoot, typesOutputPath),
1440
+ path8.relative(projectRoot, instanceOutputPath)
1441
+ ];
1442
+ writeFileIfChanged(instanceOutputPath, instanceContent);
1443
+ const dictionarySpecBlock = formatDictionarySpecBlock(entries, argsSpecByNamespace);
1444
+ const dictionarySchemaContent = formatDictionarySchemaFile(
1445
+ schemaTypeName,
1446
+ typesModule,
1447
+ dictionarySpecBlock,
1448
+ importExtension
1449
+ );
1450
+ writeFileIfChanged(dictionarySchemaOutputPath, dictionarySchemaContent);
1451
+ generatedFiles.push(path8.relative(projectRoot, dictionarySchemaOutputPath));
1452
+ const lazyEntriesWithPaths = entries.map((entry) => ({
1453
+ ...entry,
1454
+ absolutePath: path8.resolve(projectRoot, entry.filePath)
1455
+ }));
1456
+ const namespaceLoadersContent = formatNamespaceLoadersFile({
1457
+ loadersOutputPath: namespaceLoadersOutputPath,
1458
+ lazyEntries: lazyEntriesWithPaths,
1459
+ schemaTypeName,
1460
+ localeTypeName,
1461
+ typesModule,
1462
+ importExtension,
1463
+ projectRoot,
1464
+ delivery,
1465
+ splitPathsByNamespace,
1466
+ loaderStrategy,
1467
+ ...delivery === "split-by-locale" ? { requestLocales: requestLocalesList } : {},
1468
+ ...delivery === "custom" && deliveryAreaTypeName && deliveryAreaNames ? { deliveryAreaTypeName, deliveryAreaNames } : {}
1469
+ });
1470
+ writeFileIfChanged(namespaceLoadersOutputPath, namespaceLoadersContent);
1471
+ generatedFiles.push(path8.relative(projectRoot, namespaceLoadersOutputPath));
1472
+ if (log) {
1473
+ console.log(`\u2705 Generated: ${generatedFiles.join(", ")}`);
1474
+ if (compiledFiles.length > 0) {
1475
+ console.log(`\u2705 Compiled: ${compiledFiles.join(", ")}`);
1476
+ }
1477
+ }
1478
+ return { projectRoot, generatedFiles, compiledFiles };
1479
+ }
1480
+ function buildDictionarySpecFromAnalysis(entries, argsSpecByNamespace) {
1481
+ const requiredKeys = {};
1482
+ const argsByKey = {};
1483
+ for (const entry of entries) {
1484
+ const argsByKeyForNs = argsSpecByNamespace[entry.namespace] ?? {};
1485
+ requiredKeys[entry.namespace] = Object.keys(argsByKeyForNs).sort();
1486
+ argsByKey[entry.namespace] = argsByKeyForNs;
1487
+ }
1488
+ return {
1489
+ mode: "multi",
1490
+ requiredKeys,
1491
+ argsByKey
1492
+ };
1493
+ }
1494
+ function loadDictionarySpecFromSchemaFile(absoluteSchemaPath) {
1495
+ if (!fs3.existsSync(absoluteSchemaPath)) {
1496
+ throw new Error(
1497
+ `[Codegen Error] Missing dictionary schema at ${absoluteSchemaPath}. Run runCodegen first.`
1498
+ );
1499
+ }
1500
+ const source = fs3.readFileSync(absoluteSchemaPath, "utf8");
1501
+ const match = source.match(
1502
+ /export const DICTIONARY_SPEC = (\{[\s\S]*?\n\}) satisfies DictionarySpec;/
1503
+ );
1504
+ if (!match?.[1]) {
1505
+ throw new Error(
1506
+ `[Codegen Error] Could not parse DICTIONARY_SPEC from ${absoluteSchemaPath}. Re-run runCodegen.`
1507
+ );
1508
+ }
1509
+ const cleaned = match[1].replace(/ as const/g, "");
1510
+ let parsed;
1511
+ try {
1512
+ parsed = new Function(`"use strict"; return (${cleaned});`)();
1513
+ } catch (error) {
1514
+ const message = error instanceof Error ? error.message : String(error);
1515
+ throw new Error(
1516
+ `[Codegen Error] Failed to evaluate DICTIONARY_SPEC from ${absoluteSchemaPath}: ${message}`
1517
+ );
1518
+ }
1519
+ if (!parsed || typeof parsed !== "object" || parsed.mode !== "multi" || !("requiredKeys" in parsed) || !("argsByKey" in parsed)) {
1520
+ throw new Error(
1521
+ `[Codegen Error] DICTIONARY_SPEC in ${absoluteSchemaPath} is not a multi-mode DictionarySpec.`
1522
+ );
1523
+ }
1524
+ return parsed;
1525
+ }
1526
+ function sortedKeys(keys) {
1527
+ return [...keys].sort();
1528
+ }
1529
+ function variableSpecsEqual(a, b) {
1530
+ const aKeys = Object.keys(a).sort();
1531
+ const bKeys = Object.keys(b).sort();
1532
+ if (aKeys.length !== bKeys.length) {
1533
+ return false;
1534
+ }
1535
+ for (let i = 0; i < aKeys.length; i++) {
1536
+ const key = aKeys[i];
1537
+ if (key !== bKeys[i] || a[key] !== b[key]) {
1538
+ return false;
1539
+ }
1540
+ }
1541
+ return true;
1542
+ }
1543
+ function namespaceArgsEqual(a, b) {
1544
+ const aKeys = Object.keys(a ?? {}).sort();
1545
+ const bKeys = Object.keys(b ?? {}).sort();
1546
+ if (aKeys.length !== bKeys.length) {
1547
+ return false;
1548
+ }
1549
+ for (const key of aKeys) {
1550
+ if (!bKeys.includes(key)) {
1551
+ return false;
1552
+ }
1553
+ if (!variableSpecsEqual(a[key], b[key])) {
1554
+ return false;
1555
+ }
1556
+ }
1557
+ return true;
1558
+ }
1559
+ function namespaceContractsMatch(namespaces, current, established) {
1560
+ for (const namespace of namespaces) {
1561
+ const currentKeys = sortedKeys(current.requiredKeys[namespace] ?? []);
1562
+ const establishedKeys = sortedKeys(established.requiredKeys[namespace] ?? []);
1563
+ if (currentKeys.length !== establishedKeys.length || currentKeys.some((key, index) => key !== establishedKeys[index])) {
1564
+ return false;
1565
+ }
1566
+ if (!namespaceArgsEqual(current.argsByKey[namespace], established.argsByKey[namespace])) {
1567
+ return false;
1568
+ }
1569
+ }
1570
+ return true;
1571
+ }
1572
+
1573
+ // src/codegen/regenerate-namespaces.ts
1574
+ function resolveRegenerateInput(input) {
1575
+ const log = input.log !== false;
1576
+ const namespaces = input.namespaces;
1577
+ if (input.config !== void 0) {
1578
+ return {
1579
+ projectRoot: path8.resolve(input.projectRoot ?? process.cwd()),
1580
+ config: input.config,
1581
+ namespaces,
1582
+ log
1583
+ };
1584
+ }
1585
+ const configPath = path8.resolve(process.cwd(), input.configPath ?? "i18n/i18n.codegen.json");
1586
+ if (!fs3.existsSync(configPath)) {
1587
+ throw new Error(`[Codegen Error] Config file not found: ${configPath}`);
1588
+ }
1589
+ return {
1590
+ projectRoot: path8.dirname(configPath),
1591
+ config: loadConfig(configPath),
1592
+ namespaces,
1593
+ log
1594
+ };
1595
+ }
1596
+ function regenerateNamespaces(input) {
1597
+ const { projectRoot, config, namespaces, log } = resolveRegenerateInput(input);
1598
+ if (namespaces.length === 0) {
1599
+ throw new Error("[Codegen Error] regenerateNamespaces requires a non-empty namespaces list.");
1600
+ }
1601
+ const sourceEntries = resolveNamespaces(config);
1602
+ const known = new Set(sourceEntries.map((entry) => entry.namespace));
1603
+ const unknown = namespaces.filter((namespace) => !known.has(namespace));
1604
+ if (unknown.length > 0) {
1605
+ throw new Error(
1606
+ `[Codegen Error] Unknown namespace(s) for regenerateNamespaces: ${unknown.join(", ")}.`
1607
+ );
1608
+ }
1609
+ const requested = new Set(namespaces);
1610
+ const selectedEntries = sourceEntries.filter((entry) => requested.has(entry.namespace));
1611
+ const paths = resolveCodegenPaths(config);
1612
+ const schemaPath = path8.resolve(projectRoot, paths.dictionarySchemaOutput);
1613
+ const established = loadDictionarySpecFromSchemaFile(schemaPath);
1614
+ const artifactsPathRelative = resolveArtifactsPath(config);
1615
+ const delivery = config.delivery ?? "split-by-locale";
1616
+ const analysisResult = analyzeDictionaries(projectRoot, sourceEntries);
1617
+ if (!analysisResult.ok) {
1618
+ throw new Error(
1619
+ "[Codegen Error] Dictionary ICU analysis failed while regenerating namespaces."
1620
+ );
1621
+ }
1622
+ const {
1623
+ argsSpecByNamespace,
1624
+ locales,
1625
+ dictionariesByNamespace: sourceDictionaries
1626
+ } = analysisResult.analysis;
1627
+ const current = buildDictionarySpecFromAnalysis(selectedEntries, argsSpecByNamespace);
1628
+ if (!namespaceContractsMatch(namespaces, current, established)) {
1629
+ throw new Error(
1630
+ "[Codegen Error] Namespace ICU contract changed (keys or params). Contract change requires runCodegen."
1631
+ );
1632
+ }
1633
+ if (config.localeFallback) {
1634
+ const localeFallbackIssues = getCodegenLocaleFallbackIssues(config.localeFallback, locales);
1635
+ if (localeFallbackIssues.length > 0) {
1636
+ reportCodegenIssues(localeFallbackIssues);
1637
+ throw new Error("[Codegen Error] Invalid localeFallback configuration.");
1638
+ }
1639
+ }
1640
+ const requestLocales = collectRequestLocales(locales, config.localeFallback);
1641
+ const requestLocalesList = [...requestLocales].sort();
1642
+ if (delivery === "custom" && config.deliveryArtifacts) {
1643
+ const deliveryArtifactsIssues = getDeliveryArtifactsIssues(
1644
+ config.deliveryArtifacts,
1645
+ requestLocales
1646
+ );
1647
+ if (deliveryArtifactsIssues.length > 0) {
1648
+ reportCodegenIssues(deliveryArtifactsIssues);
1649
+ throw new Error("[Codegen Error] Invalid deliveryArtifacts configuration.");
1650
+ }
1651
+ }
1652
+ const dictionariesByNamespace = {};
1653
+ for (const entry of selectedEntries) {
1654
+ const dictionary = sourceDictionaries[entry.namespace];
1655
+ if (!dictionary) {
1656
+ throw new Error(
1657
+ `[Codegen Error] Missing parsed dictionary for namespace "${entry.namespace}".`
1658
+ );
1659
+ }
1660
+ dictionariesByNamespace[entry.namespace] = dictionary;
1661
+ }
1662
+ const { splitPathsByNamespace, compiledFiles } = prepareDictionaryEntries(
1663
+ projectRoot,
1664
+ selectedEntries,
1665
+ artifactsPathRelative,
1666
+ {
1667
+ dictionariesByNamespace,
1668
+ delivery,
1669
+ localeFallback: config.localeFallback,
1670
+ requestLocales: delivery === "split-by-locale" ? requestLocalesList : void 0,
1671
+ deliveryArtifacts: delivery === "custom" ? config.deliveryArtifacts : void 0
1672
+ }
1673
+ );
1674
+ if (log) {
1675
+ if (compiledFiles.length > 0) {
1676
+ console.log(`\u2705 Regenerated delivery artifacts: ${compiledFiles.join(", ")}`);
1677
+ } else {
1678
+ console.log("\u2705 Regenerated delivery artifacts: unchanged");
1679
+ }
1680
+ }
1681
+ return { projectRoot, compiledFiles, splitPathsByNamespace };
1682
+ }
191
1683
 
192
- export { DELIVERY_MODES, SUPPORTED_IMPORT_EXTENSIONS, buildCodegenConfig, codegenConfigKeys, inferProjectName, resolveDeliveryOutputDir, resolveDictionaryOutputPath, typeNamesForProject, writeCodegenConfig };
1684
+ export { DEFAULT_FACTORY_NAME, DEFAULT_LOCALE_FALLBACK_CONST_NAME, DELIVERY_MODES, GENERATED_BASENAMES, LOADER_STRATEGIES, SUPPORTED_IMPORT_EXTENSIONS, buildCodegenConfig, codegenConfigKeys, formatCodegenConfigIssues, inferProjectName, loadConfig, regenerateNamespaces, resolveArtifactsPath, resolveCodegenPaths, resolveNamespaces, runCodegen, typeNamesForProject, writeCodegenConfig };
193
1685
  //# sourceMappingURL=index.js.map
194
1686
  //# sourceMappingURL=index.js.map