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