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