@tolgee/core 5.20.3 → 5.21.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.
- package/dist/tolgee.cjs.js +93 -38
- package/dist/tolgee.cjs.js.map +1 -1
- package/dist/tolgee.cjs.min.js +1 -1
- package/dist/tolgee.cjs.min.js.map +1 -1
- package/dist/tolgee.esm.js +91 -39
- package/dist/tolgee.esm.js.map +1 -1
- package/dist/tolgee.esm.min.js +1 -1
- package/dist/tolgee.esm.min.js.map +1 -1
- package/dist/tolgee.esm.min.mjs +1 -1
- package/dist/tolgee.esm.min.mjs.map +1 -1
- package/dist/tolgee.esm.mjs +91 -39
- package/dist/tolgee.esm.mjs.map +1 -1
- package/dist/tolgee.umd.js +93 -38
- package/dist/tolgee.umd.js.map +1 -1
- package/dist/tolgee.umd.min.js +1 -1
- package/dist/tolgee.umd.min.js.map +1 -1
- package/lib/Controller/Cache/Cache.d.ts +3 -3
- package/lib/Controller/Controller.d.ts +4 -3
- package/lib/Controller/Events/Events.d.ts +2 -1
- package/lib/Controller/Plugins/Plugins.d.ts +4 -3
- package/lib/helpers.d.ts +4 -2
- package/lib/types/errors.d.ts +20 -0
- package/lib/types/events.d.ts +6 -0
- package/lib/types/index.d.ts +1 -0
- package/package.json +2 -2
- package/src/Controller/Cache/Cache.ts +30 -18
- package/src/Controller/Controller.ts +4 -11
- package/src/Controller/Events/Events.ts +4 -0
- package/src/Controller/Plugins/Plugins.ts +33 -19
- package/src/__test/errors.detector.test.ts +64 -0
- package/src/__test/errors.record.test.ts +127 -0
- package/src/__test/errors.storage.test.ts +117 -0
- package/src/helpers.ts +29 -2
- package/src/types/errors.ts +40 -0
- package/src/types/events.ts +7 -0
- package/src/types/index.ts +1 -0
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
TFnType,
|
|
7
7
|
NsType,
|
|
8
8
|
KeyAndNamespacesInternal,
|
|
9
|
-
TranslationDescriptor,
|
|
10
9
|
} from '../types';
|
|
11
10
|
import { Cache } from './Cache/Cache';
|
|
12
11
|
import { getFallbackArray } from '../helpers';
|
|
@@ -47,11 +46,11 @@ export function Controller({ options }: StateServiceProps) {
|
|
|
47
46
|
getTranslationNs,
|
|
48
47
|
getTranslation,
|
|
49
48
|
changeTranslation,
|
|
50
|
-
|
|
49
|
+
events
|
|
51
50
|
);
|
|
52
51
|
|
|
53
52
|
const cache = Cache(
|
|
54
|
-
events
|
|
53
|
+
events,
|
|
55
54
|
pluginService.getBackendRecord,
|
|
56
55
|
pluginService.getBackendDevRecord,
|
|
57
56
|
state.withDefaultNs,
|
|
@@ -110,13 +109,6 @@ export function Controller({ options }: StateServiceProps) {
|
|
|
110
109
|
};
|
|
111
110
|
}
|
|
112
111
|
|
|
113
|
-
function onPermanentChange(props: TranslationDescriptor) {
|
|
114
|
-
events.onPermanentChange.emit({
|
|
115
|
-
key: props.key,
|
|
116
|
-
namespace: props.namespace,
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
|
|
120
112
|
function init(options: Partial<TolgeeOptions>) {
|
|
121
113
|
state.init(options);
|
|
122
114
|
cache.addStaticData(state.getInitialOptions().staticData);
|
|
@@ -182,6 +174,7 @@ export function Controller({ options }: StateServiceProps) {
|
|
|
182
174
|
return;
|
|
183
175
|
}
|
|
184
176
|
const languageOrPromise = pluginService.getInitialLanguage();
|
|
177
|
+
|
|
185
178
|
return valueOrPromise(languageOrPromise, (lang) => {
|
|
186
179
|
const language =
|
|
187
180
|
(lang as string | undefined) ||
|
|
@@ -231,7 +224,7 @@ export function Controller({ options }: StateServiceProps) {
|
|
|
231
224
|
// there might be parallel language change
|
|
232
225
|
// we only want to apply latest
|
|
233
226
|
state.setLanguage(language);
|
|
234
|
-
pluginService.setStoredLanguage(language);
|
|
227
|
+
await pluginService.setStoredLanguage(language);
|
|
235
228
|
}
|
|
236
229
|
},
|
|
237
230
|
|
|
@@ -2,6 +2,7 @@ import { EventEmitter } from './EventEmitter';
|
|
|
2
2
|
import { EventEmitterSelective } from './EventEmitterSelective';
|
|
3
3
|
import {
|
|
4
4
|
CacheDescriptorWithKey,
|
|
5
|
+
TolgeeError,
|
|
5
6
|
TolgeeOn,
|
|
6
7
|
TranslationDescriptor,
|
|
7
8
|
} from '../../types';
|
|
@@ -26,6 +27,7 @@ export function Events(
|
|
|
26
27
|
onCacheChange: EventEmitter<CacheDescriptorWithKey>(isActive),
|
|
27
28
|
onUpdate: EventEmitterSelective(isActive, getFallbackNs, getDefaultNs),
|
|
28
29
|
onPermanentChange: EventEmitter<TranslationDescriptor>(isActive),
|
|
30
|
+
onError: EventEmitter<TolgeeError>(isActive),
|
|
29
31
|
setEmitterActive(active: boolean) {
|
|
30
32
|
emitterActive = active;
|
|
31
33
|
},
|
|
@@ -49,6 +51,8 @@ export function Events(
|
|
|
49
51
|
return self.onUpdate.listen(handler as any);
|
|
50
52
|
case 'permanentChange':
|
|
51
53
|
return self.onPermanentChange.listen(handler as any);
|
|
54
|
+
case 'error':
|
|
55
|
+
return self.onError.listen(handler as any);
|
|
52
56
|
}
|
|
53
57
|
}) as TolgeeOn,
|
|
54
58
|
});
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
getErrorMessage,
|
|
3
|
+
valueOrPromise,
|
|
4
|
+
handleRegularOrAsyncErr,
|
|
5
|
+
} from '../../helpers';
|
|
2
6
|
import {
|
|
3
7
|
BackendDevMiddleware,
|
|
4
8
|
BackendMiddleware,
|
|
@@ -22,8 +26,10 @@ import {
|
|
|
22
26
|
FormatErrorHandler,
|
|
23
27
|
FindPositionsInterface,
|
|
24
28
|
BackendGetRecordInternal,
|
|
25
|
-
|
|
29
|
+
LanguageStorageError,
|
|
30
|
+
LanguageDetectorError,
|
|
26
31
|
} from '../../types';
|
|
32
|
+
import { EventsInstance } from '../Events/Events';
|
|
27
33
|
import { DEFAULT_FORMAT_ERROR } from '../State/initState';
|
|
28
34
|
|
|
29
35
|
export function Plugins(
|
|
@@ -34,7 +40,7 @@ export function Plugins(
|
|
|
34
40
|
getTranslationNs: (props: KeyAndNamespacesInternal) => string[],
|
|
35
41
|
getTranslation: (props: KeyAndNamespacesInternal) => string | undefined,
|
|
36
42
|
changeTranslation: ChangeTranslationInterface,
|
|
37
|
-
|
|
43
|
+
events: EventsInstance
|
|
38
44
|
) {
|
|
39
45
|
const plugins = {
|
|
40
46
|
ui: undefined as UiMiddleware | undefined,
|
|
@@ -125,6 +131,14 @@ export function Plugins(
|
|
|
125
131
|
instances.languageDetector = detector;
|
|
126
132
|
}
|
|
127
133
|
|
|
134
|
+
function storageLoadLanguage() {
|
|
135
|
+
return handleRegularOrAsyncErr(
|
|
136
|
+
events.onError,
|
|
137
|
+
(e) => new LanguageStorageError('Tolgee: Failed to load language', e),
|
|
138
|
+
() => instances.languageStorage?.getLanguage(getCommonProps())
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
128
142
|
function detectLanguage() {
|
|
129
143
|
if (!instances.languageDetector) {
|
|
130
144
|
return undefined;
|
|
@@ -132,10 +146,15 @@ export function Plugins(
|
|
|
132
146
|
|
|
133
147
|
const availableLanguages = getAvailableLanguages()!;
|
|
134
148
|
|
|
135
|
-
return
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
149
|
+
return handleRegularOrAsyncErr(
|
|
150
|
+
events.onError,
|
|
151
|
+
(e) => new LanguageDetectorError('Tolgee: Failed to detect language', e),
|
|
152
|
+
() =>
|
|
153
|
+
instances.languageDetector?.getLanguage({
|
|
154
|
+
availableLanguages,
|
|
155
|
+
...getCommonProps(),
|
|
156
|
+
})
|
|
157
|
+
);
|
|
139
158
|
}
|
|
140
159
|
|
|
141
160
|
function addBackend(backend: BackendMiddleware | undefined) {
|
|
@@ -177,7 +196,7 @@ export function Plugins(
|
|
|
177
196
|
highlight: self.highlight,
|
|
178
197
|
changeTranslation,
|
|
179
198
|
findPositions,
|
|
180
|
-
onPermanentChange,
|
|
199
|
+
onPermanentChange: (data) => events.onPermanentChange.emit(data),
|
|
181
200
|
});
|
|
182
201
|
|
|
183
202
|
instances.observer?.run({
|
|
@@ -199,9 +218,7 @@ export function Plugins(
|
|
|
199
218
|
|
|
200
219
|
getInitialLanguage() {
|
|
201
220
|
const availableLanguages = getAvailableLanguages();
|
|
202
|
-
const languageOrPromise =
|
|
203
|
-
getCommonProps()
|
|
204
|
-
);
|
|
221
|
+
const languageOrPromise = storageLoadLanguage();
|
|
205
222
|
|
|
206
223
|
return valueOrPromise(languageOrPromise, (language) => {
|
|
207
224
|
if (
|
|
@@ -215,7 +232,11 @@ export function Plugins(
|
|
|
215
232
|
},
|
|
216
233
|
|
|
217
234
|
setStoredLanguage(language: string) {
|
|
218
|
-
|
|
235
|
+
return handleRegularOrAsyncErr(
|
|
236
|
+
events.onError,
|
|
237
|
+
(e) => new LanguageStorageError('Tolgee: Failed to store language', e),
|
|
238
|
+
() => instances.languageStorage?.setLanguage(language, getCommonProps())
|
|
239
|
+
);
|
|
219
240
|
},
|
|
220
241
|
|
|
221
242
|
getDevBackend() {
|
|
@@ -229,13 +250,6 @@ export function Plugins(
|
|
|
229
250
|
namespace,
|
|
230
251
|
...getCommonProps(),
|
|
231
252
|
});
|
|
232
|
-
if (isPromise(data)) {
|
|
233
|
-
return data?.catch((e) => {
|
|
234
|
-
// eslint-disable-next-line no-console
|
|
235
|
-
console.error(e);
|
|
236
|
-
return {};
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
253
|
if (data !== undefined) {
|
|
240
254
|
return data;
|
|
241
255
|
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
import { TolgeeCore } from '../TolgeeCore';
|
|
3
|
+
import { LanguageDetectorError, TolgeePlugin } from '../types';
|
|
4
|
+
|
|
5
|
+
const failingDetector = (async: boolean): TolgeePlugin => {
|
|
6
|
+
return (tolgee, tools) => {
|
|
7
|
+
tools.setLanguageDetector({
|
|
8
|
+
async getLanguage() {
|
|
9
|
+
if (async) {
|
|
10
|
+
return Promise.reject(new Error('failed to fetch'));
|
|
11
|
+
} else {
|
|
12
|
+
throw new Error('failed to fetch');
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
return tolgee;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
describe('language detector errors', () => {
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
console.warn = jest.fn();
|
|
23
|
+
console.error = jest.fn();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('emitts error when detector fails async', async () => {
|
|
27
|
+
const errorHandler = jest.fn();
|
|
28
|
+
const tolgee = TolgeeCore()
|
|
29
|
+
.use(failingDetector(true))
|
|
30
|
+
.init({ availableLanguages: ['en', 'cs'], defaultLanguage: 'en' });
|
|
31
|
+
tolgee.on('error', errorHandler);
|
|
32
|
+
await expect(() => tolgee.run()).rejects.toThrow(LanguageDetectorError);
|
|
33
|
+
const firstArgument = errorHandler.mock.calls[0][0]
|
|
34
|
+
.value as LanguageDetectorError;
|
|
35
|
+
expect(firstArgument).toBeInstanceOf(LanguageDetectorError);
|
|
36
|
+
expect(firstArgument).toBeInstanceOf(Error);
|
|
37
|
+
expect(firstArgument).toHaveProperty('name', 'LanguageDetectorError');
|
|
38
|
+
expect(firstArgument).toHaveProperty(
|
|
39
|
+
'message',
|
|
40
|
+
'Tolgee: Failed to detect language'
|
|
41
|
+
);
|
|
42
|
+
expect(console.warn).toBeCalledTimes(0);
|
|
43
|
+
expect(console.error).toBeCalledTimes(1);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('emitts error when detector fails sync', async () => {
|
|
47
|
+
const errorHandler = jest.fn();
|
|
48
|
+
const tolgee = TolgeeCore()
|
|
49
|
+
.use(failingDetector(false))
|
|
50
|
+
.init({ availableLanguages: ['en', 'cs'], defaultLanguage: 'en' });
|
|
51
|
+
tolgee.on('error', errorHandler);
|
|
52
|
+
await expect(() => tolgee.run()).rejects.toThrow(LanguageDetectorError);
|
|
53
|
+
const firstArgument = errorHandler.mock.calls[0][0]
|
|
54
|
+
.value as LanguageDetectorError;
|
|
55
|
+
expect(firstArgument).toBeInstanceOf(LanguageDetectorError);
|
|
56
|
+
expect(firstArgument).toHaveProperty('name', 'LanguageDetectorError');
|
|
57
|
+
expect(firstArgument).toHaveProperty(
|
|
58
|
+
'message',
|
|
59
|
+
'Tolgee: Failed to detect language'
|
|
60
|
+
);
|
|
61
|
+
expect(console.warn).toBeCalledTimes(0);
|
|
62
|
+
expect(console.error).toBeCalledTimes(1);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
import { TolgeeCore } from '../TolgeeCore';
|
|
3
|
+
import { RecordFetchError, TolgeePlugin } from '../types';
|
|
4
|
+
|
|
5
|
+
const failingBackend = (): TolgeePlugin => {
|
|
6
|
+
return (tolgee, tools) => {
|
|
7
|
+
tools.addBackend({
|
|
8
|
+
async getRecord() {
|
|
9
|
+
return Promise.reject(new Error('failed to fetch'));
|
|
10
|
+
},
|
|
11
|
+
});
|
|
12
|
+
return tolgee;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const failingDevBackend = (): TolgeePlugin => {
|
|
17
|
+
return (tolgee, tools) => {
|
|
18
|
+
tools.setDevBackend({
|
|
19
|
+
async getRecord() {
|
|
20
|
+
return Promise.reject(new Error('failed to fetch'));
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
return tolgee;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
describe('translation records', () => {
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
console.warn = jest.fn();
|
|
30
|
+
console.error = jest.fn();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('emitts error when fails to fetch dev record', async () => {
|
|
34
|
+
const errorHandler = jest.fn();
|
|
35
|
+
const tolgee = TolgeeCore()
|
|
36
|
+
.use(failingDevBackend())
|
|
37
|
+
.init({ language: 'en', apiKey: 'test', apiUrl: 'test' });
|
|
38
|
+
tolgee.on('error', errorHandler);
|
|
39
|
+
await tolgee.run();
|
|
40
|
+
const firstArgument = errorHandler.mock.calls[0][0]
|
|
41
|
+
.value as RecordFetchError;
|
|
42
|
+
expect(firstArgument).toBeInstanceOf(RecordFetchError);
|
|
43
|
+
expect(firstArgument).toBeInstanceOf(Error);
|
|
44
|
+
expect(firstArgument).toHaveProperty('language', 'en');
|
|
45
|
+
expect(firstArgument).toHaveProperty('namespace', '');
|
|
46
|
+
expect(firstArgument).toHaveProperty('isDev', true);
|
|
47
|
+
expect(firstArgument).toHaveProperty('name', 'RecordFetchError');
|
|
48
|
+
expect(firstArgument).toHaveProperty(
|
|
49
|
+
'message',
|
|
50
|
+
'Tolgee: Failed to fetch record for "en"'
|
|
51
|
+
);
|
|
52
|
+
expect(console.warn).toBeCalledTimes(1);
|
|
53
|
+
expect(console.error).toBeCalledTimes(0);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('emitts error when fails to fetch record', async () => {
|
|
57
|
+
const errorHandler = jest.fn();
|
|
58
|
+
const tolgee = TolgeeCore().use(failingBackend()).init({ language: 'en' });
|
|
59
|
+
tolgee.on('error', errorHandler);
|
|
60
|
+
await expect(() => tolgee.run()).rejects.toThrow(RecordFetchError);
|
|
61
|
+
const firstArgument = errorHandler.mock.calls[0][0]
|
|
62
|
+
.value as RecordFetchError;
|
|
63
|
+
expect(firstArgument).toBeInstanceOf(RecordFetchError);
|
|
64
|
+
expect(firstArgument).toHaveProperty('language', 'en');
|
|
65
|
+
expect(firstArgument).toHaveProperty('namespace', '');
|
|
66
|
+
expect(firstArgument).toHaveProperty('isDev', false);
|
|
67
|
+
expect(firstArgument).toHaveProperty('name', 'RecordFetchError');
|
|
68
|
+
expect(firstArgument).toHaveProperty(
|
|
69
|
+
'message',
|
|
70
|
+
'Tolgee: Failed to fetch record for "en"'
|
|
71
|
+
);
|
|
72
|
+
expect(console.warn).toBeCalledTimes(0);
|
|
73
|
+
expect(console.error).toBeCalledTimes(1);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('emitts error when fails to fetch promise in static data', async () => {
|
|
77
|
+
const errorHandler = jest.fn();
|
|
78
|
+
const tolgee = TolgeeCore()
|
|
79
|
+
.use(failingDevBackend())
|
|
80
|
+
.init({
|
|
81
|
+
language: 'en',
|
|
82
|
+
staticData: { en: () => Promise.reject(new Error('No data')) },
|
|
83
|
+
});
|
|
84
|
+
tolgee.on('error', errorHandler);
|
|
85
|
+
await expect(() => tolgee.run()).rejects.toThrow(RecordFetchError);
|
|
86
|
+
|
|
87
|
+
const firstArgument = errorHandler.mock.calls[0][0]
|
|
88
|
+
.value as RecordFetchError;
|
|
89
|
+
expect(firstArgument).toBeInstanceOf(RecordFetchError);
|
|
90
|
+
expect(firstArgument).toHaveProperty('language', 'en');
|
|
91
|
+
expect(firstArgument).toHaveProperty('namespace', '');
|
|
92
|
+
expect(firstArgument).toHaveProperty('isDev', false);
|
|
93
|
+
expect(firstArgument).toHaveProperty('name', 'RecordFetchError');
|
|
94
|
+
expect(firstArgument).toHaveProperty(
|
|
95
|
+
'message',
|
|
96
|
+
'Tolgee: Failed to fetch record for "en"'
|
|
97
|
+
);
|
|
98
|
+
expect(console.warn).toBeCalledTimes(0);
|
|
99
|
+
expect(console.error).toBeCalledTimes(1);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('emitts error when it fails', async () => {
|
|
103
|
+
const errorHandler = jest.fn();
|
|
104
|
+
const tolgee = TolgeeCore()
|
|
105
|
+
.use(failingDevBackend())
|
|
106
|
+
.init({
|
|
107
|
+
language: 'en',
|
|
108
|
+
staticData: { en: () => Promise.reject(new Error('No data')) },
|
|
109
|
+
});
|
|
110
|
+
tolgee.on('error', errorHandler);
|
|
111
|
+
await expect(() => tolgee.run()).rejects.toThrow(RecordFetchError);
|
|
112
|
+
|
|
113
|
+
const firstArgument = errorHandler.mock.calls[0][0]
|
|
114
|
+
.value as RecordFetchError;
|
|
115
|
+
expect(firstArgument).toBeInstanceOf(RecordFetchError);
|
|
116
|
+
expect(firstArgument).toHaveProperty('language', 'en');
|
|
117
|
+
expect(firstArgument).toHaveProperty('namespace', '');
|
|
118
|
+
expect(firstArgument).toHaveProperty('isDev', false);
|
|
119
|
+
expect(firstArgument).toHaveProperty('name', 'RecordFetchError');
|
|
120
|
+
expect(firstArgument).toHaveProperty(
|
|
121
|
+
'message',
|
|
122
|
+
'Tolgee: Failed to fetch record for "en"'
|
|
123
|
+
);
|
|
124
|
+
expect(console.warn).toBeCalledTimes(0);
|
|
125
|
+
expect(console.error).toBeCalledTimes(1);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
import { TolgeeCore } from '../TolgeeCore';
|
|
3
|
+
import { LanguageStorageError, TolgeePlugin } from '../types';
|
|
4
|
+
|
|
5
|
+
const failingStorage = (async: boolean): TolgeePlugin => {
|
|
6
|
+
return (tolgee, tools) => {
|
|
7
|
+
tools.setLanguageStorage({
|
|
8
|
+
async getLanguage() {
|
|
9
|
+
if (async) {
|
|
10
|
+
return Promise.reject(new Error('failed to fetch'));
|
|
11
|
+
} else {
|
|
12
|
+
throw new Error('failed to fetch');
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
async setLanguage() {
|
|
16
|
+
if (async) {
|
|
17
|
+
return Promise.reject(new Error('failed to fetch'));
|
|
18
|
+
} else {
|
|
19
|
+
throw new Error('failed to fetch');
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
return tolgee;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
describe('language storage errors', () => {
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
console.warn = jest.fn();
|
|
30
|
+
console.error = jest.fn();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('emitts error when language is loaded async', async () => {
|
|
34
|
+
const errorHandler = jest.fn();
|
|
35
|
+
const tolgee = TolgeeCore()
|
|
36
|
+
.use(failingStorage(true))
|
|
37
|
+
.init({ availableLanguages: ['en', 'cs'], defaultLanguage: 'en' });
|
|
38
|
+
tolgee.on('error', errorHandler);
|
|
39
|
+
await expect(() => tolgee.run()).rejects.toThrow(LanguageStorageError);
|
|
40
|
+
const firstArgument = errorHandler.mock.calls[0][0]
|
|
41
|
+
.value as LanguageStorageError;
|
|
42
|
+
|
|
43
|
+
expect(firstArgument).toHaveProperty(
|
|
44
|
+
'message',
|
|
45
|
+
'Tolgee: Failed to load language'
|
|
46
|
+
);
|
|
47
|
+
expect(firstArgument).toBeInstanceOf(Error);
|
|
48
|
+
expect(firstArgument).toBeInstanceOf(LanguageStorageError);
|
|
49
|
+
expect(firstArgument).toHaveProperty('name', 'LanguageStorageError');
|
|
50
|
+
expect(console.warn).toBeCalledTimes(0);
|
|
51
|
+
expect(console.error).toBeCalledTimes(1);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('emitts error when language is loaded sync', async () => {
|
|
55
|
+
const errorHandler = jest.fn();
|
|
56
|
+
const tolgee = TolgeeCore()
|
|
57
|
+
.use(failingStorage(false))
|
|
58
|
+
.init({ availableLanguages: ['en', 'cs'], defaultLanguage: 'en' });
|
|
59
|
+
tolgee.on('error', errorHandler);
|
|
60
|
+
await expect(() => tolgee.run()).rejects.toThrow(LanguageStorageError);
|
|
61
|
+
const firstArgument = errorHandler.mock.calls[0][0]
|
|
62
|
+
.value as LanguageStorageError;
|
|
63
|
+
|
|
64
|
+
expect(firstArgument).toHaveProperty(
|
|
65
|
+
'message',
|
|
66
|
+
'Tolgee: Failed to load language'
|
|
67
|
+
);
|
|
68
|
+
expect(firstArgument).toBeInstanceOf(LanguageStorageError);
|
|
69
|
+
expect(firstArgument).toHaveProperty('name', 'LanguageStorageError');
|
|
70
|
+
expect(console.warn).toBeCalledTimes(0);
|
|
71
|
+
expect(console.error).toBeCalledTimes(1);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('emitts error when language is saved async', async () => {
|
|
75
|
+
const errorHandler = jest.fn();
|
|
76
|
+
const tolgee = TolgeeCore()
|
|
77
|
+
.use(failingStorage(true))
|
|
78
|
+
.init({ availableLanguages: ['en', 'cs'], language: 'en' });
|
|
79
|
+
tolgee.on('error', errorHandler);
|
|
80
|
+
await tolgee.run();
|
|
81
|
+
await expect(() => tolgee.changeLanguage('cs')).rejects.toThrow(
|
|
82
|
+
LanguageStorageError
|
|
83
|
+
);
|
|
84
|
+
const firstArgument = errorHandler.mock.calls[0][0]
|
|
85
|
+
.value as LanguageStorageError;
|
|
86
|
+
expect(firstArgument).toHaveProperty(
|
|
87
|
+
'message',
|
|
88
|
+
'Tolgee: Failed to store language'
|
|
89
|
+
);
|
|
90
|
+
expect(firstArgument).toBeInstanceOf(LanguageStorageError);
|
|
91
|
+
expect(firstArgument).toHaveProperty('name', 'LanguageStorageError');
|
|
92
|
+
expect(console.warn).toBeCalledTimes(0);
|
|
93
|
+
expect(console.error).toBeCalledTimes(1);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('emitts error when language is saved sync', async () => {
|
|
97
|
+
const errorHandler = jest.fn();
|
|
98
|
+
const tolgee = TolgeeCore()
|
|
99
|
+
.use(failingStorage(false))
|
|
100
|
+
.init({ availableLanguages: ['en', 'cs'], language: 'en' });
|
|
101
|
+
tolgee.on('error', errorHandler);
|
|
102
|
+
await tolgee.run();
|
|
103
|
+
await expect(() => tolgee.changeLanguage('cs')).rejects.toThrow(
|
|
104
|
+
LanguageStorageError
|
|
105
|
+
);
|
|
106
|
+
const firstArgument = errorHandler.mock.calls[0][0]
|
|
107
|
+
.value as LanguageStorageError;
|
|
108
|
+
expect(firstArgument).toHaveProperty(
|
|
109
|
+
'message',
|
|
110
|
+
'Tolgee: Failed to store language'
|
|
111
|
+
);
|
|
112
|
+
expect(firstArgument).toBeInstanceOf(LanguageStorageError);
|
|
113
|
+
expect(firstArgument).toHaveProperty('name', 'LanguageStorageError');
|
|
114
|
+
expect(console.warn).toBeCalledTimes(0);
|
|
115
|
+
expect(console.error).toBeCalledTimes(1);
|
|
116
|
+
});
|
|
117
|
+
});
|
package/src/helpers.ts
CHANGED
|
@@ -3,10 +3,14 @@ import {
|
|
|
3
3
|
FallbackLanguageObject,
|
|
4
4
|
FallbackLanguageOption,
|
|
5
5
|
FetchFn,
|
|
6
|
+
TolgeeError,
|
|
6
7
|
} from './types';
|
|
8
|
+
import { EventEmitterInstance } from './Controller/Events/EventEmitter';
|
|
7
9
|
|
|
8
|
-
export function isPromise(value:
|
|
9
|
-
return Boolean(
|
|
10
|
+
export function isPromise(value: unknown): value is Promise<unknown> {
|
|
11
|
+
return Boolean(
|
|
12
|
+
value && typeof (value as unknown as Promise<unknown>).then === 'function'
|
|
13
|
+
);
|
|
10
14
|
}
|
|
11
15
|
|
|
12
16
|
export function valueOrPromise<T, R>(
|
|
@@ -20,6 +24,29 @@ export function valueOrPromise<T, R>(
|
|
|
20
24
|
}
|
|
21
25
|
}
|
|
22
26
|
|
|
27
|
+
export function handleRegularOrAsyncErr<T>(
|
|
28
|
+
onError: EventEmitterInstance<TolgeeError>,
|
|
29
|
+
createError: (e: any) => TolgeeError,
|
|
30
|
+
callback: () => Promise<T> | T
|
|
31
|
+
): Promise<T> | T {
|
|
32
|
+
function handle(e: any): never {
|
|
33
|
+
const error = createError(e);
|
|
34
|
+
onError.emit(error);
|
|
35
|
+
// eslint-disable-next-line no-console
|
|
36
|
+
console.error(error);
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
const result = callback();
|
|
41
|
+
if (isPromise(result)) {
|
|
42
|
+
return result.catch(handle);
|
|
43
|
+
}
|
|
44
|
+
return result;
|
|
45
|
+
} catch (e) {
|
|
46
|
+
handle(e);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
23
50
|
export function missingOptionError(option: string | string[]) {
|
|
24
51
|
const options = (Array.isArray(option) ? option : [option]).map(
|
|
25
52
|
(val) => `'${val}'`
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { CacheDescriptorInternal } from './cache';
|
|
2
|
+
|
|
3
|
+
export class RecordFetchError extends Error {
|
|
4
|
+
public name = 'RecordFetchError' as const;
|
|
5
|
+
public language: string;
|
|
6
|
+
public namespace: string | undefined;
|
|
7
|
+
constructor(
|
|
8
|
+
descriptor: CacheDescriptorInternal,
|
|
9
|
+
public cause: any,
|
|
10
|
+
public isDev: boolean = false
|
|
11
|
+
) {
|
|
12
|
+
const { language, namespace } = descriptor;
|
|
13
|
+
super(
|
|
14
|
+
`Tolgee: Failed to fetch record for "${language}"${
|
|
15
|
+
namespace && ` and "${namespace}"`
|
|
16
|
+
}`
|
|
17
|
+
);
|
|
18
|
+
this.language = language;
|
|
19
|
+
this.namespace = namespace;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class LanguageDetectorError extends Error {
|
|
24
|
+
public name = 'LanguageDetectorError' as const;
|
|
25
|
+
constructor(message: string, public cause: any) {
|
|
26
|
+
super(message);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class LanguageStorageError extends Error {
|
|
31
|
+
public name = 'LanguageStorageError' as const;
|
|
32
|
+
constructor(message: string, public cause: any) {
|
|
33
|
+
super(message);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type TolgeeError =
|
|
38
|
+
| RecordFetchError
|
|
39
|
+
| LanguageDetectorError
|
|
40
|
+
| LanguageStorageError;
|
package/src/types/events.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { NsFallback } from './general';
|
|
2
2
|
import type { CacheDescriptorWithKey } from './cache';
|
|
3
|
+
import { TolgeeError } from './errors';
|
|
3
4
|
|
|
4
5
|
export type Subscription = {
|
|
5
6
|
unsubscribe: () => void;
|
|
@@ -39,6 +40,7 @@ export interface EventType {
|
|
|
39
40
|
running: boolean;
|
|
40
41
|
cache: CacheDescriptorWithKey;
|
|
41
42
|
update: void;
|
|
43
|
+
error: TolgeeError;
|
|
42
44
|
permanentChange: CacheDescriptorWithKey;
|
|
43
45
|
}
|
|
44
46
|
|
|
@@ -84,6 +86,11 @@ export type TolgeeOn<E extends keyof EventType = keyof EventType> = {
|
|
|
84
86
|
*/
|
|
85
87
|
(event: 'cache', handler: Listener<CacheDescriptorWithKey>): Subscription;
|
|
86
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Emitted on errors
|
|
91
|
+
*/
|
|
92
|
+
(event: 'error', handler: Listener<TolgeeError>): Subscription;
|
|
93
|
+
|
|
87
94
|
/**
|
|
88
95
|
* Translation was changed or created via dev tools
|
|
89
96
|
*/
|