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