@tolgee/core 5.0.0-rc.9be0f0e.0 → 5.0.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 (96) hide show
  1. package/README.md +174 -0
  2. package/README.njk.md +61 -0
  3. package/dist/tolgee.cjs.js +723 -351
  4. package/dist/tolgee.cjs.js.map +1 -1
  5. package/dist/tolgee.cjs.min.js +1 -1
  6. package/dist/tolgee.cjs.min.js.map +1 -1
  7. package/dist/{tolgee.esm.mjs → tolgee.esm.js} +722 -346
  8. package/dist/tolgee.esm.js.map +1 -0
  9. package/dist/tolgee.esm.min.mjs +1 -1
  10. package/dist/tolgee.esm.min.mjs.map +1 -1
  11. package/dist/tolgee.umd.js +723 -351
  12. package/dist/tolgee.umd.js.map +1 -1
  13. package/dist/tolgee.umd.min.js +1 -1
  14. package/dist/tolgee.umd.min.js.map +1 -1
  15. package/lib/Controller/Cache/Cache.d.ts +10 -9
  16. package/lib/Controller/Controller.d.ts +104 -45
  17. package/lib/Controller/Events/EventEmitter.d.ts +6 -0
  18. package/lib/Controller/Events/EventEmitterSelective.d.ts +7 -0
  19. package/lib/Controller/Events/Events.d.ts +14 -0
  20. package/lib/Controller/Plugins/Plugins.d.ts +12 -25
  21. package/lib/Controller/State/State.d.ts +27 -9
  22. package/lib/Controller/State/initState.d.ts +46 -15
  23. package/lib/Controller/State/observerOptions.d.ts +41 -0
  24. package/lib/Controller/ValueObserver.d.ts +5 -5
  25. package/lib/FormatSimple/FormatError.d.ts +7 -0
  26. package/lib/FormatSimple/FormatSimple.d.ts +2 -0
  27. package/lib/FormatSimple/formatParser.d.ts +1 -0
  28. package/lib/FormatSimple/formatter.d.ts +2 -0
  29. package/lib/TolgeeCore.d.ts +204 -0
  30. package/lib/TranslateParams.d.ts +1 -1
  31. package/lib/helpers.d.ts +8 -0
  32. package/lib/index.d.ts +4 -4
  33. package/lib/types/cache.d.ts +25 -0
  34. package/lib/types/events.d.ts +66 -0
  35. package/lib/types/general.d.ts +34 -0
  36. package/lib/types/index.d.ts +7 -0
  37. package/lib/types/plugin.d.ts +127 -0
  38. package/package.json +5 -4
  39. package/src/Controller/Cache/Cache.ts +31 -31
  40. package/src/Controller/Cache/helpers.ts +6 -6
  41. package/src/Controller/Controller.ts +78 -50
  42. package/src/Controller/Events/EventEmitter.ts +34 -0
  43. package/src/Controller/Events/EventEmitterSelective.test.ts +110 -0
  44. package/src/Controller/Events/EventEmitterSelective.ts +132 -0
  45. package/src/Controller/Events/Events.ts +69 -0
  46. package/src/Controller/Plugins/Plugins.ts +182 -133
  47. package/src/Controller/State/State.ts +43 -26
  48. package/src/Controller/State/initState.ts +97 -25
  49. package/src/Controller/State/observerOptions.ts +66 -0
  50. package/src/Controller/ValueObserver.ts +5 -2
  51. package/src/FormatSimple/FormatError.ts +26 -0
  52. package/src/FormatSimple/FormatSimple.ts +13 -0
  53. package/src/FormatSimple/formatParser.ts +133 -0
  54. package/src/FormatSimple/formatter.test.ts +190 -0
  55. package/src/FormatSimple/formatter.ts +19 -0
  56. package/src/TolgeeCore.ts +267 -0
  57. package/src/TranslateParams.test.ts +9 -12
  58. package/src/TranslateParams.ts +6 -5
  59. package/src/__test/backend.test.ts +6 -6
  60. package/src/__test/cache.test.ts +190 -0
  61. package/src/__test/client.test.ts +2 -2
  62. package/src/__test/events.test.ts +32 -7
  63. package/src/__test/format.simple.test.ts +14 -0
  64. package/src/__test/formatError.test.ts +61 -0
  65. package/src/__test/initialization.test.ts +15 -3
  66. package/src/__test/languageDetection.test.ts +14 -8
  67. package/src/__test/languageStorage.test.ts +10 -11
  68. package/src/__test/languages.test.ts +30 -6
  69. package/src/__test/loading.test.ts +2 -2
  70. package/src/__test/{namespacesFallback.test.ts → namespaces.fallback.test.ts} +10 -8
  71. package/src/__test/namespaces.test.ts +30 -7
  72. package/src/__test/options.test.ts +64 -0
  73. package/src/__test/plugins.test.ts +29 -18
  74. package/src/helpers.ts +53 -0
  75. package/src/index.ts +4 -10
  76. package/src/types/cache.ts +37 -0
  77. package/src/types/events.ts +85 -0
  78. package/src/types/general.ts +50 -0
  79. package/src/types/index.ts +19 -0
  80. package/src/types/plugin.ts +181 -0
  81. package/dist/tolgee.esm.mjs.map +0 -1
  82. package/lib/Controller/State/helpers.d.ts +0 -6
  83. package/lib/Events/EventEmitter.d.ts +0 -6
  84. package/lib/Events/EventEmitterSelective.d.ts +0 -15
  85. package/lib/Events/Events.d.ts +0 -50
  86. package/lib/Tolgee.d.ts +0 -2
  87. package/lib/constants.d.ts +0 -5
  88. package/lib/types.d.ts +0 -274
  89. package/src/Controller/State/helpers.ts +0 -41
  90. package/src/Events/EventEmitter.ts +0 -27
  91. package/src/Events/EventEmitterSelective.test.ts +0 -108
  92. package/src/Events/EventEmitterSelective.ts +0 -160
  93. package/src/Events/Events.ts +0 -66
  94. package/src/Tolgee.ts +0 -77
  95. package/src/constants.ts +0 -7
  96. package/src/types.ts +0 -380
@@ -4,25 +4,18 @@
4
4
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["@tolgee/core"] = {}));
5
5
  })(this, (function (exports) { 'use strict';
6
6
 
7
- const EventEmitter = () => {
8
- let handlers = [];
9
- const listen = (handler) => {
10
- const handlerWrapper = (e) => {
11
- handler(e);
12
- };
13
- handlers.push(handlerWrapper);
14
- return {
15
- unsubscribe: () => {
16
- handlers = handlers.filter((i) => handlerWrapper !== i);
17
- },
18
- };
19
- };
20
- const emit = (data) => {
21
- handlers.forEach((handler) => handler({ value: data }));
22
- };
23
- return Object.freeze({ listen, emit });
7
+ function isPromise(value) {
8
+ return Boolean(value && typeof value.then === 'function');
9
+ }
10
+ const valueOrPromise = (value, callback) => {
11
+ if (isPromise(value)) {
12
+ return Promise.resolve(value).then(callback);
13
+ }
14
+ else {
15
+ return callback(value);
16
+ }
24
17
  };
25
-
18
+ const missingOptionError = (option) => `Tolgee: You need to specify '${option}' option`;
26
19
  function isObject(item) {
27
20
  return typeof item === 'object' && !Array.isArray(item) && item !== null;
28
21
  }
@@ -49,22 +42,40 @@
49
42
  function unique(arr) {
50
43
  return Array.from(new Set(arr));
51
44
  }
52
-
53
- function incrementInMap(map, value) {
54
- const currNum = map.get(value) || 0;
55
- map.set(value, currNum + 1);
45
+ function sanitizeUrl(url) {
46
+ return url ? url.replace(/\/+$/, '') : url;
56
47
  }
57
- function decrementInMap(map, value) {
58
- let currNum = map.get(value) || 1;
59
- currNum -= 1;
60
- if (currNum <= 0) {
61
- map.delete(value);
48
+ function getErrorMessage(error) {
49
+ if (typeof error === 'string') {
50
+ return error;
62
51
  }
63
- else {
64
- map.set(value, currNum);
52
+ else if (typeof (error === null || error === void 0 ? void 0 : error.message) === 'string') {
53
+ return error.message;
65
54
  }
66
55
  }
67
- const EventEmitterSelective = () => {
56
+
57
+ const EventEmitter = (isActive) => {
58
+ let handlers = [];
59
+ const listen = (handler) => {
60
+ const handlerWrapper = (e) => {
61
+ handler(e);
62
+ };
63
+ handlers.push(handlerWrapper);
64
+ return {
65
+ unsubscribe: () => {
66
+ handlers = handlers.filter((i) => handlerWrapper !== i);
67
+ },
68
+ };
69
+ };
70
+ const emit = (data) => {
71
+ if (isActive()) {
72
+ handlers.forEach((handler) => handler({ value: data }));
73
+ }
74
+ };
75
+ return Object.freeze({ listen, emit });
76
+ };
77
+
78
+ const EventEmitterSelective = (isActive, getFallbackNs, getDefaultNs) => {
68
79
  const listeners = new Set();
69
80
  const partialListeners = new Set();
70
81
  const listen = (handler) => {
@@ -81,8 +92,7 @@
81
92
  fn: (e) => {
82
93
  handler(e);
83
94
  },
84
- keys: new Map(),
85
- namespaces: new Map(),
95
+ namespaces: new Set(),
86
96
  };
87
97
  partialListeners.add(handlerWrapper);
88
98
  const result = {
@@ -90,105 +100,85 @@
90
100
  partialListeners.delete(handlerWrapper);
91
101
  },
92
102
  subscribeNs: (ns) => {
93
- getFallbackArray(ns).forEach((val) => incrementInMap(handlerWrapper.namespaces, val));
94
- return result;
95
- },
96
- unsubscribeNs: (ns) => {
97
- getFallbackArray(ns).forEach((val) => decrementInMap(handlerWrapper.namespaces, val));
98
- return result;
99
- },
100
- subscribeKey: (descriptor) => {
101
- const { key, ns } = descriptor;
102
- incrementInMap(handlerWrapper.keys, key);
103
- getFallbackArray(ns).forEach((val) => incrementInMap(handlerWrapper.namespaces, val));
103
+ getFallbackArray(ns).forEach((val) => handlerWrapper.namespaces.add(val));
104
104
  if (ns === undefined) {
105
- // subscribing to all namespaces
106
- incrementInMap(handlerWrapper.namespaces, undefined);
107
- }
108
- return result;
109
- },
110
- unsubscribeKey: (descriptor) => {
111
- const { key, ns } = descriptor;
112
- decrementInMap(handlerWrapper.keys, key);
113
- getFallbackArray(ns).forEach((val) => decrementInMap(handlerWrapper.namespaces, val));
114
- if (ns === undefined) {
115
- // subscribing to all namespaces
116
- decrementInMap(handlerWrapper.namespaces, undefined);
105
+ // subscribing to default ns
106
+ handlerWrapper.namespaces.add(getDefaultNs());
117
107
  }
118
108
  return result;
119
109
  },
120
110
  };
121
111
  return result;
122
112
  };
123
- const callHandlers = (key, ns) => {
113
+ const callHandlers = (ns) => {
114
+ // everything is implicitly subscribed to fallbacks
115
+ // as it can always fall through to it
116
+ const fallbackNamespaces = new Set(getFallbackNs());
124
117
  partialListeners.forEach((handler) => {
125
- const nsMentioned = ns !== undefined;
126
- const nsMatches = handler.namespaces.has(undefined) ||
127
- (ns === null || ns === void 0 ? void 0 : ns.findIndex((ns) => handler.namespaces.has(ns))) !== -1;
128
- const keyMentioned = key !== undefined;
129
- const keyMatches = key === undefined || handler.keys.has(key) || handler.keys.size === 0;
130
- if ((!nsMentioned || nsMatches) && (!keyMentioned || keyMatches)) {
118
+ const nsMatches = ns === undefined ||
119
+ (ns === null || ns === void 0 ? void 0 : ns.findIndex((ns) => fallbackNamespaces.has(ns) || handler.namespaces.has(ns))) !== -1;
120
+ if (nsMatches) {
131
121
  handler.fn({ value: undefined });
132
122
  }
133
123
  });
134
124
  };
135
125
  let queue = [];
126
+ // merge events in queue into one event
136
127
  const solveQueue = () => {
137
128
  if (queue.length === 0) {
138
129
  return;
139
130
  }
131
+ const queueCopy = queue;
132
+ queue = [];
140
133
  listeners.forEach((handler) => {
141
134
  handler({ value: undefined });
142
135
  });
143
- let namespaces = [];
144
- let keys = [];
145
- queue.forEach((descriptor) => {
146
- if ((descriptor === null || descriptor === void 0 ? void 0 : descriptor.ns) === undefined) {
136
+ let namespaces = new Set();
137
+ queueCopy.forEach((ns) => {
138
+ if (ns === undefined) {
139
+ // when no ns specified, it affects all namespaces
147
140
  namespaces = undefined;
148
141
  }
149
142
  else if (namespaces !== undefined) {
150
- namespaces = [...namespaces, ...descriptor.ns];
143
+ ns.forEach((ns) => namespaces.add(ns));
151
144
  }
152
- if ((descriptor === null || descriptor === void 0 ? void 0 : descriptor.key) === undefined) {
153
- keys = undefined;
154
- }
155
- else if (keys !== undefined) {
156
- keys = [...keys, descriptor.key];
157
- }
158
- });
159
- (keys || [undefined]).forEach((key) => {
160
- callHandlers(key, namespaces);
161
145
  });
162
- queue = [];
146
+ const namespacesArray = namespaces
147
+ ? Array.from(namespaces.keys())
148
+ : undefined;
149
+ callHandlers(namespacesArray);
163
150
  };
164
- const emit = (descriptor, delayed) => {
165
- queue.push(descriptor);
166
- if (!delayed) {
167
- solveQueue();
168
- }
169
- else {
170
- Promise.resolve().then(() => {
151
+ const emit = (ns, delayed) => {
152
+ if (isActive()) {
153
+ queue.push(ns);
154
+ if (!delayed) {
171
155
  solveQueue();
172
- });
156
+ }
157
+ else {
158
+ setTimeout(solveQueue, 0);
159
+ }
173
160
  }
174
161
  };
175
162
  return Object.freeze({ listenSome, listen, emit });
176
163
  };
177
164
 
178
- const Events = () => {
179
- const onPendingLanguageChange = EventEmitter();
180
- const onLanguageChange = EventEmitter();
181
- const onKeyChange = EventEmitter();
182
- const onLoadingChange = EventEmitter();
183
- const onFetchingChange = EventEmitter();
184
- const onInitialLoaded = EventEmitter();
185
- const onKeyUpdate = EventEmitterSelective();
186
- const onCacheChange = EventEmitter();
187
- const onRunningChange = EventEmitter();
188
- onInitialLoaded.listen(() => onKeyUpdate.emit());
189
- onLanguageChange.listen(() => onKeyUpdate.emit());
165
+ const Events = (getFallbackNs, getDefaultNs) => {
166
+ let emitterActive = true;
167
+ function isActive() {
168
+ return emitterActive;
169
+ }
170
+ const onPendingLanguageChange = EventEmitter(isActive);
171
+ const onLanguageChange = EventEmitter(isActive);
172
+ const onLoadingChange = EventEmitter(isActive);
173
+ const onFetchingChange = EventEmitter(isActive);
174
+ const onInitialLoaded = EventEmitter(isActive);
175
+ const onRunningChange = EventEmitter(isActive);
176
+ const onCacheChange = EventEmitter(isActive);
177
+ const onUpdate = EventEmitterSelective(isActive, getFallbackNs, getDefaultNs);
178
+ onInitialLoaded.listen(() => onUpdate.emit());
179
+ onLanguageChange.listen(() => onUpdate.emit());
190
180
  onCacheChange.listen(({ value }) => {
191
- onKeyUpdate.emit({ ns: [value.namespace], key: value.key }, true);
181
+ onUpdate.emit([value.namespace], true);
192
182
  });
193
183
  const on = (event, handler) => {
194
184
  switch (event) {
@@ -206,20 +196,23 @@
206
196
  return onRunningChange.listen(handler);
207
197
  case 'cache':
208
198
  return onCacheChange.listen(handler);
209
- case 'keyUpdate':
210
- return onKeyUpdate.listen(handler);
199
+ case 'update':
200
+ return onUpdate.listen(handler);
211
201
  }
212
202
  };
203
+ function setEmmiterActive(active) {
204
+ emitterActive = active;
205
+ }
213
206
  return Object.freeze({
214
207
  onPendingLanguageChange,
215
208
  onLanguageChange,
216
- onKeyChange,
217
- onKeyUpdate,
218
209
  onLoadingChange,
219
210
  onFetchingChange,
220
211
  onInitialLoaded,
221
212
  onRunningChange,
222
213
  onCacheChange,
214
+ onUpdate,
215
+ setEmmiterActive,
223
216
  on,
224
217
  });
225
218
  };
@@ -232,7 +225,7 @@
232
225
  return;
233
226
  }
234
227
  if (typeof value === 'object') {
235
- Object.entries(flattenTranslations(value)).forEach(([flatKey, flatValue]) => {
228
+ flattenTranslations(value).forEach((flatValue, flatKey) => {
236
229
  result.set(key + '.' + flatKey, flatValue);
237
230
  });
238
231
  return;
@@ -242,7 +235,9 @@
242
235
  return result;
243
236
  };
244
237
  const decodeCacheKey = (key) => {
245
- const [firstPart, secondPart] = key.split(':');
238
+ const [firstPart, ...rest] = key.split(':');
239
+ // if namespaces contains ":" it won't get lost
240
+ const secondPart = rest.join(':');
246
241
  return { language: firstPart, namespace: secondPart || '' };
247
242
  };
248
243
  const encodeCacheKey = ({ language, namespace, }) => {
@@ -297,7 +292,7 @@
297
292
  }
298
293
  function getRecord(descriptor) {
299
294
  var _a;
300
- return (_a = cache.get(encodeCacheKey(descriptor))) === null || _a === void 0 ? void 0 : _a.data;
295
+ return (_a = cache.get(encodeCacheKey(withDefaultNs(descriptor)))) === null || _a === void 0 ? void 0 : _a.data;
301
296
  }
302
297
  function getTranslation(descriptor, key) {
303
298
  var _a;
@@ -310,11 +305,11 @@
310
305
  const value = (_a = cache
311
306
  .get(encodeCacheKey({ language, namespace }))) === null || _a === void 0 ? void 0 : _a.data.get(key);
312
307
  if (value !== undefined && value !== null) {
313
- return namespace;
308
+ return [namespace];
314
309
  }
315
310
  }
316
311
  }
317
- return Array.from(new Set(namespaces));
312
+ return unique(namespaces);
318
313
  }
319
314
  function getTranslationFallback(namespaces, languages, key) {
320
315
  var _a;
@@ -335,9 +330,6 @@
335
330
  record === null || record === void 0 ? void 0 : record.set(key, value);
336
331
  onCacheChange.emit(Object.assign(Object.assign({}, descriptor), { key }));
337
332
  }
338
- function clear() {
339
- cache.clear();
340
- }
341
333
  function isFetching(ns) {
342
334
  if (isInitialLoading()) {
343
335
  return true;
@@ -360,24 +352,20 @@
360
352
  }));
361
353
  }));
362
354
  }
363
- function fetchNormal(keyObject) {
355
+ /**
356
+ * Fetches production data
357
+ */
358
+ function fetchProd(keyObject) {
364
359
  let dataPromise = undefined;
365
360
  if (!dataPromise) {
366
361
  const staticDataValue = staticData[encodeCacheKey(keyObject)];
367
362
  if (typeof staticDataValue === 'function') {
368
363
  dataPromise = staticDataValue();
369
364
  }
370
- else if (staticDataValue) {
371
- dataPromise = Promise.resolve(staticDataValue);
372
- }
373
365
  }
374
366
  if (!dataPromise) {
375
367
  dataPromise = backendGetRecord(keyObject);
376
368
  }
377
- if (!dataPromise) {
378
- // return empty data, so we know it has already been attempted to fetch
379
- dataPromise = Promise.resolve({});
380
- }
381
369
  return dataPromise;
382
370
  }
383
371
  function fetchData(keyObject, isDev) {
@@ -387,12 +375,12 @@
387
375
  dataPromise = (_a = backendGetDevRecord(keyObject)) === null || _a === void 0 ? void 0 : _a.catch(() => {
388
376
  // eslint-disable-next-line no-console
389
377
  console.warn(`Tolgee: Failed to fetch data from dev backend`);
390
- // fallback to normal fetch if dev fails
391
- return fetchNormal(keyObject);
378
+ // fallback to prod fetch if dev fails
379
+ return fetchProd(keyObject);
392
380
  });
393
381
  }
394
382
  if (!dataPromise) {
395
- dataPromise = fetchNormal(keyObject);
383
+ dataPromise = fetchProd(keyObject);
396
384
  }
397
385
  return dataPromise;
398
386
  }
@@ -409,7 +397,7 @@
409
397
  cacheKey,
410
398
  };
411
399
  }
412
- const dataPromise = fetchData(keyObject, isDev);
400
+ const dataPromise = fetchData(keyObject, isDev) || Promise.resolve(undefined);
413
401
  asyncRequests.set(cacheKey, dataPromise);
414
402
  return {
415
403
  new: true,
@@ -431,6 +419,10 @@
431
419
  if (data) {
432
420
  addRecord(value.keyObject, data);
433
421
  }
422
+ else if (!getRecord(value.keyObject)) {
423
+ // if no data exist, put empty object
424
+ addRecord(value.keyObject, {});
425
+ }
434
426
  }
435
427
  });
436
428
  fetchingObserver.notify();
@@ -456,27 +448,85 @@
456
448
  isFetching,
457
449
  isLoading,
458
450
  loadRecords,
459
- clear,
460
451
  getAllRecords,
461
452
  });
462
453
  };
463
454
 
464
- function isPromise(value) {
465
- return Boolean(value && typeof value.then === 'function');
455
+ /******************************************************************************
456
+ Copyright (c) Microsoft Corporation.
457
+
458
+ Permission to use, copy, modify, and/or distribute this software for any
459
+ purpose with or without fee is hereby granted.
460
+
461
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
462
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
463
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
464
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
465
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
466
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
467
+ PERFORMANCE OF THIS SOFTWARE.
468
+ ***************************************************************************** */
469
+
470
+ function __rest(s, e) {
471
+ var t = {};
472
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
473
+ t[p] = s[p];
474
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
475
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
476
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
477
+ t[p[i]] = s[p[i]];
478
+ }
479
+ return t;
466
480
  }
467
- const valueOrPromise = (value, callback) => {
468
- if (isPromise(value)) {
469
- return Promise.resolve(value).then(callback);
470
- }
471
- else {
472
- return callback(value);
473
- }
481
+
482
+ const defaultObserverOptions = {
483
+ tagAttributes: {
484
+ textarea: ['placeholder'],
485
+ input: ['value', 'placeholder'],
486
+ img: ['alt'],
487
+ '*': ['aria-label', 'title'],
488
+ },
489
+ restrictedElements: ['script', 'style'],
490
+ highlightKeys: ['Alt'],
491
+ highlightColor: 'rgb(255, 0, 0)',
492
+ highlightWidth: 5,
493
+ inputPrefix: '%-%tolgee:',
494
+ inputSuffix: '%-%',
495
+ passToParent: ['option', 'optgroup'],
496
+ };
497
+
498
+ const DEFAULT_FORMAT_ERROR = 'invalid';
499
+ const defaultValues = {
500
+ defaultNs: '',
501
+ observerOptions: defaultObserverOptions,
502
+ observerType: 'invisible',
503
+ onFormatError: DEFAULT_FORMAT_ERROR,
504
+ };
505
+ const combineOptions = (...states) => {
506
+ let result = {};
507
+ states.forEach((state) => {
508
+ result = Object.assign(Object.assign(Object.assign({}, result), state), { observerOptions: Object.assign(Object.assign({}, result.observerOptions), state === null || state === void 0 ? void 0 : state.observerOptions) });
509
+ });
510
+ return result;
511
+ };
512
+ const initState = (options, previousState) => {
513
+ const initialOptions = combineOptions(defaultValues, previousState === null || previousState === void 0 ? void 0 : previousState.initialOptions, options);
514
+ // remove extra '/' from url end
515
+ initialOptions.apiUrl = sanitizeUrl(initialOptions.apiUrl);
516
+ return {
517
+ initialOptions,
518
+ activeNamespaces: (previousState === null || previousState === void 0 ? void 0 : previousState.activeNamespaces) || new Map(),
519
+ language: previousState === null || previousState === void 0 ? void 0 : previousState.language,
520
+ pendingLanguage: previousState === null || previousState === void 0 ? void 0 : previousState.language,
521
+ isInitialLoading: false,
522
+ isRunning: false,
523
+ };
474
524
  };
475
- const missingOptionError = (option) => `Tolgee: You need to specify '${option}' option`;
476
525
 
477
- const PluginService = (getLanguage, getInitialOptions, getAvailableLanguages, getTranslationNs, getTranslation, changeTranslation) => {
526
+ const Plugins = (getLanguage, getInitialOptions, getAvailableLanguages, getTranslationNs, getTranslation, changeTranslation) => {
478
527
  const plugins = {
479
528
  ui: undefined,
529
+ observer: undefined,
480
530
  };
481
531
  const instances = {
482
532
  formatters: [],
@@ -488,39 +538,20 @@
488
538
  languageDetector: undefined,
489
539
  languageStorage: undefined,
490
540
  };
491
- const onClick = async (event, { keysAndDefaults }) => {
541
+ const onClick = async ({ keysAndDefaults, event }) => {
492
542
  var _a;
493
- const withNs = keysAndDefaults.map(({ key, ns, defaultValue }) => ({
494
- key,
495
- defaultValue,
496
- ns: getFallbackArray(getTranslationNs({ key, ns, defaultValue })),
497
- translation: getTranslation({
543
+ const withNs = keysAndDefaults.map(({ key, ns, defaultValue }) => {
544
+ return {
498
545
  key,
499
- ns,
500
- }),
501
- }));
502
- (_a = instances.ui) === null || _a === void 0 ? void 0 : _a.handleElementClick(event, withNs);
503
- };
504
- const run = () => {
505
- var _a;
506
- instances.ui =
507
- plugins.ui &&
508
- new plugins.ui({
509
- apiKey: getInitialOptions().apiKey,
510
- apiUrl: getInitialOptions().apiUrl,
511
- highlight,
512
- changeTranslation,
513
- });
514
- (_a = instances.observer) === null || _a === void 0 ? void 0 : _a.run({ mouseHighlight: Boolean(instances.ui) });
515
- checkCorrectConfiguration();
516
- };
517
- const checkCorrectConfiguration = () => {
518
- if (instances.languageDetector) {
519
- const availableLanguages = getAvailableLanguages();
520
- if (!availableLanguages) {
521
- throw new Error(missingOptionError('availableLanguages'));
522
- }
523
- }
546
+ defaultValue,
547
+ ns: getTranslationNs({ key, ns }),
548
+ translation: getTranslation({
549
+ key,
550
+ ns,
551
+ }),
552
+ };
553
+ });
554
+ (_a = instances.ui) === null || _a === void 0 ? void 0 : _a.handleElementClick(withNs, event);
524
555
  };
525
556
  const stop = () => {
526
557
  var _a;
@@ -532,14 +563,17 @@
532
563
  return ((_b = (_a = instances.observer) === null || _a === void 0 ? void 0 : _a.highlight) === null || _b === void 0 ? void 0 : _b.call(_a, key, ns)) || { unhighlight() { } };
533
564
  };
534
565
  const translate = (props) => {
535
- const translation = getTranslation(props);
566
+ const translation = getTranslation({
567
+ key: props.key,
568
+ ns: props.ns,
569
+ });
536
570
  return formatTranslation(Object.assign(Object.assign({}, props), { translation, formatEnabled: true }));
537
571
  };
538
572
  const setObserver = (observer) => {
539
- instances.observer = observer === null || observer === void 0 ? void 0 : observer({ translate, onClick });
573
+ plugins.observer = observer;
540
574
  };
541
- const getObserver = () => {
542
- return instances.observer;
575
+ const hasObserver = () => {
576
+ return Boolean(plugins.observer);
543
577
  };
544
578
  const addFormatter = (formatter) => {
545
579
  if (formatter) {
@@ -550,14 +584,17 @@
550
584
  instances.finalFormatter = formatter;
551
585
  };
552
586
  const setUi = (ui) => {
553
- plugins.ui = (ui === null || ui === void 0 ? void 0 : ui.UI) || ui;
587
+ plugins.ui = ui;
554
588
  };
555
- const getUi = () => {
556
- return plugins.ui;
589
+ const hasUi = () => {
590
+ return Boolean(plugins.ui);
557
591
  };
558
592
  const setLanguageStorage = (storage) => {
559
593
  instances.languageStorage = storage;
560
594
  };
595
+ const getLanguageStorage = () => {
596
+ return instances.languageStorage;
597
+ };
561
598
  const setStoredLanguage = (language) => {
562
599
  var _a;
563
600
  (_a = instances.languageStorage) === null || _a === void 0 ? void 0 : _a.setLanguage(language);
@@ -565,6 +602,9 @@
565
602
  const setLanguageDetector = (detector) => {
566
603
  instances.languageDetector = detector;
567
604
  };
605
+ const getLanguageDetector = () => {
606
+ return instances.languageDetector;
607
+ };
568
608
  const detectLanguage = () => {
569
609
  if (!instances.languageDetector) {
570
610
  return undefined;
@@ -594,14 +634,37 @@
594
634
  const setDevBackend = (backend) => {
595
635
  instances.devBackend = backend;
596
636
  };
637
+ const run = () => {
638
+ var _a, _b, _c;
639
+ if (!instances.ui) {
640
+ const { apiKey, apiUrl, projectId } = getInitialOptions();
641
+ instances.ui = (_a = plugins.ui) === null || _a === void 0 ? void 0 : _a.call(plugins, {
642
+ apiKey: apiKey,
643
+ apiUrl: apiUrl,
644
+ projectId,
645
+ highlight,
646
+ changeTranslation,
647
+ });
648
+ }
649
+ if (!instances.observer) {
650
+ instances.observer = (_b = plugins.observer) === null || _b === void 0 ? void 0 : _b.call(plugins, {
651
+ translate,
652
+ onClick,
653
+ options: getInitialOptions().observerOptions,
654
+ });
655
+ }
656
+ (_c = instances.observer) === null || _c === void 0 ? void 0 : _c.run({ mouseHighlight: true });
657
+ };
597
658
  const getDevBackend = () => {
598
659
  return instances.devBackend;
599
660
  };
600
661
  const getBackendDevRecord = ({ language, namespace }) => {
601
662
  var _a;
663
+ const { apiKey, apiUrl, projectId } = getInitialOptions();
602
664
  return (_a = instances.devBackend) === null || _a === void 0 ? void 0 : _a.getRecord({
603
- apiKey: getInitialOptions().apiKey,
604
- apiUrl: getInitialOptions().apiUrl,
665
+ apiKey,
666
+ apiUrl,
667
+ projectId,
605
668
  language,
606
669
  namespace,
607
670
  });
@@ -622,42 +685,97 @@
622
685
  }
623
686
  return undefined;
624
687
  };
625
- const formatTranslation = ({ key, translation, defaultValue, noWrap, params, orEmpty, ns, formatEnabled, }) => {
688
+ const unwrap = (text) => {
689
+ var _a;
690
+ if (instances.observer) {
691
+ return (_a = instances.observer) === null || _a === void 0 ? void 0 : _a.unwrap(text);
692
+ }
693
+ return { text, keys: [] };
694
+ };
695
+ const retranslate = () => {
626
696
  var _a;
697
+ (_a = instances.observer) === null || _a === void 0 ? void 0 : _a.retranslate();
698
+ };
699
+ function addPlugin(tolgeeInstance, plugin) {
700
+ const pluginTools = Object.freeze({
701
+ setFinalFormatter,
702
+ addFormatter,
703
+ setObserver,
704
+ hasObserver,
705
+ setUi,
706
+ hasUi,
707
+ setDevBackend,
708
+ addBackend,
709
+ setLanguageDetector,
710
+ setLanguageStorage,
711
+ });
712
+ plugin(tolgeeInstance, pluginTools);
713
+ }
714
+ function formatTranslation(_a) {
715
+ var _b;
716
+ var { formatEnabled } = _a, props = __rest(_a, ["formatEnabled"]);
717
+ const { key, translation, defaultValue, noWrap, params, orEmpty, ns } = props;
627
718
  const formattableTranslation = translation || defaultValue;
628
719
  let result = formattableTranslation || (orEmpty ? '' : key);
629
- if (instances.observer && !noWrap) {
630
- result = instances.observer.wrap({
631
- key,
632
- translation: result,
633
- defaultValue,
634
- params,
635
- ns,
636
- });
637
- }
638
720
  const language = getLanguage();
639
- const isFormatEnabled = formatEnabled || !((_a = instances.observer) === null || _a === void 0 ? void 0 : _a.outputNotFormattable);
640
- if (formattableTranslation && language && isFormatEnabled) {
641
- for (const formatter of instances.formatters) {
642
- result = formatter.format({
721
+ const isFormatEnabled = formatEnabled || !((_b = instances.observer) === null || _b === void 0 ? void 0 : _b.outputNotFormattable);
722
+ const wrap = (result) => {
723
+ if (instances.observer && !noWrap) {
724
+ return instances.observer.wrap({
725
+ key,
726
+ translation: result,
727
+ defaultValue,
728
+ params,
729
+ ns,
730
+ });
731
+ }
732
+ return result;
733
+ };
734
+ result = wrap(result);
735
+ try {
736
+ if (formattableTranslation && language && isFormatEnabled) {
737
+ for (const formatter of instances.formatters) {
738
+ result = formatter.format({
739
+ translation: result,
740
+ language,
741
+ params,
742
+ });
743
+ }
744
+ }
745
+ if (instances.finalFormatter &&
746
+ formattableTranslation &&
747
+ language &&
748
+ isFormatEnabled) {
749
+ result = instances.finalFormatter.format({
643
750
  translation: result,
644
751
  language,
645
752
  params,
646
753
  });
647
754
  }
648
755
  }
649
- if (instances.finalFormatter &&
650
- formattableTranslation &&
651
- language &&
652
- isFormatEnabled) {
653
- result = instances.finalFormatter.format({
654
- translation: result,
655
- language,
656
- params,
657
- });
756
+ catch (e) {
757
+ // eslint-disable-next-line no-console
758
+ console.error(e);
759
+ const errorMessage = getErrorMessage(e) || DEFAULT_FORMAT_ERROR;
760
+ const onFormatError = getInitialOptions().onFormatError;
761
+ const formatErrorType = typeof onFormatError;
762
+ if (formatErrorType === 'string') {
763
+ result = onFormatError;
764
+ }
765
+ else if (formatErrorType === 'function') {
766
+ result = onFormatError(errorMessage, props);
767
+ }
768
+ else {
769
+ result = DEFAULT_FORMAT_ERROR;
770
+ }
771
+ // wrap error message, so it's detectable
772
+ result = wrap(result);
658
773
  }
659
774
  return result;
660
- };
775
+ }
776
+ function hasDevBackend() {
777
+ return Boolean(getDevBackend());
778
+ }
661
779
  const wrap = (params) => {
662
780
  var _a;
663
781
  if (instances.observer) {
@@ -665,40 +783,23 @@
665
783
  }
666
784
  return params.translation;
667
785
  };
668
- const unwrap = (text) => {
669
- var _a;
670
- if (instances.observer) {
671
- return (_a = instances.observer) === null || _a === void 0 ? void 0 : _a.unwrap(text);
672
- }
673
- return { text, keys: [] };
674
- };
675
- const retranslate = () => {
676
- var _a;
677
- (_a = instances.observer) === null || _a === void 0 ? void 0 : _a.retranslate();
678
- };
679
786
  return Object.freeze({
680
- setFinalFormatter,
681
- addFormatter,
787
+ addPlugin,
682
788
  formatTranslation,
683
- setObserver,
684
- getObserver,
685
- setUi,
686
- getUi,
687
- addBackend,
688
- setDevBackend,
689
789
  getDevBackend,
690
790
  getBackendRecord,
691
791
  getBackendDevRecord,
692
- setLanguageDetector,
693
- setLanguageStorage,
792
+ getLanguageDetector,
793
+ getLanguageStorage,
694
794
  getInitialLanguage,
695
795
  setStoredLanguage,
696
796
  run,
697
797
  stop,
698
798
  retranslate,
699
799
  highlight,
700
- wrap,
701
800
  unwrap,
801
+ wrap,
802
+ hasDevBackend,
702
803
  });
703
804
  };
704
805
 
@@ -720,28 +821,9 @@
720
821
  });
721
822
  };
722
823
 
723
- const defaultValues = {
724
- enableLanguageStore: true,
725
- defaultNs: '',
726
- filesUrlPrefix: 'i18n/',
727
- };
728
- const initState = (options, previousState) => {
729
- const initialOptions = Object.assign(Object.assign(Object.assign({}, defaultValues), previousState === null || previousState === void 0 ? void 0 : previousState.initialOptions), options);
730
- // remove extra '/' from url end
731
- const apiUrl = initialOptions.apiUrl;
732
- initialOptions.apiUrl = apiUrl ? apiUrl.replace(/\/+$/, '') : apiUrl;
733
- return {
734
- initialOptions,
735
- activeNamespaces: (previousState === null || previousState === void 0 ? void 0 : previousState.activeNamespaces) || new Map(),
736
- language: previousState === null || previousState === void 0 ? void 0 : previousState.language,
737
- pendingLanguage: previousState === null || previousState === void 0 ? void 0 : previousState.language,
738
- isInitialLoading: false,
739
- isRunning: false,
740
- };
741
- };
742
-
743
824
  const State = (onLanguageChange, onPendingLanguageChange, onRunningChange) => {
744
825
  let state = initState();
826
+ let devCredentials = undefined;
745
827
  function init(options) {
746
828
  state = initState(options, state);
747
829
  }
@@ -763,13 +845,6 @@
763
845
  function getLanguage() {
764
846
  return state.language || state.initialOptions.language;
765
847
  }
766
- function getLanguageOrFail() {
767
- const language = state.language || state.initialOptions.language;
768
- if (!language) {
769
- throw new Error(`No language set`);
770
- }
771
- return language;
772
- }
773
848
  function setLanguage(language) {
774
849
  if (state.language !== language) {
775
850
  state.language = language;
@@ -786,7 +861,7 @@
786
861
  }
787
862
  }
788
863
  function getInitialOptions() {
789
- return state.initialOptions;
864
+ return Object.assign(Object.assign({}, state.initialOptions), devCredentials);
790
865
  }
791
866
  function addActiveNs(ns) {
792
867
  const namespaces = getFallbackArray(ns);
@@ -815,6 +890,7 @@
815
890
  function getRequiredNamespaces() {
816
891
  return unique([
817
892
  ...(state.initialOptions.ns || [state.initialOptions.defaultNs]),
893
+ ...getFallbackArray(state.initialOptions.fallbackNs),
818
894
  ...state.activeNamespaces.keys(),
819
895
  ]);
820
896
  }
@@ -828,11 +904,11 @@
828
904
  ...getFallbackFromStruct(language, state.initialOptions.fallbackLanguage),
829
905
  ]);
830
906
  }
831
- function getFallbackNamespaces() {
832
- const defaultNs = state.initialOptions.defaultNs;
833
- const fallbackNs = state.initialOptions.fallbackNs;
834
- const fallbackNamespaces = typeof defaultNs === 'string' ? [defaultNs] : [];
835
- return unique([...fallbackNamespaces, ...getFallbackArray(fallbackNs)]);
907
+ function getFallbackNs() {
908
+ return getFallbackArray(state.initialOptions.fallbackNs);
909
+ }
910
+ function getDefaultNs(ns) {
911
+ return ns === undefined ? state.initialOptions.defaultNs : ns;
836
912
  }
837
913
  function getAvailableLanguages() {
838
914
  if (state.initialOptions.availableLanguages) {
@@ -851,6 +927,14 @@
851
927
  language: descriptor.language,
852
928
  };
853
929
  }
930
+ function overrideCredentials(credentials) {
931
+ if (credentials) {
932
+ devCredentials = Object.assign(Object.assign({}, credentials), { apiUrl: sanitizeUrl(credentials.apiUrl) });
933
+ }
934
+ else {
935
+ devCredentials = undefined;
936
+ }
937
+ }
854
938
  return Object.freeze({
855
939
  init,
856
940
  isRunning,
@@ -858,7 +942,6 @@
858
942
  isInitialLoading,
859
943
  setInitialLoading,
860
944
  getLanguage,
861
- getLanguageOrFail,
862
945
  setLanguage,
863
946
  getPendingLanguage,
864
947
  setPendingLanguage,
@@ -867,50 +950,24 @@
867
950
  removeActiveNs,
868
951
  getRequiredNamespaces,
869
952
  getFallbackLangs,
870
- getFallbackNamespaces,
953
+ getFallbackNs,
954
+ getDefaultNs,
871
955
  getAvailableLanguages,
872
956
  withDefaultNs,
957
+ overrideCredentials,
873
958
  });
874
959
  };
875
960
 
876
- /******************************************************************************
877
- Copyright (c) Microsoft Corporation.
878
-
879
- Permission to use, copy, modify, and/or distribute this software for any
880
- purpose with or without fee is hereby granted.
881
-
882
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
883
- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
884
- AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
885
- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
886
- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
887
- OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
888
- PERFORMANCE OF THIS SOFTWARE.
889
- ***************************************************************************** */
890
-
891
- function __rest(s, e) {
892
- var t = {};
893
- for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
894
- t[p] = s[p];
895
- if (s != null && typeof Object.getOwnPropertySymbols === "function")
896
- for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
897
- if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
898
- t[p[i]] = s[p[i]];
899
- }
900
- return t;
901
- }
902
-
903
961
  function parseCombinedOptions(_a) {
904
962
  var { ns, noWrap, orEmpty, params } = _a, rest = __rest(_a, ["ns", "noWrap", "orEmpty", "params"]);
905
963
  const options = {
906
964
  ns: ns,
907
965
  noWrap: noWrap,
908
966
  orEmpty: orEmpty,
909
- params: Object.assign(Object.assign({}, rest), params),
910
967
  };
911
- return options;
968
+ return Object.assign(Object.assign({}, options), { params: Object.assign({}, rest) });
912
969
  }
913
- const getTranslateParams = (keyOrProps, ...params) => {
970
+ const getTranslateProps = (keyOrProps, ...params) => {
914
971
  let result = {};
915
972
  let options;
916
973
  if (typeof keyOrProps === 'object') {
@@ -932,28 +989,40 @@
932
989
  return result;
933
990
  };
934
991
 
935
- const Controller = ({ events, options }) => {
992
+ const Controller = ({ options }) => {
993
+ const events = Events(getFallbackNs, getDefaultNs);
936
994
  const fetchingObserver = ValueObserver(false, () => cache.isFetching(), events.onFetchingChange.emit);
937
995
  const loadingObserver = ValueObserver(false, () => isLoading(), events.onLoadingChange.emit);
938
996
  const state = State(events.onLanguageChange, events.onPendingLanguageChange, events.onRunningChange);
939
- const pluginService = PluginService(state.getLanguage, state.getInitialOptions, state.getAvailableLanguages, getTranslationNs, getTranslation, changeTranslation);
997
+ const pluginService = Plugins(state.getLanguage, state.getInitialOptions, state.getAvailableLanguages, getTranslationNs, getTranslation, changeTranslation);
940
998
  const cache = Cache(events.onCacheChange, pluginService.getBackendRecord, pluginService.getBackendDevRecord, state.withDefaultNs, state.isInitialLoading, fetchingObserver, loadingObserver);
941
- state.init(options);
942
- cache.addStaticData(state.getInitialOptions().staticData);
943
- if (isDev()) {
944
- cache.invalidate();
999
+ if (options) {
1000
+ init(options);
945
1001
  }
946
- events.onKeyUpdate.listen(() => {
1002
+ events.onUpdate.listen(() => {
947
1003
  if (state.isRunning()) {
948
1004
  pluginService.retranslate();
949
1005
  }
950
1006
  });
951
- const t = (...args) => {
952
- // @ts-ignore
953
- const params = getTranslateParams(...args);
954
- const translation = getTranslation(params);
955
- return pluginService.formatTranslation(Object.assign(Object.assign({}, params), { translation }));
956
- };
1007
+ function getFallbackNs() {
1008
+ return state.getFallbackNs();
1009
+ }
1010
+ function getDefaultNs(ns) {
1011
+ return state.getDefaultNs(ns);
1012
+ }
1013
+ // gets all namespaces where translation could be located
1014
+ // takes (ns|default, fallback ns)
1015
+ function getDefaultAndFallbackNs(ns) {
1016
+ return [...getFallbackArray(getDefaultNs(ns)), ...getFallbackNs()];
1017
+ }
1018
+ // gets all namespaces which need to be loaded
1019
+ // takes (ns|default, initial ns, fallback ns, active ns)
1020
+ function getRequiredNamespaces(ns) {
1021
+ return [
1022
+ ...getFallbackArray(ns || getDefaultNs()),
1023
+ ...state.getRequiredNamespaces(),
1024
+ ];
1025
+ }
957
1026
  function changeTranslation(descriptor, key, value) {
958
1027
  const keyObject = state.withDefaultNs(descriptor);
959
1028
  const previousValue = cache.getTranslation(keyObject, key);
@@ -972,7 +1041,7 @@
972
1041
  return cache.isLoading(state.getLanguage(), ns);
973
1042
  }
974
1043
  function isDev() {
975
- return Boolean(state.getInitialOptions().apiKey && pluginService.getDevBackend());
1044
+ return Boolean(state.getInitialOptions().apiKey && state.getInitialOptions().apiUrl);
976
1045
  }
977
1046
  async function addActiveNs(ns, forget) {
978
1047
  if (!forget) {
@@ -984,7 +1053,7 @@
984
1053
  }
985
1054
  function getRequiredRecords(lang, ns) {
986
1055
  const languages = state.getFallbackLangs(lang);
987
- const namespaces = ns !== undefined ? getFallbackArray(ns) : state.getRequiredNamespaces();
1056
+ const namespaces = getRequiredNamespaces(ns);
988
1057
  const result = [];
989
1058
  languages.forEach((language) => {
990
1059
  namespaces.forEach((namespace) => {
@@ -1001,7 +1070,7 @@
1001
1070
  return false;
1002
1071
  }
1003
1072
  const languages = state.getFallbackLangs(language);
1004
- const namespaces = ns !== undefined ? getFallbackArray(ns) : state.getRequiredNamespaces();
1073
+ const namespaces = getRequiredNamespaces(ns);
1005
1074
  const result = [];
1006
1075
  languages.forEach((language) => {
1007
1076
  namespaces.forEach((namespace) => {
@@ -1034,24 +1103,19 @@
1034
1103
  pluginService.setStoredLanguage(language);
1035
1104
  }
1036
1105
  }
1037
- function getTranslationNs({ key, ns, }) {
1038
- const namespaces = ns
1039
- ? getFallbackArray(ns)
1040
- : state.getFallbackNamespaces();
1106
+ function getTranslationNs({ key, ns }) {
1041
1107
  const languages = state.getFallbackLangs();
1108
+ const namespaces = getDefaultAndFallbackNs(ns);
1042
1109
  return cache.getTranslationNs(namespaces, languages, key);
1043
1110
  }
1044
- function getTranslation({ key, ns, }) {
1045
- const namespaces = ns
1046
- ? getFallbackArray(ns)
1047
- : state.getFallbackNamespaces();
1111
+ function getTranslation({ key, ns }) {
1112
+ const namespaces = getDefaultAndFallbackNs(ns);
1048
1113
  const languages = state.getFallbackLangs();
1049
1114
  return cache.getTranslationFallback(namespaces, languages, key);
1050
1115
  }
1051
1116
  function loadInitial() {
1052
1117
  const data = valueOrPromise(initializeLanguage(), () => {
1053
1118
  // fail if there is no language
1054
- state.getLanguageOrFail();
1055
1119
  return loadRequiredRecords();
1056
1120
  });
1057
1121
  if (isPromise(data)) {
@@ -1090,8 +1154,26 @@
1090
1154
  function loadRecords(descriptors) {
1091
1155
  return cache.loadRecords(descriptors, isDev());
1092
1156
  }
1157
+ const checkCorrectConfiguration = () => {
1158
+ const languageComputable = pluginService.getLanguageDetector() || pluginService.getLanguageStorage();
1159
+ if (languageComputable) {
1160
+ const availableLanguages = state.getAvailableLanguages();
1161
+ if (!availableLanguages) {
1162
+ throw new Error(missingOptionError('availableLanguages'));
1163
+ }
1164
+ }
1165
+ if (!state.getLanguage() && !state.getInitialOptions().defaultLanguage) {
1166
+ if (languageComputable) {
1167
+ throw new Error(missingOptionError('defaultLanguage'));
1168
+ }
1169
+ else {
1170
+ throw new Error(missingOptionError('language'));
1171
+ }
1172
+ }
1173
+ };
1093
1174
  function run() {
1094
1175
  let result = undefined;
1176
+ checkCorrectConfiguration();
1095
1177
  if (!state.isRunning()) {
1096
1178
  if (isDev()) {
1097
1179
  cache.invalidate();
@@ -1108,12 +1190,17 @@
1108
1190
  state.setRunning(false);
1109
1191
  }
1110
1192
  }
1111
- return Object.freeze(Object.assign(Object.assign(Object.assign(Object.assign({}, state), pluginService), cache), { init,
1193
+ const t = (...args) => {
1194
+ // @ts-ignore
1195
+ const params = getTranslateProps(...args);
1196
+ const translation = getTranslation(params);
1197
+ return pluginService.formatTranslation(Object.assign(Object.assign({}, params), { translation }));
1198
+ };
1199
+ return Object.freeze(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, events), state), pluginService), cache), { init,
1112
1200
  changeLanguage,
1113
1201
  getTranslation,
1114
1202
  changeTranslation,
1115
1203
  addActiveNs,
1116
- loadRequiredRecords,
1117
1204
  loadRecords,
1118
1205
  loadRecord,
1119
1206
  isLoading,
@@ -1124,24 +1211,11 @@
1124
1211
  stop }));
1125
1212
  };
1126
1213
 
1127
- const Tolgee = (options) => {
1128
- const events = Events();
1214
+ const createTolgee = (options) => {
1129
1215
  const controller = Controller({
1130
- events,
1131
1216
  options,
1132
1217
  });
1133
- const pluginTools = Object.freeze({
1134
- setFinalFormatter: controller.setFinalFormatter,
1135
- addFormatter: controller.addFormatter,
1136
- setObserver: controller.setObserver,
1137
- getObserver: controller.getObserver,
1138
- setUi: controller.setUi,
1139
- getUi: controller.getUi,
1140
- setDevBackend: controller.setDevBackend,
1141
- addBackend: controller.addBackend,
1142
- setLanguageDetector: controller.setLanguageDetector,
1143
- setLanguageStorage: controller.setLanguageStorage,
1144
- });
1218
+ // restarts tolgee while applying callback
1145
1219
  const withRestart = (callback) => {
1146
1220
  const wasRunning = controller.isRunning();
1147
1221
  wasRunning && controller.stop();
@@ -1149,65 +1223,363 @@
1149
1223
  wasRunning && controller.run();
1150
1224
  };
1151
1225
  const tolgee = Object.freeze({
1152
- // event listeners
1153
- on: events.on,
1154
- onKeyUpdate: events.onKeyUpdate.listenSome,
1155
- // state
1226
+ /**
1227
+ * Listen to tolgee events.
1228
+ */
1229
+ on: controller.on,
1230
+ /**
1231
+ * Listen for specific namespaces changes.
1232
+ *
1233
+ * ```
1234
+ * const sub = tolgee.onUpdate(handler)
1235
+ *
1236
+ * // subscribe to selected namespace
1237
+ * sub.subscribeNs(['common'])
1238
+ *
1239
+ * // unsubscribe
1240
+ * sub.unsubscribe()
1241
+ * ```
1242
+ */
1243
+ onNsUpdate: controller.onUpdate.listenSome,
1244
+ /**
1245
+ * Turn off/on events emitting. Is on by default.
1246
+ */
1247
+ setEmmiterActive: controller.setEmmiterActive,
1248
+ /**
1249
+ * @return current language if set.
1250
+ */
1156
1251
  getLanguage: controller.getLanguage,
1252
+ /**
1253
+ * `pendingLanguage` represents language which is currently being loaded.
1254
+ * @return current `pendingLanguage` if set.
1255
+ */
1157
1256
  getPendingLanguage: controller.getPendingLanguage,
1257
+ /**
1258
+ * Change current language.
1259
+ * - if not running sets `pendingLanguage`, `language` to the new value
1260
+ * - if running sets `pendingLanguage` to the value, fetches necessary data and then changes `language`
1261
+ *
1262
+ * @return Promise which is resolved when `language` is changed.
1263
+ */
1158
1264
  changeLanguage: controller.changeLanguage,
1265
+ /**
1266
+ * Temporarily change translation in cache.
1267
+ * @return object with revert method.
1268
+ */
1159
1269
  changeTranslation: controller.changeTranslation,
1270
+ /**
1271
+ * Adds namespace(s) list of active namespaces. And if tolgee is running, loads required data.
1272
+ */
1160
1273
  addActiveNs: controller.addActiveNs,
1274
+ /**
1275
+ * Remove namespace(s) from active namespaces.
1276
+ *
1277
+ * Tolgee internally counts how many times was each active namespace added,
1278
+ * so this method will remove namespace only if the counter goes down to 0.
1279
+ */
1161
1280
  removeActiveNs: controller.removeActiveNs,
1281
+ /**
1282
+ * Manually load multiple records from `Backend` (or `DevBackend` when in dev mode)
1283
+ *
1284
+ * It loads data together and adds them to cache in one operation, to prevent partly loaded state.
1285
+ */
1162
1286
  loadRecords: controller.loadRecords,
1287
+ /**
1288
+ * Manually load record from `Backend` (or `DevBackend` when in dev mode)
1289
+ */
1163
1290
  loadRecord: controller.loadRecord,
1291
+ /**
1292
+ *
1293
+ */
1164
1294
  addStaticData: controller.addStaticData,
1295
+ /**
1296
+ * Get record from cache.
1297
+ */
1165
1298
  getRecord: controller.getRecord,
1299
+ /**
1300
+ * Get all records from cache.
1301
+ */
1166
1302
  getAllRecords: controller.getAllRecords,
1303
+ /**
1304
+ * @param ns optional list of namespaces that you are interested in
1305
+ * @return `true` if there are data that need to be fetched.
1306
+ */
1167
1307
  isLoaded: controller.isLoaded,
1308
+ /**
1309
+ * @return `true` if tolgee is loading initial data (triggered by `run`).
1310
+ */
1168
1311
  isInitialLoading: controller.isInitialLoading,
1312
+ /**
1313
+ * @param ns optional list of namespaces that you are interested in
1314
+ * @return `true` if tolgee is loading some translations for the first time.
1315
+ */
1169
1316
  isLoading: controller.isLoading,
1317
+ /**
1318
+ * @param ns optional list of namespaces that you are interested in
1319
+ * @return `true` if tolgee is fetching some translations.
1320
+ */
1170
1321
  isFetching: controller.isFetching,
1322
+ /**
1323
+ * @return `true` if tolgee is running.
1324
+ */
1171
1325
  isRunning: controller.isRunning,
1326
+ /**
1327
+ * Changes internal state to running: true and loads initial files.
1328
+ * Runs runnable plugins mainly Observer if present.
1329
+ */
1172
1330
  run: controller.run,
1331
+ /**
1332
+ * Changes internal state to running: false and stops runnable plugins.
1333
+ */
1173
1334
  stop: controller.stop,
1335
+ /**
1336
+ * Returns translated and formatted key.
1337
+ * If Observer is present and tolgee is running, wraps result to be identifiable in the DOM.
1338
+ */
1174
1339
  t: controller.t,
1340
+ /**
1341
+ * Highlight keys that match selection.
1342
+ */
1175
1343
  highlight: controller.highlight,
1344
+ /**
1345
+ * @return current Tolgee options.
1346
+ */
1176
1347
  getInitialOptions: controller.getInitialOptions,
1348
+ /**
1349
+ * Tolgee is in dev mode if `DevTools` plugin is used and `apiKey` + `apiUrl` are specified.
1350
+ * @return `true` if tolgee is in dev mode.
1351
+ */
1177
1352
  isDev: controller.isDev,
1353
+ /**
1354
+ * Wraps translation if there is `Observer` plugin
1355
+ */
1178
1356
  wrap: controller.wrap,
1357
+ /**
1358
+ * Unwrap translation
1359
+ */
1179
1360
  unwrap: controller.unwrap,
1180
- // plugins
1181
- use: (plugin) => {
1361
+ /**
1362
+ * Override creadentials passed on initialization.
1363
+ *
1364
+ * When called in running state, tolgee stops and runs again.
1365
+ */
1366
+ overrideCredentials(credentials) {
1367
+ withRestart(() => controller.overrideCredentials(credentials));
1368
+ },
1369
+ /**
1370
+ * Add tolgee plugin after initialization.
1371
+ *
1372
+ * When called in running state, tolgee stops and runs again.
1373
+ */
1374
+ addPlugin(plugin) {
1182
1375
  if (plugin) {
1183
- withRestart(() => plugin(tolgee, pluginTools));
1376
+ withRestart(() => controller.addPlugin(tolgee, plugin));
1184
1377
  }
1185
- return tolgee;
1186
1378
  },
1187
- init: (options) => {
1188
- withRestart(() => controller.init(options));
1189
- return tolgee;
1379
+ /**
1380
+ * Updates options after instance creation. Extends existing options,
1381
+ * so it only changes the fields, that are listed.
1382
+ *
1383
+ * When called in running state, tolgee stops and runs again.
1384
+ */
1385
+ updateOptions(options) {
1386
+ if (options) {
1387
+ withRestart(() => controller.init(options));
1388
+ }
1190
1389
  },
1191
1390
  });
1192
1391
  return tolgee;
1193
1392
  };
1393
+ /**
1394
+ * Tolgee chainable constructor.
1395
+ *
1396
+ * Usage:
1397
+ * ```
1398
+ * const tolgee = Tolgee().use(...).init(...)
1399
+ * ```
1400
+ */
1401
+ const TolgeeCore = () => {
1402
+ const state = {
1403
+ plugins: [],
1404
+ options: {},
1405
+ };
1406
+ const tolgeeChain = Object.freeze({
1407
+ use(plugin) {
1408
+ state.plugins.push(plugin);
1409
+ return tolgeeChain;
1410
+ },
1411
+ updateDefaults(options) {
1412
+ state.options = combineOptions(state.options, options);
1413
+ return tolgeeChain;
1414
+ },
1415
+ init(options) {
1416
+ const tolgee = createTolgee(combineOptions(state.options, options));
1417
+ state.plugins.forEach(tolgee.addPlugin);
1418
+ return tolgee;
1419
+ },
1420
+ });
1421
+ return tolgeeChain;
1422
+ };
1194
1423
 
1195
- const RESTRICTED_ASCENDANT_ATTRIBUTE = 'data-tolgee-restricted';
1196
- const TOLGEE_ATTRIBUTE_NAME = '_tolgee';
1197
- const TOLGEE_HIGHLIGHTER_CLASS = '_tolgee-highlighter';
1198
- const TOLGEE_WRAPPED_ONLY_DATA_ATTRIBUTE = 'data-tolgee-key-only';
1199
- // needs to be same as in @tolgee/ui package
1200
- const DEVTOOLS_ID = '__tolgee_dev_tools';
1424
+ const ERROR_PARAM_EMPTY = 0, ERROR_UNEXPECTED_CHAR = 1, ERROR_UNEXPECTED_END = 2;
1425
+ class FormatError extends Error {
1426
+ constructor(code, index, text) {
1427
+ let error;
1428
+ if (code === ERROR_PARAM_EMPTY) {
1429
+ error = 'Empty parameter';
1430
+ }
1431
+ else if (code === ERROR_UNEXPECTED_CHAR) {
1432
+ error = 'Unexpected character';
1433
+ }
1434
+ else {
1435
+ error = 'Unexpected end';
1436
+ }
1437
+ super(`Tolgee parser: ${error} at ${index} in "${text}"`);
1438
+ this.code = code;
1439
+ this.index = index;
1440
+ }
1441
+ }
1442
+
1443
+ function isWhitespace(ch) {
1444
+ return /\s/.test(ch);
1445
+ }
1446
+ const STATE_TEXT = 0, STATE_ESCAPE_MAYBE = 1, STATE_ESCAPE = 2, STATE_PARAM = 3, STATE_PARAM_AFTER = 4;
1447
+ const END_STATES = new Set([
1448
+ STATE_ESCAPE,
1449
+ STATE_ESCAPE_MAYBE,
1450
+ STATE_TEXT,
1451
+ ]);
1452
+ const CHAR_ESCAPE = "'";
1453
+ const ESCAPABLE = new Set(['{', '}', CHAR_ESCAPE]);
1454
+ const isAllowedInParam = (char) => {
1455
+ return /[0-9a-zA-Z_]/.test(char);
1456
+ };
1457
+ function formatParser(translation) {
1458
+ let state = STATE_TEXT;
1459
+ let text = '';
1460
+ let param = '';
1461
+ let ch = '';
1462
+ const texts = [];
1463
+ const params = [];
1464
+ let i = 0;
1465
+ function parsingError(code) {
1466
+ throw new FormatError(code, i, translation);
1467
+ }
1468
+ const addText = () => {
1469
+ texts.push(text);
1470
+ text = '';
1471
+ };
1472
+ const addParamChar = () => {
1473
+ if (!isAllowedInParam(ch)) {
1474
+ parsingError(ERROR_UNEXPECTED_CHAR);
1475
+ }
1476
+ param += ch;
1477
+ };
1478
+ const addParam = () => {
1479
+ if (param === '') {
1480
+ parsingError(ERROR_PARAM_EMPTY);
1481
+ }
1482
+ params.push(param);
1483
+ param = '';
1484
+ };
1485
+ for (i = 0; i < translation.length; i++) {
1486
+ ch = translation[i];
1487
+ switch (state) {
1488
+ case STATE_TEXT:
1489
+ if (ch === CHAR_ESCAPE) {
1490
+ text += ch;
1491
+ state = STATE_ESCAPE_MAYBE;
1492
+ }
1493
+ else if (ch === '{') {
1494
+ addText();
1495
+ state = STATE_PARAM;
1496
+ }
1497
+ else {
1498
+ text += ch;
1499
+ state = STATE_TEXT;
1500
+ }
1501
+ break;
1502
+ case STATE_ESCAPE_MAYBE:
1503
+ if (ESCAPABLE.has(ch)) {
1504
+ text = text.slice(0, -1) + ch;
1505
+ state = STATE_ESCAPE;
1506
+ }
1507
+ else {
1508
+ text += ch;
1509
+ state = STATE_TEXT;
1510
+ }
1511
+ break;
1512
+ case STATE_ESCAPE:
1513
+ if (ch === CHAR_ESCAPE) {
1514
+ state = STATE_TEXT;
1515
+ }
1516
+ else {
1517
+ text += ch;
1518
+ state = STATE_ESCAPE;
1519
+ }
1520
+ break;
1521
+ case STATE_PARAM:
1522
+ if (ch === '}') {
1523
+ addParam();
1524
+ state = STATE_TEXT;
1525
+ }
1526
+ else if (!isWhitespace(ch)) {
1527
+ addParamChar();
1528
+ state = STATE_PARAM;
1529
+ }
1530
+ else if (param !== '') {
1531
+ addParam();
1532
+ state = STATE_PARAM_AFTER;
1533
+ }
1534
+ break;
1535
+ case STATE_PARAM_AFTER:
1536
+ if (ch == '}') {
1537
+ state = STATE_TEXT;
1538
+ }
1539
+ else if (isWhitespace(ch)) {
1540
+ state = STATE_PARAM_AFTER;
1541
+ }
1542
+ else {
1543
+ parsingError(ERROR_UNEXPECTED_CHAR);
1544
+ }
1545
+ }
1546
+ }
1547
+ if (!END_STATES.has(state)) {
1548
+ parsingError(ERROR_UNEXPECTED_END);
1549
+ }
1550
+ addText();
1551
+ return [texts, params];
1552
+ }
1553
+
1554
+ function formatter(translation, params) {
1555
+ const [texts, pars] = formatParser(translation);
1556
+ const result = [texts[0]];
1557
+ for (let i = 1; i < texts.length; i++) {
1558
+ const parameter = params === null || params === void 0 ? void 0 : params[pars[i - 1]];
1559
+ if (parameter === undefined) {
1560
+ throw new Error(`Missing parameter "${pars[i - 1]}" in "${translation}"`);
1561
+ }
1562
+ result.push(String(parameter));
1563
+ result.push(texts[i]);
1564
+ }
1565
+ return result.join('');
1566
+ }
1567
+
1568
+ function createFormatSimple() {
1569
+ return {
1570
+ format: ({ translation, params }) => formatter(translation, params),
1571
+ };
1572
+ }
1573
+ const FormatSimple = () => (tolgee, tools) => {
1574
+ tools.setFinalFormatter(createFormatSimple());
1575
+ return tolgee;
1576
+ };
1201
1577
 
1202
- exports.DEVTOOLS_ID = DEVTOOLS_ID;
1203
- exports.RESTRICTED_ASCENDANT_ATTRIBUTE = RESTRICTED_ASCENDANT_ATTRIBUTE;
1204
- exports.TOLGEE_ATTRIBUTE_NAME = TOLGEE_ATTRIBUTE_NAME;
1205
- exports.TOLGEE_HIGHLIGHTER_CLASS = TOLGEE_HIGHLIGHTER_CLASS;
1206
- exports.TOLGEE_WRAPPED_ONLY_DATA_ATTRIBUTE = TOLGEE_WRAPPED_ONLY_DATA_ATTRIBUTE;
1207
- exports.Tolgee = Tolgee;
1578
+ exports.FormatSimple = FormatSimple;
1579
+ exports.TolgeeCore = TolgeeCore;
1208
1580
  exports.getFallback = getFallback;
1209
1581
  exports.getFallbackArray = getFallbackArray;
1210
- exports.getTranslateParams = getTranslateParams;
1582
+ exports.getTranslateProps = getTranslateProps;
1211
1583
 
1212
1584
  Object.defineProperty(exports, '__esModule', { value: true });
1213
1585