@tolgee/core 5.33.2 → 5.33.3-prerelease.2efc0e6b.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 +227 -186
- 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 +227 -186
- 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 +227 -186
- package/dist/tolgee.esm.mjs.map +1 -1
- package/dist/tolgee.umd.js +227 -186
- 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 +8 -12
- package/lib/Controller/Cache/helpers.d.ts +3 -2
- package/lib/Controller/Controller.d.ts +57 -26
- package/lib/Controller/Events/EventEmitter.d.ts +5 -5
- package/lib/Controller/Events/EventEmitterCombined.d.ts +6 -0
- package/lib/Controller/Events/Events.d.ts +39 -12
- package/lib/Controller/State/State.d.ts +10 -4
- package/lib/Controller/State/initState.d.ts +29 -7
- package/lib/TolgeeCore.d.ts +25 -28
- package/lib/helpers.d.ts +2 -2
- package/lib/types/cache.d.ts +19 -1
- package/lib/types/events.d.ts +24 -23
- package/lib/types/index.d.ts +1 -1
- package/package.json +2 -2
- package/src/Controller/Cache/Cache.test.ts +189 -0
- package/src/Controller/Cache/Cache.ts +118 -57
- package/src/Controller/Cache/helpers.ts +12 -3
- package/src/Controller/Controller.ts +81 -27
- package/src/Controller/Events/EventEmitter.ts +23 -15
- package/src/Controller/Events/EventEmitterCombined.ts +55 -0
- package/src/Controller/Events/Events.ts +31 -23
- package/src/Controller/Plugins/Plugins.ts +5 -0
- package/src/Controller/State/State.ts +26 -6
- package/src/Controller/State/initState.ts +35 -7
- package/src/TolgeeCore.ts +14 -17
- package/src/__test/cache.test.ts +2 -26
- package/src/__test/client.test.ts +3 -3
- package/src/__test/events.test.ts +40 -13
- package/src/__test/load.matrix.test.ts +123 -0
- package/src/__test/load.required.test.ts +71 -0
- package/src/__test/namespaces.required.test.ts +52 -0
- package/src/__test/options.test.ts +1 -1
- package/src/__test/testTools.ts +39 -0
- package/src/helpers.ts +2 -1
- package/src/types/cache.ts +24 -1
- package/src/types/events.ts +33 -24
- package/src/types/index.ts +1 -0
- package/lib/Controller/Events/EventEmitterSelective.d.ts +0 -7
- package/src/Controller/Events/EventEmitterSelective.test.ts +0 -110
- package/src/Controller/Events/EventEmitterSelective.ts +0 -133
|
@@ -2,14 +2,16 @@ import {
|
|
|
2
2
|
CacheDescriptor,
|
|
3
3
|
CacheDescriptorInternal,
|
|
4
4
|
NsFallback,
|
|
5
|
-
TranslationsFlat,
|
|
6
5
|
TranslationValue,
|
|
7
6
|
TreeTranslationsData,
|
|
8
7
|
BackendGetRecordInternal,
|
|
9
8
|
RecordFetchError,
|
|
9
|
+
LoadOptions,
|
|
10
|
+
CacheInternalRecord,
|
|
11
|
+
TranslationsFlat,
|
|
10
12
|
} from '../../types';
|
|
11
13
|
import { getFallbackArray, isPromise, unique } from '../../helpers';
|
|
12
|
-
import { TolgeeStaticData } from '../State/initState';
|
|
14
|
+
import { TolgeeStaticData, TolgeeStaticDataProp } from '../State/initState';
|
|
13
15
|
import { ValueObserverInstance } from '../ValueObserver';
|
|
14
16
|
|
|
15
17
|
import { decodeCacheKey, encodeCacheKey, flattenTranslations } from './helpers';
|
|
@@ -33,6 +35,7 @@ export function Cache(
|
|
|
33
35
|
backendGetDevRecord: BackendGetRecordInternal,
|
|
34
36
|
withDefaultNs: (descriptor: CacheDescriptor) => CacheDescriptorInternal,
|
|
35
37
|
isInitialLoading: () => boolean,
|
|
38
|
+
isCacheDisabled: () => boolean,
|
|
36
39
|
fetchingObserver: ValueObserverInstance<boolean>,
|
|
37
40
|
loadingObserver: ValueObserverInstance<boolean>
|
|
38
41
|
) {
|
|
@@ -43,7 +46,7 @@ export function Cache(
|
|
|
43
46
|
|
|
44
47
|
function addRecordInternal(
|
|
45
48
|
descriptor: CacheDescriptorInternal,
|
|
46
|
-
data:
|
|
49
|
+
data: TranslationsFlat,
|
|
47
50
|
recordVersion: number
|
|
48
51
|
) {
|
|
49
52
|
const cacheKey = encodeCacheKey(descriptor);
|
|
@@ -51,7 +54,7 @@ export function Cache(
|
|
|
51
54
|
data: flattenTranslations(data),
|
|
52
55
|
version: recordVersion,
|
|
53
56
|
});
|
|
54
|
-
events.onCacheChange.emit(
|
|
57
|
+
events.onCacheChange.emit(decodeCacheKey(cacheKey));
|
|
55
58
|
}
|
|
56
59
|
|
|
57
60
|
/**
|
|
@@ -108,15 +111,23 @@ export function Cache(
|
|
|
108
111
|
}
|
|
109
112
|
|
|
110
113
|
const self = Object.freeze({
|
|
111
|
-
addStaticData(data:
|
|
112
|
-
if (data) {
|
|
114
|
+
addStaticData(data: TolgeeStaticDataProp | undefined) {
|
|
115
|
+
if (Array.isArray(data)) {
|
|
116
|
+
for (const record of data) {
|
|
117
|
+
const key = encodeCacheKey(record);
|
|
118
|
+
const existing = cache.get(key);
|
|
119
|
+
if (!existing || existing.version === 0) {
|
|
120
|
+
addRecordInternal(record, flattenTranslations(record.data), 0);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
} else if (data) {
|
|
113
124
|
staticData = { ...staticData, ...data };
|
|
114
125
|
Object.entries(data).forEach(([key, value]) => {
|
|
115
126
|
if (typeof value !== 'function') {
|
|
116
127
|
const descriptor = decodeCacheKey(key);
|
|
117
128
|
const existing = cache.get(key);
|
|
118
129
|
if (!existing || existing.version === 0) {
|
|
119
|
-
addRecordInternal(descriptor, value, 0);
|
|
130
|
+
addRecordInternal(descriptor, flattenTranslations(value), 0);
|
|
120
131
|
}
|
|
121
132
|
}
|
|
122
133
|
});
|
|
@@ -129,7 +140,7 @@ export function Cache(
|
|
|
129
140
|
},
|
|
130
141
|
|
|
131
142
|
addRecord(descriptor: CacheDescriptorInternal, data: TreeTranslationsData) {
|
|
132
|
-
addRecordInternal(descriptor, data, version);
|
|
143
|
+
addRecordInternal(descriptor, flattenTranslations(data), version);
|
|
133
144
|
},
|
|
134
145
|
|
|
135
146
|
exists(descriptor: CacheDescriptorInternal, strict = false) {
|
|
@@ -140,20 +151,34 @@ export function Cache(
|
|
|
140
151
|
return Boolean(record);
|
|
141
152
|
},
|
|
142
153
|
|
|
143
|
-
getRecord(descriptor: CacheDescriptor) {
|
|
144
|
-
|
|
154
|
+
getRecord(descriptor: CacheDescriptor): CacheInternalRecord | undefined {
|
|
155
|
+
const descriptorWithNs = withDefaultNs(descriptor);
|
|
156
|
+
const cacheKey = encodeCacheKey(descriptorWithNs);
|
|
157
|
+
const cacheRecord = cache.get(cacheKey);
|
|
158
|
+
if (!cacheRecord) {
|
|
159
|
+
return undefined;
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
...descriptorWithNs,
|
|
163
|
+
cacheKey,
|
|
164
|
+
data: cacheRecord.data,
|
|
165
|
+
};
|
|
166
|
+
},
|
|
167
|
+
|
|
168
|
+
getAllRecords() {
|
|
169
|
+
const entries = Array.from(cache.entries());
|
|
170
|
+
return entries.map(([key]) => self.getRecord(decodeCacheKey(key)));
|
|
145
171
|
},
|
|
146
172
|
|
|
147
173
|
getTranslation(descriptor: CacheDescriptorInternal, key: string) {
|
|
148
|
-
return cache.get(encodeCacheKey(descriptor))?.data
|
|
174
|
+
return cache.get(encodeCacheKey(descriptor))?.data[key];
|
|
149
175
|
},
|
|
150
176
|
|
|
151
177
|
getTranslationNs(namespaces: string[], languages: string[], key: string) {
|
|
152
178
|
for (const namespace of namespaces) {
|
|
153
179
|
for (const language of languages) {
|
|
154
|
-
const value = cache
|
|
155
|
-
|
|
156
|
-
?.data.get(key);
|
|
180
|
+
const value = cache.get(encodeCacheKey({ language, namespace }))
|
|
181
|
+
?.data[key];
|
|
157
182
|
if (value !== undefined && value !== null) {
|
|
158
183
|
return [namespace];
|
|
159
184
|
}
|
|
@@ -169,9 +194,8 @@ export function Cache(
|
|
|
169
194
|
) {
|
|
170
195
|
for (const namespace of namespaces) {
|
|
171
196
|
for (const language of languages) {
|
|
172
|
-
const value = cache
|
|
173
|
-
|
|
174
|
-
?.data.get(key);
|
|
197
|
+
const value = cache.get(encodeCacheKey({ language, namespace }))
|
|
198
|
+
?.data[key];
|
|
175
199
|
if (value !== undefined && value !== null) {
|
|
176
200
|
return value;
|
|
177
201
|
}
|
|
@@ -186,8 +210,10 @@ export function Cache(
|
|
|
186
210
|
value: TranslationValue
|
|
187
211
|
) {
|
|
188
212
|
const record = cache.get(encodeCacheKey(descriptor))?.data;
|
|
189
|
-
record?.
|
|
190
|
-
|
|
213
|
+
if (record?.[key]) {
|
|
214
|
+
record[key] = value;
|
|
215
|
+
events.onCacheChange.emit({ ...descriptor, key });
|
|
216
|
+
}
|
|
191
217
|
},
|
|
192
218
|
|
|
193
219
|
isFetching(ns?: NsFallback) {
|
|
@@ -206,84 +232,119 @@ export function Cache(
|
|
|
206
232
|
);
|
|
207
233
|
},
|
|
208
234
|
|
|
209
|
-
isLoading(language: string
|
|
235
|
+
isLoading(language: string, ns?: NsFallback) {
|
|
210
236
|
const namespaces = getFallbackArray(ns);
|
|
211
237
|
|
|
238
|
+
if (isInitialLoading()) {
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const pendingCacheKeys = Array.from(asyncRequests.keys());
|
|
243
|
+
|
|
212
244
|
return Boolean(
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
);
|
|
224
|
-
})
|
|
245
|
+
pendingCacheKeys.find((key) => {
|
|
246
|
+
const descriptor = decodeCacheKey(key);
|
|
247
|
+
return (
|
|
248
|
+
(!namespaces.length || namespaces.includes(descriptor.namespace)) &&
|
|
249
|
+
!self.exists({
|
|
250
|
+
namespace: descriptor.namespace,
|
|
251
|
+
language: language,
|
|
252
|
+
})
|
|
253
|
+
);
|
|
254
|
+
})
|
|
225
255
|
);
|
|
226
256
|
},
|
|
227
257
|
|
|
228
|
-
async loadRecords(
|
|
229
|
-
|
|
258
|
+
async loadRecords(
|
|
259
|
+
descriptors: CacheDescriptor[],
|
|
260
|
+
options?: LoadOptions
|
|
261
|
+
): Promise<CacheInternalRecord[]> {
|
|
262
|
+
type WithPromise = {
|
|
263
|
+
new: boolean;
|
|
264
|
+
language: string;
|
|
265
|
+
namespace: string;
|
|
266
|
+
cacheKey: string;
|
|
267
|
+
promise?: Promise<TreeTranslationsData | undefined>;
|
|
268
|
+
data?: TranslationsFlat;
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
const withPromises: WithPromise[] = descriptors.map((descriptor) => {
|
|
230
272
|
const keyObject = withDefaultNs(descriptor);
|
|
231
273
|
const cacheKey = encodeCacheKey(keyObject);
|
|
274
|
+
if (options?.useCache) {
|
|
275
|
+
const exists = self.exists(keyObject, true);
|
|
276
|
+
|
|
277
|
+
if (exists) {
|
|
278
|
+
return {
|
|
279
|
+
...keyObject,
|
|
280
|
+
new: false,
|
|
281
|
+
cacheKey,
|
|
282
|
+
data: self.getRecord(keyObject)!.data,
|
|
283
|
+
} as WithPromise;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
232
287
|
const existingPromise = asyncRequests.get(cacheKey);
|
|
233
288
|
|
|
234
289
|
if (existingPromise) {
|
|
235
290
|
return {
|
|
291
|
+
...keyObject,
|
|
236
292
|
new: false,
|
|
237
293
|
promise: existingPromise,
|
|
238
|
-
keyObject,
|
|
239
294
|
cacheKey,
|
|
240
295
|
};
|
|
241
296
|
}
|
|
297
|
+
|
|
242
298
|
const dataPromise =
|
|
243
|
-
fetchData(keyObject,
|
|
299
|
+
fetchData(keyObject, !options?.noDev) || Promise.resolve(undefined);
|
|
300
|
+
|
|
244
301
|
asyncRequests.set(cacheKey, dataPromise);
|
|
302
|
+
|
|
245
303
|
return {
|
|
304
|
+
...keyObject,
|
|
246
305
|
new: true,
|
|
247
306
|
promise: dataPromise,
|
|
248
|
-
keyObject,
|
|
249
307
|
cacheKey,
|
|
250
308
|
};
|
|
251
309
|
});
|
|
252
310
|
fetchingObserver.notify();
|
|
253
311
|
loadingObserver.notify();
|
|
254
312
|
|
|
255
|
-
const
|
|
313
|
+
const promisesToWait = withPromises
|
|
314
|
+
.map((val) => val.promise)
|
|
315
|
+
.filter(Boolean);
|
|
256
316
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
317
|
+
const fetchedData = await Promise.all(promisesToWait);
|
|
318
|
+
|
|
319
|
+
withPromises.forEach((value) => {
|
|
320
|
+
if (value.promise) {
|
|
321
|
+
value.data = flattenTranslations(fetchedData[0] ?? {});
|
|
322
|
+
fetchedData.shift();
|
|
323
|
+
}
|
|
260
324
|
// if promise has changed in between, it means cache been invalidated or
|
|
261
325
|
// new data are being fetched
|
|
326
|
+
const promiseChanged =
|
|
327
|
+
asyncRequests.get(value.cacheKey) !== value.promise;
|
|
262
328
|
if (value.new && !promiseChanged) {
|
|
263
329
|
asyncRequests.delete(value.cacheKey);
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
} else if (!self.getRecord(value.keyObject)) {
|
|
330
|
+
if (value.data) {
|
|
331
|
+
self.addRecord(value, value.data);
|
|
332
|
+
} else if (!self.getRecord(value)) {
|
|
268
333
|
// if no data exist, put empty object
|
|
269
|
-
|
|
334
|
+
// so we know we don't have to fetch again
|
|
335
|
+
self.addRecord(value, {});
|
|
270
336
|
}
|
|
271
337
|
}
|
|
272
338
|
});
|
|
273
339
|
fetchingObserver.notify();
|
|
274
340
|
loadingObserver.notify();
|
|
275
341
|
|
|
276
|
-
return withPromises.map((val) =>
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
return {
|
|
283
|
-
...decodeCacheKey(key),
|
|
284
|
-
data: entry.data,
|
|
285
|
-
};
|
|
286
|
-
});
|
|
342
|
+
return withPromises.map((val) => ({
|
|
343
|
+
language: val.language,
|
|
344
|
+
namespace: val.namespace,
|
|
345
|
+
data: val.data ?? {},
|
|
346
|
+
cacheKey: val.cacheKey,
|
|
347
|
+
}));
|
|
287
348
|
},
|
|
288
349
|
});
|
|
289
350
|
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
CacheDescriptorInternal,
|
|
3
|
+
TranslationsFlat,
|
|
4
|
+
TreeTranslationsData,
|
|
5
|
+
} from '../../types';
|
|
2
6
|
|
|
3
|
-
export const
|
|
7
|
+
export const flattenTranslationsToMap = (
|
|
4
8
|
data: TreeTranslationsData
|
|
5
9
|
): Map<string, string> => {
|
|
6
10
|
const result: Map<string, string> = new Map();
|
|
@@ -10,7 +14,7 @@ export const flattenTranslations = (
|
|
|
10
14
|
return;
|
|
11
15
|
}
|
|
12
16
|
if (typeof value === 'object') {
|
|
13
|
-
|
|
17
|
+
flattenTranslationsToMap(value).forEach((flatValue, flatKey) => {
|
|
14
18
|
result.set(key + '.' + flatKey, flatValue);
|
|
15
19
|
});
|
|
16
20
|
return;
|
|
@@ -20,6 +24,11 @@ export const flattenTranslations = (
|
|
|
20
24
|
return result;
|
|
21
25
|
};
|
|
22
26
|
|
|
27
|
+
export const flattenTranslations = (
|
|
28
|
+
data: TreeTranslationsData
|
|
29
|
+
): TranslationsFlat => {
|
|
30
|
+
return Object.fromEntries(flattenTranslationsToMap(data).entries());
|
|
31
|
+
};
|
|
23
32
|
export const decodeCacheKey = (key: string): CacheDescriptorInternal => {
|
|
24
33
|
const [firstPart, ...rest] = key.split(':');
|
|
25
34
|
// if namespaces contains ":" it won't get lost
|
|
@@ -6,9 +6,14 @@ import {
|
|
|
6
6
|
TFnType,
|
|
7
7
|
NsType,
|
|
8
8
|
KeyAndNamespacesInternal,
|
|
9
|
+
CacheDescriptorInternal,
|
|
10
|
+
LoadOptions,
|
|
11
|
+
LoadRequiredOptions,
|
|
12
|
+
LoadMatrixOptions,
|
|
13
|
+
MatrixOptions,
|
|
9
14
|
} from '../types';
|
|
10
15
|
import { Cache } from './Cache/Cache';
|
|
11
|
-
import { getFallbackArray } from '../helpers';
|
|
16
|
+
import { getFallbackArray, unique } from '../helpers';
|
|
12
17
|
import { Plugins } from './Plugins/Plugins';
|
|
13
18
|
import { ValueObserver } from './ValueObserver';
|
|
14
19
|
import { State } from './State/State';
|
|
@@ -20,7 +25,7 @@ type StateServiceProps = {
|
|
|
20
25
|
};
|
|
21
26
|
|
|
22
27
|
export function Controller({ options }: StateServiceProps) {
|
|
23
|
-
const events = Events(
|
|
28
|
+
const events = Events();
|
|
24
29
|
const fetchingObserver = ValueObserver<boolean>(
|
|
25
30
|
false,
|
|
26
31
|
() => cache.isFetching(),
|
|
@@ -55,6 +60,7 @@ export function Controller({ options }: StateServiceProps) {
|
|
|
55
60
|
pluginService.getBackendDevRecord,
|
|
56
61
|
state.withDefaultNs,
|
|
57
62
|
state.isInitialLoading,
|
|
63
|
+
state.isCacheDisabled,
|
|
58
64
|
fetchingObserver,
|
|
59
65
|
loadingObserver
|
|
60
66
|
);
|
|
@@ -82,16 +88,16 @@ export function Controller({ options }: StateServiceProps) {
|
|
|
82
88
|
// gets all namespaces where translation could be located
|
|
83
89
|
// takes (ns|default, fallback ns)
|
|
84
90
|
function getDefaultAndFallbackNs(ns?: NsType) {
|
|
85
|
-
return [...getFallbackArray(getDefaultNs(ns)), ...getFallbackNs()];
|
|
91
|
+
return unique([...getFallbackArray(getDefaultNs(ns)), ...getFallbackNs()]);
|
|
86
92
|
}
|
|
87
93
|
|
|
88
94
|
// gets all namespaces which need to be loaded
|
|
89
95
|
// takes (ns|default, initial ns, fallback ns, active ns)
|
|
90
|
-
function getRequiredNamespaces(ns
|
|
91
|
-
return [
|
|
96
|
+
function getRequiredNamespaces(ns?: NsFallback) {
|
|
97
|
+
return unique([
|
|
92
98
|
...getFallbackArray(ns ?? getDefaultNs()),
|
|
93
99
|
...state.getRequiredNamespaces(),
|
|
94
|
-
];
|
|
100
|
+
]);
|
|
95
101
|
}
|
|
96
102
|
|
|
97
103
|
function changeTranslation(
|
|
@@ -114,25 +120,55 @@ export function Controller({ options }: StateServiceProps) {
|
|
|
114
120
|
cache.addStaticData(state.getInitialOptions().staticData);
|
|
115
121
|
}
|
|
116
122
|
|
|
117
|
-
function
|
|
123
|
+
function getRequiredDescriptors(lang?: string, ns?: NsFallback) {
|
|
118
124
|
const languages = state.getFallbackLangs(lang);
|
|
119
125
|
const namespaces = getRequiredNamespaces(ns);
|
|
120
|
-
const result:
|
|
126
|
+
const result: CacheDescriptorInternal[] = [];
|
|
121
127
|
languages.forEach((language) => {
|
|
122
128
|
namespaces.forEach((namespace) => {
|
|
123
|
-
|
|
124
|
-
result.push({ language, namespace });
|
|
125
|
-
}
|
|
129
|
+
result.push({ language, namespace });
|
|
126
130
|
});
|
|
127
131
|
});
|
|
128
132
|
return result;
|
|
129
133
|
}
|
|
130
134
|
|
|
131
|
-
function
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
+
function getMissingDescriptors(lang?: string, ns?: NsFallback) {
|
|
136
|
+
return getRequiredDescriptors(lang, ns).filter(
|
|
137
|
+
(descriptor) => !cache.exists(descriptor, true)
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function getMatrixRecords(options: MatrixOptions) {
|
|
142
|
+
let languages: string[] = [];
|
|
143
|
+
let namespaces: string[] = [];
|
|
144
|
+
if (Array.isArray(options.languages)) {
|
|
145
|
+
languages = options.languages;
|
|
146
|
+
} else if (options.languages === 'all') {
|
|
147
|
+
const availableLanguages = self.getAvailableLanguages();
|
|
148
|
+
if (!availableLanguages) {
|
|
149
|
+
throw new Error(missingOptionError('availableLanguages'));
|
|
150
|
+
}
|
|
151
|
+
languages = availableLanguages;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (Array.isArray(options.namespaces)) {
|
|
155
|
+
namespaces = options.namespaces;
|
|
156
|
+
} else if (options.namespaces === 'all') {
|
|
157
|
+
const availableNs = self.getAvailableNs();
|
|
158
|
+
if (!availableNs) {
|
|
159
|
+
throw new Error(missingOptionError('availableNs'));
|
|
160
|
+
}
|
|
161
|
+
namespaces = availableNs;
|
|
135
162
|
}
|
|
163
|
+
|
|
164
|
+
const records: CacheDescriptorInternal[] = [];
|
|
165
|
+
|
|
166
|
+
languages.forEach((language) => {
|
|
167
|
+
namespaces.forEach((namespace) => {
|
|
168
|
+
records.push({ language, namespace });
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
return records;
|
|
136
172
|
}
|
|
137
173
|
|
|
138
174
|
function getTranslationNs({ key, ns }: KeyAndNamespacesInternal) {
|
|
@@ -149,8 +185,13 @@ export function Controller({ options }: StateServiceProps) {
|
|
|
149
185
|
|
|
150
186
|
function loadInitial() {
|
|
151
187
|
const data = valueOrPromise(initializeLanguage(), () => {
|
|
152
|
-
|
|
153
|
-
|
|
188
|
+
const missingDescriptors = getMissingDescriptors();
|
|
189
|
+
if (
|
|
190
|
+
missingDescriptors.length &&
|
|
191
|
+
state.getInitialOptions().autoLoadRequiredData
|
|
192
|
+
) {
|
|
193
|
+
return cache.loadRecords(missingDescriptors, { useCache: true });
|
|
194
|
+
}
|
|
154
195
|
});
|
|
155
196
|
|
|
156
197
|
if (isPromise(data)) {
|
|
@@ -208,7 +249,7 @@ export function Controller({ options }: StateServiceProps) {
|
|
|
208
249
|
getTranslationNs: getTranslationNs,
|
|
209
250
|
getDefaultAndFallbackNs: getDefaultAndFallbackNs,
|
|
210
251
|
findPositions: pluginService.findPositions,
|
|
211
|
-
|
|
252
|
+
getRequiredDescriptors: getRequiredDescriptors,
|
|
212
253
|
async changeLanguage(language: string) {
|
|
213
254
|
if (
|
|
214
255
|
state.getPendingLanguage() === language &&
|
|
@@ -218,8 +259,10 @@ export function Controller({ options }: StateServiceProps) {
|
|
|
218
259
|
}
|
|
219
260
|
state.setPendingLanguage(language);
|
|
220
261
|
|
|
221
|
-
if (state.isRunning()) {
|
|
222
|
-
await
|
|
262
|
+
if (state.isRunning() && state.getInitialOptions().autoLoadRequiredData) {
|
|
263
|
+
await cache.loadRecords(getRequiredDescriptors(language), {
|
|
264
|
+
useCache: true,
|
|
265
|
+
});
|
|
223
266
|
}
|
|
224
267
|
|
|
225
268
|
if (language === state.getPendingLanguage()) {
|
|
@@ -235,16 +278,14 @@ export function Controller({ options }: StateServiceProps) {
|
|
|
235
278
|
state.addActiveNs(ns);
|
|
236
279
|
}
|
|
237
280
|
if (state.isRunning()) {
|
|
238
|
-
await
|
|
281
|
+
await cache.loadRecords(getRequiredDescriptors(undefined, ns), {
|
|
282
|
+
useCache: true,
|
|
283
|
+
});
|
|
239
284
|
}
|
|
240
285
|
},
|
|
241
286
|
|
|
242
|
-
|
|
243
|
-
return
|
|
244
|
-
},
|
|
245
|
-
|
|
246
|
-
async loadRecord(descriptor: CacheDescriptor) {
|
|
247
|
-
return (await self.loadRecords([descriptor]))[0];
|
|
287
|
+
async loadRecord(descriptor: CacheDescriptor, options?: LoadOptions) {
|
|
288
|
+
return (await self.loadRecords([descriptor], options))[0]?.data;
|
|
248
289
|
},
|
|
249
290
|
|
|
250
291
|
isLoading(ns?: NsFallback) {
|
|
@@ -282,6 +323,19 @@ export function Controller({ options }: StateServiceProps) {
|
|
|
282
323
|
);
|
|
283
324
|
},
|
|
284
325
|
|
|
326
|
+
async loadRequired(options?: LoadRequiredOptions) {
|
|
327
|
+
if (!options?.language) {
|
|
328
|
+
await initializeLanguage();
|
|
329
|
+
}
|
|
330
|
+
const requiredRecords = getRequiredDescriptors(options?.language);
|
|
331
|
+
return self.loadRecords(requiredRecords, options);
|
|
332
|
+
},
|
|
333
|
+
|
|
334
|
+
async loadMatrix(options: LoadMatrixOptions) {
|
|
335
|
+
const records = getMatrixRecords(options);
|
|
336
|
+
return self.loadRecords(records, options);
|
|
337
|
+
},
|
|
338
|
+
|
|
285
339
|
run() {
|
|
286
340
|
checkCorrectConfiguration();
|
|
287
341
|
if (!state.isRunning()) {
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import { Subscription,
|
|
1
|
+
import { Subscription, Handler, ListenerEvent } from '../../types';
|
|
2
2
|
|
|
3
|
-
export
|
|
3
|
+
export const EventEmitter = <Event extends ListenerEvent<string, any>>(
|
|
4
|
+
type: Event['type'],
|
|
4
5
|
isActive: () => boolean
|
|
5
|
-
): EventEmitterInstance<
|
|
6
|
-
let handlers:
|
|
6
|
+
): EventEmitterInstance<Event> => {
|
|
7
|
+
let handlers: Handler<Event>[] = [];
|
|
7
8
|
|
|
8
|
-
return
|
|
9
|
-
listen(handler:
|
|
10
|
-
const handlerWrapper:
|
|
9
|
+
return {
|
|
10
|
+
listen(handler: (e: Event) => void): Subscription {
|
|
11
|
+
const handlerWrapper: Handler<Event> = (e) => {
|
|
11
12
|
handler(e);
|
|
12
13
|
};
|
|
13
14
|
|
|
@@ -19,15 +20,22 @@ export function EventEmitter<T>(
|
|
|
19
20
|
},
|
|
20
21
|
};
|
|
21
22
|
},
|
|
22
|
-
emit(data:
|
|
23
|
+
emit(data: Event['value']) {
|
|
23
24
|
if (isActive()) {
|
|
24
|
-
handlers.forEach((handler) =>
|
|
25
|
+
handlers.forEach((handler) =>
|
|
26
|
+
handler({ type: type, value: data } as Event)
|
|
27
|
+
);
|
|
25
28
|
}
|
|
26
29
|
},
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export type EventEmitterInstance<T> = {
|
|
31
|
-
readonly listen: (handler: Listener<T>) => Subscription;
|
|
32
|
-
readonly emit: (data: T) => void;
|
|
30
|
+
} as unknown as EventEmitterInstance<Event>;
|
|
33
31
|
};
|
|
32
|
+
|
|
33
|
+
export type EventEmitterInstance<Event> =
|
|
34
|
+
Event extends ListenerEvent<infer E, infer T>
|
|
35
|
+
? {
|
|
36
|
+
readonly listen: (
|
|
37
|
+
handler: Handler<ListenerEvent<E, T>>
|
|
38
|
+
) => Subscription;
|
|
39
|
+
readonly emit: (data: T) => void;
|
|
40
|
+
}
|
|
41
|
+
: never;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Subscription, ListenerEvent, CombinedHandler } from '../../types';
|
|
2
|
+
|
|
3
|
+
export function EventEmitterCombined<E extends ListenerEvent<string, any>>(
|
|
4
|
+
isActive: () => boolean
|
|
5
|
+
): EventEmitterCombinedInstance<E> {
|
|
6
|
+
let handlers: CombinedHandler<E>[] = [];
|
|
7
|
+
|
|
8
|
+
let queue: E[] = [];
|
|
9
|
+
|
|
10
|
+
// merge events in queue into one event
|
|
11
|
+
function solveQueue() {
|
|
12
|
+
if (queue.length === 0) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const queueCopy = queue;
|
|
16
|
+
queue = [];
|
|
17
|
+
handlers.forEach((handler) => {
|
|
18
|
+
handler(queueCopy);
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return Object.freeze({
|
|
23
|
+
listen(handler: (e: E[]) => void): Subscription {
|
|
24
|
+
const handlerWrapper: CombinedHandler<E> = (events) => {
|
|
25
|
+
handler(events);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
handlers.push(handlerWrapper);
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
unsubscribe() {
|
|
32
|
+
handlers = handlers.filter((i) => handlerWrapper !== i);
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
},
|
|
36
|
+
emit(e: E, delayed: boolean) {
|
|
37
|
+
if (isActive()) {
|
|
38
|
+
if (isActive()) {
|
|
39
|
+
queue.push(e);
|
|
40
|
+
if (!delayed) {
|
|
41
|
+
solveQueue();
|
|
42
|
+
} else {
|
|
43
|
+
setTimeout(solveQueue, 0);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export type EventEmitterCombinedInstance<E extends ListenerEvent<string, any>> =
|
|
52
|
+
{
|
|
53
|
+
readonly listen: (handler: CombinedHandler<E>) => Subscription;
|
|
54
|
+
readonly emit: (e: E, delayed: boolean) => void;
|
|
55
|
+
};
|