gt-react 10.19.17 → 10.19.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/macros.mjs CHANGED
@@ -4188,14 +4188,17 @@ function extractVars(icuString) {
4188
4188
  });
4189
4189
  return variables;
4190
4190
  }
4191
+ const CONTAINS_INDEXED_GT_REGEX = new RegExp(`${VAR_IDENTIFIER}\\d+`);
4191
4192
  /**
4192
4193
  * Given an indexed ICU string, condenses any select to an argument
4194
+ * Unindexed _gt_ source strings and indexed _gt_# translation strings
4195
+ * are mutually exclusive.
4193
4196
  * indexVars('Hello {_gt_1, select, other {World}}') => 'Hello {_gt_1}'
4194
4197
  * @param {string} icuString - The ICU string to condense.
4195
4198
  * @returns {string} The condensed ICU string.
4196
4199
  */
4197
4200
  function condenseVars(icuString) {
4198
- if (!icuString.includes("_gt_")) return icuString;
4201
+ if (!CONTAINS_INDEXED_GT_REGEX.test(icuString)) return icuString;
4199
4202
  function visitor(child) {
4200
4203
  child.type = import_types.TYPE.argument;
4201
4204
  Reflect.deleteProperty(child, "options");
@@ -4342,7 +4345,7 @@ function sanitizeJsxChildren(childrenAsObjects) {
4342
4345
  return Array.isArray(childrenAsObjects) ? childrenAsObjects.map(sanitizeChild) : sanitizeChild(childrenAsObjects);
4343
4346
  }
4344
4347
  //#endregion
4345
- //#region ../i18n/dist/versionId-BkJZGHXr.mjs
4348
+ //#region ../i18n/dist/versionId-BTjLA0FZ.mjs
4346
4349
  /**
4347
4350
  * Throw errors if there are any errors and log warnings if there are any warnings
4348
4351
  * @param {ValidationResult[]} results - The results to print
@@ -4608,83 +4611,269 @@ function routeCreateTranslationLoader({ type, remoteTranslationLoaderParams, loa
4608
4611
  case "disabled": return createFallbackTranslationLoader();
4609
4612
  }
4610
4613
  }
4611
- function isPlainObject(value) {
4612
- if (value == null || typeof value !== "object") return false;
4613
- const prototype = Object.getPrototypeOf(value);
4614
- return prototype === Object.prototype || prototype === null;
4614
+ function getDictionaryPath(id) {
4615
+ const path = id ? id.split(".") : [];
4616
+ for (const segment of path) assertSafeDictionaryPathSegment(segment, id);
4617
+ return path;
4618
+ }
4619
+ function assertSafeDictionaryPathSegment(segment, path) {
4620
+ if (segment === "__proto__" || segment === "constructor" || segment === "prototype") throw new Error(`Dictionary path "${path}" contains an unsafe segment`);
4615
4621
  }
4616
- function copyCacheValue(value) {
4617
- if (Array.isArray(value)) return [...value];
4618
- if (isPlainObject(value)) return { ...value };
4619
- return value;
4622
+ function isDictionaryObject(value) {
4623
+ return typeof value === "object" && value != null && !Array.isArray(value);
4624
+ }
4625
+ function cloneDictionaryValue(value) {
4626
+ if (value === void 0 || typeof value === "string") return value;
4627
+ return structuredClone(value);
4628
+ }
4629
+ function getDictionaryValueAtPath(dictionary, path) {
4630
+ let current = dictionary;
4631
+ for (const segment of getDictionaryPath(path)) {
4632
+ if (!isDictionaryObject(current)) return;
4633
+ current = current[segment];
4634
+ }
4635
+ return current;
4636
+ }
4637
+ function setDictionaryValueAtPath(dictionary, path, value) {
4638
+ const segments = getDictionaryPath(path);
4639
+ if (isDictionaryObject(value)) assertSafeDictionaryObject(value, path);
4640
+ if (segments.length === 0) {
4641
+ if (isDictionaryObject(value)) replaceDictionary(dictionary, value);
4642
+ return;
4643
+ }
4644
+ let current = dictionary;
4645
+ for (const segment of segments.slice(0, -1)) {
4646
+ const next = current[segment];
4647
+ if (!isDictionaryObject(next)) current[segment] = {};
4648
+ current = current[segment];
4649
+ }
4650
+ const leafSegment = segments[segments.length - 1];
4651
+ current[leafSegment] = value;
4652
+ }
4653
+ function getDictionaryEntry(value) {
4654
+ if (!isDictionaryLeafNode(value)) return;
4655
+ return {
4656
+ entry: Array.isArray(value) ? value[0] : value,
4657
+ options: Array.isArray(value) ? value[1] ?? {} : {}
4658
+ };
4659
+ }
4660
+ function getDictionaryValue(value) {
4661
+ if (Object.keys(value.options).length === 0) return value.entry;
4662
+ return [value.entry, value.options];
4620
4663
  }
4664
+ function resolveDictionaryLookupOptions(options) {
4665
+ const { $format, context, ...rest } = options;
4666
+ return {
4667
+ ...rest,
4668
+ $format: isStringFormat($format) ? $format : "ICU",
4669
+ ...rest.$context === void 0 && typeof context === "string" && { $context: context }
4670
+ };
4671
+ }
4672
+ function isDictionaryLeafNode(value) {
4673
+ if (typeof value === "string") return true;
4674
+ if (!Array.isArray(value) || typeof value[0] !== "string") return false;
4675
+ if (value.length === 1) return true;
4676
+ return value.length === 2 && isDictionaryOptions(value[1]);
4677
+ }
4678
+ function isDictionaryOptions(value) {
4679
+ if (typeof value !== "object" || value == null || Array.isArray(value)) return false;
4680
+ const options = value;
4681
+ return (options.$context === void 0 || typeof options.$context === "string") && (options.$format === void 0 || isStringFormat(options.$format)) && (options.$maxChars === void 0 || typeof options.$maxChars === "number") && (options.context === void 0 || typeof options.context === "string");
4682
+ }
4683
+ function isStringFormat(value) {
4684
+ return value === "ICU" || value === "I18NEXT" || value === "STRING";
4685
+ }
4686
+ function replaceDictionary(target, source) {
4687
+ for (const key of Object.keys(target)) delete target[key];
4688
+ for (const key of Object.keys(source)) target[key] = source[key];
4689
+ }
4690
+ function assertSafeDictionaryObject(dictionary, parentPath = "") {
4691
+ for (const [key, value] of Object.entries(dictionary)) {
4692
+ const path = parentPath ? `${parentPath}.${key}` : key;
4693
+ assertSafeDictionaryPathSegment(key, path);
4694
+ if (isDictionaryObject(value)) assertSafeDictionaryObject(value, path);
4695
+ }
4696
+ }
4697
+ var DictionarySourceNotFoundError = class extends Error {
4698
+ constructor(id) {
4699
+ super(`I18nManager: source dictionary entry ${id} is not defined`);
4700
+ this.name = "DictionarySourceNotFoundError";
4701
+ }
4702
+ };
4621
4703
  /**
4622
- * Cache class
4623
- * This is designed in such a way that it is the responsibility of the client
4624
- * to invoke the cache miss method when a cache miss occurs.
4625
- *
4626
- * TODO: maybe add "OutputValue" as a reflection of "InputKey"
4704
+ * Builds the dictionary value for a requested path by combining existing target
4705
+ * translations with runtime translations of any source leaves that are missing.
4627
4706
  */
4628
- var Cache = class {
4629
- /**
4630
- * Constructor
4631
- * @param {Object} params - The parameters for the cache
4632
- * @param {Record<CacheKey, CacheValue>} params.init - The initial cache
4633
- * @param {CacheLifecycle} [lifecycle] - Optional lifecycle callbacks
4634
- */
4635
- constructor(init, lifecycle) {
4636
- this.cache = {};
4637
- this.fallbackPromises = {};
4707
+ async function materializeDictionaryValue({ key, sourceValue, targetValue, translateEntry }) {
4708
+ if (getDictionaryEntry(targetValue) !== void 0) return cloneDictionaryValue(targetValue);
4709
+ if (isDictionaryObject(targetValue) && !isDictionaryObject(sourceValue)) return cloneDictionaryValue(targetValue);
4710
+ const sourceEntry = getDictionaryEntry(sourceValue);
4711
+ if (sourceEntry !== void 0) return await translateEntry(key, sourceEntry);
4712
+ if (!isDictionaryObject(sourceValue)) throw new DictionarySourceNotFoundError(key);
4713
+ const targetDictionary = isDictionaryObject(targetValue) ? targetValue : {};
4714
+ const keys = new Set([...Object.keys(sourceValue), ...Object.keys(targetDictionary)]);
4715
+ const entries = await Promise.all(Array.from(keys).map(async (childKey) => {
4716
+ const childPath = key ? `${key}.${childKey}` : childKey;
4717
+ assertSafeDictionaryPathSegment(childKey, childPath);
4718
+ const childSource = sourceValue[childKey];
4719
+ if (childSource === void 0) return [childKey, cloneDictionaryValue(targetDictionary[childKey])];
4720
+ return [childKey, await materializeDictionaryValue({
4721
+ key: childPath,
4722
+ sourceValue: childSource,
4723
+ targetValue: targetDictionary[childKey],
4724
+ translateEntry
4725
+ })];
4726
+ }));
4727
+ return Object.fromEntries(entries);
4728
+ }
4729
+ function cloneDictionaryEntry(entry) {
4730
+ return {
4731
+ entry: entry.entry,
4732
+ options: structuredClone(entry.options)
4733
+ };
4734
+ }
4735
+ var DictionaryCache = class {
4736
+ constructor({ init, lifecycle = {}, runtimeTranslate }) {
4737
+ this.pendingTranslations = /* @__PURE__ */ new Map();
4738
+ this.pendingMaterializations = /* @__PURE__ */ new Map();
4638
4739
  this.cache = structuredClone(init);
4639
- this.onHit = lifecycle?.onHit;
4640
- this.onMiss = lifecycle?.onMiss;
4740
+ this.runtimeTranslate = runtimeTranslate;
4741
+ this.lifecycle = lifecycle;
4641
4742
  }
4642
- /**
4643
- * Set the value for a key
4644
- */
4645
- setCache(cacheKey, value) {
4646
- this.cache[cacheKey] = value;
4743
+ getEntry(key) {
4744
+ const value = getDictionaryValueAtPath(this.cache, key);
4745
+ const entry = getDictionaryEntry(value);
4746
+ if (entry === void 0) return;
4747
+ const outputEntry = cloneDictionaryEntry(entry);
4748
+ this.lifecycle.onHit?.({
4749
+ inputKey: key,
4750
+ cacheKey: key,
4751
+ cacheValue: value,
4752
+ outputValue: outputEntry
4753
+ });
4754
+ return outputEntry;
4647
4755
  }
4648
- /**
4649
- * Look up the key
4650
- */
4651
- getCache(key) {
4652
- const cacheKey = this.genKey(key);
4653
- return this.cache[cacheKey];
4756
+ getValue(key) {
4757
+ const value = getDictionaryValueAtPath(this.cache, key);
4758
+ if (value === void 0) return;
4759
+ const outputValue = cloneDictionaryValue(value);
4760
+ this.lifecycle.onDictionaryObjectCacheHit?.({
4761
+ inputKey: key,
4762
+ cacheKey: key,
4763
+ cacheValue: value,
4764
+ outputValue
4765
+ });
4766
+ return outputValue;
4767
+ }
4768
+ setValue(key, value) {
4769
+ setDictionaryValueAtPath(this.cache, key, cloneDictionaryValue(value));
4654
4770
  }
4655
- /**
4656
- * Get the internal cache
4657
- * @returns The internal cache
4658
- *
4659
- * @internal - used by gt-tanstack-start
4660
- */
4661
4771
  getInternalCache() {
4662
- return Object.fromEntries(Object.entries(this.cache).map(([key, value]) => [key, copyCacheValue(value)]));
4772
+ return cloneDictionaryValue(this.cache);
4773
+ }
4774
+ async materializeValue(key, sourceValue, targetValue = getDictionaryValueAtPath(this.cache, key)) {
4775
+ let materializationPromise = this.pendingMaterializations.get(key);
4776
+ if (!materializationPromise) {
4777
+ materializationPromise = materializeDictionaryValue({
4778
+ key,
4779
+ sourceValue,
4780
+ targetValue,
4781
+ translateEntry: async (entryKey, sourceEntry) => getDictionaryValue(await this.materializeEntry(entryKey, sourceEntry))
4782
+ }).then((value) => {
4783
+ this.setValue(key, value);
4784
+ return value;
4785
+ });
4786
+ this.pendingMaterializations.set(key, materializationPromise);
4787
+ }
4788
+ try {
4789
+ return await materializationPromise;
4790
+ } finally {
4791
+ this.pendingMaterializations.delete(key);
4792
+ }
4663
4793
  }
4664
- /**
4665
- * Get the mutable cache for subclasses that need custom read/write behavior.
4666
- */
4667
- getMutableCache() {
4668
- return this.cache;
4794
+ async materializeEntry(key, sourceEntry) {
4795
+ let translationPromise = this.pendingTranslations.get(key);
4796
+ if (!translationPromise) {
4797
+ translationPromise = this.runtimeTranslate(key, sourceEntry).then((value) => {
4798
+ setDictionaryValueAtPath(this.cache, key, value);
4799
+ const entry = getDictionaryEntry(value);
4800
+ if (entry === void 0) throw new Error("DictionaryCache materializeEntry did not return a DictionaryEntry");
4801
+ this.lifecycle.onMiss?.({
4802
+ inputKey: key,
4803
+ cacheKey: key,
4804
+ cacheValue: value,
4805
+ outputValue: cloneDictionaryEntry(entry)
4806
+ });
4807
+ return cloneDictionaryEntry(entry);
4808
+ });
4809
+ this.pendingTranslations.set(key, translationPromise);
4810
+ }
4811
+ try {
4812
+ return cloneDictionaryEntry(await translationPromise);
4813
+ } finally {
4814
+ this.pendingTranslations.delete(key);
4815
+ }
4669
4816
  }
4670
- /**
4671
- * Fallback to the value from the fallback function on a cache miss
4672
- * @important assumes that the fallback error handling done upstream
4673
- */
4674
- async missCache(...args) {
4675
- const key = args[0];
4676
- const cacheKey = this.genKey(key);
4677
- if (this.fallbackPromises[cacheKey] !== void 0) return await this.fallbackPromises[cacheKey];
4678
- const fallbackPromise = this.fallback(...args);
4679
- this.fallbackPromises[cacheKey] = fallbackPromise;
4817
+ };
4818
+ var ResourceCache = class {
4819
+ constructor({ load, lifecycle = {}, ttl }) {
4820
+ this.cache = /* @__PURE__ */ new Map();
4821
+ this.pendingLoads = /* @__PURE__ */ new Map();
4822
+ this.loadResource = load;
4823
+ this.lifecycle = lifecycle;
4824
+ this.ttl = ttl === null ? -1 : ttl ?? 6e4;
4825
+ }
4826
+ get(key) {
4827
+ const entry = this.cache.get(key);
4828
+ if (!entry || this.isExpired(entry)) return;
4829
+ this.lifecycle.onHit?.({
4830
+ inputKey: key,
4831
+ cacheKey: key,
4832
+ cacheValue: entry,
4833
+ outputValue: entry.value
4834
+ });
4835
+ return entry.value;
4836
+ }
4837
+ set(key, value, { expiresAt = this.getExpiresAt() } = {}) {
4838
+ this.cache.set(key, {
4839
+ expiresAt,
4840
+ value
4841
+ });
4842
+ }
4843
+ async getOrLoad(key) {
4844
+ return this.get(key) ?? await this.load(key);
4845
+ }
4846
+ async load(key) {
4847
+ let loadPromise = this.pendingLoads.get(key);
4848
+ if (!loadPromise) {
4849
+ loadPromise = this.loadResource(key).then((value) => {
4850
+ const entry = {
4851
+ expiresAt: this.getExpiresAt(),
4852
+ value
4853
+ };
4854
+ this.cache.set(key, entry);
4855
+ this.lifecycle.onMiss?.({
4856
+ inputKey: key,
4857
+ cacheKey: key,
4858
+ cacheValue: entry,
4859
+ outputValue: entry.value
4860
+ });
4861
+ return entry;
4862
+ });
4863
+ this.pendingLoads.set(key, loadPromise);
4864
+ }
4680
4865
  try {
4681
- const value = await fallbackPromise;
4682
- this.setCache(cacheKey, value);
4683
- return value;
4866
+ return (await loadPromise).value;
4684
4867
  } finally {
4685
- delete this.fallbackPromises[cacheKey];
4868
+ this.pendingLoads.delete(key);
4686
4869
  }
4687
4870
  }
4871
+ getExpiresAt() {
4872
+ return this.ttl < 0 ? this.ttl : Date.now() + this.ttl;
4873
+ }
4874
+ isExpired(entry) {
4875
+ return entry.expiresAt > 0 && entry.expiresAt < Date.now();
4876
+ }
4688
4877
  };
4689
4878
  /**
4690
4879
  * Hash a message string
@@ -4729,20 +4918,22 @@ function normalizeBatchConfig(batchConfig) {
4729
4918
  * Locale logic is handled at the LocalesCache level. Use a callback function that has the
4730
4919
  * locale parameter embedded if you wish to use the locale code.
4731
4920
  */
4732
- var TranslationsCache = class extends Cache {
4921
+ var TranslationsCache = class {
4733
4922
  /**
4734
4923
  * Constructor
4735
4924
  * @param {Object} params - The parameters for the cache
4736
4925
  * @param {Record<Hash, TranslationValue>} params.init - The initial cache
4737
4926
  * @param {Function} params.fallback - Get the fallback value for a cache miss
4738
4927
  */
4739
- constructor({ init, translateMany, lifecycle, batchConfig }) {
4740
- super(init, lifecycle);
4741
- this._queue = [];
4742
- this._batchTimer = null;
4743
- this._activeRequests = 0;
4744
- this._translateMany = translateMany;
4745
- this._batchConfig = normalizeBatchConfig(batchConfig);
4928
+ constructor({ init, translateMany, lifecycle = {}, batchConfig }) {
4929
+ this.pendingTranslations = /* @__PURE__ */ new Map();
4930
+ this.queue = [];
4931
+ this.batchTimer = null;
4932
+ this.activeRequests = 0;
4933
+ this.cache = structuredClone(init);
4934
+ this.translateMany = translateMany;
4935
+ this.batchConfig = normalizeBatchConfig(batchConfig);
4936
+ this.lifecycle = lifecycle;
4746
4937
  }
4747
4938
  /**
4748
4939
  * Get the translation value for a given key
@@ -4750,10 +4941,11 @@ var TranslationsCache = class extends Cache {
4750
4941
  * @returns The translation value
4751
4942
  */
4752
4943
  get(key) {
4753
- const value = this.getCache(key);
4754
- if (value != null && this.onHit) this.onHit({
4944
+ const cacheKey = this.getCacheKey(key);
4945
+ const value = this.cache[cacheKey];
4946
+ if (value != null) this.lifecycle.onHit?.({
4755
4947
  inputKey: key,
4756
- cacheKey: this.genKey(key),
4948
+ cacheKey,
4757
4949
  cacheValue: value,
4758
4950
  outputValue: value
4759
4951
  });
@@ -4765,75 +4957,64 @@ var TranslationsCache = class extends Cache {
4765
4957
  * @returns The translation value
4766
4958
  */
4767
4959
  async miss(key) {
4768
- const value = await this.missCache(key);
4769
- if (value != null && this.onMiss) this.onMiss({
4770
- inputKey: key,
4771
- cacheKey: this.genKey(key),
4772
- cacheValue: value,
4773
- outputValue: value
4774
- });
4775
- return value;
4960
+ const cacheKey = this.getCacheKey(key);
4961
+ let translationPromise = this.pendingTranslations.get(cacheKey);
4962
+ if (!translationPromise) {
4963
+ translationPromise = this.translate(key);
4964
+ this.pendingTranslations.set(cacheKey, translationPromise);
4965
+ }
4966
+ try {
4967
+ const value = await translationPromise;
4968
+ if (value != null) this.lifecycle.onMiss?.({
4969
+ inputKey: key,
4970
+ cacheKey,
4971
+ cacheValue: value,
4972
+ outputValue: value
4973
+ });
4974
+ return value;
4975
+ } finally {
4976
+ this.pendingTranslations.delete(cacheKey);
4977
+ }
4776
4978
  }
4777
- /**
4778
- * Generate a key for the cache
4779
- * @param key - The translation key
4780
- * @returns The key
4781
- */
4782
- genKey(key) {
4979
+ getInternalCache() {
4980
+ return structuredClone(this.cache);
4981
+ }
4982
+ getCacheKey(key) {
4783
4983
  return hashMessage(key.message, key.options);
4784
4984
  }
4785
- /**
4786
- * Get the fallback value for a cache miss
4787
- * @param key - The translation key
4788
- * @returns The fallback value
4789
- */
4790
- fallback(key) {
4791
- const translationPromise = this._enqueueTranslation(key);
4792
- if (this._queue.length >= this._batchConfig.maxBatchSize) this._flushNow();
4793
- else this._scheduleBatch();
4985
+ translate(key) {
4986
+ const translationPromise = this.enqueueTranslation(key);
4987
+ if (this.queue.length >= this.batchConfig.maxBatchSize) this.flushNow();
4988
+ else this.scheduleBatch();
4794
4989
  return translationPromise;
4795
4990
  }
4796
- /**
4797
- * Flush the queue now
4798
- */
4799
- _flushNow() {
4800
- if (this._batchTimer) {
4801
- clearTimeout(this._batchTimer);
4802
- this._batchTimer = null;
4991
+ flushNow() {
4992
+ if (this.batchTimer) {
4993
+ clearTimeout(this.batchTimer);
4994
+ this.batchTimer = null;
4803
4995
  }
4804
- this._drainQueue();
4805
- }
4806
- /**
4807
- * Schedule a batch of translations
4808
- */
4809
- _scheduleBatch() {
4810
- if (this._batchTimer) return;
4811
- this._batchTimer = setTimeout(() => {
4812
- this._batchTimer = null;
4813
- this._drainQueue();
4814
- }, this._batchConfig.batchInterval);
4815
- }
4816
- /**
4817
- * Drain the queue
4818
- */
4819
- _drainQueue() {
4820
- while (this._queue.length > 0 && this._activeRequests < this._batchConfig.maxConcurrentRequests) {
4821
- const batch = this._queue.splice(0, this._batchConfig.maxBatchSize);
4822
- this._sendBatchRequest(batch);
4996
+ this.drainQueue();
4997
+ }
4998
+ scheduleBatch() {
4999
+ if (this.batchTimer) return;
5000
+ this.batchTimer = setTimeout(() => {
5001
+ this.batchTimer = null;
5002
+ this.drainQueue();
5003
+ }, this.batchConfig.batchInterval);
5004
+ }
5005
+ drainQueue() {
5006
+ while (this.queue.length > 0 && this.activeRequests < this.batchConfig.maxConcurrentRequests) {
5007
+ const batch = this.queue.splice(0, this.batchConfig.maxBatchSize);
5008
+ this.sendBatchRequest(batch);
4823
5009
  }
4824
- if (this._queue.length > 0) this._scheduleBatch();
5010
+ if (this.queue.length > 0) this.scheduleBatch();
4825
5011
  }
4826
- /**
4827
- * Enqueue translation request and return a promise that resolves when the translation is ready
4828
- * @param {TranslationKey<TranslationValue>} key - The translation key
4829
- * @returns {Promise<TranslationValue>} The translation promise
4830
- */
4831
- _enqueueTranslation(key) {
4832
- const hash = this.genKey(key);
5012
+ enqueueTranslation(key) {
5013
+ const hash = this.getCacheKey(key);
4833
5014
  const options = key.options;
4834
5015
  const metadataOptions = options;
4835
5016
  return new Promise((resolve, reject) => {
4836
- this._queue.push({
5017
+ this.queue.push({
4837
5018
  key: hash,
4838
5019
  source: key.message,
4839
5020
  metadata: {
@@ -4848,38 +5029,28 @@ var TranslationsCache = class extends Cache {
4848
5029
  });
4849
5030
  });
4850
5031
  }
4851
- /**
4852
- * Send a batch request for translations
4853
- * @param {QueueEntry<TranslationValue>[]} batch - The batch of requests to send
4854
- */
4855
- async _sendBatchRequest(batch) {
4856
- this._activeRequests++;
5032
+ async sendBatchRequest(batch) {
5033
+ this.activeRequests++;
4857
5034
  const requests = convertBatchToTranslateManyParams(batch);
4858
- const response = await this._sendBatchRequestWithErrorHandling(batch, requests);
4859
- if (response) this._handleTranslationResponse(batch, response);
4860
- this._activeRequests--;
5035
+ const response = await this.sendBatchRequestWithErrorHandling(batch, requests);
5036
+ if (response) this.handleTranslationResponse(batch, response);
5037
+ this.activeRequests--;
4861
5038
  }
4862
- /**
4863
- * Send a translation request with error handling
4864
- */
4865
- async _sendBatchRequestWithErrorHandling(batch, requests) {
5039
+ async sendBatchRequestWithErrorHandling(batch, requests) {
4866
5040
  try {
4867
- return await this._translateMany(requests);
5041
+ return await this.translateMany(requests);
4868
5042
  } catch (error) {
4869
5043
  for (const entry of batch) entry.reject(error);
4870
5044
  return;
4871
5045
  }
4872
5046
  }
4873
- /**
4874
- * Handle a translation response
4875
- */
4876
- _handleTranslationResponse(batch, response) {
5047
+ handleTranslationResponse(batch, response) {
4877
5048
  for (const entry of batch) {
4878
5049
  const { key } = entry;
4879
5050
  const result = response[key];
4880
5051
  if (result && result.success) {
4881
5052
  const translation = result.translation;
4882
- this.setCache(key, translation);
5053
+ this.cache[key] = translation;
4883
5054
  entry.resolve(translation);
4884
5055
  } else entry.reject(result?.error);
4885
5056
  }
@@ -4897,415 +5068,87 @@ function convertBatchToTranslateManyParams(batch) {
4897
5068
  return acc;
4898
5069
  }, {});
4899
5070
  }
4900
- /**
4901
- * Default cache expiry time in milliseconds
4902
- */
4903
- const DEFAULT_CACHE_EXPIRY_TIME = 6e4;
4904
- /**
4905
- * Cache for looking up translations by locale
4906
- */
4907
- var LocalesCache = class extends Cache {
4908
- /**
4909
- * Constructor
4910
- * @param {Object} params - The parameters for the cache
4911
- * @param {Record<string, CacheEntry<TranslationValue>>} params.init - The initial cache
4912
- * @param {number | null} params.ttl - The time to live for cache entries
4913
- * @param {SafeTranslationsLoader<TranslationValue>} params.loadTranslations - The translation loader function
4914
- * @param {CreateTranslateMany} params.createTranslateMany - Factory function for creating a translate many function
4915
- */
4916
- constructor({ init = {}, ttl, batchConfig, loadTranslations, createTranslateMany, lifecycle: { onLocalesCacheHit: onHit, onLocalesCacheMiss: onMiss, onTranslationsCacheHit, onTranslationsCacheMiss } }) {
4917
- super(init, {
4918
- onHit,
4919
- onMiss
5071
+ var LocalesCache = class {
5072
+ constructor({ ttl, batchConfig, defaultLocale, dictionary = {}, loadTranslations, loadDictionary, createTranslateMany, translateDictionaryEntry, lifecycle }) {
5073
+ this.translations = new ResourceCache({
5074
+ ttl,
5075
+ load: async (locale) => new TranslationsCache({
5076
+ init: await loadTranslations(locale),
5077
+ lifecycle: createTranslationsCacheLifecycle(locale, lifecycle),
5078
+ translateMany: createTranslateMany(locale),
5079
+ batchConfig
5080
+ }),
5081
+ lifecycle: {
5082
+ onHit: lifecycle.onLocalesCacheHit,
5083
+ onMiss: lifecycle.onLocalesCacheMiss
5084
+ }
4920
5085
  });
4921
- this.ttl = DEFAULT_CACHE_EXPIRY_TIME;
4922
- this.ttl = ttl === null ? -1 : ttl ?? 6e4;
4923
- this._translationLoader = loadTranslations;
4924
- this._createTranslateMany = createTranslateMany;
4925
- this._batchConfig = batchConfig;
4926
- this._onTranslationsCacheHit = onTranslationsCacheHit;
4927
- this._onTranslationsCacheMiss = onTranslationsCacheMiss;
4928
- }
4929
- /**
4930
- * Get the translations for a given locale
4931
- * @param key - The locale
4932
- * @returns The translations
4933
- */
4934
- get(key) {
4935
- const entry = this.getCache(key);
4936
- if (!entry || entry.expiresAt > 0 && entry.expiresAt < Date.now()) return;
4937
- const value = entry.translationsCache;
4938
- if (value != null && this.onHit) this.onHit({
4939
- inputKey: key,
4940
- cacheKey: this.genKey(key),
4941
- cacheValue: entry,
4942
- outputValue: value
5086
+ this.dictionaries = new ResourceCache({
5087
+ ttl,
5088
+ load: async (locale) => createDictionaryCache({
5089
+ locale,
5090
+ dictionary: await loadDictionary(locale),
5091
+ translate: translateDictionaryEntry,
5092
+ lifecycle
5093
+ }),
5094
+ lifecycle: {
5095
+ onHit: lifecycle.onLocalesDictionaryCacheHit,
5096
+ onMiss: lifecycle.onLocalesDictionaryCacheMiss
5097
+ }
4943
5098
  });
4944
- return value;
5099
+ this.dictionaries.set(defaultLocale, createDictionaryCache({
5100
+ locale: defaultLocale,
5101
+ dictionary,
5102
+ translate: translateDictionaryEntry,
5103
+ lifecycle
5104
+ }), { expiresAt: -1 });
4945
5105
  }
4946
- /**
4947
- * Miss the cache
4948
- * @param key - The locale
4949
- * @returns The translations cache
4950
- */
4951
- async miss(key) {
4952
- const cacheValue = await this.missCache(key);
4953
- const value = cacheValue.translationsCache;
4954
- if (value != null && this.onMiss) this.onMiss({
4955
- inputKey: key,
4956
- cacheKey: this.genKey(key),
4957
- cacheValue,
4958
- outputValue: value
4959
- });
4960
- return value;
5106
+ getTranslations(locale) {
5107
+ return this.translations.get(locale);
4961
5108
  }
4962
- /**
4963
- * Generate the cache key for a given locale
4964
- * @param key - The locale
4965
- * @returns The cache key
4966
- *
4967
- * This is just an identity function, no transformation needed
4968
- */
4969
- genKey(key) {
4970
- return key;
5109
+ getOrLoadTranslations(locale) {
5110
+ return this.translations.getOrLoad(locale);
4971
5111
  }
4972
- /**
4973
- * Fallback for a cache miss
4974
- * @param locale - The locale
4975
- * @returns The cache entry
4976
- */
4977
- async fallback(locale) {
4978
- return {
4979
- translationsCache: new TranslationsCache({
4980
- init: await this._translationLoader(locale),
4981
- lifecycle: this._createTranslationsCacheLifecycle(locale),
4982
- translateMany: this._createTranslateMany(locale),
4983
- batchConfig: this._batchConfig
4984
- }),
4985
- expiresAt: this.ttl < 0 ? this.ttl : Date.now() + this.ttl
4986
- };
5112
+ getDictionary(locale) {
5113
+ return this.dictionaries.get(locale);
4987
5114
  }
4988
- /**
4989
- * Create the translations cache lifecycle
4990
- * @param locale - The locale
4991
- * @returns The translations cache lifecycle
4992
- */
4993
- _createTranslationsCacheLifecycle(locale) {
4994
- return {
4995
- onHit: this._onTranslationsCacheHit ? (params) => this._onTranslationsCacheHit({
4996
- locale,
4997
- ...params
4998
- }) : void 0,
4999
- onMiss: this._onTranslationsCacheMiss ? (params) => this._onTranslationsCacheMiss({
5000
- locale,
5001
- ...params
5002
- }) : void 0
5003
- };
5115
+ getOrLoadDictionary(locale) {
5116
+ return this.dictionaries.getOrLoad(locale);
5004
5117
  }
5005
5118
  };
5006
- function getDictionaryPath(id) {
5007
- if (!id) return [];
5008
- return id.split(".");
5009
- }
5010
- function isDictionaryValue(value) {
5011
- return typeof value === "object" && value != null && !Array.isArray(value);
5012
- }
5013
- function getDictionaryEntry(value) {
5014
- if (!isDictionaryLeafNode(value)) return;
5119
+ function createTranslationsCacheLifecycle(locale, lifecycle) {
5015
5120
  return {
5016
- entry: Array.isArray(value) ? value[0] : value,
5017
- options: Array.isArray(value) ? value[1] ?? {} : {}
5121
+ onHit: withLocale(locale, lifecycle.onTranslationsCacheHit),
5122
+ onMiss: withLocale(locale, lifecycle.onTranslationsCacheMiss)
5018
5123
  };
5019
5124
  }
5020
- function getDictionaryValue(value) {
5021
- if (Object.keys(value.options).length === 0) return value.entry;
5022
- return [value.entry, value.options];
5023
- }
5024
- function resolveDictionaryLookupOptions(options) {
5025
- const { $format, ...rest } = options;
5026
- return {
5027
- ...rest,
5028
- $format: isStringFormat($format) ? $format : "ICU",
5029
- ...rest.$context === void 0 && typeof rest.context === "string" && { $context: rest.context }
5030
- };
5031
- }
5032
- function isDictionaryLeafNode(value) {
5033
- if (typeof value === "string") return true;
5034
- if (!Array.isArray(value) || typeof value[0] !== "string") return false;
5035
- if (value.length === 1) return true;
5036
- return value.length === 2 && isDictionaryOptions(value[1]);
5037
- }
5038
- function isDictionaryOptions(value) {
5039
- if (typeof value !== "object" || value == null || Array.isArray(value)) return false;
5040
- const options = value;
5041
- return (options.$context === void 0 || typeof options.$context === "string") && (options.$format === void 0 || isStringFormat(options.$format)) && (options.$maxChars === void 0 || typeof options.$maxChars === "number") && (options.context === void 0 || typeof options.context === "string");
5125
+ function createDictionaryCache({ locale, dictionary, translate, lifecycle }) {
5126
+ const { onDictionaryCacheHit, onDictionaryCacheMiss, onDictionaryObjectCacheHit } = lifecycle;
5127
+ return new DictionaryCache({
5128
+ init: dictionary,
5129
+ runtimeTranslate: (key, sourceEntry) => translate(locale, key, sourceEntry),
5130
+ lifecycle: {
5131
+ onHit: withLocale(locale, onDictionaryCacheHit),
5132
+ onMiss: withLocale(locale, onDictionaryCacheMiss),
5133
+ onDictionaryObjectCacheHit: withLocale(locale, onDictionaryObjectCacheHit)
5134
+ }
5135
+ });
5042
5136
  }
5043
- function isStringFormat(value) {
5044
- return value === "ICU" || value === "I18NEXT" || value === "STRING";
5137
+ function withLocale(locale, callback) {
5138
+ return callback ? (params) => callback({
5139
+ locale,
5140
+ ...params
5141
+ }) : void 0;
5045
5142
  }
5046
- function replaceDictionary(target, source) {
5047
- for (const key of Object.keys(target)) delete target[key];
5048
- Object.assign(target, source);
5049
- }
5050
- var DictionarySourceNotFoundError = class extends Error {
5051
- constructor(id) {
5052
- super(`I18nManager: source dictionary entry ${id} is not defined`);
5053
- this.name = "DictionarySourceNotFoundError";
5054
- }
5055
- };
5056
- /**
5057
- * A cache for a single locale's dictionary
5058
- *
5059
- * Principles:
5060
- * - This class is language agnostic, and should never store the locale code as a parameter.
5061
- * Locale logic is handled at the LocalesDictionaryCache level. Use a callback function
5062
- * that has the locale parameter embedded if you wish to use the locale code.
5063
- */
5064
- var DictionaryCache = class extends Cache {
5065
- /**
5066
- * Constructor
5067
- * @param {Object} params - The parameters for the cache
5068
- * @param {Dictionary} params.init - The initial cache
5069
- */
5070
- constructor({ init, lifecycle, runtimeTranslate }) {
5071
- super(init, lifecycle);
5072
- this._runtimeTranslate = runtimeTranslate;
5073
- this.onHitObj = lifecycle?.onHitObj;
5074
- this.onMissObj = lifecycle?.onMissObj;
5075
- }
5076
- /**
5077
- * Get the dictionary value for a given key
5078
- * @param key - The dictionary key
5079
- * @returns The dictionary value
5080
- */
5081
- get(key) {
5082
- const value = this.getCache(key);
5083
- const entry = getDictionaryEntry(value);
5084
- if (entry === void 0) return;
5085
- if (this.onHit) this.onHit({
5086
- inputKey: key,
5087
- cacheKey: this.genKey(key),
5088
- cacheValue: value,
5089
- outputValue: entry
5090
- });
5091
- return entry;
5092
- }
5093
- set(key, value) {
5094
- const dictionaryValue = getDictionaryValue(value);
5095
- this.setCache(this.genKey(key), dictionaryValue);
5096
- }
5097
- getObj(key) {
5098
- const value = this.getCache(key);
5099
- if (value === void 0) return;
5100
- const outputValue = structuredClone(value);
5101
- if (this.onHitObj) this.onHitObj({
5102
- inputKey: key,
5103
- cacheKey: this.genKey(key),
5104
- cacheValue: value,
5105
- outputValue
5106
- });
5107
- return outputValue;
5108
- }
5109
- setObj(key, value) {
5110
- this.setCache(this.genKey(key), structuredClone(value));
5111
- }
5112
- async missObj(key, sourceObject) {
5113
- const sourceEntry = getDictionaryEntry(sourceObject);
5114
- if (sourceEntry !== void 0) return getDictionaryValue(await this.miss(key, sourceEntry));
5115
- if (!isDictionaryValue(sourceObject)) throw new DictionarySourceNotFoundError(key);
5116
- const translatedEntries = await Promise.all(Object.entries(sourceObject).map(async ([childKey, childSource]) => {
5117
- const childPath = key ? `${key}.${childKey}` : childKey;
5118
- return [childKey, await this.missObj(childPath, childSource)];
5119
- }));
5120
- const translatedObject = Object.fromEntries(translatedEntries);
5121
- this.setObj(key, translatedObject);
5122
- return translatedObject;
5123
- }
5124
- /**
5125
- * Miss the cache
5126
- * @param key - The dictionary key
5127
- * @returns The dictionary value
5128
- */
5129
- async miss(key, sourceEntry) {
5130
- const value = await this.missCache(key, sourceEntry);
5131
- const entry = getDictionaryEntry(value);
5132
- if (entry === void 0) throw new Error("DictionaryCache missCache did not return a DictionaryEntry");
5133
- if (this.onMiss) this.onMiss({
5134
- inputKey: key,
5135
- cacheKey: this.genKey(key),
5136
- cacheValue: value,
5137
- outputValue: entry
5138
- });
5139
- return entry;
5140
- }
5141
- /**
5142
- * Set the value for a key
5143
- */
5144
- setCache(cacheKey, value) {
5145
- const cache = this.getMutableCache();
5146
- const dictionaryPath = getDictionaryPath(cacheKey);
5147
- if (dictionaryPath.length === 0) {
5148
- if (isDictionaryValue(value)) replaceDictionary(cache, value);
5149
- return;
5150
- }
5151
- let current = cache;
5152
- for (const key of dictionaryPath.slice(0, -1)) {
5153
- const next = current[key];
5154
- if (!isDictionaryValue(next)) current[key] = {};
5155
- current = current[key];
5156
- }
5157
- current[dictionaryPath[dictionaryPath.length - 1]] = value;
5158
- }
5159
- /**
5160
- * Look up the key
5161
- */
5162
- getCache(key) {
5163
- const dictionaryPath = getDictionaryPath(this.genKey(key));
5164
- let current = this.getMutableCache();
5165
- if (dictionaryPath.length === 0) return current;
5166
- for (const pathSegment of dictionaryPath) {
5167
- if (!isDictionaryValue(current)) return;
5168
- current = current[pathSegment];
5169
- }
5170
- return current;
5171
- }
5172
- /**
5173
- * Generate a key for the cache
5174
- * @param key - The dictionary key
5175
- * @returns The key
5176
- */
5177
- genKey(key) {
5178
- return key;
5179
- }
5180
- /**
5181
- * Get the fallback value for a cache miss
5182
- * @param key - The dictionary key
5183
- * @returns The fallback value
5184
- *
5185
- * @throws {Error} - If the fallback is not implemented
5186
- */
5187
- fallback(key, sourceEntry) {
5188
- return this._runtimeTranslate(key, sourceEntry);
5189
- }
5190
- };
5191
- /**
5192
- * Cache for looking up dictionaries by locale
5193
- */
5194
- var LocalesDictionaryCache = class extends Cache {
5195
- /**
5196
- * Constructor
5197
- * @param {Object} params - The parameters for the cache
5198
- * @param {number | null} params.ttl - The time to live for cache entries
5199
- * @param {DictionaryLoader} params.loadDictionary - The dictionary loader function
5200
- */
5201
- constructor({ ttl, defaultLocale, dictionary = {}, loadDictionary, runtimeTranslate, lifecycle: { onLocalesDictionaryCacheHit: onHit, onLocalesDictionaryCacheMiss: onMiss, onDictionaryCacheHit, onDictionaryCacheMiss, onDictionaryObjectCacheHit } }) {
5202
- super({}, {
5203
- onHit,
5204
- onMiss
5205
- });
5206
- this.ttl = DEFAULT_CACHE_EXPIRY_TIME;
5207
- this.ttl = ttl === null ? -1 : ttl ?? 6e4;
5208
- this._dictionaryLoader = loadDictionary;
5209
- this._runtimeTranslate = runtimeTranslate;
5210
- this._onDictionaryCacheHit = onDictionaryCacheHit;
5211
- this._onDictionaryCacheMiss = onDictionaryCacheMiss;
5212
- this._onDictionaryObjectCacheHit = onDictionaryObjectCacheHit;
5213
- this.setCache(defaultLocale, {
5214
- dictionaryCache: new DictionaryCache({
5215
- init: dictionary,
5216
- runtimeTranslate: this._createDictionaryRuntimeTranslate(defaultLocale),
5217
- lifecycle: this._createDictionaryCacheLifecycle(defaultLocale)
5218
- }),
5219
- expiresAt: -1
5220
- });
5221
- }
5222
- /**
5223
- * Get the dictionary for a given locale
5224
- * @param key - The locale
5225
- * @returns The dictionary
5226
- */
5227
- get(key) {
5228
- const entry = this.getCache(key);
5229
- if (!entry || entry.expiresAt > 0 && entry.expiresAt < Date.now()) return;
5230
- const value = entry.dictionaryCache;
5231
- if (value != null && this.onHit) this.onHit({
5232
- inputKey: key,
5233
- cacheKey: this.genKey(key),
5234
- cacheValue: entry,
5235
- outputValue: value
5236
- });
5237
- return value;
5238
- }
5239
- /**
5240
- * Miss the cache
5241
- * @param key - The locale
5242
- * @returns The dictionary cache
5243
- */
5244
- async miss(key) {
5245
- const cacheValue = await this.missCache(key);
5246
- const value = cacheValue.dictionaryCache;
5247
- if (value != null && this.onMiss) this.onMiss({
5248
- inputKey: key,
5249
- cacheKey: this.genKey(key),
5250
- cacheValue,
5251
- outputValue: value
5252
- });
5253
- return value;
5254
- }
5255
- /**
5256
- * Generate the cache key for a given locale
5257
- * @param key - The locale
5258
- * @returns The cache key
5259
- *
5260
- * This is just an identity function, no transformation needed
5261
- */
5262
- genKey(key) {
5263
- return key;
5264
- }
5265
- /**
5266
- * Fallback for a cache miss
5267
- * @param locale - The locale
5268
- * @returns The cache entry
5269
- */
5270
- async fallback(locale) {
5271
- return {
5272
- dictionaryCache: new DictionaryCache({
5273
- init: await this._dictionaryLoader(locale),
5274
- runtimeTranslate: this._createDictionaryRuntimeTranslate(locale),
5275
- lifecycle: this._createDictionaryCacheLifecycle(locale)
5276
- }),
5277
- expiresAt: this.ttl < 0 ? this.ttl : Date.now() + this.ttl
5278
- };
5279
- }
5280
- /**
5281
- * Create the dictionary cache lifecycle
5282
- * @param locale - The locale
5283
- * @returns The dictionary cache lifecycle
5284
- */
5285
- _createDictionaryCacheLifecycle(locale) {
5286
- return {
5287
- onHit: this._onDictionaryCacheHit ? (params) => this._onDictionaryCacheHit({
5288
- locale,
5289
- ...params
5290
- }) : void 0,
5291
- onMiss: this._onDictionaryCacheMiss ? (params) => this._onDictionaryCacheMiss({
5292
- locale,
5293
- ...params
5294
- }) : void 0,
5295
- onHitObj: this._onDictionaryObjectCacheHit ? (params) => this._onDictionaryObjectCacheHit({
5296
- locale,
5297
- ...params
5298
- }) : void 0
5299
- };
5300
- }
5301
- _createDictionaryRuntimeTranslate(locale) {
5302
- return (key, sourceEntry) => this._runtimeTranslate(locale, key, sourceEntry);
5303
- }
5304
- };
5143
+ const LOCALES_CACHE_HIT_EVENT_NAME = "locales-cache-hit";
5305
5144
  const LOCALES_CACHE_MISS_EVENT_NAME = "locales-cache-miss";
5145
+ const TRANSLATIONS_CACHE_HIT_EVENT_NAME = "translations-cache-hit";
5306
5146
  const TRANSLATIONS_CACHE_MISS_EVENT_NAME = "translations-cache-miss";
5147
+ const LOCALES_DICTIONARY_CACHE_HIT_EVENT_NAME = "locales-dictionary-cache-hit";
5307
5148
  const LOCALES_DICTIONARY_CACHE_MISS_EVENT_NAME = "locales-dictionary-cache-miss";
5149
+ const DICTIONARY_CACHE_HIT_EVENT_NAME = "dictionary-cache-hit";
5308
5150
  const DICTIONARY_CACHE_MISS_EVENT_NAME = "dictionary-cache-miss";
5151
+ const DICTIONARY_OBJECT_CACHE_HIT_EVENT_NAME = "dictionary-object-cache-hit";
5309
5152
  /**
5310
5153
  * Maps consumer-facing lifecycle callbacks to internal locales cache lifecycle callbacks.
5311
5154
  * The consumer API exposes simplified params (locale, hash, value) while the internal
@@ -5313,22 +5156,24 @@ const DICTIONARY_CACHE_MISS_EVENT_NAME = "dictionary-cache-miss";
5313
5156
  *
5314
5157
  * @deprecated - move to subscription api instead
5315
5158
  */
5316
- function createLifecycleCallbacks(emit) {
5159
+ function createLifecycleCallbacks(emit, hasListeners = () => true) {
5317
5160
  return {
5318
5161
  onLocalesCacheHit: (params) => {
5319
- emit("locales-cache-hit", {
5162
+ if (!hasListeners("locales-cache-hit")) return;
5163
+ emit(LOCALES_CACHE_HIT_EVENT_NAME, {
5320
5164
  locale: params.inputKey,
5321
5165
  translations: params.outputValue.getInternalCache()
5322
5166
  });
5323
5167
  },
5324
5168
  onLocalesCacheMiss: (params) => {
5169
+ if (!hasListeners("locales-cache-miss")) return;
5325
5170
  emit(LOCALES_CACHE_MISS_EVENT_NAME, {
5326
5171
  locale: params.inputKey,
5327
5172
  translations: params.outputValue.getInternalCache()
5328
5173
  });
5329
5174
  },
5330
5175
  onTranslationsCacheHit: (params) => {
5331
- emit("translations-cache-hit", {
5176
+ emit(TRANSLATIONS_CACHE_HIT_EVENT_NAME, {
5332
5177
  locale: params.locale,
5333
5178
  hash: params.cacheKey,
5334
5179
  translation: params.outputValue
@@ -5342,19 +5187,21 @@ function createLifecycleCallbacks(emit) {
5342
5187
  });
5343
5188
  },
5344
5189
  onLocalesDictionaryCacheHit: (params) => {
5345
- emit("locales-dictionary-cache-hit", {
5190
+ if (!hasListeners("locales-dictionary-cache-hit")) return;
5191
+ emit(LOCALES_DICTIONARY_CACHE_HIT_EVENT_NAME, {
5346
5192
  locale: params.inputKey,
5347
5193
  dictionary: params.outputValue.getInternalCache()
5348
5194
  });
5349
5195
  },
5350
5196
  onLocalesDictionaryCacheMiss: (params) => {
5197
+ if (!hasListeners("locales-dictionary-cache-miss")) return;
5351
5198
  emit(LOCALES_DICTIONARY_CACHE_MISS_EVENT_NAME, {
5352
5199
  locale: params.inputKey,
5353
5200
  dictionary: params.outputValue.getInternalCache()
5354
5201
  });
5355
5202
  },
5356
5203
  onDictionaryCacheHit: (params) => {
5357
- emit("dictionary-cache-hit", {
5204
+ emit(DICTIONARY_CACHE_HIT_EVENT_NAME, {
5358
5205
  locale: params.locale,
5359
5206
  id: params.cacheKey,
5360
5207
  dictionaryEntry: params.outputValue
@@ -5368,7 +5215,7 @@ function createLifecycleCallbacks(emit) {
5368
5215
  });
5369
5216
  },
5370
5217
  onDictionaryObjectCacheHit: (params) => {
5371
- emit("dictionary-object-cache-hit", {
5218
+ emit(DICTIONARY_OBJECT_CACHE_HIT_EVENT_NAME, {
5372
5219
  locale: params.locale,
5373
5220
  id: params.cacheKey,
5374
5221
  dictionaryValue: params.outputValue
@@ -5403,6 +5250,9 @@ var EventEmitter = class {
5403
5250
  emit(eventName, event) {
5404
5251
  this.listeners[eventName]?.forEach((subscriber) => subscriber(event));
5405
5252
  }
5253
+ hasListeners(eventName) {
5254
+ return (this.listeners[eventName]?.size ?? 0) > 0;
5255
+ }
5406
5256
  };
5407
5257
  /**
5408
5258
  * Subscribes to the lifecycle callbacks and emits the events to the event emitter
@@ -5412,7 +5262,7 @@ var EventEmitter = class {
5412
5262
  * and is only used internally
5413
5263
  */
5414
5264
  function subscribeLifecycleCallbacks({ onLocalesCacheHit, onLocalesCacheMiss, onTranslationsCacheHit, onTranslationsCacheMiss, onLocalesDictionaryCacheHit, onLocalesDictionaryCacheMiss, onDictionaryCacheHit, onDictionaryCacheMiss, onDictionaryObjectCacheHit }, subscribe) {
5415
- if (onLocalesCacheHit) subscribe("locales-cache-hit", (event) => {
5265
+ if (onLocalesCacheHit) subscribe(LOCALES_CACHE_HIT_EVENT_NAME, (event) => {
5416
5266
  onLocalesCacheHit({
5417
5267
  ...event,
5418
5268
  value: event.translations
@@ -5424,7 +5274,7 @@ function subscribeLifecycleCallbacks({ onLocalesCacheHit, onLocalesCacheMiss, on
5424
5274
  value: event.translations
5425
5275
  });
5426
5276
  });
5427
- if (onTranslationsCacheHit) subscribe("translations-cache-hit", (event) => {
5277
+ if (onTranslationsCacheHit) subscribe(TRANSLATIONS_CACHE_HIT_EVENT_NAME, (event) => {
5428
5278
  onTranslationsCacheHit({
5429
5279
  ...event,
5430
5280
  value: event.translation
@@ -5436,19 +5286,19 @@ function subscribeLifecycleCallbacks({ onLocalesCacheHit, onLocalesCacheMiss, on
5436
5286
  value: event.translation
5437
5287
  });
5438
5288
  });
5439
- if (onLocalesDictionaryCacheHit) subscribe("locales-dictionary-cache-hit", (event) => {
5289
+ if (onLocalesDictionaryCacheHit) subscribe(LOCALES_DICTIONARY_CACHE_HIT_EVENT_NAME, (event) => {
5440
5290
  onLocalesDictionaryCacheHit(event);
5441
5291
  });
5442
5292
  if (onLocalesDictionaryCacheMiss) subscribe(LOCALES_DICTIONARY_CACHE_MISS_EVENT_NAME, (event) => {
5443
5293
  onLocalesDictionaryCacheMiss(event);
5444
5294
  });
5445
- if (onDictionaryCacheHit) subscribe("dictionary-cache-hit", (event) => {
5295
+ if (onDictionaryCacheHit) subscribe(DICTIONARY_CACHE_HIT_EVENT_NAME, (event) => {
5446
5296
  onDictionaryCacheHit(event);
5447
5297
  });
5448
5298
  if (onDictionaryCacheMiss) subscribe(DICTIONARY_CACHE_MISS_EVENT_NAME, (event) => {
5449
5299
  onDictionaryCacheMiss(event);
5450
5300
  });
5451
- if (onDictionaryObjectCacheHit) subscribe("dictionary-object-cache-hit", (event) => {
5301
+ if (onDictionaryObjectCacheHit) subscribe(DICTIONARY_OBJECT_CACHE_HIT_EVENT_NAME, (event) => {
5452
5302
  onDictionaryObjectCacheHit(event);
5453
5303
  });
5454
5304
  }
@@ -5479,26 +5329,32 @@ var I18nManager = class extends EventEmitter {
5479
5329
  locales: this.config.locales,
5480
5330
  customMapping: this.config.customMapping
5481
5331
  });
5482
- const loadTranslations = createTranslationLoader(params);
5483
- const loadDictionary = createDictionaryLoader(params);
5332
+ const loadTranslations = routeCreateTranslationLoader({
5333
+ loadTranslations: params.loadTranslations,
5334
+ type: getLoadTranslationsType(params),
5335
+ remoteTranslationLoaderParams: {
5336
+ cacheUrl: params.cacheUrl,
5337
+ projectId: params.projectId,
5338
+ _versionId: params._versionId,
5339
+ _branchId: params._branchId,
5340
+ customMapping: params.customMapping
5341
+ }
5342
+ });
5343
+ const loadDictionary = params.loadDictionary ?? (() => Promise.resolve({}));
5484
5344
  const runtimeTranslationTimeout = this.config.runtimeTranslation?.timeout ?? DEFAULT_TRANSLATION_TIMEOUT;
5485
5345
  const runtimeTranslationMetadata = this.config.runtimeTranslation?.metadata ?? {};
5486
5346
  const createTranslateMany = createTranslateManyFactory(this.getGTClassClean(), runtimeTranslationTimeout, runtimeTranslationMetadata);
5487
5347
  subscribeLifecycleCallbacks(params.lifecycle ?? {}, (...args) => this.subscribe(...args));
5488
- const lifecycle = createLifecycleCallbacks((...args) => this.emit(...args));
5348
+ const lifecycle = createLifecycleCallbacks((...args) => this.emit(...args), (eventName) => this.hasListeners(eventName));
5489
5349
  this.localesCache = new LocalesCache({
5490
- loadTranslations,
5491
- createTranslateMany,
5492
- lifecycle,
5493
- ttl: this.config.cacheExpiryTime,
5494
- batchConfig: this.config.batchConfig
5495
- });
5496
- this.localesDictionaryCache = new LocalesDictionaryCache({
5497
5350
  defaultLocale: this.config.defaultLocale,
5498
5351
  dictionary: params.dictionary,
5352
+ loadTranslations,
5499
5353
  loadDictionary,
5500
- runtimeTranslate: (locale, id, sourceEntry) => this.dictionaryRuntimeTranslate(locale, id, sourceEntry),
5354
+ createTranslateMany,
5355
+ translateDictionaryEntry: (locale, id, sourceEntry) => this.translateDictionaryEntry(locale, id, sourceEntry),
5501
5356
  ttl: this.config.cacheExpiryTime,
5357
+ batchConfig: this.config.batchConfig,
5502
5358
  lifecycle
5503
5359
  });
5504
5360
  }
@@ -5570,9 +5426,7 @@ var I18nManager = class extends EventEmitter {
5570
5426
  try {
5571
5427
  const translationLocale = this.resolveCacheLocale(locale);
5572
5428
  if (!translationLocale) return {};
5573
- let txCache = this.localesCache.get(translationLocale);
5574
- if (!txCache) txCache = await this.localesCache.miss(translationLocale);
5575
- return txCache.getInternalCache();
5429
+ return (await this.localesCache.getOrLoadTranslations(translationLocale)).getInternalCache();
5576
5430
  } catch (error) {
5577
5431
  this.handleError(error);
5578
5432
  return {};
@@ -5585,10 +5439,8 @@ var I18nManager = class extends EventEmitter {
5585
5439
  async loadDictionary(locale) {
5586
5440
  try {
5587
5441
  const dictionaryLocale = this.resolveCacheLocale(locale);
5588
- if (!dictionaryLocale) return this.localesDictionaryCache.get(this.config.defaultLocale)?.getInternalCache() ?? {};
5589
- let dictionaryCache = this.localesDictionaryCache.get(dictionaryLocale);
5590
- if (!dictionaryCache) dictionaryCache = await this.localesDictionaryCache.miss(dictionaryLocale);
5591
- return dictionaryCache.getInternalCache();
5442
+ if (!dictionaryLocale) return this.getDefaultDictionaryCache()?.getInternalCache() ?? {};
5443
+ return (await this.localesCache.getOrLoadDictionary(dictionaryLocale)).getInternalCache();
5592
5444
  } catch (error) {
5593
5445
  this.handleError(error);
5594
5446
  return {};
@@ -5599,8 +5451,8 @@ var I18nManager = class extends EventEmitter {
5599
5451
  */
5600
5452
  lookupDictionary(locale, id) {
5601
5453
  try {
5602
- const dictionaryLocale = this.resolveCacheLocale(locale) ?? this.config.defaultLocale;
5603
- return this.localesDictionaryCache.get(dictionaryLocale)?.get(id);
5454
+ const dictionaryLocale = this.resolveDictionaryCacheLocale(locale);
5455
+ return this.localesCache.getDictionary(dictionaryLocale)?.getEntry(id);
5604
5456
  } catch (error) {
5605
5457
  this.handleError(error);
5606
5458
  return;
@@ -5611,8 +5463,8 @@ var I18nManager = class extends EventEmitter {
5611
5463
  */
5612
5464
  lookupDictionaryObj(locale, id) {
5613
5465
  try {
5614
- const dictionaryLocale = this.resolveCacheLocale(locale) ?? this.config.defaultLocale;
5615
- return this.localesDictionaryCache.get(dictionaryLocale)?.getObj(id);
5466
+ const dictionaryLocale = this.resolveDictionaryCacheLocale(locale);
5467
+ return this.localesCache.getDictionary(dictionaryLocale)?.getValue(id);
5616
5468
  } catch (error) {
5617
5469
  this.handleError(error);
5618
5470
  return;
@@ -5625,19 +5477,10 @@ var I18nManager = class extends EventEmitter {
5625
5477
  async lookupDictionaryWithFallback(locale, id) {
5626
5478
  try {
5627
5479
  const dictionaryLocale = this.resolveCacheLocale(locale);
5628
- if (!dictionaryLocale) {
5629
- const sourceEntry = this.localesDictionaryCache.get(this.config.defaultLocale)?.get(id);
5630
- if (sourceEntry === void 0) throw new DictionarySourceNotFoundError(id);
5631
- return sourceEntry;
5632
- }
5633
- let dictionaryCache = this.localesDictionaryCache.get(dictionaryLocale);
5634
- if (!dictionaryCache) dictionaryCache = await this.localesDictionaryCache.miss(dictionaryLocale);
5635
- let dictionaryEntry = dictionaryCache.get(id);
5636
- if (dictionaryEntry === void 0) {
5637
- const sourceEntry = this.localesDictionaryCache.get(this.config.defaultLocale)?.get(id);
5638
- if (sourceEntry === void 0) throw new DictionarySourceNotFoundError(id);
5639
- dictionaryEntry = await dictionaryCache.miss(id, sourceEntry);
5640
- }
5480
+ if (!dictionaryLocale) return this.getSourceDictionaryEntry(id);
5481
+ const dictionaryCache = await this.localesCache.getOrLoadDictionary(dictionaryLocale);
5482
+ let dictionaryEntry = dictionaryCache.getEntry(id);
5483
+ if (dictionaryEntry === void 0) dictionaryEntry = await dictionaryCache.materializeEntry(id, this.getSourceDictionaryEntry(id));
5641
5484
  return dictionaryEntry;
5642
5485
  } catch (error) {
5643
5486
  this.handleError(error);
@@ -5651,25 +5494,41 @@ var I18nManager = class extends EventEmitter {
5651
5494
  async lookupDictionaryObjWithFallback(locale, id) {
5652
5495
  try {
5653
5496
  const dictionaryLocale = this.resolveCacheLocale(locale);
5654
- if (!dictionaryLocale) {
5655
- const sourceObject = this.localesDictionaryCache.get(this.config.defaultLocale)?.getObj(id);
5656
- if (sourceObject === void 0) throw new DictionarySourceNotFoundError(id);
5657
- return sourceObject;
5497
+ if (!dictionaryLocale) return this.getSourceDictionaryObject(id);
5498
+ const dictionaryCache = await this.localesCache.getOrLoadDictionary(dictionaryLocale);
5499
+ const targetObject = dictionaryCache.getValue(id);
5500
+ const sourceObject = this.getSourceDictionaryObject(id, { throwOnMissing: false });
5501
+ if (sourceObject === void 0) {
5502
+ if (targetObject !== void 0) return targetObject;
5503
+ throw new DictionarySourceNotFoundError(id);
5658
5504
  }
5659
- let dictionaryCache = this.localesDictionaryCache.get(dictionaryLocale);
5660
- if (!dictionaryCache) dictionaryCache = await this.localesDictionaryCache.miss(dictionaryLocale);
5661
- let dictionaryObject = dictionaryCache.getObj(id);
5662
- if (dictionaryObject === void 0) {
5663
- const sourceObject = this.localesDictionaryCache.get(this.config.defaultLocale)?.getObj(id);
5664
- if (sourceObject === void 0) throw new DictionarySourceNotFoundError(id);
5665
- dictionaryObject = await dictionaryCache.missObj(id, sourceObject);
5666
- }
5667
- return dictionaryObject;
5505
+ return await dictionaryCache.materializeValue(id, sourceObject, targetObject);
5668
5506
  } catch (error) {
5669
5507
  this.handleError(error);
5670
5508
  return;
5671
5509
  }
5672
5510
  }
5511
+ async translateDictionaryEntry(locale, id, sourceEntry) {
5512
+ const translation = await this.lookupTranslationWithFallbackResolved(locale, sourceEntry.entry, resolveDictionaryLookupOptions(sourceEntry.options));
5513
+ if (typeof translation !== "string") throw new Error(`Dictionary entry "${id}" could not be translated into a string. Check the source entry and translation loader output.`);
5514
+ return translation;
5515
+ }
5516
+ getSourceDictionaryEntry(id) {
5517
+ const sourceEntry = this.getDefaultDictionaryCache()?.getEntry(id);
5518
+ if (sourceEntry === void 0) throw new DictionarySourceNotFoundError(id);
5519
+ return sourceEntry;
5520
+ }
5521
+ getSourceDictionaryObject(id, { throwOnMissing = true } = {}) {
5522
+ const sourceObject = this.getDefaultDictionaryCache()?.getValue(id);
5523
+ if (sourceObject === void 0 && throwOnMissing) throw new DictionarySourceNotFoundError(id);
5524
+ return sourceObject;
5525
+ }
5526
+ getDefaultDictionaryCache() {
5527
+ return this.localesCache.getDictionary(this.config.defaultLocale);
5528
+ }
5529
+ resolveDictionaryCacheLocale(locale) {
5530
+ return this.resolveCacheLocale(locale) ?? this.config.defaultLocale;
5531
+ }
5673
5532
  /**
5674
5533
  * Just lookup a translation
5675
5534
  */
@@ -5677,7 +5536,7 @@ var I18nManager = class extends EventEmitter {
5677
5536
  try {
5678
5537
  const { translationLocale, options: lookupOptions } = this.resolveLookupParams(locale, options);
5679
5538
  if (!translationLocale) return message;
5680
- const txCache = this.localesCache.get(translationLocale);
5539
+ const txCache = this.localesCache.getTranslations(translationLocale);
5681
5540
  if (!txCache) return void 0;
5682
5541
  return txCache.get({
5683
5542
  message,
@@ -5715,9 +5574,7 @@ var I18nManager = class extends EventEmitter {
5715
5574
  if (!translationLocale) return (message) => message;
5716
5575
  const resolvedPrefetchEntries = resolvePrefetchEntriesByLocale(prefetchEntries, translationLocale, (entryLocale) => this.resolveCacheLocale(entryLocale) ?? this.resolveLocale(entryLocale));
5717
5576
  if (resolvedPrefetchEntries.length !== prefetchEntries.length) logger_default.warn(`I18nManager: getLookupTranslation(): prefetchEntries must all be the same locale, ignoring all entries that are not for ${translationLocale}`);
5718
- let txCache = this.localesCache.get(translationLocale);
5719
- if (!txCache) txCache = await this.localesCache.miss(translationLocale);
5720
- if (!txCache) return () => void 0;
5577
+ const txCache = await this.localesCache.getOrLoadTranslations(translationLocale);
5721
5578
  await Promise.all(resolvedPrefetchEntries.filter((entry) => txCache.get(entry) == null).map((entry) => txCache.miss(entry)));
5722
5579
  return (message, options = {}) => {
5723
5580
  return txCache.get({
@@ -5819,8 +5676,7 @@ var I18nManager = class extends EventEmitter {
5819
5676
  async lookupTranslationWithFallbackResolved(locale, message, options) {
5820
5677
  const { translationLocale, options: lookupOptions } = this.resolveLookupParams(locale, options);
5821
5678
  if (!translationLocale) return message;
5822
- let txCache = this.localesCache.get(translationLocale);
5823
- if (!txCache) txCache = await this.localesCache.miss(translationLocale);
5679
+ const txCache = await this.localesCache.getOrLoadTranslations(translationLocale);
5824
5680
  let translation = txCache.get({
5825
5681
  message,
5826
5682
  options: lookupOptions
@@ -5832,14 +5688,6 @@ var I18nManager = class extends EventEmitter {
5832
5688
  return translation;
5833
5689
  }
5834
5690
  /**
5835
- * Runtime lookup function for dictionaries
5836
- */
5837
- async dictionaryRuntimeTranslate(locale, id, sourceEntry) {
5838
- const translation = await this.lookupTranslationWithFallbackResolved(locale, sourceEntry.entry, resolveDictionaryLookupOptions(sourceEntry.options));
5839
- if (typeof translation !== "string") throw new Error(`Dictionary entry "${id}" could not be translated into a string. Check the source entry and translation loader output.`);
5840
- return translation;
5841
- }
5842
- /**
5843
5691
  * A helper function to create a gt class that is locale agnostic
5844
5692
  * This is helpful for when our getLocale function is bound to a
5845
5693
  * specific context
@@ -5936,28 +5784,6 @@ function resolvePrefetchEntriesByLocale(prefetchEntries, locale, resolveLocale)
5936
5784
  }
5937
5785
  });
5938
5786
  }
5939
- /**
5940
- * Helper function for creating a translation loader
5941
- */
5942
- function createTranslationLoader(params) {
5943
- return routeCreateTranslationLoader({
5944
- loadTranslations: params.loadTranslations,
5945
- type: getLoadTranslationsType(params),
5946
- remoteTranslationLoaderParams: {
5947
- cacheUrl: params.cacheUrl,
5948
- projectId: params.projectId,
5949
- _versionId: params._versionId,
5950
- _branchId: params._branchId,
5951
- customMapping: params.customMapping
5952
- }
5953
- });
5954
- }
5955
- /**
5956
- * Helper function for creating a dictionary loader
5957
- */
5958
- function createDictionaryLoader(params) {
5959
- return params.loadDictionary ?? (() => Promise.resolve({}));
5960
- }
5961
5787
  let i18nManager = void 0;
5962
5788
  let fallbackDefaultLocale = "en";
5963
5789
  let conditionStore = { getLocale: () => fallbackDefaultLocale };