@tolgee/core 5.33.2 → 5.33.3-prerelease.2efc0e6b.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 (56) hide show
  1. package/dist/tolgee.cjs.js +227 -186
  2. package/dist/tolgee.cjs.js.map +1 -1
  3. package/dist/tolgee.cjs.min.js +1 -1
  4. package/dist/tolgee.cjs.min.js.map +1 -1
  5. package/dist/tolgee.esm.js +227 -186
  6. package/dist/tolgee.esm.js.map +1 -1
  7. package/dist/tolgee.esm.min.js +1 -1
  8. package/dist/tolgee.esm.min.js.map +1 -1
  9. package/dist/tolgee.esm.min.mjs +1 -1
  10. package/dist/tolgee.esm.min.mjs.map +1 -1
  11. package/dist/tolgee.esm.mjs +227 -186
  12. package/dist/tolgee.esm.mjs.map +1 -1
  13. package/dist/tolgee.umd.js +227 -186
  14. package/dist/tolgee.umd.js.map +1 -1
  15. package/dist/tolgee.umd.min.js +1 -1
  16. package/dist/tolgee.umd.min.js.map +1 -1
  17. package/lib/Controller/Cache/Cache.d.ts +8 -12
  18. package/lib/Controller/Cache/helpers.d.ts +3 -2
  19. package/lib/Controller/Controller.d.ts +57 -26
  20. package/lib/Controller/Events/EventEmitter.d.ts +5 -5
  21. package/lib/Controller/Events/EventEmitterCombined.d.ts +6 -0
  22. package/lib/Controller/Events/Events.d.ts +39 -12
  23. package/lib/Controller/State/State.d.ts +10 -4
  24. package/lib/Controller/State/initState.d.ts +29 -7
  25. package/lib/TolgeeCore.d.ts +25 -28
  26. package/lib/helpers.d.ts +2 -2
  27. package/lib/types/cache.d.ts +19 -1
  28. package/lib/types/events.d.ts +24 -23
  29. package/lib/types/index.d.ts +1 -1
  30. package/package.json +2 -2
  31. package/src/Controller/Cache/Cache.test.ts +189 -0
  32. package/src/Controller/Cache/Cache.ts +118 -57
  33. package/src/Controller/Cache/helpers.ts +12 -3
  34. package/src/Controller/Controller.ts +81 -27
  35. package/src/Controller/Events/EventEmitter.ts +23 -15
  36. package/src/Controller/Events/EventEmitterCombined.ts +55 -0
  37. package/src/Controller/Events/Events.ts +31 -23
  38. package/src/Controller/Plugins/Plugins.ts +5 -0
  39. package/src/Controller/State/State.ts +26 -6
  40. package/src/Controller/State/initState.ts +35 -7
  41. package/src/TolgeeCore.ts +14 -17
  42. package/src/__test/cache.test.ts +2 -26
  43. package/src/__test/client.test.ts +3 -3
  44. package/src/__test/events.test.ts +40 -13
  45. package/src/__test/load.matrix.test.ts +123 -0
  46. package/src/__test/load.required.test.ts +71 -0
  47. package/src/__test/namespaces.required.test.ts +52 -0
  48. package/src/__test/options.test.ts +1 -1
  49. package/src/__test/testTools.ts +39 -0
  50. package/src/helpers.ts +2 -1
  51. package/src/types/cache.ts +24 -1
  52. package/src/types/events.ts +33 -24
  53. package/src/types/index.ts +1 -0
  54. package/lib/Controller/Events/EventEmitterSelective.d.ts +0 -7
  55. package/src/Controller/Events/EventEmitterSelective.test.ts +0 -110
  56. package/src/Controller/Events/EventEmitterSelective.ts +0 -133
@@ -92,9 +92,9 @@
92
92
  };
93
93
  };
94
94
 
95
- function EventEmitter(isActive) {
95
+ const EventEmitter = (type, isActive) => {
96
96
  let handlers = [];
97
- return Object.freeze({
97
+ return {
98
98
  listen(handler) {
99
99
  const handlerWrapper = (e) => {
100
100
  handler(e);
@@ -108,27 +108,14 @@
108
108
  },
109
109
  emit(data) {
110
110
  if (isActive()) {
111
- handlers.forEach((handler) => handler({ value: data }));
111
+ handlers.forEach((handler) => handler({ type: type, value: data }));
112
112
  }
113
113
  },
114
- });
115
- }
114
+ };
115
+ };
116
116
 
117
- function EventEmitterSelective(isActive, getFallbackNs, getDefaultNs) {
118
- const listeners = new Set();
119
- const partialListeners = new Set();
120
- function callHandlers(ns) {
121
- // everything is implicitly subscribed to fallbacks
122
- // as it can always fall through to it
123
- const fallbackNamespaces = new Set(getFallbackNs());
124
- partialListeners.forEach((handler) => {
125
- const nsMatches = ns === undefined ||
126
- (ns === null || ns === void 0 ? void 0 : ns.findIndex((ns) => fallbackNamespaces.has(ns) || handler.namespaces.has(ns))) !== -1;
127
- if (nsMatches) {
128
- handler.fn({ value: undefined });
129
- }
130
- });
131
- }
117
+ function EventEmitterCombined(isActive) {
118
+ let handlers = [];
132
119
  let queue = [];
133
120
  // merge events in queue into one event
134
121
  function solveQueue() {
@@ -137,87 +124,54 @@
137
124
  }
138
125
  const queueCopy = queue;
139
126
  queue = [];
140
- listeners.forEach((handler) => {
141
- handler({ value: undefined });
127
+ handlers.forEach((handler) => {
128
+ handler(queueCopy);
142
129
  });
143
- let namespaces = new Set();
144
- queueCopy.forEach((ns) => {
145
- if (ns === undefined) {
146
- // when no ns specified, it affects all namespaces
147
- namespaces = undefined;
148
- }
149
- else if (namespaces !== undefined) {
150
- ns.forEach((ns) => namespaces.add(ns));
151
- }
152
- });
153
- const namespacesArray = namespaces
154
- ? Array.from(namespaces.keys())
155
- : undefined;
156
- callHandlers(namespacesArray);
157
130
  }
158
131
  return Object.freeze({
159
- emit(ns, delayed) {
160
- if (isActive()) {
161
- queue.push(ns);
162
- if (!delayed) {
163
- solveQueue();
164
- }
165
- else {
166
- setTimeout(solveQueue, 0);
167
- }
168
- }
169
- },
170
132
  listen(handler) {
171
- listeners.add(handler);
172
- const result = {
173
- unsubscribe: () => {
174
- listeners.delete(handler);
175
- },
133
+ const handlerWrapper = (events) => {
134
+ handler(events);
176
135
  };
177
- return result;
178
- },
179
- listenSome(handler) {
180
- const handlerWrapper = {
181
- fn: (e) => {
182
- handler(e);
136
+ handlers.push(handlerWrapper);
137
+ return {
138
+ unsubscribe() {
139
+ handlers = handlers.filter((i) => handlerWrapper !== i);
183
140
  },
184
- namespaces: new Set(),
185
141
  };
186
- partialListeners.add(handlerWrapper);
187
- const result = {
188
- unsubscribe: () => {
189
- partialListeners.delete(handlerWrapper);
190
- },
191
- subscribeNs: (ns) => {
192
- getFallbackArray(ns).forEach((val) => handlerWrapper.namespaces.add(val));
193
- if (ns === undefined) {
194
- // subscribing to default ns
195
- handlerWrapper.namespaces.add(getDefaultNs());
142
+ },
143
+ emit(e, delayed) {
144
+ if (isActive()) {
145
+ if (isActive()) {
146
+ queue.push(e);
147
+ if (!delayed) {
148
+ solveQueue();
196
149
  }
197
- return result;
198
- },
199
- };
200
- return result;
150
+ else {
151
+ setTimeout(solveQueue, 0);
152
+ }
153
+ }
154
+ }
201
155
  },
202
156
  });
203
157
  }
204
158
 
205
- function Events(getFallbackNs, getDefaultNs) {
159
+ function Events() {
206
160
  let emitterActive = true;
207
161
  function isActive() {
208
162
  return emitterActive;
209
163
  }
210
164
  const self = Object.freeze({
211
- onPendingLanguageChange: EventEmitter(isActive),
212
- onLanguageChange: EventEmitter(isActive),
213
- onLoadingChange: EventEmitter(isActive),
214
- onFetchingChange: EventEmitter(isActive),
215
- onInitialLoaded: EventEmitter(isActive),
216
- onRunningChange: EventEmitter(isActive),
217
- onCacheChange: EventEmitter(isActive),
218
- onUpdate: EventEmitterSelective(isActive, getFallbackNs, getDefaultNs),
219
- onPermanentChange: EventEmitter(isActive),
220
- onError: EventEmitter(isActive),
165
+ onPendingLanguageChange: EventEmitter('pendingLanguage', isActive),
166
+ onLanguageChange: EventEmitter('language', isActive),
167
+ onLoadingChange: EventEmitter('loading', isActive),
168
+ onFetchingChange: EventEmitter('fetching', isActive),
169
+ onInitialLoaded: EventEmitter('initialLoad', isActive),
170
+ onRunningChange: EventEmitter('running', isActive),
171
+ onCacheChange: EventEmitter('cache', isActive),
172
+ onPermanentChange: EventEmitter('permanentChange', isActive),
173
+ onError: EventEmitter('error', isActive),
174
+ onUpdate: EventEmitterCombined(isActive),
221
175
  setEmitterActive(active) {
222
176
  emitterActive = active;
223
177
  },
@@ -246,9 +200,9 @@
246
200
  }
247
201
  }),
248
202
  });
249
- self.onInitialLoaded.listen(() => self.onUpdate.emit());
250
- self.onLanguageChange.listen(() => self.onUpdate.emit());
251
- self.onCacheChange.listen(({ value }) => self.onUpdate.emit([value.namespace], true));
203
+ self.onInitialLoaded.listen((e) => self.onUpdate.emit(e, false));
204
+ self.onLanguageChange.listen((e) => self.onUpdate.emit(e, false));
205
+ self.onCacheChange.listen((e) => self.onUpdate.emit(e, true));
252
206
  return self;
253
207
  }
254
208
 
@@ -278,7 +232,7 @@
278
232
  }
279
233
  }
280
234
 
281
- const flattenTranslations = (data) => {
235
+ const flattenTranslationsToMap = (data) => {
282
236
  const result = new Map();
283
237
  Object.entries(data).forEach(([key, value]) => {
284
238
  // ignore empty values
@@ -286,7 +240,7 @@
286
240
  return;
287
241
  }
288
242
  if (typeof value === 'object') {
289
- flattenTranslations(value).forEach((flatValue, flatKey) => {
243
+ flattenTranslationsToMap(value).forEach((flatValue, flatKey) => {
290
244
  result.set(key + '.' + flatKey, flatValue);
291
245
  });
292
246
  return;
@@ -295,6 +249,9 @@
295
249
  });
296
250
  return result;
297
251
  };
252
+ const flattenTranslations = (data) => {
253
+ return Object.fromEntries(flattenTranslationsToMap(data).entries());
254
+ };
298
255
  const decodeCacheKey = (key) => {
299
256
  const [firstPart, ...rest] = key.split(':');
300
257
  // if namespaces contains ":" it won't get lost
@@ -310,7 +267,7 @@
310
267
  }
311
268
  };
312
269
 
313
- function Cache(events, backendGetRecord, backendGetDevRecord, withDefaultNs, isInitialLoading, fetchingObserver, loadingObserver) {
270
+ function Cache(events, backendGetRecord, backendGetDevRecord, withDefaultNs, isInitialLoading, isCacheDisabled, fetchingObserver, loadingObserver) {
314
271
  const asyncRequests = new Map();
315
272
  const cache = new Map();
316
273
  let staticData = {};
@@ -321,7 +278,7 @@
321
278
  data: flattenTranslations(data),
322
279
  version: recordVersion,
323
280
  });
324
- events.onCacheChange.emit(descriptor);
281
+ events.onCacheChange.emit(decodeCacheKey(cacheKey));
325
282
  }
326
283
  /**
327
284
  * Fetches production data
@@ -374,14 +331,23 @@
374
331
  }
375
332
  const self = Object.freeze({
376
333
  addStaticData(data) {
377
- if (data) {
334
+ if (Array.isArray(data)) {
335
+ for (const record of data) {
336
+ const key = encodeCacheKey(record);
337
+ const existing = cache.get(key);
338
+ if (!existing || existing.version === 0) {
339
+ addRecordInternal(record, flattenTranslations(record.data), 0);
340
+ }
341
+ }
342
+ }
343
+ else if (data) {
378
344
  staticData = Object.assign(Object.assign({}, staticData), data);
379
345
  Object.entries(data).forEach(([key, value]) => {
380
346
  if (typeof value !== 'function') {
381
347
  const descriptor = decodeCacheKey(key);
382
348
  const existing = cache.get(key);
383
349
  if (!existing || existing.version === 0) {
384
- addRecordInternal(descriptor, value, 0);
350
+ addRecordInternal(descriptor, flattenTranslations(value), 0);
385
351
  }
386
352
  }
387
353
  });
@@ -392,7 +358,7 @@
392
358
  version += 1;
393
359
  },
394
360
  addRecord(descriptor, data) {
395
- addRecordInternal(descriptor, data, version);
361
+ addRecordInternal(descriptor, flattenTranslations(data), version);
396
362
  },
397
363
  exists(descriptor, strict = false) {
398
364
  const record = cache.get(encodeCacheKey(descriptor));
@@ -402,19 +368,27 @@
402
368
  return Boolean(record);
403
369
  },
404
370
  getRecord(descriptor) {
405
- var _a;
406
- return (_a = cache.get(encodeCacheKey(withDefaultNs(descriptor)))) === null || _a === void 0 ? void 0 : _a.data;
371
+ const descriptorWithNs = withDefaultNs(descriptor);
372
+ const cacheKey = encodeCacheKey(descriptorWithNs);
373
+ const cacheRecord = cache.get(cacheKey);
374
+ if (!cacheRecord) {
375
+ return undefined;
376
+ }
377
+ return Object.assign(Object.assign({}, descriptorWithNs), { cacheKey, data: cacheRecord.data });
378
+ },
379
+ getAllRecords() {
380
+ const entries = Array.from(cache.entries());
381
+ return entries.map(([key]) => self.getRecord(decodeCacheKey(key)));
407
382
  },
408
383
  getTranslation(descriptor, key) {
409
384
  var _a;
410
- return (_a = cache.get(encodeCacheKey(descriptor))) === null || _a === void 0 ? void 0 : _a.data.get(key);
385
+ return (_a = cache.get(encodeCacheKey(descriptor))) === null || _a === void 0 ? void 0 : _a.data[key];
411
386
  },
412
387
  getTranslationNs(namespaces, languages, key) {
413
388
  var _a;
414
389
  for (const namespace of namespaces) {
415
390
  for (const language of languages) {
416
- const value = (_a = cache
417
- .get(encodeCacheKey({ language, namespace }))) === null || _a === void 0 ? void 0 : _a.data.get(key);
391
+ const value = (_a = cache.get(encodeCacheKey({ language, namespace }))) === null || _a === void 0 ? void 0 : _a.data[key];
418
392
  if (value !== undefined && value !== null) {
419
393
  return [namespace];
420
394
  }
@@ -426,8 +400,7 @@
426
400
  var _a;
427
401
  for (const namespace of namespaces) {
428
402
  for (const language of languages) {
429
- const value = (_a = cache
430
- .get(encodeCacheKey({ language, namespace }))) === null || _a === void 0 ? void 0 : _a.data.get(key);
403
+ const value = (_a = cache.get(encodeCacheKey({ language, namespace }))) === null || _a === void 0 ? void 0 : _a.data[key];
431
404
  if (value !== undefined && value !== null) {
432
405
  return value;
433
406
  }
@@ -438,8 +411,10 @@
438
411
  changeTranslation(descriptor, key, value) {
439
412
  var _a;
440
413
  const record = (_a = cache.get(encodeCacheKey(descriptor))) === null || _a === void 0 ? void 0 : _a.data;
441
- record === null || record === void 0 ? void 0 : record.set(key, value);
442
- events.onCacheChange.emit(Object.assign(Object.assign({}, descriptor), { key }));
414
+ if (record === null || record === void 0 ? void 0 : record[key]) {
415
+ record[key] = value;
416
+ events.onCacheChange.emit(Object.assign(Object.assign({}, descriptor), { key }));
417
+ }
443
418
  },
444
419
  isFetching(ns) {
445
420
  if (isInitialLoading()) {
@@ -453,66 +428,74 @@
453
428
  },
454
429
  isLoading(language, ns) {
455
430
  const namespaces = getFallbackArray(ns);
456
- return Boolean(isInitialLoading() ||
457
- Array.from(asyncRequests.keys()).find((key) => {
458
- const descriptor = decodeCacheKey(key);
459
- return ((!namespaces.length ||
460
- namespaces.includes(descriptor.namespace)) &&
461
- !self.exists({
462
- namespace: descriptor.namespace,
463
- language: language,
464
- }));
465
- }));
466
- },
467
- async loadRecords(descriptors, isDev) {
431
+ if (isInitialLoading()) {
432
+ return true;
433
+ }
434
+ const pendingCacheKeys = Array.from(asyncRequests.keys());
435
+ return Boolean(pendingCacheKeys.find((key) => {
436
+ const descriptor = decodeCacheKey(key);
437
+ return ((!namespaces.length || namespaces.includes(descriptor.namespace)) &&
438
+ !self.exists({
439
+ namespace: descriptor.namespace,
440
+ language: language,
441
+ }));
442
+ }));
443
+ },
444
+ async loadRecords(descriptors, options) {
468
445
  const withPromises = descriptors.map((descriptor) => {
469
446
  const keyObject = withDefaultNs(descriptor);
470
447
  const cacheKey = encodeCacheKey(keyObject);
448
+ if (options === null || options === void 0 ? void 0 : options.useCache) {
449
+ const exists = self.exists(keyObject, true);
450
+ if (exists) {
451
+ return Object.assign(Object.assign({}, keyObject), { new: false, cacheKey, data: self.getRecord(keyObject).data });
452
+ }
453
+ }
471
454
  const existingPromise = asyncRequests.get(cacheKey);
472
455
  if (existingPromise) {
473
- return {
474
- new: false,
475
- promise: existingPromise,
476
- keyObject,
477
- cacheKey,
478
- };
456
+ return Object.assign(Object.assign({}, keyObject), { new: false, promise: existingPromise, cacheKey });
479
457
  }
480
- const dataPromise = fetchData(keyObject, isDev) || Promise.resolve(undefined);
458
+ const dataPromise = fetchData(keyObject, !(options === null || options === void 0 ? void 0 : options.noDev)) || Promise.resolve(undefined);
481
459
  asyncRequests.set(cacheKey, dataPromise);
482
- return {
483
- new: true,
484
- promise: dataPromise,
485
- keyObject,
486
- cacheKey,
487
- };
460
+ return Object.assign(Object.assign({}, keyObject), { new: true, promise: dataPromise, cacheKey });
488
461
  });
489
462
  fetchingObserver.notify();
490
463
  loadingObserver.notify();
491
- const results = await Promise.all(withPromises.map((val) => val.promise));
492
- withPromises.forEach((value, i) => {
493
- const promiseChanged = asyncRequests.get(value.cacheKey) !== value.promise;
464
+ const promisesToWait = withPromises
465
+ .map((val) => val.promise)
466
+ .filter(Boolean);
467
+ const fetchedData = await Promise.all(promisesToWait);
468
+ withPromises.forEach((value) => {
469
+ var _a;
470
+ if (value.promise) {
471
+ value.data = flattenTranslations((_a = fetchedData[0]) !== null && _a !== void 0 ? _a : {});
472
+ fetchedData.shift();
473
+ }
494
474
  // if promise has changed in between, it means cache been invalidated or
495
475
  // new data are being fetched
476
+ const promiseChanged = asyncRequests.get(value.cacheKey) !== value.promise;
496
477
  if (value.new && !promiseChanged) {
497
478
  asyncRequests.delete(value.cacheKey);
498
- const data = results[i];
499
- if (data) {
500
- self.addRecord(value.keyObject, data);
479
+ if (value.data) {
480
+ self.addRecord(value, value.data);
501
481
  }
502
- else if (!self.getRecord(value.keyObject)) {
482
+ else if (!self.getRecord(value)) {
503
483
  // if no data exist, put empty object
504
- self.addRecord(value.keyObject, {});
484
+ // so we know we don't have to fetch again
485
+ self.addRecord(value, {});
505
486
  }
506
487
  }
507
488
  });
508
489
  fetchingObserver.notify();
509
490
  loadingObserver.notify();
510
- return withPromises.map((val) => self.getRecord(val.keyObject));
511
- },
512
- getAllRecords() {
513
- const entries = Array.from(cache.entries());
514
- return entries.map(([key, entry]) => {
515
- return Object.assign(Object.assign({}, decodeCacheKey(key)), { data: entry.data });
491
+ return withPromises.map((val) => {
492
+ var _a;
493
+ return ({
494
+ language: val.language,
495
+ namespace: val.namespace,
496
+ data: (_a = val.data) !== null && _a !== void 0 ? _a : {},
497
+ cacheKey: val.cacheKey,
498
+ });
516
499
  });
517
500
  },
518
501
  });
@@ -567,13 +550,14 @@
567
550
  const DEFAULT_API_URL = 'https://app.tolgee.io';
568
551
  const DEFAULT_MISSING_TRANSLATION = ({ key, }) => key;
569
552
  const defaultValues = {
570
- defaultNs: '',
571
553
  observerOptions: defaultObserverOptions,
572
554
  observerType: 'invisible',
573
555
  onFormatError: DEFAULT_FORMAT_ERROR,
574
556
  apiUrl: DEFAULT_API_URL,
557
+ autoLoadRequiredData: true,
575
558
  fetch: createFetchFunction(),
576
559
  onTranslationMissing: DEFAULT_MISSING_TRANSLATION,
560
+ disableCache: false,
577
561
  };
578
562
  const combineOptions = (...states) => {
579
563
  let result = {};
@@ -767,6 +751,9 @@
767
751
  getBackendDevRecord: (async ({ language, namespace }) => {
768
752
  var _a;
769
753
  const { apiKey, apiUrl, projectId, filterTag } = getInitialOptions();
754
+ if (!apiKey || !apiUrl || !self.hasDevBackend()) {
755
+ return undefined;
756
+ }
770
757
  return (_a = instances.devBackend) === null || _a === void 0 ? void 0 : _a.getRecord(Object.assign({ apiKey,
771
758
  apiUrl,
772
759
  projectId,
@@ -910,6 +897,9 @@
910
897
  isInitialLoading() {
911
898
  return state.isInitialLoading;
912
899
  },
900
+ isCacheDisabled() {
901
+ return state.initialOptions.disableCache;
902
+ },
913
903
  setInitialLoading(value) {
914
904
  state.isInitialLoading = value;
915
905
  },
@@ -960,7 +950,8 @@
960
950
  },
961
951
  getRequiredNamespaces() {
962
952
  return unique([
963
- ...(state.initialOptions.ns || [state.initialOptions.defaultNs]),
953
+ self.getDefaultNs(),
954
+ ...(state.initialOptions.ns || []),
964
955
  ...getFallbackArray(state.initialOptions.fallbackNs),
965
956
  ...state.activeNamespaces.keys(),
966
957
  ]);
@@ -978,8 +969,17 @@
978
969
  getFallbackNs() {
979
970
  return getFallbackArray(state.initialOptions.fallbackNs);
980
971
  },
972
+ getNs() {
973
+ var _a, _b;
974
+ return ((_a = state.initialOptions.ns) === null || _a === void 0 ? void 0 : _a.length)
975
+ ? state.initialOptions.ns
976
+ : [(_b = state.initialOptions.defaultNs) !== null && _b !== void 0 ? _b : ''];
977
+ },
981
978
  getDefaultNs(ns) {
982
- return ns === undefined ? state.initialOptions.defaultNs : ns;
979
+ var _a, _b, _c;
980
+ return ns === undefined
981
+ ? (_c = (_a = state.initialOptions.defaultNs) !== null && _a !== void 0 ? _a : (_b = state.initialOptions.ns) === null || _b === void 0 ? void 0 : _b[0]) !== null && _c !== void 0 ? _c : ''
982
+ : ns;
983
983
  },
984
984
  getAvailableLanguages() {
985
985
  if (state.initialOptions.availableLanguages) {
@@ -990,10 +990,13 @@
990
990
  return Array.from(new Set(languagesFromStaticData));
991
991
  }
992
992
  },
993
+ getAvailableNs() {
994
+ return state.initialOptions.availableNs;
995
+ },
993
996
  withDefaultNs(descriptor) {
994
997
  return {
995
998
  namespace: descriptor.namespace === undefined
996
- ? self.getInitialOptions().defaultNs
999
+ ? self.getDefaultNs()
997
1000
  : descriptor.namespace,
998
1001
  language: descriptor.language,
999
1002
  };
@@ -1043,12 +1046,12 @@
1043
1046
  };
1044
1047
 
1045
1048
  function Controller({ options }) {
1046
- const events = Events(getFallbackNs, getDefaultNs);
1049
+ const events = Events();
1047
1050
  const fetchingObserver = ValueObserver(false, () => cache.isFetching(), events.onFetchingChange.emit);
1048
1051
  const loadingObserver = ValueObserver(false, () => self.isLoading(), events.onLoadingChange.emit);
1049
1052
  const state = State(events.onLanguageChange, events.onPendingLanguageChange, events.onRunningChange);
1050
1053
  const pluginService = Plugins(state.getLanguage, state.getInitialOptions, state.getAvailableLanguages, getDefaultAndFallbackNs, getTranslationNs, getTranslation, changeTranslation, events);
1051
- const cache = Cache(events, pluginService.getBackendRecord, pluginService.getBackendDevRecord, state.withDefaultNs, state.isInitialLoading, fetchingObserver, loadingObserver);
1054
+ const cache = Cache(events, pluginService.getBackendRecord, pluginService.getBackendDevRecord, state.withDefaultNs, state.isInitialLoading, state.isCacheDisabled, fetchingObserver, loadingObserver);
1052
1055
  if (options) {
1053
1056
  init(options);
1054
1057
  }
@@ -1067,15 +1070,15 @@
1067
1070
  // gets all namespaces where translation could be located
1068
1071
  // takes (ns|default, fallback ns)
1069
1072
  function getDefaultAndFallbackNs(ns) {
1070
- return [...getFallbackArray(getDefaultNs(ns)), ...getFallbackNs()];
1073
+ return unique([...getFallbackArray(getDefaultNs(ns)), ...getFallbackNs()]);
1071
1074
  }
1072
1075
  // gets all namespaces which need to be loaded
1073
1076
  // takes (ns|default, initial ns, fallback ns, active ns)
1074
1077
  function getRequiredNamespaces(ns) {
1075
- return [
1078
+ return unique([
1076
1079
  ...getFallbackArray(ns !== null && ns !== void 0 ? ns : getDefaultNs()),
1077
1080
  ...state.getRequiredNamespaces(),
1078
- ];
1081
+ ]);
1079
1082
  }
1080
1083
  function changeTranslation(descriptor, key, value) {
1081
1084
  const keyObject = state.withDefaultNs(descriptor);
@@ -1091,24 +1094,50 @@
1091
1094
  state.init(options);
1092
1095
  cache.addStaticData(state.getInitialOptions().staticData);
1093
1096
  }
1094
- function getRequiredRecords(lang, ns) {
1097
+ function getRequiredDescriptors(lang, ns) {
1095
1098
  const languages = state.getFallbackLangs(lang);
1096
1099
  const namespaces = getRequiredNamespaces(ns);
1097
1100
  const result = [];
1098
1101
  languages.forEach((language) => {
1099
1102
  namespaces.forEach((namespace) => {
1100
- if (!cache.exists({ language, namespace }, true)) {
1101
- result.push({ language, namespace });
1102
- }
1103
+ result.push({ language, namespace });
1103
1104
  });
1104
1105
  });
1105
1106
  return result;
1106
1107
  }
1107
- function loadRequiredRecords(lang, ns) {
1108
- const descriptors = getRequiredRecords(lang, ns);
1109
- if (descriptors.length) {
1110
- return valueOrPromise(self.loadRecords(descriptors), () => { });
1108
+ function getMissingDescriptors(lang, ns) {
1109
+ return getRequiredDescriptors(lang, ns).filter((descriptor) => !cache.exists(descriptor, true));
1110
+ }
1111
+ function getMatrixRecords(options) {
1112
+ let languages = [];
1113
+ let namespaces = [];
1114
+ if (Array.isArray(options.languages)) {
1115
+ languages = options.languages;
1116
+ }
1117
+ else if (options.languages === 'all') {
1118
+ const availableLanguages = self.getAvailableLanguages();
1119
+ if (!availableLanguages) {
1120
+ throw new Error(missingOptionError('availableLanguages'));
1121
+ }
1122
+ languages = availableLanguages;
1123
+ }
1124
+ if (Array.isArray(options.namespaces)) {
1125
+ namespaces = options.namespaces;
1126
+ }
1127
+ else if (options.namespaces === 'all') {
1128
+ const availableNs = self.getAvailableNs();
1129
+ if (!availableNs) {
1130
+ throw new Error(missingOptionError('availableNs'));
1131
+ }
1132
+ namespaces = availableNs;
1111
1133
  }
1134
+ const records = [];
1135
+ languages.forEach((language) => {
1136
+ namespaces.forEach((namespace) => {
1137
+ records.push({ language, namespace });
1138
+ });
1139
+ });
1140
+ return records;
1112
1141
  }
1113
1142
  function getTranslationNs({ key, ns }) {
1114
1143
  const languages = state.getFallbackLangs();
@@ -1122,8 +1151,11 @@
1122
1151
  }
1123
1152
  function loadInitial() {
1124
1153
  const data = valueOrPromise(initializeLanguage(), () => {
1125
- // fail if there is no language
1126
- return loadRequiredRecords();
1154
+ const missingDescriptors = getMissingDescriptors();
1155
+ if (missingDescriptors.length &&
1156
+ state.getInitialOptions().autoLoadRequiredData) {
1157
+ return cache.loadRecords(missingDescriptors, { useCache: true });
1158
+ }
1127
1159
  });
1128
1160
  if (isPromise(data)) {
1129
1161
  state.setInitialLoading(true);
@@ -1164,14 +1196,16 @@
1164
1196
  throw new Error(missingOptionError(['defaultLanguage', 'language']));
1165
1197
  }
1166
1198
  }
1167
- const self = Object.freeze(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, events), state), pluginService), cache), { init: init, getTranslation: getTranslation, changeTranslation: changeTranslation, getTranslationNs: getTranslationNs, getDefaultAndFallbackNs: getDefaultAndFallbackNs, findPositions: pluginService.findPositions, getRequiredRecords: getRequiredRecords, async changeLanguage(language) {
1199
+ const self = Object.freeze(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, events), state), pluginService), cache), { init: init, getTranslation: getTranslation, changeTranslation: changeTranslation, getTranslationNs: getTranslationNs, getDefaultAndFallbackNs: getDefaultAndFallbackNs, findPositions: pluginService.findPositions, getRequiredDescriptors: getRequiredDescriptors, async changeLanguage(language) {
1168
1200
  if (state.getPendingLanguage() === language &&
1169
1201
  state.getLanguage() === language) {
1170
1202
  return;
1171
1203
  }
1172
1204
  state.setPendingLanguage(language);
1173
- if (state.isRunning()) {
1174
- await loadRequiredRecords(language);
1205
+ if (state.isRunning() && state.getInitialOptions().autoLoadRequiredData) {
1206
+ await cache.loadRecords(getRequiredDescriptors(language), {
1207
+ useCache: true,
1208
+ });
1175
1209
  }
1176
1210
  if (language === state.getPendingLanguage()) {
1177
1211
  // there might be parallel language change
@@ -1185,14 +1219,14 @@
1185
1219
  state.addActiveNs(ns);
1186
1220
  }
1187
1221
  if (state.isRunning()) {
1188
- await loadRequiredRecords(undefined, ns);
1222
+ await cache.loadRecords(getRequiredDescriptors(undefined, ns), {
1223
+ useCache: true,
1224
+ });
1189
1225
  }
1190
1226
  },
1191
- loadRecords(descriptors) {
1192
- return cache.loadRecords(descriptors, self.isDev());
1193
- },
1194
- async loadRecord(descriptor) {
1195
- return (await self.loadRecords([descriptor]))[0];
1227
+ async loadRecord(descriptor, options) {
1228
+ var _a;
1229
+ return (_a = (await self.loadRecords([descriptor], options))[0]) === null || _a === void 0 ? void 0 : _a.data;
1196
1230
  },
1197
1231
  isLoading(ns) {
1198
1232
  return cache.isLoading(state.getLanguage(), ns);
@@ -1221,6 +1255,17 @@
1221
1255
  }), isDev() {
1222
1256
  return Boolean(state.getInitialOptions().apiKey && state.getInitialOptions().apiUrl);
1223
1257
  },
1258
+ async loadRequired(options) {
1259
+ if (!(options === null || options === void 0 ? void 0 : options.language)) {
1260
+ await initializeLanguage();
1261
+ }
1262
+ const requiredRecords = getRequiredDescriptors(options === null || options === void 0 ? void 0 : options.language);
1263
+ return self.loadRecords(requiredRecords, options);
1264
+ },
1265
+ async loadMatrix(options) {
1266
+ const records = getMatrixRecords(options);
1267
+ return self.loadRecords(records, options);
1268
+ },
1224
1269
  run() {
1225
1270
  checkCorrectConfiguration();
1226
1271
  if (!state.isRunning()) {
@@ -1261,20 +1306,6 @@
1261
1306
  * Listen to tolgee events.
1262
1307
  */
1263
1308
  on: controller.on,
1264
- /**
1265
- * Listen for specific namespaces changes.
1266
- *
1267
- * ```
1268
- * const sub = tolgee.onUpdate(handler)
1269
- *
1270
- * // subscribe to selected namespace
1271
- * sub.subscribeNs(['common'])
1272
- *
1273
- * // unsubscribe
1274
- * sub.unsubscribe()
1275
- * ```
1276
- */
1277
- onNsUpdate: controller.onUpdate.listenSome,
1278
1309
  /**
1279
1310
  * Turn off/on events emitting. Is on by default.
1280
1311
  */
@@ -1312,6 +1343,16 @@
1312
1343
  * so this method will remove namespace only if the counter goes down to 0.
1313
1344
  */
1314
1345
  removeActiveNs: controller.removeActiveNs,
1346
+ /**
1347
+ * Load records which would be loaded by `run` function
1348
+ *
1349
+ * You can provide language if not previously set on tolgee instance
1350
+ */
1351
+ loadRequired: controller.loadRequired,
1352
+ /**
1353
+ * Load records in matrix (languages x namespaces)
1354
+ */
1355
+ loadMatrix: controller.loadMatrix,
1315
1356
  /**
1316
1357
  * Manually load multiple records from `Backend` (or `DevBackend` when in dev mode)
1317
1358
  *
@@ -1340,9 +1381,9 @@
1340
1381
  */
1341
1382
  isLoaded: controller.isLoaded,
1342
1383
  /**
1343
- * Returns records needed for instance to be `loaded`
1384
+ * Returns descriptors of records needed for instance to be `loaded`
1344
1385
  */
1345
- getRequiredRecords: controller.getRequiredRecords,
1386
+ getRequiredDescriptors: controller.getRequiredDescriptors,
1346
1387
  /**
1347
1388
  * @return `true` if tolgee is loading initial data (triggered by `run`).
1348
1389
  */