@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
@@ -0,0 +1,132 @@
1
+ import { getFallbackArray } from '../../helpers';
2
+ import {
3
+ NsFallback,
4
+ Subscription,
5
+ Listener,
6
+ ListenerEvent,
7
+ SubscriptionSelective,
8
+ NsType,
9
+ } from '../../types';
10
+
11
+ type NsListType = string;
12
+
13
+ type HandlerWrapperType = {
14
+ fn: Listener<undefined>;
15
+ namespaces: Set<string>;
16
+ };
17
+
18
+ export const EventEmitterSelective = (
19
+ isActive: () => boolean,
20
+ getFallbackNs: () => string[],
21
+ getDefaultNs: () => string
22
+ ): EventEmitterSelectiveInstance => {
23
+ const listeners: Set<Listener<undefined>> = new Set();
24
+ const partialListeners: Set<HandlerWrapperType> = new Set();
25
+
26
+ const listen = (handler: Listener<undefined>) => {
27
+ listeners.add(handler);
28
+ const result = {
29
+ unsubscribe: () => {
30
+ listeners.delete(handler);
31
+ },
32
+ };
33
+ return result;
34
+ };
35
+
36
+ const listenSome = (handler: Listener<undefined>) => {
37
+ const handlerWrapper = {
38
+ fn: (e: ListenerEvent<undefined>) => {
39
+ handler(e);
40
+ },
41
+ namespaces: new Set<NsListType>(),
42
+ };
43
+
44
+ partialListeners.add(handlerWrapper);
45
+
46
+ const result = {
47
+ unsubscribe: () => {
48
+ partialListeners.delete(handlerWrapper);
49
+ },
50
+ subscribeNs: (ns: NsFallback) => {
51
+ getFallbackArray(ns).forEach((val) =>
52
+ handlerWrapper.namespaces.add(val)
53
+ );
54
+ if (ns === undefined) {
55
+ // subscribing to default ns
56
+ handlerWrapper.namespaces.add(getDefaultNs());
57
+ }
58
+ return result;
59
+ },
60
+ };
61
+
62
+ return result;
63
+ };
64
+
65
+ const callHandlers = (ns: Array<string> | undefined) => {
66
+ // everything is implicitly subscribed to fallbacks
67
+ // as it can always fall through to it
68
+ const fallbackNamespaces = new Set(getFallbackNs());
69
+
70
+ partialListeners.forEach((handler) => {
71
+ const nsMatches =
72
+ ns === undefined ||
73
+ ns?.findIndex(
74
+ (ns) => fallbackNamespaces.has(ns) || handler.namespaces.has(ns!)
75
+ ) !== -1;
76
+
77
+ if (nsMatches) {
78
+ handler.fn({ value: undefined as any });
79
+ }
80
+ });
81
+ };
82
+
83
+ let queue: (string[] | undefined)[] = [];
84
+ // merge events in queue into one event
85
+ const solveQueue = () => {
86
+ if (queue.length === 0) {
87
+ return;
88
+ }
89
+ const queueCopy = queue;
90
+ queue = [];
91
+
92
+ listeners.forEach((handler) => {
93
+ handler({ value: undefined as any });
94
+ });
95
+
96
+ let namespaces: Set<NsType> | undefined = new Set<NsType>();
97
+
98
+ queueCopy.forEach((ns) => {
99
+ if (ns === undefined) {
100
+ // when no ns specified, it affects all namespaces
101
+ namespaces = undefined;
102
+ } else if (namespaces !== undefined) {
103
+ ns.forEach((ns) => namespaces!.add(ns));
104
+ }
105
+ });
106
+
107
+ const namespacesArray = namespaces
108
+ ? Array.from(namespaces.keys())
109
+ : undefined;
110
+
111
+ callHandlers(namespacesArray);
112
+ };
113
+
114
+ const emit = (ns?: string[], delayed?: boolean) => {
115
+ if (isActive()) {
116
+ queue.push(ns);
117
+ if (!delayed) {
118
+ solveQueue();
119
+ } else {
120
+ setTimeout(solveQueue, 0);
121
+ }
122
+ }
123
+ };
124
+
125
+ return Object.freeze({ listenSome, listen, emit });
126
+ };
127
+
128
+ export type EventEmitterSelectiveInstance = {
129
+ readonly listenSome: (handler: Listener<undefined>) => SubscriptionSelective;
130
+ readonly listen: (handler: Listener<undefined>) => Subscription;
131
+ readonly emit: (ns?: string[], delayed?: boolean) => void;
132
+ };
@@ -0,0 +1,69 @@
1
+ import { EventEmitter } from './EventEmitter';
2
+ import { EventEmitterSelective } from './EventEmitterSelective';
3
+ import { CacheDescriptorWithKey, TolgeeOn } from '../../types';
4
+
5
+ export const Events = (
6
+ getFallbackNs: () => string[],
7
+ getDefaultNs: () => string
8
+ ) => {
9
+ let emitterActive = true;
10
+
11
+ function isActive() {
12
+ return emitterActive;
13
+ }
14
+
15
+ const onPendingLanguageChange = EventEmitter<string>(isActive);
16
+ const onLanguageChange = EventEmitter<string>(isActive);
17
+ const onLoadingChange = EventEmitter<boolean>(isActive);
18
+ const onFetchingChange = EventEmitter<boolean>(isActive);
19
+ const onInitialLoaded = EventEmitter<void>(isActive);
20
+ const onRunningChange = EventEmitter<boolean>(isActive);
21
+ const onCacheChange = EventEmitter<CacheDescriptorWithKey>(isActive);
22
+ const onUpdate = EventEmitterSelective(isActive, getFallbackNs, getDefaultNs);
23
+
24
+ onInitialLoaded.listen(() => onUpdate.emit());
25
+ onLanguageChange.listen(() => onUpdate.emit());
26
+ onCacheChange.listen(({ value }) => {
27
+ onUpdate.emit([value.namespace], true);
28
+ });
29
+
30
+ const on: TolgeeOn = (event, handler): any => {
31
+ switch (event) {
32
+ case 'pendingLanguage':
33
+ return onPendingLanguageChange.listen(handler as any);
34
+ case 'language':
35
+ return onLanguageChange.listen(handler as any);
36
+ case 'loading':
37
+ return onLoadingChange.listen(handler as any);
38
+ case 'fetching':
39
+ return onFetchingChange.listen(handler as any);
40
+ case 'initialLoad':
41
+ return onInitialLoaded.listen(handler as any);
42
+ case 'running':
43
+ return onRunningChange.listen(handler as any);
44
+ case 'cache':
45
+ return onCacheChange.listen(handler as any);
46
+ case 'update':
47
+ return onUpdate.listen(handler as any);
48
+ }
49
+ };
50
+
51
+ function setEmmiterActive(active: boolean) {
52
+ emitterActive = active;
53
+ }
54
+
55
+ return Object.freeze({
56
+ onPendingLanguageChange,
57
+ onLanguageChange,
58
+ onLoadingChange,
59
+ onFetchingChange,
60
+ onInitialLoaded,
61
+ onRunningChange,
62
+ onCacheChange,
63
+ onUpdate,
64
+ setEmmiterActive,
65
+ on,
66
+ });
67
+ };
68
+
69
+ export type EventsInstance = ReturnType<typeof Events>;
@@ -1,86 +1,68 @@
1
- import { isPromise, missingOptionError, valueOrPromise } from '../../helpers';
1
+ import { getErrorMessage, isPromise, valueOrPromise } from '../../helpers';
2
2
  import {
3
- BackendDevInterface,
3
+ BackendDevMiddleware,
4
4
  BackendGetRecord,
5
- BackendInterface,
6
- FormatterInterface,
7
- ObserverInterface,
5
+ BackendMiddleware,
6
+ FormatterMiddleware,
7
+ ObserverMiddleware,
8
8
  TranslatePropsInternal,
9
9
  TranslationOnClick,
10
- UiInterface,
11
- UiLibInterface,
12
- UiType,
13
- FinalFormatterInterface,
10
+ UiMiddleware,
11
+ FinalFormatterMiddleware,
14
12
  HighlightInterface,
15
- UiConstructor,
16
13
  UiKeyOption,
17
- LanguageDetectorInterface,
18
- LanguageStorageInterface,
19
- Options,
14
+ LanguageDetectorMiddleware,
15
+ LanguageStorageMiddleware,
20
16
  ChangeTranslationInterface,
21
17
  WrapperWrapProps,
22
18
  Unwrapped,
19
+ KeyAndNamespacesInternal,
20
+ TolgeePlugin,
21
+ TolgeeInstance,
22
+ TolgeeOptionsInternal,
23
+ FormatErrorHandler,
23
24
  } from '../../types';
24
- import { getFallbackArray } from '../State/helpers';
25
+ import { DEFAULT_FORMAT_ERROR } from '../State/initState';
25
26
 
26
- export const PluginService = (
27
+ export const Plugins = (
27
28
  getLanguage: () => string | undefined,
28
- getInitialOptions: () => Options,
29
+ getInitialOptions: () => TolgeeOptionsInternal,
29
30
  getAvailableLanguages: () => string[] | undefined,
30
- getTranslationNs: (props: TranslatePropsInternal) => string[] | string,
31
- getTranslation: (props: TranslatePropsInternal) => string | undefined,
31
+ getTranslationNs: (props: KeyAndNamespacesInternal) => string[],
32
+ getTranslation: (props: KeyAndNamespacesInternal) => string | undefined,
32
33
  changeTranslation: ChangeTranslationInterface
33
34
  ) => {
34
35
  const plugins = {
35
- ui: undefined as UiConstructor | undefined,
36
+ ui: undefined as UiMiddleware | undefined,
37
+ observer: undefined as ObserverMiddleware | undefined,
36
38
  };
37
39
 
38
40
  const instances = {
39
- formatters: [] as FormatterInterface[],
40
- finalFormatter: undefined as FinalFormatterInterface | undefined,
41
- observer: undefined as ReturnType<ObserverInterface> | undefined,
42
- devBackend: undefined as BackendDevInterface | undefined,
43
- backends: [] as BackendInterface[],
44
- ui: undefined as UiInterface | undefined,
45
- languageDetector: undefined as LanguageDetectorInterface | undefined,
46
- languageStorage: undefined as LanguageStorageInterface | undefined,
41
+ formatters: [] as FormatterMiddleware[],
42
+ finalFormatter: undefined as FinalFormatterMiddleware | undefined,
43
+ observer: undefined as ReturnType<ObserverMiddleware> | undefined,
44
+ devBackend: undefined as BackendDevMiddleware | undefined,
45
+ backends: [] as BackendMiddleware[],
46
+ ui: undefined as ReturnType<UiMiddleware> | undefined,
47
+ languageDetector: undefined as LanguageDetectorMiddleware | undefined,
48
+ languageStorage: undefined as LanguageStorageMiddleware | undefined,
47
49
  };
48
50
 
49
- const onClick: TranslationOnClick = async (event, { keysAndDefaults }) => {
51
+ const onClick: TranslationOnClick = async ({ keysAndDefaults, event }) => {
50
52
  const withNs: UiKeyOption[] = keysAndDefaults.map(
51
- ({ key, ns, defaultValue }) => ({
52
- key,
53
- defaultValue,
54
- ns: getFallbackArray(getTranslationNs({ key, ns, defaultValue })),
55
- translation: getTranslation({
53
+ ({ key, ns, defaultValue }) => {
54
+ return {
56
55
  key,
57
- ns,
58
- }),
59
- })
60
- );
61
- instances.ui?.handleElementClick(event, withNs);
62
- };
63
-
64
- const run = () => {
65
- instances.ui =
66
- plugins.ui &&
67
- new plugins.ui({
68
- apiKey: getInitialOptions().apiKey!,
69
- apiUrl: getInitialOptions().apiUrl!,
70
- highlight,
71
- changeTranslation,
72
- });
73
- instances.observer?.run({ mouseHighlight: Boolean(instances.ui) });
74
- checkCorrectConfiguration();
75
- };
76
-
77
- const checkCorrectConfiguration = () => {
78
- if (instances.languageDetector) {
79
- const availableLanguages = getAvailableLanguages();
80
- if (!availableLanguages) {
81
- throw new Error(missingOptionError('availableLanguages'));
56
+ defaultValue,
57
+ ns: getTranslationNs({ key, ns }),
58
+ translation: getTranslation({
59
+ key,
60
+ ns,
61
+ }),
62
+ };
82
63
  }
83
- }
64
+ );
65
+ instances.ui?.handleElementClick(withNs, event);
84
66
  };
85
67
 
86
68
  const stop = () => {
@@ -93,54 +75,65 @@ export const PluginService = (
93
75
  };
94
76
 
95
77
  const translate = (props: TranslatePropsInternal) => {
96
- const translation = getTranslation(props);
78
+ const translation = getTranslation({
79
+ key: props.key,
80
+ ns: props.ns,
81
+ });
97
82
  return formatTranslation({ ...props, translation, formatEnabled: true });
98
83
  };
99
84
 
100
- const setObserver = (observer: ObserverInterface | undefined) => {
101
- instances.observer = observer?.({ translate, onClick });
85
+ const setObserver = (observer: ObserverMiddleware | undefined) => {
86
+ plugins.observer = observer;
102
87
  };
103
88
 
104
- const getObserver = () => {
105
- return instances.observer;
89
+ const hasObserver = () => {
90
+ return Boolean(plugins.observer);
106
91
  };
107
92
 
108
- const addFormatter = (formatter: FormatterInterface | undefined) => {
93
+ const addFormatter = (formatter: FormatterMiddleware | undefined) => {
109
94
  if (formatter) {
110
95
  instances.formatters.push(formatter);
111
96
  }
112
97
  };
113
98
 
114
99
  const setFinalFormatter = (
115
- formatter: FinalFormatterInterface | undefined
100
+ formatter: FinalFormatterMiddleware | undefined
116
101
  ) => {
117
102
  instances.finalFormatter = formatter;
118
103
  };
119
104
 
120
- const setUi = (ui: UiType | undefined) => {
121
- plugins.ui = (ui as UiLibInterface)?.UI || ui;
105
+ const setUi = (ui: UiMiddleware | undefined) => {
106
+ plugins.ui = ui as UiMiddleware;
122
107
  };
123
108
 
124
- const getUi = () => {
125
- return plugins.ui;
109
+ const hasUi = () => {
110
+ return Boolean(plugins.ui);
126
111
  };
127
112
 
128
113
  const setLanguageStorage = (
129
- storage: LanguageStorageInterface | undefined
114
+ storage: LanguageStorageMiddleware | undefined
130
115
  ) => {
131
116
  instances.languageStorage = storage;
132
117
  };
133
118
 
119
+ const getLanguageStorage = () => {
120
+ return instances.languageStorage;
121
+ };
122
+
134
123
  const setStoredLanguage = (language: string) => {
135
124
  instances.languageStorage?.setLanguage(language);
136
125
  };
137
126
 
138
127
  const setLanguageDetector = (
139
- detector: LanguageDetectorInterface | undefined
128
+ detector: LanguageDetectorMiddleware | undefined
140
129
  ) => {
141
130
  instances.languageDetector = detector;
142
131
  };
143
132
 
133
+ const getLanguageDetector = () => {
134
+ return instances.languageDetector;
135
+ };
136
+
144
137
  const detectLanguage = () => {
145
138
  if (!instances.languageDetector) {
146
139
  return undefined;
@@ -168,24 +161,47 @@ export const PluginService = (
168
161
  });
169
162
  };
170
163
 
171
- const addBackend = (backend: BackendInterface | undefined) => {
164
+ const addBackend = (backend: BackendMiddleware | undefined) => {
172
165
  if (backend) {
173
166
  instances.backends.push(backend);
174
167
  }
175
168
  };
176
169
 
177
- const setDevBackend = (backend: BackendDevInterface | undefined) => {
170
+ const setDevBackend = (backend: BackendDevMiddleware | undefined) => {
178
171
  instances.devBackend = backend;
179
172
  };
180
173
 
174
+ const run = () => {
175
+ if (!instances.ui) {
176
+ const { apiKey, apiUrl, projectId } = getInitialOptions();
177
+ instances.ui = plugins.ui?.({
178
+ apiKey: apiKey!,
179
+ apiUrl: apiUrl!,
180
+ projectId,
181
+ highlight,
182
+ changeTranslation,
183
+ });
184
+ }
185
+ if (!instances.observer) {
186
+ instances.observer = plugins.observer?.({
187
+ translate,
188
+ onClick,
189
+ options: getInitialOptions().observerOptions,
190
+ });
191
+ }
192
+ instances.observer?.run({ mouseHighlight: true });
193
+ };
194
+
181
195
  const getDevBackend = () => {
182
196
  return instances.devBackend;
183
197
  };
184
198
 
185
199
  const getBackendDevRecord: BackendGetRecord = ({ language, namespace }) => {
200
+ const { apiKey, apiUrl, projectId } = getInitialOptions();
186
201
  return instances.devBackend?.getRecord({
187
- apiKey: getInitialOptions().apiKey,
188
- apiUrl: getInitialOptions().apiUrl,
202
+ apiKey,
203
+ apiUrl,
204
+ projectId,
189
205
  language,
190
206
  namespace,
191
207
  });
@@ -208,55 +224,105 @@ export const PluginService = (
208
224
  return undefined;
209
225
  };
210
226
 
211
- const formatTranslation = ({
212
- key,
213
- translation,
214
- defaultValue,
215
- noWrap,
216
- params,
217
- orEmpty,
218
- ns,
227
+ const unwrap = (text: string): Unwrapped => {
228
+ if (instances.observer) {
229
+ return instances.observer?.unwrap(text);
230
+ }
231
+ return { text, keys: [] };
232
+ };
233
+
234
+ const retranslate = () => {
235
+ instances.observer?.retranslate();
236
+ };
237
+
238
+ function addPlugin(tolgeeInstance: TolgeeInstance, plugin: TolgeePlugin) {
239
+ const pluginTools = Object.freeze({
240
+ setFinalFormatter,
241
+ addFormatter,
242
+ setObserver,
243
+ hasObserver,
244
+ setUi,
245
+ hasUi,
246
+ setDevBackend,
247
+ addBackend,
248
+ setLanguageDetector,
249
+ setLanguageStorage,
250
+ });
251
+ plugin(tolgeeInstance, pluginTools);
252
+ }
253
+
254
+ function formatTranslation({
219
255
  formatEnabled,
220
- }: TranslatePropsInternal & { formatEnabled?: boolean }) => {
256
+ ...props
257
+ }: TranslatePropsInternal & { formatEnabled?: boolean }) {
258
+ const { key, translation, defaultValue, noWrap, params, orEmpty, ns } =
259
+ props;
221
260
  const formattableTranslation = translation || defaultValue;
222
261
  let result = formattableTranslation || (orEmpty ? '' : key);
223
- if (instances.observer && !noWrap) {
224
- result = instances.observer.wrap({
225
- key,
226
- translation: result,
227
- defaultValue,
228
- params,
229
- ns,
230
- });
231
- }
232
262
 
233
263
  const language = getLanguage();
234
264
  const isFormatEnabled =
235
265
  formatEnabled || !instances.observer?.outputNotFormattable;
236
- if (formattableTranslation && language && isFormatEnabled) {
237
- for (const formatter of instances.formatters) {
238
- result = formatter.format({
266
+
267
+ const wrap = (result: string) => {
268
+ if (instances.observer && !noWrap) {
269
+ return instances.observer.wrap({
270
+ key,
271
+ translation: result,
272
+ defaultValue,
273
+ params,
274
+ ns,
275
+ });
276
+ }
277
+ return result;
278
+ };
279
+
280
+ result = wrap(result);
281
+ try {
282
+ if (formattableTranslation && language && isFormatEnabled) {
283
+ for (const formatter of instances.formatters) {
284
+ result = formatter.format({
285
+ translation: result,
286
+ language,
287
+ params,
288
+ });
289
+ }
290
+ }
291
+ if (
292
+ instances.finalFormatter &&
293
+ formattableTranslation &&
294
+ language &&
295
+ isFormatEnabled
296
+ ) {
297
+ result = instances.finalFormatter.format({
239
298
  translation: result,
240
299
  language,
241
300
  params,
242
301
  });
243
302
  }
303
+ } catch (e: any) {
304
+ // eslint-disable-next-line no-console
305
+ console.error(e);
306
+ const errorMessage = getErrorMessage(e) || DEFAULT_FORMAT_ERROR;
307
+ const onFormatError = getInitialOptions().onFormatError;
308
+ const formatErrorType = typeof onFormatError;
309
+ if (formatErrorType === 'string') {
310
+ result = onFormatError as string;
311
+ } else if (formatErrorType === 'function') {
312
+ result = (onFormatError as FormatErrorHandler)(errorMessage, props);
313
+ } else {
314
+ result = DEFAULT_FORMAT_ERROR;
315
+ }
316
+ // wrap error message, so it's detectable
317
+ result = wrap(result);
244
318
  }
245
319
 
246
- if (
247
- instances.finalFormatter &&
248
- formattableTranslation &&
249
- language &&
250
- isFormatEnabled
251
- ) {
252
- result = instances.finalFormatter.format({
253
- translation: result,
254
- language,
255
- params,
256
- });
257
- }
258
320
  return result;
259
- };
321
+ }
322
+
323
+ function hasDevBackend() {
324
+ return Boolean(getDevBackend());
325
+ }
260
326
 
261
327
  const wrap = (params: WrapperWrapProps) => {
262
328
  if (instances.observer) {
@@ -265,41 +331,24 @@ export const PluginService = (
265
331
  return params.translation;
266
332
  };
267
333
 
268
- const unwrap = (text: string): Unwrapped => {
269
- if (instances.observer) {
270
- return instances.observer?.unwrap(text);
271
- }
272
- return { text, keys: [] };
273
- };
274
-
275
- const retranslate = () => {
276
- instances.observer?.retranslate();
277
- };
278
-
279
334
  return Object.freeze({
280
- setFinalFormatter,
281
- addFormatter,
335
+ addPlugin,
282
336
  formatTranslation,
283
- setObserver,
284
- getObserver,
285
- setUi,
286
- getUi,
287
- addBackend,
288
- setDevBackend,
289
337
  getDevBackend,
290
338
  getBackendRecord,
291
339
  getBackendDevRecord,
292
- setLanguageDetector,
293
- setLanguageStorage,
340
+ getLanguageDetector,
341
+ getLanguageStorage,
294
342
  getInitialLanguage,
295
343
  setStoredLanguage,
296
344
  run,
297
345
  stop,
298
346
  retranslate,
299
347
  highlight,
300
- wrap,
301
348
  unwrap,
349
+ wrap,
350
+ hasDevBackend,
302
351
  });
303
352
  };
304
353
 
305
- export type PluginServiceType = ReturnType<typeof PluginService>;
354
+ export type PluginsInstance = ReturnType<typeof Plugins>;