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