@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
package/dist/index.js CHANGED
@@ -1,102 +1,57 @@
1
1
  import { IntlMessageFormat } from 'intl-messageformat';
2
- import { parse } from '@formatjs/icu-messageformat-parser';
3
2
 
4
3
  // src/builder-loaders.ts
5
- async function invokeNamespaceLoader(loader, partition) {
6
- if (partition === void 0) {
7
- return await loader();
8
- }
9
- return await loader(partition);
4
+ async function invokeNamespaceLoader(loader, partition, context) {
5
+ return await loader(partition, context);
10
6
  }
11
7
 
12
- // src/builder-multi.ts
13
- var I18nBuilderMultiInitialImpl = class {
8
+ // src/i18n-handle.ts
9
+ var I18nHandleImpl = class {
14
10
  constructor(engine, options) {
15
11
  this.engine = engine;
16
12
  this.options = options;
13
+ this.partitionForLocale = options?.partitionForLocale ?? ((locale) => locale);
17
14
  }
18
15
  engine;
19
16
  options;
20
- withNamespaces(namespaces) {
21
- return new I18nBuilderMultiReadyImpl(this.engine, this.options, { namespaces });
22
- }
23
- };
24
- var I18nBuilderMultiReadyImpl = class _I18nBuilderMultiReadyImpl {
25
- constructor(engine, options, state) {
26
- this.engine = engine;
27
- this.options = options;
28
- this.state = state;
29
- }
30
- engine;
31
- options;
32
- state;
33
- clone() {
34
- return {
35
- namespaces: this.state.namespaces,
36
- ...this.state.locale !== void 0 ? { locale: this.state.locale } : {},
37
- ...this.state.deliveryArea !== void 0 ? { deliveryArea: this.state.deliveryArea } : {}
38
- };
39
- }
40
- withNamespaces(namespaces) {
41
- return new _I18nBuilderMultiReadyImpl(this.engine, this.options, {
42
- ...this.clone(),
43
- namespaces
44
- });
45
- }
46
- withLocale(locale) {
47
- const { deliveryArea: _deliveryArea, ...rest } = this.clone();
48
- return new I18nBuilderMultiForLocaleImpl(this.engine, this.options, { ...rest, locale });
49
- }
50
- withDeliveryArea(area) {
51
- const { locale: _locale, ...rest } = this.clone();
52
- return new I18nBuilderMultiPartitionedImpl(this.engine, this.options, { ...rest, deliveryArea: area });
53
- }
54
- async load() {
55
- await applyMultiBuilderLoad(this.engine, this.options, this.state);
56
- return this.engine.toScope({ namespaces: this.state.namespaces });
57
- }
58
- };
59
- var I18nBuilderMultiPartitionedImpl = class _I18nBuilderMultiPartitionedImpl {
60
- constructor(engine, options, state) {
61
- this.engine = engine;
62
- this.options = options;
63
- this.state = state;
17
+ partitionForLocale;
18
+ async load(input) {
19
+ await applyHandleLoad(this.engine, this.options, input, this.partitionForLocale);
20
+ return this.engine.toScope({ namespaces: input.namespaces }).forLocale(input.locale);
64
21
  }
65
- engine;
66
- options;
67
- state;
68
- withNamespaces(namespaces) {
69
- return new _I18nBuilderMultiPartitionedImpl(this.engine, this.options, {
70
- ...this.state,
71
- namespaces
72
- });
22
+ peek(input) {
23
+ if (!areHandleResourcesReady(this.engine, this.options, input, this.partitionForLocale)) {
24
+ return null;
25
+ }
26
+ return this.engine.toScope({ namespaces: input.namespaces }).forLocale(input.locale);
73
27
  }
74
- async load() {
75
- await applyMultiBuilderLoad(this.engine, this.options, this.state);
76
- return this.engine.toScope({ namespaces: this.state.namespaces });
28
+ serialize() {
29
+ return this.engine.serialize();
77
30
  }
78
31
  };
79
- var I18nBuilderMultiForLocaleImpl = class {
80
- constructor(engine, options, state) {
81
- this.engine = engine;
82
- this.options = options;
83
- this.state = state;
84
- }
85
- engine;
86
- options;
87
- state;
88
- async load() {
89
- await applyMultiBuilderLoad(this.engine, this.options, this.state);
90
- return this.engine.toScope({ namespaces: this.state.namespaces }).forLocale(this.state.locale);
32
+ function createI18nHandle(engine, options) {
33
+ return new I18nHandleImpl(engine, options);
34
+ }
35
+ function areHandleResourcesReady(engine, options, input, partitionForLocale) {
36
+ if (input.namespaces.length === 0) {
37
+ return false;
91
38
  }
92
- };
93
- async function applyMultiBuilderLoad(engine, options, state) {
94
- if (state.namespaces.length === 0) {
95
- throw new Error("[i18n] withNamespaces(...) is required before load().");
39
+ const partition = partitionForLocale(input.locale);
40
+ return input.namespaces.every((namespace) => {
41
+ const loader = options?.namespaceLoaders?.[namespace];
42
+ if (loader === void 0) {
43
+ return true;
44
+ }
45
+ return engine.hasBuilderResourceLoaded(namespace, partition);
46
+ });
47
+ }
48
+ async function applyHandleLoad(engine, options, input, partitionForLocale) {
49
+ if (input.namespaces.length === 0) {
50
+ throw new Error("[i18n] load() requires a non-empty namespaces array.");
96
51
  }
97
- const partition = state.locale ?? state.deliveryArea;
52
+ const partition = partitionForLocale(input.locale);
98
53
  await Promise.all(
99
- state.namespaces.map(async (namespace) => {
54
+ input.namespaces.map(async (namespace) => {
100
55
  const loader = options?.namespaceLoaders?.[namespace];
101
56
  if (loader === void 0) {
102
57
  return;
@@ -104,127 +59,25 @@ async function applyMultiBuilderLoad(engine, options, state) {
104
59
  if (engine.hasBuilderResourceLoaded(namespace, partition)) {
105
60
  return;
106
61
  }
107
- const data = await invokeNamespaceLoader(loader, partition);
62
+ const data = await invokeNamespaceLoader(loader, partition, { locale: input.locale });
108
63
  engine.applyLoadMergeNamespace(namespace, data);
109
64
  engine.markBuilderResourceLoaded(namespace, partition);
110
65
  })
111
66
  );
112
67
  }
113
68
 
114
- // src/single-builder.ts
115
- var I18nBuilderSingleImpl = class _I18nBuilderSingleImpl {
116
- constructor(engine, options, state) {
117
- this.engine = engine;
118
- this.options = options;
119
- if (state !== void 0) {
120
- this.state = {
121
- ...state.locale !== void 0 ? { locale: state.locale } : {},
122
- ...state.deliveryArea !== void 0 ? { deliveryArea: state.deliveryArea } : {}
123
- };
124
- }
125
- }
126
- engine;
127
- options;
128
- state = {};
129
- clone() {
130
- return {
131
- ...this.state.locale !== void 0 ? { locale: this.state.locale } : {},
132
- ...this.state.deliveryArea !== void 0 ? { deliveryArea: this.state.deliveryArea } : {}
133
- };
134
- }
135
- withLocale(locale) {
136
- const { deliveryArea: _deliveryArea, ...rest } = this.clone();
137
- return new I18nBuilderSingleForLocaleImpl(
138
- this.engine,
139
- this.options,
140
- { ...rest, locale }
141
- );
142
- }
143
- withDeliveryArea(_area) {
144
- const { locale: _locale, ...rest } = this.clone();
145
- return new _I18nBuilderSingleImpl(this.engine, this.options, {
146
- ...rest,
147
- deliveryArea: _area
148
- });
149
- }
150
- async load() {
151
- await applySingleBuilderLoad(this.engine, this.options, this.state);
152
- return this.engine.toScope();
153
- }
154
- };
155
- var I18nBuilderSingleForLocaleImpl = class {
156
- constructor(engine, options, state) {
157
- this.engine = engine;
158
- this.options = options;
159
- this.state = state;
160
- }
161
- engine;
162
- options;
163
- state;
164
- async load() {
165
- await applySingleBuilderLoad(this.engine, this.options, this.state);
166
- return this.engine.toScope({ locale: this.state.locale });
167
- }
168
- };
169
- async function applySingleBuilderLoad(engine, options, state) {
170
- const partition = state.locale ?? state.deliveryArea;
171
- const loader = options?.dictionaryLoader;
172
- if (loader !== void 0) {
173
- if (engine.hasBuilderResourceLoaded(partition)) {
174
- return;
175
- }
176
- const data = await invokeNamespaceLoader(loader, partition);
177
- engine.applyLoadMergeSingle(data);
178
- engine.markBuilderResourceLoaded(partition);
179
- }
180
- }
181
-
182
- // src/builder.ts
183
- function isMultiEngine(engine) {
184
- return engine.__i18nEngineMode === "multi";
185
- }
186
- function createI18nSingleBuilder(engine, options) {
187
- return new I18nBuilderSingleImpl(engine, options);
188
- }
189
- function createI18nMultiBuilder(engine, options) {
190
- return new I18nBuilderMultiInitialImpl(engine, options);
191
- }
192
- function createI18nBuilder(engine, options) {
193
- if (isMultiEngine(engine)) {
194
- return createI18nMultiBuilder(
195
- engine,
196
- options
197
- );
198
- }
199
- return createI18nSingleBuilder(
200
- engine,
201
- options
202
- );
203
- }
204
-
205
- // src/deep-freeze.ts
206
- function deepFreeze(value) {
207
- if (value === null || typeof value !== "object") {
208
- return value;
209
- }
210
- if (Object.isFrozen(value)) {
211
- return value;
212
- }
213
- Object.freeze(value);
214
- for (const child of Object.values(value)) {
215
- deepFreeze(child);
216
- }
217
- return value;
218
- }
219
- function cloneAndFreeze(value) {
220
- return deepFreeze(structuredClone(value));
69
+ // src/serialized-state.ts
70
+ function normalizeI18nCreateInput(value) {
71
+ return {
72
+ dictionary: value?.dictionary ?? {},
73
+ resources: value?.resources ?? []
74
+ };
221
75
  }
222
76
 
223
77
  // src/builder-load-registry.ts
224
78
  function formatBuilderResourceKey(namespace, partition) {
225
- return `${namespace}\0${partition ?? ""}`;
79
+ return `${namespace}\0${partition}`;
226
80
  }
227
- var SINGLE_BUILDER_RESOURCE = "__dictionary__";
228
81
  var BuilderLoadRegistry = class {
229
82
  loaded = /* @__PURE__ */ new Set();
230
83
  has(namespace, partition) {
@@ -233,6 +86,20 @@ var BuilderLoadRegistry = class {
233
86
  mark(namespace, partition) {
234
87
  this.loaded.add(formatBuilderResourceKey(namespace, partition));
235
88
  }
89
+ /** Snapshot of loaded resources as `[namespace, partition]` tuples. */
90
+ entries() {
91
+ const out = [];
92
+ for (const key of this.loaded) {
93
+ const sep = key.indexOf("\0");
94
+ out.push([key.slice(0, sep), key.slice(sep + 1)]);
95
+ }
96
+ return out;
97
+ }
98
+ seed(resources) {
99
+ for (const [namespace, partition] of resources) {
100
+ this.mark(namespace, partition);
101
+ }
102
+ }
236
103
  };
237
104
 
238
105
  // src/resolve-locale.ts
@@ -360,328 +227,7 @@ function resolveAndFormat(options) {
360
227
  }
361
228
  }
362
229
 
363
- // src/icu/extract-variables.ts
364
- function isPluralOrOrdinalNode(node) {
365
- return node.type === 6 && "pluralType" in node && Boolean(node.pluralType);
366
- }
367
- function hasVariableName(node) {
368
- return "value" in node && typeof node.value === "string";
369
- }
370
- function addVariableMeta(variables, name, type, role) {
371
- const existing = variables[name];
372
- if (!existing) {
373
- variables[name] = { type, roles: /* @__PURE__ */ new Set([role]) };
374
- return;
375
- }
376
- existing.roles.add(role);
377
- existing.type = mergeVariableTypes(existing.type, type);
378
- }
379
- function mergeVariableTypes(left, right) {
380
- if (left === right) {
381
- return left;
382
- }
383
- if (left === "string" && right === "number" || left === "number" && right === "string") {
384
- return "number";
385
- }
386
- return right;
387
- }
388
- function extractVariableMeta(nodes) {
389
- const variables = {};
390
- const walk = (walkNodes) => {
391
- for (const node of walkNodes) {
392
- if (node.type === 1 && hasVariableName(node)) {
393
- addVariableMeta(variables, node.value, "string", "simple");
394
- } else if (node.type === 2 && hasVariableName(node)) {
395
- addVariableMeta(variables, node.value, "number", "number");
396
- } else if (isPluralOrOrdinalNode(node) && hasVariableName(node)) {
397
- const role = node.pluralType === "ordinal" ? "selectordinal" : "plural";
398
- addVariableMeta(variables, node.value, "number", role);
399
- } else if (node.type === 3 && hasVariableName(node)) {
400
- addVariableMeta(variables, node.value, "date", "date");
401
- } else if (node.type === 4 && hasVariableName(node)) {
402
- addVariableMeta(variables, node.value, "date", "time");
403
- } else if (node.type === 5 && hasVariableName(node)) {
404
- addVariableMeta(variables, node.value, "string", "select");
405
- }
406
- if ("options" in node && node.options) {
407
- for (const option of Object.values(node.options)) {
408
- walk(option.value);
409
- }
410
- }
411
- if ("children" in node && Array.isArray(node.children)) {
412
- walk(node.children);
413
- }
414
- }
415
- };
416
- walk(nodes);
417
- return variables;
418
- }
419
- function variableMetaToSpec(meta) {
420
- return Object.fromEntries(Object.entries(meta).map(([name, entry]) => [name, entry.type]));
421
- }
422
- function inferMergedVariableType(roles) {
423
- const hasPlural = roles.has("plural");
424
- const hasSelectordinal = roles.has("selectordinal");
425
- const hasSelect = roles.has("select");
426
- const hasSimple = roles.has("simple");
427
- const hasNumber = roles.has("number");
428
- const hasDate = roles.has("date");
429
- const hasTime = roles.has("time");
430
- if (hasPlural && hasSelectordinal) {
431
- return "CONFLICT";
432
- }
433
- if (hasSelect && (hasPlural || hasSelectordinal || hasNumber)) {
434
- return "CONFLICT";
435
- }
436
- if (hasPlural && hasSelect) {
437
- return "CONFLICT";
438
- }
439
- if (hasSelectordinal && hasSelect) {
440
- return "CONFLICT";
441
- }
442
- if (hasDate || hasTime) {
443
- if (hasPlural || hasSelect || hasSelectordinal || hasSimple || hasNumber) {
444
- return "CONFLICT";
445
- }
446
- return "date";
447
- }
448
- if (hasPlural || hasSelectordinal || hasNumber) {
449
- return "number";
450
- }
451
- if (hasSelect || hasSimple) {
452
- return "string";
453
- }
454
- return "string";
455
- }
456
- function mergeVariableMetaAcrossLocales(localeMetas) {
457
- const keySignatures = localeMetas.map((meta) => Object.keys(meta).sort().join(","));
458
- if (new Set(keySignatures).size > 1) {
459
- return {
460
- ok: false,
461
- message: `Inconsistent ICU variable names across locales (keys: ${keySignatures.join(" vs ")})`
462
- };
463
- }
464
- const merged = {};
465
- const variableNames = new Set(localeMetas.flatMap((meta) => Object.keys(meta)));
466
- for (const variableName of variableNames) {
467
- const combinedRoles = /* @__PURE__ */ new Set();
468
- for (const meta of localeMetas) {
469
- for (const role of meta[variableName]?.roles ?? []) {
470
- combinedRoles.add(role);
471
- }
472
- }
473
- const mergedType = inferMergedVariableType(combinedRoles);
474
- if (mergedType === "CONFLICT") {
475
- const rolesByLocale = localeMetas.map((meta) => {
476
- const entry = meta[variableName];
477
- if (!entry) {
478
- return null;
479
- }
480
- return [...entry.roles].sort().join("|");
481
- }).filter((value) => value !== null);
482
- return {
483
- ok: false,
484
- message: `Incompatible ICU variable "${variableName}" across locales (roles: ${rolesByLocale.join(" vs ")})`
485
- };
486
- }
487
- merged[variableName] = mergedType;
488
- }
489
- return { ok: true, merged };
490
- }
491
- function parseTemplate(template) {
492
- try {
493
- const ast = parse(template);
494
- const meta = extractVariableMeta(ast);
495
- return { ok: true, args: variableMetaToSpec(meta), meta };
496
- } catch (error) {
497
- const message = error instanceof Error ? error.message : String(error);
498
- return { ok: false, message };
499
- }
500
- }
501
-
502
- // src/patch-key.ts
503
- function preloadKeySingle(key, locale) {
504
- return `${key}:${locale}`;
505
- }
506
- function preloadKeyMulti(namespace, key, locale) {
507
- return `${namespace}:${key}:${locale}`;
508
- }
509
- function seedPreloadedKeysSingle(dictionary, preloadedKeys) {
510
- for (const [key, localeByKey] of Object.entries(dictionary)) {
511
- if (localeByKey === void 0 || typeof localeByKey !== "object") {
512
- continue;
513
- }
514
- for (const locale of Object.keys(localeByKey)) {
515
- preloadedKeys.add(preloadKeySingle(key, locale));
516
- }
517
- }
518
- }
519
- function seedPreloadedKeysMulti(dictionary, preloadedKeys) {
520
- for (const [namespace, keyDictionary] of Object.entries(dictionary)) {
521
- if (keyDictionary === void 0 || typeof keyDictionary !== "object") {
522
- continue;
523
- }
524
- for (const [key, localeByKey] of Object.entries(keyDictionary)) {
525
- if (localeByKey === void 0 || typeof localeByKey !== "object") {
526
- continue;
527
- }
528
- for (const locale of Object.keys(localeByKey)) {
529
- preloadedKeys.add(preloadKeyMulti(namespace, key, locale));
530
- }
531
- }
532
- }
533
- }
534
- function recordPreloadedKeysSingle(partial, preloadedKeys) {
535
- for (const [key, incomingLocales] of Object.entries(partial)) {
536
- if (incomingLocales === void 0 || typeof incomingLocales !== "object") {
537
- continue;
538
- }
539
- for (const locale of Object.keys(incomingLocales)) {
540
- preloadedKeys.add(preloadKeySingle(key, locale));
541
- }
542
- }
543
- }
544
- function recordPreloadedKeysMulti(namespace, partial, preloadedKeys) {
545
- for (const [key, incomingLocales] of Object.entries(partial)) {
546
- if (incomingLocales === void 0 || typeof incomingLocales !== "object") {
547
- continue;
548
- }
549
- for (const locale of Object.keys(incomingLocales)) {
550
- preloadedKeys.add(preloadKeyMulti(namespace, key, locale));
551
- }
552
- }
553
- }
554
- function collectLocaleMetasForKey(localeByKey, patchedLocale, patchedMeta) {
555
- const localeMetas = [patchedMeta];
556
- for (const [locale, template] of Object.entries(localeByKey)) {
557
- if (locale === patchedLocale) {
558
- continue;
559
- }
560
- const parsed = parseTemplate(template);
561
- if (parsed.ok) {
562
- localeMetas.push(parsed.meta);
563
- }
564
- }
565
- return localeMetas;
566
- }
567
- function assertPatchKeySingle(key, locale, template, preloadedKeys, localeByKey) {
568
- if (!preloadedKeys.has(preloadKeySingle(key, locale))) {
569
- throw new Error(`[i18n] Key not preloaded: ${key} (${locale})`);
570
- }
571
- const parsed = parseTemplate(template);
572
- if (!parsed.ok) {
573
- throw new Error(`[i18n] ICU syntax error on patch: ${parsed.message}`);
574
- }
575
- const localeMetas = collectLocaleMetasForKey(localeByKey ?? {}, locale, parsed.meta);
576
- const merged = mergeVariableMetaAcrossLocales(localeMetas);
577
- if (!merged.ok) {
578
- throw new Error(`[i18n] ICU args mismatch on patch: ${merged.message}`);
579
- }
580
- }
581
- function assertPatchKeyMulti(namespace, key, locale, template, preloadedKeys, localeByKey) {
582
- if (!preloadedKeys.has(preloadKeyMulti(namespace, key, locale))) {
583
- throw new Error(`[i18n] Key not preloaded: ${namespace}.${key} (${locale})`);
584
- }
585
- const parsed = parseTemplate(template);
586
- if (!parsed.ok) {
587
- throw new Error(`[i18n] ICU syntax error on patch: ${parsed.message}`);
588
- }
589
- const localeMetas = collectLocaleMetasForKey(localeByKey ?? {}, locale, parsed.meta);
590
- const merged = mergeVariableMetaAcrossLocales(localeMetas);
591
- if (!merged.ok) {
592
- throw new Error(`[i18n] ICU args mismatch on patch: ${merged.message}`);
593
- }
594
- }
595
-
596
230
  // src/project-locales.ts
597
- function classifyAreaLocales(areaLocales, localeFallback) {
598
- const areaSet = new Set(areaLocales);
599
- const full = [];
600
- const preserve = [];
601
- for (const locale of areaLocales) {
602
- const fallback = localeFallback?.[locale];
603
- if (fallback !== null && fallback !== void 0 && areaSet.has(fallback)) {
604
- preserve.push(locale);
605
- } else {
606
- full.push(locale);
607
- }
608
- }
609
- return { full, preserve };
610
- }
611
- function projectKeyDictionary(dictionary, locales, localeFallback) {
612
- const targetLocales = [...new Set(locales)];
613
- const result = {};
614
- for (const [key, localesByKey] of Object.entries(dictionary)) {
615
- const projected = {};
616
- for (const locale of targetLocales) {
617
- const resolved = resolveLocaleTemplate(localesByKey, locale, localeFallback);
618
- if (resolved !== void 0) {
619
- projected[locale] = resolved.template;
620
- }
621
- }
622
- if (Object.keys(projected).length > 0) {
623
- result[key] = projected;
624
- }
625
- }
626
- return result;
627
- }
628
- function projectNamespaceLocalesCore(dictionary, locales, localeFallback) {
629
- return projectKeyDictionary(dictionary, locales, localeFallback);
630
- }
631
- function projectNamespaceForDeliveryAreaCore(dictionary, areaLocales, localeFallback) {
632
- const { full, preserve } = classifyAreaLocales(areaLocales, localeFallback);
633
- const fullProjection = full.length > 0 ? projectKeyDictionary(dictionary, full, localeFallback) : void 0;
634
- const result = {};
635
- for (const [key, localesByKey] of Object.entries(dictionary)) {
636
- const projected = {};
637
- if (fullProjection !== void 0) {
638
- const fullEntry = fullProjection[key];
639
- if (fullEntry !== void 0) {
640
- Object.assign(projected, fullEntry);
641
- }
642
- }
643
- for (const locale of preserve) {
644
- const template = localesByKey[locale];
645
- if (template !== void 0) {
646
- projected[locale] = template;
647
- }
648
- }
649
- if (Object.keys(projected).length > 0) {
650
- result[key] = projected;
651
- }
652
- }
653
- return result;
654
- }
655
- function projectDictionaryForDeliveryAreaCore(dictionary, areaLocales, localeFallback) {
656
- const result = {};
657
- for (const namespace of Object.keys(dictionary)) {
658
- const namespaceDictionary = dictionary[namespace];
659
- if (namespaceDictionary === void 0) {
660
- continue;
661
- }
662
- result[namespace] = projectNamespaceForDeliveryAreaCore(
663
- namespaceDictionary,
664
- areaLocales,
665
- localeFallback
666
- );
667
- }
668
- return result;
669
- }
670
- function projectDictionaryLocalesCore(dictionary, locales, localeFallback) {
671
- const result = {};
672
- for (const namespace of Object.keys(dictionary)) {
673
- const namespaceDictionary = dictionary[namespace];
674
- if (namespaceDictionary === void 0) {
675
- continue;
676
- }
677
- result[namespace] = projectKeyDictionary(
678
- namespaceDictionary,
679
- locales,
680
- localeFallback
681
- );
682
- }
683
- return result;
684
- }
685
231
  function mergeNamespaceLocalesCore(existing, incoming) {
686
232
  const merged = structuredClone(existing);
687
233
  for (const [key, incomingLocales] of Object.entries(incoming)) {
@@ -710,112 +256,6 @@ function mergeDictionaryLocalesCore(existing, incoming) {
710
256
  return merged;
711
257
  }
712
258
 
713
- // src/scope-single.ts
714
- var I18nScopeSingleImpl = class {
715
- constructor(engine) {
716
- this.engine = engine;
717
- }
718
- engine;
719
- t = (key, locale, ...args) => {
720
- const params = args[0];
721
- return this.engine.getWithLocale(String(key), locale, params);
722
- };
723
- forLocale = (locale) => {
724
- return new I18nScopeSingleForLocaleImpl(this.engine, locale);
725
- };
726
- };
727
- var I18nScopeSingleForLocaleImpl = class _I18nScopeSingleForLocaleImpl {
728
- constructor(engine, locale) {
729
- this.engine = engine;
730
- this.locale = locale;
731
- }
732
- engine;
733
- locale;
734
- t = (key, ...args) => {
735
- const params = args[0];
736
- return this.engine.getWithLocale(String(key), this.locale, params);
737
- };
738
- set = (key, template) => {
739
- this.engine.patchKey(String(key), this.locale, template);
740
- };
741
- forLocale = (locale) => {
742
- return new _I18nScopeSingleForLocaleImpl(this.engine, locale);
743
- };
744
- };
745
-
746
- // src/IcuTranslationProviderSingle.ts
747
- var IcuTranslationProviderSingle = class {
748
- __i18nEngineMode = "single";
749
- dictionary;
750
- compiledCache = {};
751
- preloadedKeys = /* @__PURE__ */ new Set();
752
- builderLoadRegistry = new BuilderLoadRegistry();
753
- localeFallback;
754
- onMissing;
755
- constructor(dictionary, options) {
756
- this.dictionary = structuredClone(dictionary);
757
- seedPreloadedKeysSingle(this.dictionary, this.preloadedKeys);
758
- if (options?.localeFallback) {
759
- validateLocaleFallback(options.localeFallback);
760
- this.localeFallback = options.localeFallback;
761
- }
762
- this.onMissing = options?.onMissing ?? "throw";
763
- }
764
- getWithLocale(key, locale, params) {
765
- return resolveAndFormat({
766
- localeByKey: this.dictionary[key],
767
- locale,
768
- params,
769
- getCache: (resolvedLocale) => {
770
- if (!this.compiledCache[resolvedLocale]) {
771
- this.compiledCache[resolvedLocale] = {};
772
- }
773
- return this.compiledCache[resolvedLocale];
774
- },
775
- context: {
776
- key,
777
- locale,
778
- localeFallback: this.localeFallback,
779
- onMissing: this.onMissing
780
- }
781
- });
782
- }
783
- toScope(options) {
784
- if (options?.locale !== void 0) {
785
- return new I18nScopeSingleForLocaleImpl(this, options.locale);
786
- }
787
- return new I18nScopeSingleImpl(this);
788
- }
789
- getAll() {
790
- return cloneAndFreeze(this.dictionary);
791
- }
792
- hasBuilderResourceLoaded(partition) {
793
- return this.builderLoadRegistry.has(SINGLE_BUILDER_RESOURCE, partition);
794
- }
795
- markBuilderResourceLoaded(partition) {
796
- this.builderLoadRegistry.mark(SINGLE_BUILDER_RESOURCE, partition);
797
- }
798
- applyLoadMergeSingle(values) {
799
- this.dictionary = mergeNamespaceLocalesCore(this.dictionary, values);
800
- recordPreloadedKeysSingle(values, this.preloadedKeys);
801
- for (const [key, incomingLocales] of Object.entries(values)) {
802
- if (incomingLocales === void 0 || typeof incomingLocales !== "object") {
803
- continue;
804
- }
805
- for (const locale of Object.keys(incomingLocales)) {
806
- delete this.compiledCache[locale]?.[key];
807
- }
808
- }
809
- }
810
- patchKey(key, locale, template) {
811
- assertPatchKeySingle(key, locale, template, this.preloadedKeys, this.dictionary[key]);
812
- this.dictionary = mergeNamespaceLocalesCore(this.dictionary, {
813
- [key]: { [locale]: template }
814
- });
815
- delete this.compiledCache[locale]?.[key];
816
- }
817
- };
818
-
819
259
  // src/scope-multi.ts
820
260
  var I18nScopeMultiImpl = class {
821
261
  constructor(engine) {
@@ -841,9 +281,6 @@ var I18nScopeMultiForLocaleImpl = class _I18nScopeMultiForLocaleImpl {
841
281
  const params = args[0];
842
282
  return this.engine.getWithLocale(String(namespace), String(key), this.locale, params);
843
283
  };
844
- set = (namespace, key, template) => {
845
- this.engine.patchKeyMulti(String(namespace), String(key), this.locale, template);
846
- };
847
284
  forLocale = (locale) => {
848
285
  return new _I18nScopeMultiForLocaleImpl(this.engine, locale);
849
286
  };
@@ -852,15 +289,14 @@ var I18nScopeMultiForLocaleImpl = class _I18nScopeMultiForLocaleImpl {
852
289
  // src/IcuTranslationProviderMulti.ts
853
290
  var IcuTranslationProviderMulti = class {
854
291
  __i18nEngineMode = "multi";
292
+ /** Loaded namespaces only — cold start is `{}`; never claim full Schema until loaded. */
855
293
  dictionary;
856
294
  compiledCache = {};
857
- preloadedKeys = /* @__PURE__ */ new Set();
858
295
  builderLoadRegistry = new BuilderLoadRegistry();
859
296
  localeFallback;
860
297
  onMissing;
861
- constructor(dictionary, options) {
298
+ constructor(dictionary = {}, options) {
862
299
  this.dictionary = structuredClone(dictionary);
863
- seedPreloadedKeysMulti(this.dictionary, this.preloadedKeys);
864
300
  if (options?.localeFallback) {
865
301
  validateLocaleFallback(options.localeFallback);
866
302
  this.localeFallback = options.localeFallback;
@@ -897,8 +333,9 @@ var IcuTranslationProviderMulti = class {
897
333
  }
898
334
  return new I18nScopeMultiImpl(this);
899
335
  }
336
+ /** Live dictionary reference (loaded namespaces only). Do not mutate. */
900
337
  getAll() {
901
- return cloneAndFreeze(this.dictionary);
338
+ return this.dictionary;
902
339
  }
903
340
  hasBuilderResourceLoaded(namespace, partition) {
904
341
  return this.builderLoadRegistry.has(namespace, partition);
@@ -906,14 +343,22 @@ var IcuTranslationProviderMulti = class {
906
343
  markBuilderResourceLoaded(namespace, partition) {
907
344
  this.builderLoadRegistry.mark(namespace, partition);
908
345
  }
346
+ seedBuilderResources(resources) {
347
+ this.builderLoadRegistry.seed(resources);
348
+ }
349
+ serialize() {
350
+ return {
351
+ dictionary: this.dictionary,
352
+ resources: this.builderLoadRegistry.entries()
353
+ };
354
+ }
909
355
  applyLoadMergeNamespace(namespace, values) {
910
- const existing = this.dictionary[namespace];
911
- const merged = mergeNamespaceLocalesCore(existing ?? {}, values);
356
+ const existing = this.dictionary[namespace] ?? {};
357
+ const merged = mergeNamespaceLocalesCore(existing, values);
912
358
  this.dictionary = {
913
359
  ...this.dictionary,
914
360
  [namespace]: merged
915
361
  };
916
- recordPreloadedKeysMulti(namespace, values, this.preloadedKeys);
917
362
  for (const [key, incomingLocales] of Object.entries(values)) {
918
363
  if (incomingLocales === void 0 || typeof incomingLocales !== "object") {
919
364
  continue;
@@ -931,17 +376,8 @@ var IcuTranslationProviderMulti = class {
931
376
  }
932
377
  }
933
378
  }
934
- patchKeyMulti(namespace, key, locale, template) {
935
- const namespaceDictionary = this.dictionary[namespace];
936
- const localeByKey = namespaceDictionary?.[key];
937
- assertPatchKeyMulti(namespace, key, locale, template, this.preloadedKeys, localeByKey);
938
- this.applyLoadMergeNamespace(
939
- namespace,
940
- { [key]: { [locale]: template } }
941
- );
942
- }
943
379
  };
944
380
 
945
- export { IcuTranslationProviderMulti, IcuTranslationProviderSingle, createI18nBuilder, createI18nMultiBuilder, createI18nSingleBuilder, formatLocaleFallbackChain, invokeNamespaceLoader, mergeDictionaryLocalesCore, mergeNamespaceLocalesCore, projectDictionaryForDeliveryAreaCore, projectDictionaryLocalesCore, projectNamespaceForDeliveryAreaCore, projectNamespaceLocalesCore, resolveLocaleTemplate, validateLocaleFallback };
381
+ export { IcuTranslationProviderMulti, createI18nHandle, formatLocaleFallbackChain, invokeNamespaceLoader, mergeDictionaryLocalesCore, mergeNamespaceLocalesCore, normalizeI18nCreateInput, resolveLocaleTemplate, validateLocaleFallback };
946
382
  //# sourceMappingURL=index.js.map
947
383
  //# sourceMappingURL=index.js.map