@tolgee/core 4.10.0-rc.f068ae1.0 → 5.0.0-alpha.1

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 (84) hide show
  1. package/dist/tolgee.cjs.js +551 -277
  2. package/dist/tolgee.cjs.js.map +1 -1
  3. package/dist/tolgee.cjs.min.js +1 -1
  4. package/dist/tolgee.cjs.min.js.map +1 -1
  5. package/dist/{tolgee.esm.mjs → tolgee.esm.js} +551 -273
  6. package/dist/tolgee.esm.js.map +1 -0
  7. package/dist/tolgee.esm.min.mjs +1 -1
  8. package/dist/tolgee.esm.min.mjs.map +1 -1
  9. package/dist/tolgee.umd.js +551 -277
  10. package/dist/tolgee.umd.js.map +1 -1
  11. package/dist/tolgee.umd.min.js +1 -1
  12. package/dist/tolgee.umd.min.js.map +1 -1
  13. package/lib/Controller/Cache/Cache.d.ts +9 -7
  14. package/lib/Controller/Controller.d.ts +85 -84
  15. package/lib/Controller/Events/EventEmitter.d.ts +6 -6
  16. package/lib/Controller/Events/EventEmitterSelective.d.ts +7 -15
  17. package/lib/Controller/Events/Events.d.ts +12 -49
  18. package/lib/Controller/Plugins/Plugins.d.ts +11 -19
  19. package/lib/Controller/State/State.d.ts +14 -15
  20. package/lib/Controller/State/initState.d.ts +35 -11
  21. package/lib/Controller/State/observerOptions.d.ts +41 -0
  22. package/lib/Controller/ValueObserver.d.ts +5 -5
  23. package/lib/FormatSimple/FormatError.d.ts +7 -0
  24. package/lib/FormatSimple/FormatSimple.d.ts +2 -0
  25. package/lib/FormatSimple/formatParser.d.ts +1 -0
  26. package/lib/FormatSimple/formatter.d.ts +2 -0
  27. package/lib/Tolgee.d.ts +195 -2
  28. package/lib/TranslateParams.d.ts +1 -1
  29. package/lib/helpers.d.ts +7 -0
  30. package/lib/index.d.ts +3 -3
  31. package/lib/types/cache.d.ts +25 -0
  32. package/lib/types/events.d.ts +66 -0
  33. package/lib/types/general.d.ts +34 -0
  34. package/lib/types/index.d.ts +7 -0
  35. package/lib/types/plugin.d.ts +130 -0
  36. package/package.json +5 -4
  37. package/src/Controller/Cache/Cache.ts +29 -22
  38. package/src/Controller/Cache/helpers.ts +3 -1
  39. package/src/Controller/Controller.ts +49 -43
  40. package/src/Controller/Events/EventEmitter.ts +9 -6
  41. package/src/Controller/Events/EventEmitterSelective.test.ts +36 -74
  42. package/src/Controller/Events/EventEmitterSelective.ts +60 -110
  43. package/src/Controller/Events/Events.ts +21 -29
  44. package/src/Controller/Plugins/Plugins.ts +131 -95
  45. package/src/Controller/State/State.ts +37 -30
  46. package/src/Controller/State/initState.ts +81 -20
  47. package/src/Controller/State/observerOptions.ts +66 -0
  48. package/src/Controller/ValueObserver.ts +5 -2
  49. package/src/FormatSimple/FormatError.ts +26 -0
  50. package/src/FormatSimple/FormatSimple.ts +13 -0
  51. package/src/FormatSimple/formatParser.ts +133 -0
  52. package/src/FormatSimple/formatter.test.ts +190 -0
  53. package/src/FormatSimple/formatter.ts +19 -0
  54. package/src/Tolgee.ts +210 -36
  55. package/src/TranslateParams.test.ts +9 -12
  56. package/src/TranslateParams.ts +6 -5
  57. package/src/__test/backend.test.ts +4 -4
  58. package/src/__test/cache.test.ts +51 -12
  59. package/src/__test/client.test.ts +1 -1
  60. package/src/__test/events.test.ts +4 -4
  61. package/src/__test/format.simple.test.ts +26 -0
  62. package/src/__test/initialization.test.ts +4 -4
  63. package/src/__test/languages.test.ts +24 -6
  64. package/src/__test/loading.test.ts +1 -1
  65. package/src/__test/{namespacesFallback.test.ts → namespaces.fallback.test.ts} +8 -7
  66. package/src/__test/namespaces.test.ts +29 -6
  67. package/src/__test/options.test.ts +64 -0
  68. package/src/__test/plugins.test.ts +22 -49
  69. package/src/helpers.ts +45 -0
  70. package/src/index.ts +3 -9
  71. package/src/types/cache.ts +37 -0
  72. package/src/types/events.ts +85 -0
  73. package/src/types/general.ts +50 -0
  74. package/src/types/index.ts +19 -0
  75. package/src/types/plugin.ts +181 -0
  76. package/dist/tolgee.esm.mjs.map +0 -1
  77. package/lib/Controller/State/helpers.d.ts +0 -6
  78. package/lib/Controller/State/initObserverOptions.d.ts +0 -13
  79. package/lib/constants.d.ts +0 -5
  80. package/lib/types.d.ts +0 -283
  81. package/src/Controller/State/helpers.ts +0 -41
  82. package/src/Controller/State/initObserverOptions.ts +0 -38
  83. package/src/constants.ts +0 -7
  84. package/src/types.ts +0 -392
@@ -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,30 @@ 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);
60
- }
61
- else {
62
- map.set(value, currNum);
63
- }
64
- }
65
- const EventEmitterSelective = (getFallbackNamespaces) => {
46
+
47
+ const EventEmitter = () => {
48
+ let handlers = [];
49
+ const listen = (handler) => {
50
+ const handlerWrapper = (e) => {
51
+ handler(e);
52
+ };
53
+ handlers.push(handlerWrapper);
54
+ return {
55
+ unsubscribe: () => {
56
+ handlers = handlers.filter((i) => handlerWrapper !== i);
57
+ },
58
+ };
59
+ };
60
+ const emit = (data) => {
61
+ handlers.forEach((handler) => handler({ value: data }));
62
+ };
63
+ return Object.freeze({ listen, emit });
64
+ };
65
+
66
+ const EventEmitterSelective = (getFallbackNs, getDefaultNs) => {
66
67
  const listeners = new Set();
67
68
  const partialListeners = new Set();
68
69
  const listen = (handler) => {
@@ -79,8 +80,7 @@ const EventEmitterSelective = (getFallbackNamespaces) => {
79
80
  fn: (e) => {
80
81
  handler(e);
81
82
  },
82
- keys: new Map(),
83
- namespaces: new Map(),
83
+ namespaces: new Set(),
84
84
  };
85
85
  partialListeners.add(handlerWrapper);
86
86
  const result = {
@@ -88,52 +88,24 @@ const EventEmitterSelective = (getFallbackNamespaces) => {
88
88
  partialListeners.delete(handlerWrapper);
89
89
  },
90
90
  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));
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));
91
+ getFallbackArray(ns).forEach((val) => handlerWrapper.namespaces.add(val));
112
92
  if (ns === undefined) {
113
- // subscribing to all namespaces
114
- decrementInMap(handlerWrapper.namespaces, undefined);
93
+ // subscribing to default ns
94
+ handlerWrapper.namespaces.add(getDefaultNs());
115
95
  }
116
96
  return result;
117
97
  },
118
98
  };
119
99
  return result;
120
100
  };
121
- const namespacesWithFallbacks = (namespaces) => {
122
- if (namespaces.has(undefined)) {
123
- const result = new Set(namespaces.keys());
124
- result.delete(undefined);
125
- getFallbackNamespaces().forEach((ns) => result.add(ns));
126
- return result;
127
- }
128
- return namespaces;
129
- };
130
- const callHandlers = (key, ns) => {
101
+ const callHandlers = (ns) => {
102
+ // everything is implicitly subscribed to fallbacks
103
+ // as it can always fall through to it
104
+ const fallbackNamespaces = new Set(getFallbackNs());
131
105
  partialListeners.forEach((handler) => {
132
- const handlerNamespaces = namespacesWithFallbacks(handler.namespaces);
133
106
  const nsMatches = ns === undefined ||
134
- (ns === null || ns === void 0 ? void 0 : ns.findIndex((ns) => handlerNamespaces.has(ns))) !== -1;
135
- const keyMatches = key === undefined || handler.keys.has(key) || handler.keys.size === 0;
136
- if (nsMatches && keyMatches) {
107
+ (ns === null || ns === void 0 ? void 0 : ns.findIndex((ns) => fallbackNamespaces.has(ns) || handler.namespaces.has(ns))) !== -1;
108
+ if (nsMatches) {
137
109
  handler.fn({ value: undefined });
138
110
  }
139
111
  });
@@ -144,61 +116,51 @@ const EventEmitterSelective = (getFallbackNamespaces) => {
144
116
  if (queue.length === 0) {
145
117
  return;
146
118
  }
119
+ const queueCopy = queue;
120
+ queue = [];
147
121
  listeners.forEach((handler) => {
148
122
  handler({ value: undefined });
149
123
  });
150
- const namespaces = new Set();
151
- let keys = new Set();
152
- queue.forEach((descriptor) => {
153
- if ((descriptor === null || descriptor === void 0 ? void 0 : descriptor.ns) === undefined) {
154
- // when no ns specified, it affets all fallback namespaces
155
- namespaces.add(undefined);
124
+ let namespaces = new Set();
125
+ queueCopy.forEach((ns) => {
126
+ if (ns === undefined) {
127
+ // when no ns specified, it affects all namespaces
128
+ namespaces = undefined;
156
129
  }
157
- else {
158
- descriptor.ns.forEach((ns) => namespaces.add(ns));
130
+ else if (namespaces !== undefined) {
131
+ ns.forEach((ns) => namespaces.add(ns));
159
132
  }
160
- if ((descriptor === null || descriptor === void 0 ? void 0 : descriptor.key) === undefined) {
161
- // when no key specified, it affects all keys
162
- keys = undefined;
163
- }
164
- else if (keys !== undefined) {
165
- keys.add(descriptor.key);
166
- }
167
- });
168
- const namespacesArray = Array.from(namespacesWithFallbacks(namespaces).keys());
169
- (keys || [undefined]).forEach((key) => {
170
- callHandlers(key, namespacesArray);
171
133
  });
172
- queue = [];
134
+ const namespacesArray = namespaces
135
+ ? Array.from(namespaces.keys())
136
+ : undefined;
137
+ callHandlers(namespacesArray);
173
138
  };
174
- const emit = (descriptor, delayed) => {
175
- queue.push(descriptor);
139
+ const emit = (ns, delayed) => {
140
+ queue.push(ns);
176
141
  if (!delayed) {
177
142
  solveQueue();
178
143
  }
179
144
  else {
180
- Promise.resolve().then(() => {
181
- solveQueue();
182
- });
145
+ setTimeout(solveQueue, 0);
183
146
  }
184
147
  };
185
148
  return Object.freeze({ listenSome, listen, emit });
186
149
  };
187
150
 
188
- const Events = (getFallbackNamespaces) => {
151
+ const Events = (getFallbackNs, getDefaultNs) => {
189
152
  const onPendingLanguageChange = EventEmitter();
190
153
  const onLanguageChange = EventEmitter();
191
- const onKeyChange = EventEmitter();
192
154
  const onLoadingChange = EventEmitter();
193
155
  const onFetchingChange = EventEmitter();
194
156
  const onInitialLoaded = EventEmitter();
195
- const onKeyUpdate = EventEmitterSelective(getFallbackNamespaces);
196
- const onCacheChange = EventEmitter();
197
157
  const onRunningChange = EventEmitter();
198
- onInitialLoaded.listen(() => onKeyUpdate.emit());
199
- onLanguageChange.listen(() => onKeyUpdate.emit());
158
+ const onCacheChange = EventEmitter();
159
+ const onUpdate = EventEmitterSelective(getFallbackNs, getDefaultNs);
160
+ onInitialLoaded.listen(() => onUpdate.emit());
161
+ onLanguageChange.listen(() => onUpdate.emit());
200
162
  onCacheChange.listen(({ value }) => {
201
- onKeyUpdate.emit({ ns: [value.namespace], key: value.key }, true);
163
+ onUpdate.emit([value.namespace], true);
202
164
  });
203
165
  const on = (event, handler) => {
204
166
  switch (event) {
@@ -216,20 +178,19 @@ const Events = (getFallbackNamespaces) => {
216
178
  return onRunningChange.listen(handler);
217
179
  case 'cache':
218
180
  return onCacheChange.listen(handler);
219
- case 'keyUpdate':
220
- return onKeyUpdate.listen(handler);
181
+ case 'update':
182
+ return onUpdate.listen(handler);
221
183
  }
222
184
  };
223
185
  return Object.freeze({
224
186
  onPendingLanguageChange,
225
187
  onLanguageChange,
226
- onKeyChange,
227
- onKeyUpdate,
228
188
  onLoadingChange,
229
189
  onFetchingChange,
230
190
  onInitialLoaded,
231
191
  onRunningChange,
232
192
  onCacheChange,
193
+ onUpdate,
233
194
  on,
234
195
  });
235
196
  };
@@ -252,7 +213,9 @@ const flattenTranslations = (data) => {
252
213
  return result;
253
214
  };
254
215
  const decodeCacheKey = (key) => {
255
- const [firstPart, secondPart] = key.split(':');
216
+ const [firstPart, ...rest] = key.split(':');
217
+ // if namespaces contains ":" it won't get lost
218
+ const secondPart = rest.join(':');
256
219
  return { language: firstPart, namespace: secondPart || '' };
257
220
  };
258
221
  const encodeCacheKey = ({ language, namespace, }) => {
@@ -320,11 +283,11 @@ const Cache = (onCacheChange, backendGetRecord, backendGetDevRecord, withDefault
320
283
  const value = (_a = cache
321
284
  .get(encodeCacheKey({ language, namespace }))) === null || _a === void 0 ? void 0 : _a.data.get(key);
322
285
  if (value !== undefined && value !== null) {
323
- return namespace;
286
+ return [namespace];
324
287
  }
325
288
  }
326
289
  }
327
- return Array.from(new Set(namespaces));
290
+ return unique(namespaces);
328
291
  }
329
292
  function getTranslationFallback(namespaces, languages, key) {
330
293
  var _a;
@@ -367,7 +330,10 @@ const Cache = (onCacheChange, backendGetRecord, backendGetDevRecord, withDefault
367
330
  }));
368
331
  }));
369
332
  }
370
- function fetchNormal(keyObject) {
333
+ /**
334
+ * Fetches production data
335
+ */
336
+ function fetchProd(keyObject) {
371
337
  let dataPromise = undefined;
372
338
  if (!dataPromise) {
373
339
  const staticDataValue = staticData[encodeCacheKey(keyObject)];
@@ -378,10 +344,6 @@ const Cache = (onCacheChange, backendGetRecord, backendGetDevRecord, withDefault
378
344
  if (!dataPromise) {
379
345
  dataPromise = backendGetRecord(keyObject);
380
346
  }
381
- if (!dataPromise) {
382
- // return empty data, so we know it has already been attempted to fetch
383
- dataPromise = Promise.resolve({});
384
- }
385
347
  return dataPromise;
386
348
  }
387
349
  function fetchData(keyObject, isDev) {
@@ -391,12 +353,12 @@ const Cache = (onCacheChange, backendGetRecord, backendGetDevRecord, withDefault
391
353
  dataPromise = (_a = backendGetDevRecord(keyObject)) === null || _a === void 0 ? void 0 : _a.catch(() => {
392
354
  // eslint-disable-next-line no-console
393
355
  console.warn(`Tolgee: Failed to fetch data from dev backend`);
394
- // fallback to normal fetch if dev fails
395
- return fetchNormal(keyObject);
356
+ // fallback to prod fetch if dev fails
357
+ return fetchProd(keyObject);
396
358
  });
397
359
  }
398
360
  if (!dataPromise) {
399
- dataPromise = fetchNormal(keyObject);
361
+ dataPromise = fetchProd(keyObject);
400
362
  }
401
363
  return dataPromise;
402
364
  }
@@ -413,7 +375,7 @@ const Cache = (onCacheChange, backendGetRecord, backendGetDevRecord, withDefault
413
375
  cacheKey,
414
376
  };
415
377
  }
416
- const dataPromise = fetchData(keyObject, isDev);
378
+ const dataPromise = fetchData(keyObject, isDev) || Promise.resolve(undefined);
417
379
  asyncRequests.set(cacheKey, dataPromise);
418
380
  return {
419
381
  new: true,
@@ -435,6 +397,10 @@ const Cache = (onCacheChange, backendGetRecord, backendGetDevRecord, withDefault
435
397
  if (data) {
436
398
  addRecord(value.keyObject, data);
437
399
  }
400
+ else if (!getRecord(value.keyObject)) {
401
+ // if no data exist, put empty object
402
+ addRecord(value.keyObject, {});
403
+ }
438
404
  }
439
405
  });
440
406
  fetchingObserver.notify();
@@ -464,20 +430,9 @@ const Cache = (onCacheChange, backendGetRecord, backendGetDevRecord, withDefault
464
430
  });
465
431
  };
466
432
 
467
- function isPromise(value) {
468
- return Boolean(value && typeof value.then === 'function');
469
- }
470
- const valueOrPromise = (value, callback) => {
471
- if (isPromise(value)) {
472
- return Promise.resolve(value).then(callback);
473
- }
474
- else {
475
- return callback(value);
476
- }
477
- };
478
- const missingOptionError = (option) => `Tolgee: You need to specify '${option}' option`;
479
-
480
- const PluginService = (getLanguage, getInitialOptions, getObserverOptions, getAvailableLanguages, getTranslationNs, getTranslation, changeTranslation) => {
433
+ const Plugins = (getLanguage, getInitialOptions, getAvailableLanguages, getTranslationNs, getTranslation, changeTranslation) => {
434
+ let prepared = false;
435
+ let onPrepareQueue = [];
481
436
  const plugins = {
482
437
  ui: undefined,
483
438
  observer: undefined,
@@ -492,37 +447,20 @@ const PluginService = (getLanguage, getInitialOptions, getObserverOptions, getAv
492
447
  languageDetector: undefined,
493
448
  languageStorage: undefined,
494
449
  };
495
- const onClick = async (event, { keysAndDefaults }) => {
450
+ const onClick = async ({ keysAndDefaults, event }) => {
496
451
  var _a;
497
- const withNs = keysAndDefaults.map(({ key, ns, defaultValue }) => ({
498
- key,
499
- defaultValue,
500
- ns: getFallbackArray(getTranslationNs({ key, ns, defaultValue })),
501
- translation: getTranslation({
452
+ const withNs = keysAndDefaults.map(({ key, ns, defaultValue }) => {
453
+ return {
502
454
  key,
503
- ns,
504
- }),
505
- }));
506
- (_a = instances.ui) === null || _a === void 0 ? void 0 : _a.handleElementClick(event, withNs);
507
- };
508
- const run = (isDev) => {
509
- var _a, _b;
510
- instances.ui =
511
- plugins.ui &&
512
- new plugins.ui({
513
- apiKey: getInitialOptions().apiKey,
514
- apiUrl: getInitialOptions().apiUrl,
515
- highlight,
516
- changeTranslation,
517
- });
518
- if (!instances.observer) {
519
- instances.observer = (_a = plugins.observer) === null || _a === void 0 ? void 0 : _a.call(plugins, {
520
- translate,
521
- onClick,
522
- options: getObserverOptions(),
523
- });
524
- }
525
- (_b = instances.observer) === null || _b === void 0 ? void 0 : _b.run({ mouseHighlight: isDev });
455
+ defaultValue,
456
+ ns: getTranslationNs({ key, ns }),
457
+ translation: getTranslation({
458
+ key,
459
+ ns,
460
+ }),
461
+ };
462
+ });
463
+ (_a = instances.ui) === null || _a === void 0 ? void 0 : _a.handleElementClick(withNs, event);
526
464
  };
527
465
  const stop = () => {
528
466
  var _a;
@@ -534,7 +472,10 @@ const PluginService = (getLanguage, getInitialOptions, getObserverOptions, getAv
534
472
  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() { } };
535
473
  };
536
474
  const translate = (props) => {
537
- const translation = getTranslation(props);
475
+ const translation = getTranslation({
476
+ key: props.key,
477
+ ns: props.ns,
478
+ });
538
479
  return formatTranslation(Object.assign(Object.assign({}, props), { translation, formatEnabled: true }));
539
480
  };
540
481
  const setObserver = (observer) => {
@@ -560,9 +501,6 @@ const PluginService = (getLanguage, getInitialOptions, getObserverOptions, getAv
560
501
  const setLanguageStorage = (storage) => {
561
502
  instances.languageStorage = storage;
562
503
  };
563
- const getLanguageStorage = () => {
564
- return instances.languageStorage;
565
- };
566
504
  const setStoredLanguage = (language) => {
567
505
  var _a;
568
506
  (_a = instances.languageStorage) === null || _a === void 0 ? void 0 : _a.setLanguage(language);
@@ -602,14 +540,37 @@ const PluginService = (getLanguage, getInitialOptions, getObserverOptions, getAv
602
540
  const setDevBackend = (backend) => {
603
541
  instances.devBackend = backend;
604
542
  };
543
+ const run = (isDev) => {
544
+ var _a, _b;
545
+ if (!instances.ui && plugins.ui) {
546
+ const { apiKey, apiUrl, projectId } = getInitialOptions();
547
+ instances.ui = new plugins.ui({
548
+ apiKey: apiKey,
549
+ apiUrl: apiUrl,
550
+ projectId,
551
+ highlight,
552
+ changeTranslation,
553
+ });
554
+ }
555
+ if (!instances.observer) {
556
+ instances.observer = (_a = plugins.observer) === null || _a === void 0 ? void 0 : _a.call(plugins, {
557
+ translate,
558
+ onClick,
559
+ options: getInitialOptions().observerOptions,
560
+ });
561
+ }
562
+ (_b = instances.observer) === null || _b === void 0 ? void 0 : _b.run({ mouseHighlight: isDev });
563
+ };
605
564
  const getDevBackend = () => {
606
565
  return instances.devBackend;
607
566
  };
608
567
  const getBackendDevRecord = ({ language, namespace }) => {
609
568
  var _a;
569
+ const { apiKey, apiUrl, projectId } = getInitialOptions();
610
570
  return (_a = instances.devBackend) === null || _a === void 0 ? void 0 : _a.getRecord({
611
- apiKey: getInitialOptions().apiKey,
612
- apiUrl: getInitialOptions().apiUrl,
571
+ apiKey,
572
+ apiUrl,
573
+ projectId,
613
574
  language,
614
575
  namespace,
615
576
  });
@@ -630,7 +591,40 @@ const PluginService = (getLanguage, getInitialOptions, getObserverOptions, getAv
630
591
  }
631
592
  return undefined;
632
593
  };
633
- const formatTranslation = ({ key, translation, defaultValue, noWrap, params, orEmpty, ns, formatEnabled, }) => {
594
+ const unwrap = (text) => {
595
+ var _a;
596
+ if (instances.observer) {
597
+ return (_a = instances.observer) === null || _a === void 0 ? void 0 : _a.unwrap(text);
598
+ }
599
+ return { text, keys: [] };
600
+ };
601
+ const retranslate = () => {
602
+ var _a;
603
+ (_a = instances.observer) === null || _a === void 0 ? void 0 : _a.retranslate();
604
+ };
605
+ const onPrepare = (callback) => {
606
+ onPrepareQueue.push(callback);
607
+ };
608
+ function addPlugin(tolgeeInstance, plugin) {
609
+ const pluginTools = Object.freeze({
610
+ setFinalFormatter,
611
+ addFormatter,
612
+ setObserver,
613
+ hasObserver,
614
+ setUi,
615
+ hasUi,
616
+ setDevBackend,
617
+ addBackend,
618
+ setLanguageDetector,
619
+ setLanguageStorage,
620
+ onPrepare,
621
+ });
622
+ plugin(tolgeeInstance, pluginTools);
623
+ if (prepared) {
624
+ prepare();
625
+ }
626
+ }
627
+ function formatTranslation({ key, translation, defaultValue, noWrap, params, orEmpty, ns, formatEnabled, }) {
634
628
  var _a;
635
629
  const formattableTranslation = translation || defaultValue;
636
630
  let result = formattableTranslation || (orEmpty ? '' : key);
@@ -665,7 +659,10 @@ const PluginService = (getLanguage, getInitialOptions, getObserverOptions, getAv
665
659
  });
666
660
  }
667
661
  return result;
668
- };
662
+ }
663
+ function hasDevBackend() {
664
+ return Boolean(getDevBackend());
665
+ }
669
666
  const wrap = (params) => {
670
667
  var _a;
671
668
  if (instances.observer) {
@@ -673,42 +670,31 @@ const PluginService = (getLanguage, getInitialOptions, getObserverOptions, getAv
673
670
  }
674
671
  return params.translation;
675
672
  };
676
- const unwrap = (text) => {
677
- var _a;
678
- if (instances.observer) {
679
- return (_a = instances.observer) === null || _a === void 0 ? void 0 : _a.unwrap(text);
673
+ function prepare() {
674
+ prepared = true;
675
+ while (onPrepareQueue.length) {
676
+ const queue = onPrepareQueue;
677
+ onPrepareQueue = [];
678
+ queue.forEach((callback) => callback());
680
679
  }
681
- return { text, keys: [] };
682
- };
683
- const retranslate = () => {
684
- var _a;
685
- (_a = instances.observer) === null || _a === void 0 ? void 0 : _a.retranslate();
686
- };
680
+ }
687
681
  return Object.freeze({
688
- setFinalFormatter,
689
- addFormatter,
682
+ prepare,
683
+ addPlugin,
690
684
  formatTranslation,
691
- setObserver,
692
- hasObserver,
693
- setUi,
694
- hasUi,
695
- addBackend,
696
- setDevBackend,
697
685
  getDevBackend,
698
686
  getBackendRecord,
699
687
  getBackendDevRecord,
700
- setLanguageDetector,
701
688
  getLanguageDetector,
702
- setLanguageStorage,
703
- getLanguageStorage,
704
689
  getInitialLanguage,
705
690
  setStoredLanguage,
706
691
  run,
707
692
  stop,
708
693
  retranslate,
709
694
  highlight,
710
- wrap,
711
695
  unwrap,
696
+ wrap,
697
+ hasDevBackend,
712
698
  });
713
699
  };
714
700
 
@@ -730,7 +716,7 @@ const ValueObserver = (initialValue, valueGetter, handler) => {
730
716
  });
731
717
  };
732
718
 
733
- const defaultValues$1 = {
719
+ const defaultObserverOptions = {
734
720
  tagAttributes: {
735
721
  textarea: ['placeholder'],
736
722
  input: ['value', 'placeholder'],
@@ -745,19 +731,23 @@ const defaultValues$1 = {
745
731
  inputSuffix: '%-%',
746
732
  passToParent: ['option', 'optgroup'],
747
733
  };
748
- const initObserverOptions = (options) => {
749
- return Object.assign(Object.assign({}, defaultValues$1), options);
750
- };
751
734
 
752
735
  const defaultValues = {
753
736
  defaultNs: '',
754
- filesUrlPrefix: 'i18n/',
737
+ observerOptions: defaultObserverOptions,
738
+ observerType: 'invisible',
739
+ };
740
+ const combineOptions = (...states) => {
741
+ let result = {};
742
+ states.forEach((state) => {
743
+ 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) });
744
+ });
745
+ return result;
755
746
  };
756
747
  const initState = (options, previousState) => {
757
- const initialOptions = Object.assign(Object.assign(Object.assign({}, defaultValues), previousState === null || previousState === void 0 ? void 0 : previousState.initialOptions), options);
748
+ const initialOptions = combineOptions(defaultValues, previousState === null || previousState === void 0 ? void 0 : previousState.initialOptions, options);
758
749
  // remove extra '/' from url end
759
- const apiUrl = initialOptions.apiUrl;
760
- initialOptions.apiUrl = apiUrl ? apiUrl.replace(/\/+$/, '') : apiUrl;
750
+ initialOptions.apiUrl = sanitizeUrl(initialOptions.apiUrl);
761
751
  return {
762
752
  initialOptions,
763
753
  activeNamespaces: (previousState === null || previousState === void 0 ? void 0 : previousState.activeNamespaces) || new Map(),
@@ -770,8 +760,7 @@ const initState = (options, previousState) => {
770
760
 
771
761
  const State = (onLanguageChange, onPendingLanguageChange, onRunningChange) => {
772
762
  let state = initState();
773
- let observerOptions = initObserverOptions();
774
- let devCredentials = {};
763
+ let devCredentials = undefined;
775
764
  function init(options) {
776
765
  state = initState(options, state);
777
766
  }
@@ -838,6 +827,7 @@ const State = (onLanguageChange, onPendingLanguageChange, onRunningChange) => {
838
827
  function getRequiredNamespaces() {
839
828
  return unique([
840
829
  ...(state.initialOptions.ns || [state.initialOptions.defaultNs]),
830
+ ...getFallbackArray(state.initialOptions.fallbackNs),
841
831
  ...state.activeNamespaces.keys(),
842
832
  ]);
843
833
  }
@@ -851,11 +841,11 @@ const State = (onLanguageChange, onPendingLanguageChange, onRunningChange) => {
851
841
  ...getFallbackFromStruct(language, state.initialOptions.fallbackLanguage),
852
842
  ]);
853
843
  }
854
- function getFallbackNamespaces() {
855
- const defaultNs = state.initialOptions.defaultNs;
856
- const fallbackNs = state.initialOptions.fallbackNs;
857
- const fallbackNamespaces = typeof defaultNs === 'string' ? [defaultNs] : [];
858
- return unique([...fallbackNamespaces, ...getFallbackArray(fallbackNs)]);
844
+ function getFallbackNs() {
845
+ return getFallbackArray(state.initialOptions.fallbackNs);
846
+ }
847
+ function getDefaultNs(ns) {
848
+ return ns === undefined ? state.initialOptions.defaultNs : ns;
859
849
  }
860
850
  function getAvailableLanguages() {
861
851
  if (state.initialOptions.availableLanguages) {
@@ -875,13 +865,12 @@ const State = (onLanguageChange, onPendingLanguageChange, onRunningChange) => {
875
865
  };
876
866
  }
877
867
  function overrideCredentials(credentials) {
878
- devCredentials = credentials;
879
- }
880
- function setObserverOptions(options) {
881
- observerOptions = initObserverOptions(options);
882
- }
883
- function getObserverOptions() {
884
- return observerOptions;
868
+ if (credentials) {
869
+ devCredentials = Object.assign(Object.assign({}, credentials), { apiUrl: sanitizeUrl(credentials.apiUrl) });
870
+ }
871
+ else {
872
+ devCredentials = undefined;
873
+ }
885
874
  }
886
875
  return Object.freeze({
887
876
  init,
@@ -898,12 +887,11 @@ const State = (onLanguageChange, onPendingLanguageChange, onRunningChange) => {
898
887
  removeActiveNs,
899
888
  getRequiredNamespaces,
900
889
  getFallbackLangs,
901
- getFallbackNamespaces,
890
+ getFallbackNs,
891
+ getDefaultNs,
902
892
  getAvailableLanguages,
903
893
  withDefaultNs,
904
894
  overrideCredentials,
905
- setObserverOptions,
906
- getObserverOptions,
907
895
  });
908
896
  };
909
897
 
@@ -940,11 +928,10 @@ function parseCombinedOptions(_a) {
940
928
  ns: ns,
941
929
  noWrap: noWrap,
942
930
  orEmpty: orEmpty,
943
- params: Object.assign(Object.assign({}, rest), params),
944
931
  };
945
- return options;
932
+ return Object.assign(Object.assign({}, options), { params: Object.assign({}, rest) });
946
933
  }
947
- const getTranslateParams = (keyOrProps, ...params) => {
934
+ const getTranslateProps = (keyOrProps, ...params) => {
948
935
  let result = {};
949
936
  let options;
950
937
  if (typeof keyOrProps === 'object') {
@@ -967,20 +954,39 @@ const getTranslateParams = (keyOrProps, ...params) => {
967
954
  };
968
955
 
969
956
  const Controller = ({ options }) => {
970
- const events = Events(getFallbackNamespaces);
957
+ const events = Events(getFallbackNs, getDefaultNs);
971
958
  const fetchingObserver = ValueObserver(false, () => cache.isFetching(), events.onFetchingChange.emit);
972
959
  const loadingObserver = ValueObserver(false, () => isLoading(), events.onLoadingChange.emit);
973
960
  const state = State(events.onLanguageChange, events.onPendingLanguageChange, events.onRunningChange);
974
- const pluginService = PluginService(state.getLanguage, state.getInitialOptions, state.getObserverOptions, state.getAvailableLanguages, getTranslationNs, getTranslation, changeTranslation);
961
+ const pluginService = Plugins(state.getLanguage, state.getInitialOptions, state.getAvailableLanguages, getTranslationNs, getTranslation, changeTranslation);
975
962
  const cache = Cache(events.onCacheChange, pluginService.getBackendRecord, pluginService.getBackendDevRecord, state.withDefaultNs, state.isInitialLoading, fetchingObserver, loadingObserver);
976
963
  if (options) {
977
964
  init(options);
978
965
  }
979
- events.onKeyUpdate.listen(() => {
966
+ events.onUpdate.listen(() => {
980
967
  if (state.isRunning()) {
981
968
  pluginService.retranslate();
982
969
  }
983
970
  });
971
+ function getFallbackNs() {
972
+ return state.getFallbackNs();
973
+ }
974
+ function getDefaultNs(ns) {
975
+ return state.getDefaultNs(ns);
976
+ }
977
+ // gets all namespaces where translation could be located
978
+ // takes (ns|default, fallback ns)
979
+ function getDefaultAndFallbackNs(ns) {
980
+ return [...getFallbackArray(getDefaultNs(ns)), ...getFallbackNs()];
981
+ }
982
+ // gets all namespaces which need to be loaded
983
+ // takes (ns|default, initial ns, fallback ns, active ns)
984
+ function getRequiredNamespaces(ns) {
985
+ return [
986
+ ...getFallbackArray(ns || getDefaultNs()),
987
+ ...state.getRequiredNamespaces(),
988
+ ];
989
+ }
984
990
  function changeTranslation(descriptor, key, value) {
985
991
  const keyObject = state.withDefaultNs(descriptor);
986
992
  const previousValue = cache.getTranslation(keyObject, key);
@@ -991,9 +997,6 @@ const Controller = ({ options }) => {
991
997
  },
992
998
  };
993
999
  }
994
- function getFallbackNamespaces() {
995
- return state.getFallbackNamespaces();
996
- }
997
1000
  function init(options) {
998
1001
  state.init(options);
999
1002
  cache.addStaticData(state.getInitialOptions().staticData);
@@ -1002,9 +1005,7 @@ const Controller = ({ options }) => {
1002
1005
  return cache.isLoading(state.getLanguage(), ns);
1003
1006
  }
1004
1007
  function isDev() {
1005
- return Boolean(state.getInitialOptions().apiKey &&
1006
- state.getInitialOptions().apiUrl &&
1007
- pluginService.getDevBackend());
1008
+ return Boolean(state.getInitialOptions().apiKey && state.getInitialOptions().apiUrl);
1008
1009
  }
1009
1010
  async function addActiveNs(ns, forget) {
1010
1011
  if (!forget) {
@@ -1016,7 +1017,7 @@ const Controller = ({ options }) => {
1016
1017
  }
1017
1018
  function getRequiredRecords(lang, ns) {
1018
1019
  const languages = state.getFallbackLangs(lang);
1019
- const namespaces = ns !== undefined ? getFallbackArray(ns) : state.getRequiredNamespaces();
1020
+ const namespaces = getRequiredNamespaces(ns);
1020
1021
  const result = [];
1021
1022
  languages.forEach((language) => {
1022
1023
  namespaces.forEach((namespace) => {
@@ -1033,7 +1034,7 @@ const Controller = ({ options }) => {
1033
1034
  return false;
1034
1035
  }
1035
1036
  const languages = state.getFallbackLangs(language);
1036
- const namespaces = ns !== undefined ? getFallbackArray(ns) : state.getRequiredNamespaces();
1037
+ const namespaces = getRequiredNamespaces(ns);
1037
1038
  const result = [];
1038
1039
  languages.forEach((language) => {
1039
1040
  namespaces.forEach((namespace) => {
@@ -1066,13 +1067,13 @@ const Controller = ({ options }) => {
1066
1067
  pluginService.setStoredLanguage(language);
1067
1068
  }
1068
1069
  }
1069
- function getTranslationNs({ key, ns, }) {
1070
- const namespaces = ns !== undefined ? getFallbackArray(ns) : state.getFallbackNamespaces();
1070
+ function getTranslationNs({ key, ns }) {
1071
1071
  const languages = state.getFallbackLangs();
1072
+ const namespaces = getDefaultAndFallbackNs(ns);
1072
1073
  return cache.getTranslationNs(namespaces, languages, key);
1073
1074
  }
1074
- function getTranslation({ key, ns, }) {
1075
- const namespaces = ns !== undefined ? getFallbackArray(ns) : state.getFallbackNamespaces();
1075
+ function getTranslation({ key, ns }) {
1076
+ const namespaces = getDefaultAndFallbackNs(ns);
1076
1077
  const languages = state.getFallbackLangs();
1077
1078
  return cache.getTranslationFallback(namespaces, languages, key);
1078
1079
  }
@@ -1155,7 +1156,7 @@ const Controller = ({ options }) => {
1155
1156
  }
1156
1157
  const t = (...args) => {
1157
1158
  // @ts-ignore
1158
- const params = getTranslateParams(...args);
1159
+ const params = getTranslateProps(...args);
1159
1160
  const translation = getTranslation(params);
1160
1161
  return pluginService.formatTranslation(Object.assign(Object.assign({}, params), { translation }));
1161
1162
  };
@@ -1164,7 +1165,6 @@ const Controller = ({ options }) => {
1164
1165
  getTranslation,
1165
1166
  changeTranslation,
1166
1167
  addActiveNs,
1167
- loadRequiredRecords,
1168
1168
  loadRecords,
1169
1169
  loadRecord,
1170
1170
  isLoading,
@@ -1175,23 +1175,11 @@ const Controller = ({ options }) => {
1175
1175
  stop }));
1176
1176
  };
1177
1177
 
1178
- const Tolgee = (options) => {
1178
+ const createTolgee = (options) => {
1179
1179
  const controller = Controller({
1180
1180
  options,
1181
1181
  });
1182
- const pluginTools = Object.freeze({
1183
- setFinalFormatter: controller.setFinalFormatter,
1184
- addFormatter: controller.addFormatter,
1185
- setObserver: controller.setObserver,
1186
- hasObserver: controller.hasObserver,
1187
- setUi: controller.setUi,
1188
- hasUi: controller.hasUi,
1189
- setDevBackend: controller.setDevBackend,
1190
- addBackend: controller.addBackend,
1191
- setLanguageDetector: controller.setLanguageDetector,
1192
- setLanguageStorage: controller.setLanguageStorage,
1193
- overrideCredentials: controller.overrideCredentials,
1194
- });
1182
+ // restarts tolgee while applying callback
1195
1183
  const withRestart = (callback) => {
1196
1184
  const wasRunning = controller.isRunning();
1197
1185
  wasRunning && controller.stop();
@@ -1199,67 +1187,353 @@ const Tolgee = (options) => {
1199
1187
  wasRunning && controller.run();
1200
1188
  };
1201
1189
  const tolgee = Object.freeze({
1202
- // event listeners
1190
+ /**
1191
+ * Listen to tolgee events.
1192
+ */
1203
1193
  on: controller.on,
1204
- onKeyUpdate: controller.onKeyUpdate.listenSome,
1205
- // state
1194
+ /**
1195
+ * Listen for specific namespaces changes.
1196
+ *
1197
+ * ```
1198
+ * const sub = tolgee.onUpdate(handler)
1199
+ *
1200
+ * // subscribe to selected namespace
1201
+ * sub.subscribeNs(['common'])
1202
+ *
1203
+ * // unsubscribe
1204
+ * sub.unsubscribe()
1205
+ * ```
1206
+ */
1207
+ onNsUpdate: controller.onUpdate.listenSome,
1208
+ /**
1209
+ * @return current language if set.
1210
+ */
1206
1211
  getLanguage: controller.getLanguage,
1212
+ /**
1213
+ * `pendingLanguage` represents language which is currently being loaded.
1214
+ * @return current `pendingLanguage` if set.
1215
+ */
1207
1216
  getPendingLanguage: controller.getPendingLanguage,
1217
+ /**
1218
+ * Change current language.
1219
+ * - if not running sets `pendingLanguage`, `language` to the new value
1220
+ * - if running sets `pendingLanguage` to the value, fetches necessary data and then changes `language`
1221
+ *
1222
+ * @return Promise which is resolved when `language` is changed.
1223
+ */
1208
1224
  changeLanguage: controller.changeLanguage,
1225
+ /**
1226
+ * Temporarily change translation in cache.
1227
+ * @return object with revert method.
1228
+ */
1209
1229
  changeTranslation: controller.changeTranslation,
1230
+ /**
1231
+ * Adds namespace(s) list of active namespaces. And if tolgee is running, loads required data.
1232
+ */
1210
1233
  addActiveNs: controller.addActiveNs,
1234
+ /**
1235
+ * Remove namespace(s) from active namespaces.
1236
+ *
1237
+ * Tolgee internally counts how many times was each active namespace added,
1238
+ * so this method will remove namespace only if the counter goes down to 0.
1239
+ */
1211
1240
  removeActiveNs: controller.removeActiveNs,
1241
+ /**
1242
+ * Manually load multiple records from `Backend` (or `DevBackend` when in dev mode)
1243
+ *
1244
+ * It loads data together and adds them to cache in one operation, to prevent partly loaded state.
1245
+ */
1212
1246
  loadRecords: controller.loadRecords,
1247
+ /**
1248
+ * Manually load record from `Backend` (or `DevBackend` when in dev mode)
1249
+ */
1213
1250
  loadRecord: controller.loadRecord,
1251
+ /**
1252
+ *
1253
+ */
1214
1254
  addStaticData: controller.addStaticData,
1255
+ /**
1256
+ * Get record from cache.
1257
+ */
1215
1258
  getRecord: controller.getRecord,
1259
+ /**
1260
+ * Get all records from cache.
1261
+ */
1216
1262
  getAllRecords: controller.getAllRecords,
1263
+ /**
1264
+ * @param ns optional list of namespaces that you are interested in
1265
+ * @return `true` if there are data that need to be fetched.
1266
+ */
1217
1267
  isLoaded: controller.isLoaded,
1268
+ /**
1269
+ * @return `true` if tolgee is loading initial data (triggered by `run`).
1270
+ */
1218
1271
  isInitialLoading: controller.isInitialLoading,
1272
+ /**
1273
+ * @param ns optional list of namespaces that you are interested in
1274
+ * @return `true` if tolgee is loading some translations for the first time.
1275
+ */
1219
1276
  isLoading: controller.isLoading,
1277
+ /**
1278
+ * @param ns optional list of namespaces that you are interested in
1279
+ * @return `true` if tolgee is fetching some translations.
1280
+ */
1220
1281
  isFetching: controller.isFetching,
1282
+ /**
1283
+ * @return `true` if tolgee is running.
1284
+ */
1221
1285
  isRunning: controller.isRunning,
1286
+ /**
1287
+ * Changes internal state to running: true and loads initial files.
1288
+ * Runs runnable plugins mainly Observer if present.
1289
+ */
1222
1290
  run: controller.run,
1291
+ /**
1292
+ * Changes internal state to running: false and stops runnable plugins.
1293
+ */
1223
1294
  stop: controller.stop,
1295
+ /**
1296
+ * Returns translated and formatted key.
1297
+ * If Observer is present and tolgee is running, wraps result to be identifiable in the DOM.
1298
+ */
1224
1299
  t: controller.t,
1300
+ /**
1301
+ * Highlight keys that match selection.
1302
+ */
1225
1303
  highlight: controller.highlight,
1304
+ /**
1305
+ * @return current Tolgee options.
1306
+ */
1226
1307
  getInitialOptions: controller.getInitialOptions,
1308
+ /**
1309
+ * Tolgee is in dev mode if `DevTools` plugin is used and `apiKey` + `apiUrl` are specified.
1310
+ * @return `true` if tolgee is in dev mode.
1311
+ */
1227
1312
  isDev: controller.isDev,
1313
+ /**
1314
+ * Wraps translation if there is `Observer` plugin
1315
+ */
1228
1316
  wrap: controller.wrap,
1317
+ /**
1318
+ * Unwrap translation
1319
+ */
1229
1320
  unwrap: controller.unwrap,
1230
- // plugins
1231
- setObserverOptions: (options) => {
1232
- controller.setObserverOptions(options);
1233
- return tolgee;
1321
+ /**
1322
+ * Override creadentials passed on initialization
1323
+ */
1324
+ overrideCredentials(credentials) {
1325
+ withRestart(() => controller.overrideCredentials(credentials));
1234
1326
  },
1235
- use: (plugin) => {
1327
+ /**
1328
+ * Add tolgee plugin.
1329
+ */
1330
+ addPlugin(plugin) {
1236
1331
  if (plugin) {
1237
- withRestart(() => plugin(tolgee, pluginTools));
1332
+ withRestart(() => controller.addPlugin(tolgee, plugin));
1238
1333
  }
1239
- return tolgee;
1240
1334
  },
1241
- init: (options) => {
1242
- withRestart(() => controller.init(options));
1243
- return tolgee;
1335
+ /**
1336
+ * Updates options after instance creation. Extends existing options,
1337
+ * so it only changes the fields, that are listed.
1338
+ *
1339
+ * When called in running state, tolgee stops and runs again.
1340
+ */
1341
+ updateOptions(options) {
1342
+ if (options) {
1343
+ withRestart(() => controller.init(options));
1344
+ }
1244
1345
  },
1245
1346
  });
1246
1347
  return tolgee;
1247
1348
  };
1349
+ /**
1350
+ * Tolgee chainable constructor.
1351
+ *
1352
+ * Usage:
1353
+ * ```
1354
+ * const tolgee = Tolgee().use(...).init(...)
1355
+ * ```
1356
+ */
1357
+ const Tolgee = () => {
1358
+ const state = {
1359
+ plugins: [],
1360
+ options: {},
1361
+ };
1362
+ const tolgeeChain = Object.freeze({
1363
+ use(plugin) {
1364
+ state.plugins.push(plugin);
1365
+ return tolgeeChain;
1366
+ },
1367
+ updateDefaults(options) {
1368
+ state.options = combineOptions(state.options, options);
1369
+ return tolgeeChain;
1370
+ },
1371
+ init(options) {
1372
+ const tolgee = createTolgee(combineOptions(state.options, options));
1373
+ state.plugins.forEach(tolgee.addPlugin);
1374
+ return tolgee;
1375
+ },
1376
+ });
1377
+ return tolgeeChain;
1378
+ };
1379
+
1380
+ const ERROR_PARAM_EMPTY = 0, ERROR_UNEXPECTED_CHAR = 1, ERROR_UNEXPECTED_END = 2;
1381
+ class FormatError extends Error {
1382
+ constructor(code, index, text) {
1383
+ let error;
1384
+ if (code === ERROR_PARAM_EMPTY) {
1385
+ error = 'Empty parameter';
1386
+ }
1387
+ else if (code === ERROR_UNEXPECTED_CHAR) {
1388
+ error = 'Unexpected character';
1389
+ }
1390
+ else {
1391
+ error = 'Unexpected end';
1392
+ }
1393
+ super(`Tolgee parser: ${error} at ${index} in "${text}"`);
1394
+ this.code = code;
1395
+ this.index = index;
1396
+ }
1397
+ }
1398
+
1399
+ function isWhitespace(ch) {
1400
+ return /\s/.test(ch);
1401
+ }
1402
+ const STATE_TEXT = 0, STATE_ESCAPE_MAYBE = 1, STATE_ESCAPE = 2, STATE_PARAM = 3, STATE_PARAM_AFTER = 4;
1403
+ const END_STATES = new Set([
1404
+ STATE_ESCAPE,
1405
+ STATE_ESCAPE_MAYBE,
1406
+ STATE_TEXT,
1407
+ ]);
1408
+ const CHAR_ESCAPE = "'";
1409
+ const ESCAPABLE = new Set(['{', '}', CHAR_ESCAPE]);
1410
+ const isAllowedInParam = (char) => {
1411
+ return /[0-9a-zA-Z_]/.test(char);
1412
+ };
1413
+ function formatParser(translation) {
1414
+ let state = STATE_TEXT;
1415
+ let text = '';
1416
+ let param = '';
1417
+ let ch = '';
1418
+ const texts = [];
1419
+ const params = [];
1420
+ let i = 0;
1421
+ function parsingError(code) {
1422
+ throw new FormatError(code, i, translation);
1423
+ }
1424
+ const addText = () => {
1425
+ texts.push(text);
1426
+ text = '';
1427
+ };
1428
+ const addParamChar = () => {
1429
+ if (!isAllowedInParam(ch)) {
1430
+ parsingError(ERROR_UNEXPECTED_CHAR);
1431
+ }
1432
+ param += ch;
1433
+ };
1434
+ const addParam = () => {
1435
+ if (param === '') {
1436
+ parsingError(ERROR_PARAM_EMPTY);
1437
+ }
1438
+ params.push(param);
1439
+ param = '';
1440
+ };
1441
+ for (i = 0; i < translation.length; i++) {
1442
+ ch = translation[i];
1443
+ switch (state) {
1444
+ case STATE_TEXT:
1445
+ if (ch === CHAR_ESCAPE) {
1446
+ text += ch;
1447
+ state = STATE_ESCAPE_MAYBE;
1448
+ }
1449
+ else if (ch === '{') {
1450
+ addText();
1451
+ state = STATE_PARAM;
1452
+ }
1453
+ else {
1454
+ text += ch;
1455
+ state = STATE_TEXT;
1456
+ }
1457
+ break;
1458
+ case STATE_ESCAPE_MAYBE:
1459
+ if (ESCAPABLE.has(ch)) {
1460
+ text = text.slice(0, -1) + ch;
1461
+ state = STATE_ESCAPE;
1462
+ }
1463
+ else {
1464
+ text += ch;
1465
+ state = STATE_TEXT;
1466
+ }
1467
+ break;
1468
+ case STATE_ESCAPE:
1469
+ if (ch === CHAR_ESCAPE) {
1470
+ state = STATE_TEXT;
1471
+ }
1472
+ else {
1473
+ text += ch;
1474
+ state = STATE_ESCAPE;
1475
+ }
1476
+ break;
1477
+ case STATE_PARAM:
1478
+ if (ch === '}') {
1479
+ addParam();
1480
+ state = STATE_TEXT;
1481
+ }
1482
+ else if (!isWhitespace(ch)) {
1483
+ addParamChar();
1484
+ state = STATE_PARAM;
1485
+ }
1486
+ else if (param !== '') {
1487
+ addParam();
1488
+ state = STATE_PARAM_AFTER;
1489
+ }
1490
+ break;
1491
+ case STATE_PARAM_AFTER:
1492
+ if (ch == '}') {
1493
+ state = STATE_TEXT;
1494
+ }
1495
+ else if (isWhitespace(ch)) {
1496
+ state = STATE_PARAM_AFTER;
1497
+ }
1498
+ else {
1499
+ parsingError(ERROR_UNEXPECTED_CHAR);
1500
+ }
1501
+ }
1502
+ }
1503
+ if (!END_STATES.has(state)) {
1504
+ parsingError(ERROR_UNEXPECTED_END);
1505
+ }
1506
+ addText();
1507
+ return [texts, params];
1508
+ }
1248
1509
 
1249
- const RESTRICTED_ASCENDANT_ATTRIBUTE = 'data-tolgee-restricted';
1250
- const TOLGEE_ATTRIBUTE_NAME = '_tolgee';
1251
- const TOLGEE_HIGHLIGHTER_CLASS = '_tolgee-highlighter';
1252
- const TOLGEE_WRAPPED_ONLY_DATA_ATTRIBUTE = 'data-tolgee-key-only';
1253
- // needs to be same as in @tolgee/ui package
1254
- const DEVTOOLS_ID = '__tolgee_dev_tools';
1510
+ function formatter(translation, params) {
1511
+ const [texts, pars] = formatParser(translation);
1512
+ const result = [texts[0]];
1513
+ for (let i = 1; i < texts.length; i++) {
1514
+ const parameter = params === null || params === void 0 ? void 0 : params[pars[i - 1]];
1515
+ if (parameter === undefined) {
1516
+ throw new Error(`Missing parameter "${pars[i - 1]}" in "${translation}"`);
1517
+ }
1518
+ result.push(String(parameter));
1519
+ result.push(texts[i]);
1520
+ }
1521
+ return result.join('');
1522
+ }
1523
+
1524
+ function createFormatSimple() {
1525
+ return {
1526
+ format: ({ translation, params }) => formatter(translation, params),
1527
+ };
1528
+ }
1529
+ const FormatSimple = () => (tolgee, tools) => {
1530
+ tools.setFinalFormatter(createFormatSimple());
1531
+ return tolgee;
1532
+ };
1255
1533
 
1256
- exports.DEVTOOLS_ID = DEVTOOLS_ID;
1257
- exports.RESTRICTED_ASCENDANT_ATTRIBUTE = RESTRICTED_ASCENDANT_ATTRIBUTE;
1258
- exports.TOLGEE_ATTRIBUTE_NAME = TOLGEE_ATTRIBUTE_NAME;
1259
- exports.TOLGEE_HIGHLIGHTER_CLASS = TOLGEE_HIGHLIGHTER_CLASS;
1260
- exports.TOLGEE_WRAPPED_ONLY_DATA_ATTRIBUTE = TOLGEE_WRAPPED_ONLY_DATA_ATTRIBUTE;
1534
+ exports.FormatSimple = FormatSimple;
1261
1535
  exports.Tolgee = Tolgee;
1262
1536
  exports.getFallback = getFallback;
1263
1537
  exports.getFallbackArray = getFallbackArray;
1264
- exports.getTranslateParams = getTranslateParams;
1538
+ exports.getTranslateProps = getTranslateProps;
1265
1539
  //# sourceMappingURL=tolgee.cjs.js.map