@xndrjs/i18n 0.6.1 → 0.7.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 (51) hide show
  1. package/README.md +154 -141
  2. package/dist/codegen/index.js.map +1 -1
  3. package/dist/index.d.ts +268 -69
  4. package/dist/index.js +572 -65
  5. package/dist/index.js.map +1 -1
  6. package/dist/validation/index.d.ts +23 -7
  7. package/dist/validation/index.js +196 -89
  8. package/dist/validation/index.js.map +1 -1
  9. package/package.json +1 -1
  10. package/src/IcuTranslationProviderMulti.test.ts +80 -234
  11. package/src/IcuTranslationProviderMulti.ts +101 -112
  12. package/src/IcuTranslationProviderSingle.test.ts +30 -309
  13. package/src/IcuTranslationProviderSingle.ts +42 -75
  14. package/src/builder-load-registry.ts +18 -0
  15. package/src/builder-loaders.ts +18 -0
  16. package/src/builder-multi.ts +481 -0
  17. package/src/builder-types.test.ts +80 -0
  18. package/src/builder-types.ts +24 -0
  19. package/src/builder.test.ts +421 -0
  20. package/src/builder.ts +112 -0
  21. package/src/codegen/delivery-artifacts.test.ts +2 -1
  22. package/src/codegen/delivery-artifacts.ts +2 -1
  23. package/src/codegen/emit/dictionary-file.test.ts +0 -7
  24. package/src/codegen/emit/dictionary-schema-file.ts +55 -42
  25. package/src/codegen/emit/instance-file.test.ts +88 -0
  26. package/src/codegen/emit/instance-file.ts +164 -17
  27. package/src/codegen/emit/namespace-loaders-file.test.ts +4 -18
  28. package/src/codegen/emit/namespace-loaders-file.ts +4 -54
  29. package/src/codegen/emit/types-file.test.ts +0 -2
  30. package/src/codegen/generate-i18n-types.test.ts +8 -303
  31. package/src/codegen/generate-i18n-types.ts +16 -1
  32. package/src/codegen/project-locales-set-namespace.test.ts +25 -32
  33. package/src/engine.ts +71 -0
  34. package/src/index.ts +45 -7
  35. package/src/patch-key.test.ts +186 -0
  36. package/src/patch-key.ts +140 -0
  37. package/src/project-locales.test.ts +16 -6
  38. package/src/project-locales.ts +17 -6
  39. package/src/scope-multi.ts +124 -0
  40. package/src/scope-single.ts +85 -0
  41. package/src/scope-types.ts +27 -0
  42. package/src/scope.test.ts +481 -0
  43. package/src/single-builder.ts +153 -0
  44. package/src/types.ts +16 -0
  45. package/src/validation/create-normalized-schema.ts +1 -1
  46. package/src/validation/errors.ts +2 -0
  47. package/src/validation/index.ts +135 -27
  48. package/src/validation/normalize.ts +176 -46
  49. package/src/validation/types.ts +4 -0
  50. package/src/validation/validate-normalized.ts +43 -1
  51. package/src/validation/validation.test.ts +200 -7
package/dist/index.js CHANGED
@@ -1,4 +1,206 @@
1
1
  import { IntlMessageFormat } from 'intl-messageformat';
2
+ import { parse } from '@formatjs/icu-messageformat-parser';
3
+
4
+ // 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);
10
+ }
11
+
12
+ // src/builder-multi.ts
13
+ var I18nBuilderMultiInitialImpl = class {
14
+ constructor(engine, options) {
15
+ this.engine = engine;
16
+ this.options = options;
17
+ }
18
+ engine;
19
+ 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;
64
+ }
65
+ engine;
66
+ options;
67
+ state;
68
+ withNamespaces(namespaces) {
69
+ return new _I18nBuilderMultiPartitionedImpl(this.engine, this.options, {
70
+ ...this.state,
71
+ namespaces
72
+ });
73
+ }
74
+ async load() {
75
+ await applyMultiBuilderLoad(this.engine, this.options, this.state);
76
+ return this.engine.toScope({ namespaces: this.state.namespaces });
77
+ }
78
+ };
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);
91
+ }
92
+ };
93
+ async function applyMultiBuilderLoad(engine, options, state) {
94
+ if (state.namespaces.length === 0) {
95
+ throw new Error("[i18n] withNamespaces(...) is required before load().");
96
+ }
97
+ const partition = state.locale ?? state.deliveryArea;
98
+ await Promise.all(
99
+ state.namespaces.map(async (namespace) => {
100
+ const loader = options?.namespaceLoaders?.[namespace];
101
+ if (loader === void 0) {
102
+ return;
103
+ }
104
+ if (engine.hasBuilderResourceLoaded(namespace, partition)) {
105
+ return;
106
+ }
107
+ const data = await invokeNamespaceLoader(loader, partition);
108
+ engine.applyLoadMergeNamespace(namespace, data);
109
+ engine.markBuilderResourceLoaded(namespace, partition);
110
+ })
111
+ );
112
+ }
113
+
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
+ }
2
204
 
3
205
  // src/deep-freeze.ts
4
206
  function deepFreeze(value) {
@@ -18,6 +220,21 @@ function cloneAndFreeze(value) {
18
220
  return deepFreeze(structuredClone(value));
19
221
  }
20
222
 
223
+ // src/builder-load-registry.ts
224
+ function formatBuilderResourceKey(namespace, partition) {
225
+ return `${namespace}\0${partition ?? ""}`;
226
+ }
227
+ var SINGLE_BUILDER_RESOURCE = "__dictionary__";
228
+ var BuilderLoadRegistry = class {
229
+ loaded = /* @__PURE__ */ new Set();
230
+ has(namespace, partition) {
231
+ return this.loaded.has(formatBuilderResourceKey(namespace, partition));
232
+ }
233
+ mark(namespace, partition) {
234
+ this.loaded.add(formatBuilderResourceKey(namespace, partition));
235
+ }
236
+ };
237
+
21
238
  // src/resolve-locale.ts
22
239
  function validateLocaleFallback(map) {
23
240
  for (const startLocale of Object.keys(map)) {
@@ -143,6 +360,239 @@ function resolveAndFormat(options) {
143
360
  }
144
361
  }
145
362
 
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
+
146
596
  // src/project-locales.ts
147
597
  function classifyAreaLocales(areaLocales, localeFallback) {
148
598
  const areaSet = new Set(areaLocales);
@@ -260,36 +710,57 @@ function mergeDictionaryLocalesCore(existing, incoming) {
260
710
  return merged;
261
711
  }
262
712
 
263
- // src/IcuTranslationProviderSingle.ts
264
- var IcuTranslationProviderSingleForLocale = class {
265
- constructor(provider, locale) {
266
- this.provider = provider;
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;
267
730
  this.locale = locale;
268
731
  }
269
- provider;
732
+ engine;
270
733
  locale;
271
- get(key, ...args) {
734
+ t = (key, ...args) => {
272
735
  const params = args[0];
273
- return this.provider.getWithLocale(String(key), this.locale, params);
274
- }
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
+ };
275
744
  };
745
+
746
+ // src/IcuTranslationProviderSingle.ts
276
747
  var IcuTranslationProviderSingle = class {
748
+ __i18nEngineMode = "single";
277
749
  dictionary;
278
750
  compiledCache = {};
751
+ preloadedKeys = /* @__PURE__ */ new Set();
752
+ builderLoadRegistry = new BuilderLoadRegistry();
279
753
  localeFallback;
280
754
  onMissing;
281
755
  constructor(dictionary, options) {
282
756
  this.dictionary = structuredClone(dictionary);
757
+ seedPreloadedKeysSingle(this.dictionary, this.preloadedKeys);
283
758
  if (options?.localeFallback) {
284
759
  validateLocaleFallback(options.localeFallback);
285
760
  this.localeFallback = options.localeFallback;
286
761
  }
287
762
  this.onMissing = options?.onMissing ?? "throw";
288
763
  }
289
- get(key, locale, ...args) {
290
- const params = args[0];
291
- return this.getWithLocale(String(key), locale, params);
292
- }
293
764
  getWithLocale(key, locale, params) {
294
765
  return resolveAndFormat({
295
766
  localeByKey: this.dictionary[key],
@@ -309,18 +780,24 @@ var IcuTranslationProviderSingle = class {
309
780
  }
310
781
  });
311
782
  }
312
- forLocale(locale) {
313
- return new IcuTranslationProviderSingleForLocale(this, locale);
783
+ toScope(options) {
784
+ if (options?.locale !== void 0) {
785
+ return new I18nScopeSingleForLocaleImpl(this, options.locale);
786
+ }
787
+ return new I18nScopeSingleImpl(this);
314
788
  }
315
789
  getAll() {
316
790
  return cloneAndFreeze(this.dictionary);
317
791
  }
318
- setAll(values) {
319
- this.dictionary = structuredClone(values);
320
- this.compiledCache = {};
792
+ hasBuilderResourceLoaded(partition) {
793
+ return this.builderLoadRegistry.has(SINGLE_BUILDER_RESOURCE, partition);
794
+ }
795
+ markBuilderResourceLoaded(partition) {
796
+ this.builderLoadRegistry.mark(SINGLE_BUILDER_RESOURCE, partition);
321
797
  }
322
- mergeAll(values) {
798
+ applyLoadMergeSingle(values) {
323
799
  this.dictionary = mergeNamespaceLocalesCore(this.dictionary, values);
800
+ recordPreloadedKeysSingle(values, this.preloadedKeys);
324
801
  for (const [key, incomingLocales] of Object.entries(values)) {
325
802
  if (incomingLocales === void 0 || typeof incomingLocales !== "object") {
326
803
  continue;
@@ -330,46 +807,67 @@ var IcuTranslationProviderSingle = class {
330
807
  }
331
808
  }
332
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
+ }
333
817
  };
334
818
 
335
- // src/IcuTranslationProviderMulti.ts
336
- var IcuTranslationProviderMultiForLocale = class {
337
- constructor(provider, locale) {
338
- this.provider = provider;
819
+ // src/scope-multi.ts
820
+ var I18nScopeMultiImpl = class {
821
+ constructor(engine) {
822
+ this.engine = engine;
823
+ }
824
+ engine;
825
+ t = (namespace, key, locale, ...args) => {
826
+ const params = args[0];
827
+ return this.engine.getWithLocale(String(namespace), String(key), locale, params);
828
+ };
829
+ forLocale = (locale) => {
830
+ return new I18nScopeMultiForLocaleImpl(this.engine, locale);
831
+ };
832
+ };
833
+ var I18nScopeMultiForLocaleImpl = class _I18nScopeMultiForLocaleImpl {
834
+ constructor(engine, locale) {
835
+ this.engine = engine;
339
836
  this.locale = locale;
340
837
  }
341
- provider;
838
+ engine;
342
839
  locale;
343
- get(namespace, key, ...args) {
840
+ t = (namespace, key, ...args) => {
344
841
  const params = args[0];
345
- return this.provider.getWithLocale(String(namespace), String(key), this.locale, params);
346
- }
842
+ return this.engine.getWithLocale(String(namespace), String(key), this.locale, params);
843
+ };
844
+ set = (namespace, key, template) => {
845
+ this.engine.patchKeyMulti(String(namespace), String(key), this.locale, template);
846
+ };
847
+ forLocale = (locale) => {
848
+ return new _I18nScopeMultiForLocaleImpl(this.engine, locale);
849
+ };
347
850
  };
851
+
852
+ // src/IcuTranslationProviderMulti.ts
348
853
  var IcuTranslationProviderMulti = class {
854
+ __i18nEngineMode = "multi";
349
855
  dictionary;
350
856
  compiledCache = {};
857
+ preloadedKeys = /* @__PURE__ */ new Set();
858
+ builderLoadRegistry = new BuilderLoadRegistry();
351
859
  localeFallback;
352
860
  onMissing;
353
- loadedNamespaces;
354
861
  constructor(dictionary, options) {
355
862
  this.dictionary = structuredClone(dictionary);
356
- this.loadedNamespaces = new Set(Object.keys(dictionary));
863
+ seedPreloadedKeysMulti(this.dictionary, this.preloadedKeys);
357
864
  if (options?.localeFallback) {
358
865
  validateLocaleFallback(options.localeFallback);
359
866
  this.localeFallback = options.localeFallback;
360
867
  }
361
868
  this.onMissing = options?.onMissing ?? "throw";
362
869
  }
363
- get(namespace, key, locale, ...args) {
364
- const params = args[0];
365
- return this.getWithLocale(String(namespace), String(key), locale, params);
366
- }
367
870
  getWithLocale(namespace, key, locale, params) {
368
- if (!this.loadedNamespaces.has(namespace)) {
369
- throw new Error(
370
- `[i18n] Namespace not loaded: "${namespace}". Register it with setNamespace() before calling .get().`
371
- );
372
- }
373
871
  const localeByKey = this.dictionary[namespace]?.[key];
374
872
  return resolveAndFormat({
375
873
  localeByKey,
@@ -393,48 +891,57 @@ var IcuTranslationProviderMulti = class {
393
891
  }
394
892
  });
395
893
  }
396
- forLocale(locale) {
397
- return new IcuTranslationProviderMultiForLocale(this, locale);
894
+ toScope(options) {
895
+ if (options.locale !== void 0) {
896
+ return new I18nScopeMultiForLocaleImpl(this, options.locale);
897
+ }
898
+ return new I18nScopeMultiImpl(this);
398
899
  }
399
900
  getAll() {
400
901
  return cloneAndFreeze(this.dictionary);
401
902
  }
402
- hasNamespace(namespace) {
403
- return this.loadedNamespaces.has(namespace);
903
+ hasBuilderResourceLoaded(namespace, partition) {
904
+ return this.builderLoadRegistry.has(namespace, partition);
404
905
  }
405
- setAll(values) {
406
- this.dictionary = structuredClone(values);
407
- this.compiledCache = {};
408
- this.loadedNamespaces = new Set(Object.keys(values));
906
+ markBuilderResourceLoaded(namespace, partition) {
907
+ this.builderLoadRegistry.mark(namespace, partition);
409
908
  }
410
- mergeAll(values) {
411
- for (const namespace of Object.keys(values)) {
412
- const incoming = values[namespace];
413
- if (incoming !== void 0) {
414
- this.mergeNamespace(namespace, incoming);
415
- }
416
- }
417
- }
418
- setNamespace(namespace, values) {
909
+ applyLoadMergeNamespace(namespace, values) {
910
+ const existing = this.dictionary[namespace];
911
+ const merged = mergeNamespaceLocalesCore(existing ?? {}, values);
419
912
  this.dictionary = {
420
913
  ...this.dictionary,
421
- [namespace]: structuredClone(values)
914
+ [namespace]: merged
422
915
  };
423
- this.loadedNamespaces.add(namespace);
424
- for (const locale of Object.keys(this.compiledCache)) {
425
- delete this.compiledCache[locale]?.[namespace];
916
+ recordPreloadedKeysMulti(namespace, values, this.preloadedKeys);
917
+ for (const [key, incomingLocales] of Object.entries(values)) {
918
+ if (incomingLocales === void 0 || typeof incomingLocales !== "object") {
919
+ continue;
920
+ }
921
+ for (const locale of Object.keys(incomingLocales)) {
922
+ delete this.compiledCache[locale]?.[namespace]?.[key];
923
+ }
426
924
  }
427
925
  }
428
- mergeNamespace(namespace, values) {
429
- if (!this.loadedNamespaces.has(namespace)) {
430
- this.setNamespace(namespace, values);
431
- return;
926
+ applyLoadMergeAll(values) {
927
+ for (const namespace of Object.keys(values)) {
928
+ const incoming = values[namespace];
929
+ if (incoming !== void 0) {
930
+ this.applyLoadMergeNamespace(namespace, incoming);
931
+ }
432
932
  }
433
- const existing = this.dictionary[namespace];
434
- this.setNamespace(namespace, mergeNamespaceLocalesCore(existing, values));
933
+ }
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
+ );
435
942
  }
436
943
  };
437
944
 
438
- export { IcuTranslationProviderMulti, IcuTranslationProviderSingle, formatLocaleFallbackChain, mergeDictionaryLocalesCore, mergeNamespaceLocalesCore, projectDictionaryForDeliveryAreaCore, projectDictionaryLocalesCore, projectNamespaceForDeliveryAreaCore, projectNamespaceLocalesCore, resolveLocaleTemplate, validateLocaleFallback };
945
+ export { IcuTranslationProviderMulti, IcuTranslationProviderSingle, createI18nBuilder, createI18nMultiBuilder, createI18nSingleBuilder, formatLocaleFallbackChain, invokeNamespaceLoader, mergeDictionaryLocalesCore, mergeNamespaceLocalesCore, projectDictionaryForDeliveryAreaCore, projectDictionaryLocalesCore, projectNamespaceForDeliveryAreaCore, projectNamespaceLocalesCore, resolveLocaleTemplate, validateLocaleFallback };
439
946
  //# sourceMappingURL=index.js.map
440
947
  //# sourceMappingURL=index.js.map