@tolgee/core 4.9.2 → 4.9.3-rc.23424f3.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 +1127 -7028
- 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.min.mjs +1 -1
- package/dist/tolgee.esm.min.mjs.map +1 -1
- package/dist/tolgee.esm.mjs +1125 -7023
- package/dist/tolgee.esm.mjs.map +1 -1
- package/dist/tolgee.umd.js +1127 -7028
- 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 +22 -0
- package/lib/Controller/Cache/helpers.d.ts +4 -0
- package/lib/Controller/Controller.d.ts +146 -0
- package/lib/Controller/Events/EventEmitter.d.ts +6 -0
- package/lib/Controller/Events/EventEmitterSelective.d.ts +15 -0
- package/lib/Controller/Events/Events.d.ts +50 -0
- package/lib/Controller/Plugins/Plugins.d.ts +36 -0
- package/lib/Controller/State/State.d.ts +39 -0
- package/lib/Controller/State/helpers.d.ts +6 -0
- package/lib/Controller/State/initObserverOptions.d.ts +13 -0
- package/lib/Controller/State/initState.d.ts +47 -0
- package/lib/Controller/ValueObserver.d.ts +5 -0
- package/lib/Tolgee.d.ts +2 -68
- package/lib/TranslateParams.d.ts +2 -0
- package/lib/{Constants/Global.d.ts → constants.d.ts} +1 -2
- package/lib/helpers.d.ts +3 -0
- package/lib/index.d.ts +3 -8
- package/lib/types.d.ts +244 -84
- package/package.json +20 -29
- package/src/Controller/Cache/Cache.ts +293 -0
- package/src/Controller/Cache/helpers.ts +37 -0
- package/src/Controller/Controller.ts +310 -0
- package/src/Controller/Events/EventEmitter.ts +27 -0
- package/src/Controller/Events/EventEmitterSelective.test.ts +125 -0
- package/src/Controller/Events/EventEmitterSelective.ts +179 -0
- package/src/Controller/Events/Events.ts +66 -0
- package/src/Controller/Plugins/Plugins.ts +315 -0
- package/src/Controller/State/State.ts +175 -0
- package/src/Controller/State/helpers.ts +41 -0
- package/src/Controller/State/initObserverOptions.ts +38 -0
- package/src/Controller/State/initState.ts +81 -0
- package/src/Controller/ValueObserver.ts +23 -0
- package/src/Tolgee.ts +79 -330
- package/src/TranslateParams.test.ts +41 -0
- package/src/TranslateParams.ts +51 -0
- package/src/__test/backend.test.ts +48 -0
- package/src/__test/cache.test.ts +148 -0
- package/src/__test/client.test.ts +48 -0
- package/src/__test/events.test.ts +33 -0
- package/src/__test/initialization.test.ts +85 -0
- package/src/__test/jest-setup.ts +2 -0
- package/src/__test/languageDetection.test.ts +129 -0
- package/src/__test/languageStorage.test.ts +145 -0
- package/src/__test/languages.test.ts +112 -0
- package/src/__test/loading.test.ts +39 -0
- package/src/__test/namespaces.test.ts +99 -0
- package/src/__test/namespacesFallback.test.ts +74 -0
- package/src/__test/plugins.test.ts +136 -0
- package/src/__test/testTools.ts +7 -0
- package/src/{Constants/Global.ts → constants.ts} +1 -3
- package/src/helpers.ts +17 -0
- package/src/index.ts +9 -8
- package/src/types.ts +338 -90
- package/README.md +0 -45
- package/dist/Constants/Global.d.ts +0 -6
- package/dist/Constants/ModifierKey.d.ts +0 -6
- package/dist/Errors/ApiHttpError.d.ts +0 -5
- package/dist/Observer.d.ts +0 -14
- package/dist/Observer.test.d.ts +0 -2
- package/dist/Properties.d.ts +0 -17
- package/dist/Properties.test.d.ts +0 -1
- package/dist/Tolgee.d.ts +0 -68
- package/dist/Tolgee.test.d.ts +0 -1
- package/dist/TolgeeConfig.d.ts +0 -69
- package/dist/TolgeeConfig.test.d.ts +0 -1
- package/dist/__integration/FormatterIcu.test.d.ts +0 -1
- package/dist/__integration/FormatterMissing.d.ts +0 -1
- package/dist/__integration/Tolgee.test.d.ts +0 -1
- package/dist/__integration/TolgeeInvisible.test.d.ts +0 -1
- package/dist/__integration/mockTranslations.d.ts +0 -7
- package/dist/__integration/testConfig.d.ts +0 -9
- package/dist/__testFixtures/classMock.d.ts +0 -3
- package/dist/__testFixtures/createElement.d.ts +0 -2
- package/dist/__testFixtures/createTestDom.d.ts +0 -9
- package/dist/__testFixtures/mocked.d.ts +0 -20
- package/dist/__testFixtures/setupAfterEnv.d.ts +0 -8
- package/dist/helpers/NodeHelper.d.ts +0 -14
- package/dist/helpers/TextHelper.d.ts +0 -5
- package/dist/helpers/TextHelper.test.d.ts +0 -1
- package/dist/helpers/commonTypes.d.ts +0 -2
- package/dist/helpers/encoderPolyfill.d.ts +0 -8
- package/dist/helpers/secret.d.ts +0 -6
- package/dist/helpers/secret.test.d.ts +0 -1
- package/dist/helpers/sleep.d.ts +0 -1
- package/dist/highlighter/HighlightFunctionsInitializer.d.ts +0 -10
- package/dist/highlighter/HighlightFunctionsInitializer.test.d.ts +0 -1
- package/dist/highlighter/MouseEventHandler.d.ts +0 -29
- package/dist/highlighter/MouseEventHandler.test.d.ts +0 -1
- package/dist/highlighter/TranslationHighlighter.d.ts +0 -14
- package/dist/highlighter/TranslationHighlighter.test.d.ts +0 -1
- package/dist/index.d.ts +0 -10
- package/dist/internal.d.ts +0 -2
- package/dist/modules/IcuFormatter.d.ts +0 -2
- package/dist/modules/IcuFormatter.test.d.ts +0 -1
- package/dist/modules/index.d.ts +0 -1
- package/dist/services/ApiHttpService.d.ts +0 -15
- package/dist/services/CoreService.d.ts +0 -18
- package/dist/services/CoreService.test.d.ts +0 -1
- package/dist/services/DependencyService.d.ts +0 -39
- package/dist/services/DependencyService.test.d.ts +0 -1
- package/dist/services/ElementRegistrar.d.ts +0 -19
- package/dist/services/ElementRegistrar.test.d.ts +0 -1
- package/dist/services/EventEmitter.d.ts +0 -13
- package/dist/services/EventService.d.ts +0 -9
- package/dist/services/ModuleService.d.ts +0 -5
- package/dist/services/ScreenshotService.d.ts +0 -15
- package/dist/services/Subscription.d.ts +0 -5
- package/dist/services/TextService.d.ts +0 -14
- package/dist/services/TextService.test.d.ts +0 -1
- package/dist/services/TranslationService.d.ts +0 -75
- package/dist/services/TranslationService.test.d.ts +0 -1
- package/dist/services/__mocks__/CoreService.d.ts +0 -2
- package/dist/toolsManager/Messages.d.ts +0 -8
- package/dist/toolsManager/Messages.test.d.ts +0 -1
- package/dist/toolsManager/PluginManager.d.ts +0 -21
- package/dist/toolsManager/PluginManager.test.d.ts +0 -1
- package/dist/types/DTOs.d.ts +0 -20
- package/dist/types/apiSchema.generated.d.ts +0 -6185
- package/dist/types.d.ts +0 -123
- package/dist/wrappers/AbstractWrapper.d.ts +0 -8
- package/dist/wrappers/NodeHandler.d.ts +0 -18
- package/dist/wrappers/WrappedHandler.d.ts +0 -8
- package/dist/wrappers/invisible/AttributeHandler.d.ts +0 -8
- package/dist/wrappers/invisible/Coder.d.ts +0 -7
- package/dist/wrappers/invisible/ContentHandler.d.ts +0 -6
- package/dist/wrappers/invisible/CoreHandler.d.ts +0 -10
- package/dist/wrappers/invisible/InvisibleWrapper.d.ts +0 -18
- package/dist/wrappers/invisible/ValueMemory.d.ts +0 -5
- package/dist/wrappers/invisible/ValueMemory.test.d.ts +0 -1
- package/dist/wrappers/text/AttributeHandler.d.ts +0 -8
- package/dist/wrappers/text/AttributeHandler.test.d.ts +0 -1
- package/dist/wrappers/text/Coder.d.ts +0 -15
- package/dist/wrappers/text/Coder.test.d.ts +0 -1
- package/dist/wrappers/text/ContentHandler.d.ts +0 -8
- package/dist/wrappers/text/ContentHandler.test.d.ts +0 -1
- package/dist/wrappers/text/CoreHandler.d.ts +0 -17
- package/dist/wrappers/text/CoreHandler.test.d.ts +0 -1
- package/dist/wrappers/text/TextWrapper.d.ts +0 -20
- package/index.js +0 -7
- package/lib/Constants/ModifierKey.d.ts +0 -6
- package/lib/Errors/ApiHttpError.d.ts +0 -5
- package/lib/Observer.d.ts +0 -14
- package/lib/Properties.d.ts +0 -17
- package/lib/TolgeeConfig.d.ts +0 -69
- package/lib/helpers/NodeHelper.d.ts +0 -14
- package/lib/helpers/TextHelper.d.ts +0 -5
- package/lib/helpers/commonTypes.d.ts +0 -2
- package/lib/helpers/encoderPolyfill.d.ts +0 -8
- package/lib/helpers/secret.d.ts +0 -6
- package/lib/helpers/sleep.d.ts +0 -1
- package/lib/highlighter/HighlightFunctionsInitializer.d.ts +0 -10
- package/lib/highlighter/MouseEventHandler.d.ts +0 -29
- package/lib/highlighter/TranslationHighlighter.d.ts +0 -14
- package/lib/modules/IcuFormatter.d.ts +0 -2
- package/lib/modules/index.d.ts +0 -1
- package/lib/services/ApiHttpService.d.ts +0 -15
- package/lib/services/CoreService.d.ts +0 -18
- package/lib/services/DependencyService.d.ts +0 -39
- package/lib/services/ElementRegistrar.d.ts +0 -19
- package/lib/services/EventEmitter.d.ts +0 -13
- package/lib/services/EventService.d.ts +0 -9
- package/lib/services/ModuleService.d.ts +0 -5
- package/lib/services/ScreenshotService.d.ts +0 -15
- package/lib/services/Subscription.d.ts +0 -5
- package/lib/services/TextService.d.ts +0 -14
- package/lib/services/TranslationService.d.ts +0 -75
- package/lib/toolsManager/Messages.d.ts +0 -8
- package/lib/toolsManager/PluginManager.d.ts +0 -21
- package/lib/types/DTOs.d.ts +0 -20
- package/lib/types/apiSchema.generated.d.ts +0 -6185
- package/lib/wrappers/AbstractWrapper.d.ts +0 -8
- package/lib/wrappers/NodeHandler.d.ts +0 -18
- package/lib/wrappers/WrappedHandler.d.ts +0 -8
- package/lib/wrappers/invisible/AttributeHandler.d.ts +0 -8
- package/lib/wrappers/invisible/Coder.d.ts +0 -7
- package/lib/wrappers/invisible/ContentHandler.d.ts +0 -6
- package/lib/wrappers/invisible/CoreHandler.d.ts +0 -10
- package/lib/wrappers/invisible/InvisibleWrapper.d.ts +0 -18
- package/lib/wrappers/invisible/ValueMemory.d.ts +0 -5
- package/lib/wrappers/text/AttributeHandler.d.ts +0 -8
- package/lib/wrappers/text/Coder.d.ts +0 -15
- package/lib/wrappers/text/ContentHandler.d.ts +0 -8
- package/lib/wrappers/text/CoreHandler.d.ts +0 -17
- package/lib/wrappers/text/TextWrapper.d.ts +0 -20
- package/src/Constants/ModifierKey.ts +0 -6
- package/src/Errors/ApiHttpError.ts +0 -8
- package/src/Observer.test.ts +0 -119
- package/src/Observer.ts +0 -68
- package/src/Properties.test.ts +0 -150
- package/src/Properties.ts +0 -112
- package/src/Tolgee.test.ts +0 -473
- package/src/TolgeeConfig.test.ts +0 -21
- package/src/TolgeeConfig.ts +0 -134
- package/src/__integration/FormatterIcu.test.ts +0 -80
- package/src/__integration/FormatterMissing.ts +0 -54
- package/src/__integration/Tolgee.test.ts +0 -90
- package/src/__integration/TolgeeInvisible.test.ts +0 -145
- package/src/__integration/mockTranslations.ts +0 -6
- package/src/__integration/testConfig.ts +0 -16
- package/src/__testFixtures/classMock.ts +0 -11
- package/src/__testFixtures/createElement.ts +0 -43
- package/src/__testFixtures/createTestDom.ts +0 -26
- package/src/__testFixtures/mocked.ts +0 -25
- package/src/__testFixtures/setupAfterEnv.ts +0 -34
- package/src/helpers/NodeHelper.ts +0 -90
- package/src/helpers/TextHelper.test.ts +0 -62
- package/src/helpers/TextHelper.ts +0 -58
- package/src/helpers/commonTypes.ts +0 -8
- package/src/helpers/encoderPolyfill.ts +0 -96
- package/src/helpers/secret.test.ts +0 -61
- package/src/helpers/secret.ts +0 -68
- package/src/helpers/sleep.ts +0 -2
- package/src/highlighter/HighlightFunctionsInitializer.test.ts +0 -40
- package/src/highlighter/HighlightFunctionsInitializer.ts +0 -61
- package/src/highlighter/MouseEventHandler.test.ts +0 -151
- package/src/highlighter/MouseEventHandler.ts +0 -191
- package/src/highlighter/TranslationHighlighter.test.ts +0 -177
- package/src/highlighter/TranslationHighlighter.ts +0 -113
- package/src/internal.ts +0 -2
- package/src/modules/IcuFormatter.test.ts +0 -21
- package/src/modules/IcuFormatter.ts +0 -39
- package/src/modules/index.ts +0 -1
- package/src/services/ApiHttpService.ts +0 -85
- package/src/services/CoreService.test.ts +0 -141
- package/src/services/CoreService.ts +0 -76
- package/src/services/DependencyService.test.ts +0 -51
- package/src/services/DependencyService.ts +0 -116
- package/src/services/ElementRegistrar.test.ts +0 -131
- package/src/services/ElementRegistrar.ts +0 -108
- package/src/services/EventEmitter.ts +0 -52
- package/src/services/EventService.ts +0 -14
- package/src/services/ModuleService.ts +0 -14
- package/src/services/ScreenshotService.ts +0 -31
- package/src/services/Subscription.ts +0 -7
- package/src/services/TextService.test.ts +0 -88
- package/src/services/TextService.ts +0 -82
- package/src/services/TranslationService.test.ts +0 -358
- package/src/services/TranslationService.ts +0 -417
- package/src/services/__mocks__/CoreService.ts +0 -17
- package/src/toolsManager/Messages.test.ts +0 -79
- package/src/toolsManager/Messages.ts +0 -60
- package/src/toolsManager/PluginManager.test.ts +0 -108
- package/src/toolsManager/PluginManager.ts +0 -129
- package/src/types/DTOs.ts +0 -25
- package/src/types/apiSchema.generated.ts +0 -6208
- package/src/wrappers/AbstractWrapper.ts +0 -14
- package/src/wrappers/NodeHandler.ts +0 -143
- package/src/wrappers/WrappedHandler.ts +0 -28
- package/src/wrappers/invisible/AttributeHandler.ts +0 -23
- package/src/wrappers/invisible/Coder.ts +0 -65
- package/src/wrappers/invisible/ContentHandler.ts +0 -15
- package/src/wrappers/invisible/CoreHandler.ts +0 -17
- package/src/wrappers/invisible/InvisibleWrapper.ts +0 -59
- package/src/wrappers/invisible/ValueMemory.test.ts +0 -25
- package/src/wrappers/invisible/ValueMemory.ts +0 -16
- package/src/wrappers/text/AttributeHandler.test.ts +0 -118
- package/src/wrappers/text/AttributeHandler.ts +0 -25
- package/src/wrappers/text/Coder.test.ts +0 -298
- package/src/wrappers/text/Coder.ts +0 -202
- package/src/wrappers/text/ContentHandler.test.ts +0 -185
- package/src/wrappers/text/ContentHandler.ts +0 -21
- package/src/wrappers/text/CoreHandler.test.ts +0 -106
- package/src/wrappers/text/CoreHandler.ts +0 -45
- package/src/wrappers/text/TextWrapper.ts +0 -69
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CacheAsyncRequests,
|
|
3
|
+
CacheDescriptor,
|
|
4
|
+
CacheDescriptorInternal,
|
|
5
|
+
CacheDescriptorWithKey,
|
|
6
|
+
EventEmitterType,
|
|
7
|
+
FallbackNSTranslation,
|
|
8
|
+
Options,
|
|
9
|
+
TranslationsFlat,
|
|
10
|
+
TranslationValue,
|
|
11
|
+
TreeTranslationsData,
|
|
12
|
+
BackendGetRecord,
|
|
13
|
+
BackendGetDevRecord,
|
|
14
|
+
} from '../../types';
|
|
15
|
+
import { getFallbackArray } from '../State/helpers';
|
|
16
|
+
import { ValueObserverInstance } from '../ValueObserver';
|
|
17
|
+
|
|
18
|
+
import { decodeCacheKey, encodeCacheKey, flattenTranslations } from './helpers';
|
|
19
|
+
|
|
20
|
+
type CacheRecord = {
|
|
21
|
+
version: number;
|
|
22
|
+
data: TranslationsFlat;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
type StateCache = Map<string, CacheRecord>;
|
|
26
|
+
|
|
27
|
+
export const Cache = (
|
|
28
|
+
onCacheChange: EventEmitterType<CacheDescriptorWithKey>,
|
|
29
|
+
backendGetRecord: BackendGetRecord,
|
|
30
|
+
backendGetDevRecord: BackendGetDevRecord,
|
|
31
|
+
withDefaultNs: (descriptor: CacheDescriptor) => CacheDescriptorInternal,
|
|
32
|
+
isInitialLoading: () => boolean,
|
|
33
|
+
fetchingObserver: ValueObserverInstance<boolean>,
|
|
34
|
+
loadingObserver: ValueObserverInstance<boolean>
|
|
35
|
+
) => {
|
|
36
|
+
const asyncRequests: CacheAsyncRequests = new Map();
|
|
37
|
+
const cache: StateCache = new Map();
|
|
38
|
+
let staticData: NonNullable<Options['staticData']> = {};
|
|
39
|
+
let version = 0;
|
|
40
|
+
|
|
41
|
+
function addStaticData(data: Options['staticData']) {
|
|
42
|
+
if (data) {
|
|
43
|
+
staticData = { ...staticData, ...data };
|
|
44
|
+
Object.entries(data).forEach(([key, value]) => {
|
|
45
|
+
if (typeof value !== 'function') {
|
|
46
|
+
const descriptor = decodeCacheKey(key);
|
|
47
|
+
const existing = cache.get(key);
|
|
48
|
+
if (!existing || existing.version === 0) {
|
|
49
|
+
addRecordInternal(descriptor, value, 0);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function invalidate() {
|
|
57
|
+
asyncRequests.clear();
|
|
58
|
+
version += 1;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function addRecordInternal(
|
|
62
|
+
descriptor: CacheDescriptorInternal,
|
|
63
|
+
data: TreeTranslationsData,
|
|
64
|
+
recordVersion: number
|
|
65
|
+
) {
|
|
66
|
+
const cacheKey = encodeCacheKey(descriptor);
|
|
67
|
+
cache.set(cacheKey, {
|
|
68
|
+
data: flattenTranslations(data),
|
|
69
|
+
version: recordVersion,
|
|
70
|
+
});
|
|
71
|
+
onCacheChange.emit(descriptor);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function addRecord(
|
|
75
|
+
descriptor: CacheDescriptorInternal,
|
|
76
|
+
data: TreeTranslationsData
|
|
77
|
+
) {
|
|
78
|
+
addRecordInternal(descriptor, data, version);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function exists(descriptor: CacheDescriptorInternal, strict = false) {
|
|
82
|
+
const record = cache.get(encodeCacheKey(descriptor));
|
|
83
|
+
if (record && strict) {
|
|
84
|
+
return record.version === version;
|
|
85
|
+
}
|
|
86
|
+
return Boolean(record);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function getRecord(descriptor: CacheDescriptor) {
|
|
90
|
+
return cache.get(encodeCacheKey(withDefaultNs(descriptor)))?.data;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function getTranslation(descriptor: CacheDescriptorInternal, key: string) {
|
|
94
|
+
return cache.get(encodeCacheKey(descriptor))?.data.get(key);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function getTranslationNs(
|
|
98
|
+
namespaces: string[],
|
|
99
|
+
languages: string[],
|
|
100
|
+
key: string
|
|
101
|
+
) {
|
|
102
|
+
for (const namespace of namespaces) {
|
|
103
|
+
for (const language of languages) {
|
|
104
|
+
const value = cache
|
|
105
|
+
.get(encodeCacheKey({ language, namespace }))
|
|
106
|
+
?.data.get(key);
|
|
107
|
+
if (value !== undefined && value !== null) {
|
|
108
|
+
return namespace;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return Array.from(new Set(namespaces));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function getTranslationFallback(
|
|
116
|
+
namespaces: string[],
|
|
117
|
+
languages: string[],
|
|
118
|
+
key: string
|
|
119
|
+
) {
|
|
120
|
+
for (const namespace of namespaces) {
|
|
121
|
+
for (const language of languages) {
|
|
122
|
+
const value = cache
|
|
123
|
+
.get(encodeCacheKey({ language, namespace }))
|
|
124
|
+
?.data.get(key);
|
|
125
|
+
if (value !== undefined && value !== null) {
|
|
126
|
+
return value;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return undefined;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function changeTranslation(
|
|
134
|
+
descriptor: CacheDescriptorInternal,
|
|
135
|
+
key: string,
|
|
136
|
+
value: TranslationValue
|
|
137
|
+
) {
|
|
138
|
+
const record = cache.get(encodeCacheKey(descriptor))?.data;
|
|
139
|
+
record?.set(key, value);
|
|
140
|
+
onCacheChange.emit({ ...descriptor, key });
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function isFetching(ns?: FallbackNSTranslation) {
|
|
144
|
+
if (isInitialLoading()) {
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (ns === undefined) {
|
|
149
|
+
return asyncRequests.size > 0;
|
|
150
|
+
}
|
|
151
|
+
const namespaces = getFallbackArray(ns);
|
|
152
|
+
return Boolean(
|
|
153
|
+
Array.from(asyncRequests.keys()).find((key) =>
|
|
154
|
+
namespaces.includes(decodeCacheKey(key).namespace)
|
|
155
|
+
)
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function isLoading(language: string | undefined, ns?: FallbackNSTranslation) {
|
|
160
|
+
const namespaces = getFallbackArray(ns);
|
|
161
|
+
|
|
162
|
+
return Boolean(
|
|
163
|
+
isInitialLoading() ||
|
|
164
|
+
Array.from(asyncRequests.keys()).find((key) => {
|
|
165
|
+
const descriptor = decodeCacheKey(key);
|
|
166
|
+
return (
|
|
167
|
+
(!namespaces.length || namespaces.includes(descriptor.namespace)) &&
|
|
168
|
+
!exists({
|
|
169
|
+
namespace: descriptor.namespace,
|
|
170
|
+
language: language!,
|
|
171
|
+
})
|
|
172
|
+
);
|
|
173
|
+
})
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function fetchNormal(keyObject: CacheDescriptorInternal) {
|
|
178
|
+
let dataPromise = undefined as
|
|
179
|
+
| Promise<TreeTranslationsData | undefined>
|
|
180
|
+
| undefined;
|
|
181
|
+
if (!dataPromise) {
|
|
182
|
+
const staticDataValue = staticData[encodeCacheKey(keyObject)];
|
|
183
|
+
if (typeof staticDataValue === 'function') {
|
|
184
|
+
dataPromise = staticDataValue();
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (!dataPromise) {
|
|
189
|
+
dataPromise = backendGetRecord(keyObject);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (!dataPromise) {
|
|
193
|
+
// return empty data, so we know it has already been attempted to fetch
|
|
194
|
+
dataPromise = Promise.resolve({});
|
|
195
|
+
}
|
|
196
|
+
return dataPromise;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function fetchData(keyObject: CacheDescriptorInternal, isDev: boolean) {
|
|
200
|
+
let dataPromise = undefined as
|
|
201
|
+
| Promise<TreeTranslationsData | undefined>
|
|
202
|
+
| undefined;
|
|
203
|
+
if (isDev) {
|
|
204
|
+
dataPromise = backendGetDevRecord(keyObject)?.catch(() => {
|
|
205
|
+
// eslint-disable-next-line no-console
|
|
206
|
+
console.warn(`Tolgee: Failed to fetch data from dev backend`);
|
|
207
|
+
// fallback to normal fetch if dev fails
|
|
208
|
+
return fetchNormal(keyObject);
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (!dataPromise) {
|
|
213
|
+
dataPromise = fetchNormal(keyObject);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return dataPromise;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async function loadRecords(descriptors: CacheDescriptor[], isDev: boolean) {
|
|
220
|
+
const withPromises = descriptors.map((descriptor) => {
|
|
221
|
+
const keyObject = withDefaultNs(descriptor);
|
|
222
|
+
const cacheKey = encodeCacheKey(keyObject);
|
|
223
|
+
const existingPromise = asyncRequests.get(cacheKey);
|
|
224
|
+
|
|
225
|
+
if (existingPromise) {
|
|
226
|
+
return {
|
|
227
|
+
new: false,
|
|
228
|
+
promise: existingPromise,
|
|
229
|
+
keyObject,
|
|
230
|
+
cacheKey,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
const dataPromise = fetchData(keyObject, isDev);
|
|
234
|
+
asyncRequests.set(cacheKey, dataPromise);
|
|
235
|
+
return {
|
|
236
|
+
new: true,
|
|
237
|
+
promise: dataPromise,
|
|
238
|
+
keyObject,
|
|
239
|
+
cacheKey,
|
|
240
|
+
};
|
|
241
|
+
});
|
|
242
|
+
fetchingObserver.notify();
|
|
243
|
+
loadingObserver.notify();
|
|
244
|
+
|
|
245
|
+
const results = await Promise.all(withPromises.map((val) => val.promise));
|
|
246
|
+
|
|
247
|
+
withPromises.forEach((value, i) => {
|
|
248
|
+
const promiseChanged =
|
|
249
|
+
asyncRequests.get(value.cacheKey) !== value.promise;
|
|
250
|
+
// if promise has changed in between, it means cache been invalidated or
|
|
251
|
+
// new data are being fetched
|
|
252
|
+
if (value.new && !promiseChanged) {
|
|
253
|
+
asyncRequests.delete(value.cacheKey);
|
|
254
|
+
const data = results[i];
|
|
255
|
+
if (data) {
|
|
256
|
+
addRecord(value.keyObject, data);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
fetchingObserver.notify();
|
|
261
|
+
loadingObserver.notify();
|
|
262
|
+
|
|
263
|
+
return withPromises.map((val) => getRecord(val.keyObject)!);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function getAllRecords() {
|
|
267
|
+
const entries = Array.from(cache.entries());
|
|
268
|
+
return entries.map(([key, entry]) => {
|
|
269
|
+
return {
|
|
270
|
+
...decodeCacheKey(key),
|
|
271
|
+
data: entry.data,
|
|
272
|
+
};
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return Object.freeze({
|
|
277
|
+
addStaticData,
|
|
278
|
+
invalidate,
|
|
279
|
+
addRecord,
|
|
280
|
+
exists,
|
|
281
|
+
getRecord,
|
|
282
|
+
getTranslation,
|
|
283
|
+
getTranslationNs,
|
|
284
|
+
getTranslationFallback,
|
|
285
|
+
changeTranslation,
|
|
286
|
+
isFetching,
|
|
287
|
+
isLoading,
|
|
288
|
+
loadRecords,
|
|
289
|
+
getAllRecords,
|
|
290
|
+
});
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
export type CacheType = ReturnType<typeof Cache>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { CacheDescriptorInternal, TreeTranslationsData } from '../../types';
|
|
2
|
+
|
|
3
|
+
export const flattenTranslations = (
|
|
4
|
+
data: TreeTranslationsData
|
|
5
|
+
): Map<string, string> => {
|
|
6
|
+
const result: Map<string, string> = new Map();
|
|
7
|
+
Object.entries(data).forEach(([key, value]) => {
|
|
8
|
+
// ignore empty values
|
|
9
|
+
if (value === undefined || value === null) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
if (typeof value === 'object') {
|
|
13
|
+
flattenTranslations(value).forEach((flatValue, flatKey) => {
|
|
14
|
+
result.set(key + '.' + flatKey, flatValue);
|
|
15
|
+
});
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
result.set(key, value as string);
|
|
19
|
+
});
|
|
20
|
+
return result;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const decodeCacheKey = (key: string): CacheDescriptorInternal => {
|
|
24
|
+
const [firstPart, secondPart] = key.split(':');
|
|
25
|
+
return { language: firstPart, namespace: secondPart || '' };
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const encodeCacheKey = ({
|
|
29
|
+
language,
|
|
30
|
+
namespace,
|
|
31
|
+
}: CacheDescriptorInternal) => {
|
|
32
|
+
if (namespace) {
|
|
33
|
+
return `${language}:${namespace}`;
|
|
34
|
+
} else {
|
|
35
|
+
return language;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import { Events } from './Events/Events';
|
|
2
|
+
import {
|
|
3
|
+
CacheDescriptor,
|
|
4
|
+
FallbackNSTranslation,
|
|
5
|
+
Options,
|
|
6
|
+
TFnType,
|
|
7
|
+
TranslatePropsInternal,
|
|
8
|
+
} from '../types';
|
|
9
|
+
import { Cache } from './Cache/Cache';
|
|
10
|
+
import { getFallbackArray } from './State/helpers';
|
|
11
|
+
import { PluginService } from './Plugins/Plugins';
|
|
12
|
+
import { ValueObserver } from './ValueObserver';
|
|
13
|
+
import { State } from './State/State';
|
|
14
|
+
import { isPromise, missingOptionError, valueOrPromise } from '../helpers';
|
|
15
|
+
import { getTranslateParams } from '../TranslateParams';
|
|
16
|
+
|
|
17
|
+
type StateServiceProps = {
|
|
18
|
+
options?: Partial<Options>;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const Controller = ({ options }: StateServiceProps) => {
|
|
22
|
+
const events = Events(getFallbackNamespaces);
|
|
23
|
+
const fetchingObserver = ValueObserver<boolean>(
|
|
24
|
+
false,
|
|
25
|
+
() => cache.isFetching(),
|
|
26
|
+
events.onFetchingChange.emit
|
|
27
|
+
);
|
|
28
|
+
const loadingObserver = ValueObserver<boolean>(
|
|
29
|
+
false,
|
|
30
|
+
() => isLoading(),
|
|
31
|
+
events.onLoadingChange.emit
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
const state = State(
|
|
35
|
+
events.onLanguageChange,
|
|
36
|
+
events.onPendingLanguageChange,
|
|
37
|
+
events.onRunningChange
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const pluginService = PluginService(
|
|
41
|
+
state.getLanguage,
|
|
42
|
+
state.getInitialOptions,
|
|
43
|
+
state.getObserverOptions,
|
|
44
|
+
state.getAvailableLanguages,
|
|
45
|
+
getTranslationNs,
|
|
46
|
+
getTranslation,
|
|
47
|
+
changeTranslation
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const cache = Cache(
|
|
51
|
+
events.onCacheChange,
|
|
52
|
+
pluginService.getBackendRecord,
|
|
53
|
+
pluginService.getBackendDevRecord,
|
|
54
|
+
state.withDefaultNs,
|
|
55
|
+
state.isInitialLoading,
|
|
56
|
+
fetchingObserver,
|
|
57
|
+
loadingObserver
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
if (options) {
|
|
61
|
+
init(options);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
events.onKeyUpdate.listen(() => {
|
|
65
|
+
if (state.isRunning()) {
|
|
66
|
+
pluginService.retranslate();
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
function changeTranslation(
|
|
71
|
+
descriptor: CacheDescriptor,
|
|
72
|
+
key: string,
|
|
73
|
+
value: string
|
|
74
|
+
) {
|
|
75
|
+
const keyObject = state.withDefaultNs(descriptor);
|
|
76
|
+
const previousValue = cache.getTranslation(keyObject, key);
|
|
77
|
+
cache.changeTranslation(keyObject, key, value);
|
|
78
|
+
return {
|
|
79
|
+
revert: () => {
|
|
80
|
+
cache.changeTranslation(keyObject, key, previousValue);
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function getFallbackNamespaces() {
|
|
86
|
+
return state.getFallbackNamespaces();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function init(options: Partial<Options>) {
|
|
90
|
+
state.init(options);
|
|
91
|
+
cache.addStaticData(state.getInitialOptions().staticData);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function isLoading(ns?: FallbackNSTranslation) {
|
|
95
|
+
return cache.isLoading(state.getLanguage()!, ns);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function isDev() {
|
|
99
|
+
return Boolean(
|
|
100
|
+
state.getInitialOptions().apiKey &&
|
|
101
|
+
state.getInitialOptions().apiUrl &&
|
|
102
|
+
pluginService.getDevBackend()
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function addActiveNs(ns: FallbackNSTranslation, forget?: boolean) {
|
|
107
|
+
if (!forget) {
|
|
108
|
+
state.addActiveNs(ns);
|
|
109
|
+
}
|
|
110
|
+
if (state.isRunning()) {
|
|
111
|
+
await loadRequiredRecords(undefined, ns);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function getRequiredRecords(lang?: string, ns?: FallbackNSTranslation) {
|
|
116
|
+
const languages = state.getFallbackLangs(lang);
|
|
117
|
+
const namespaces =
|
|
118
|
+
ns !== undefined ? getFallbackArray(ns) : state.getRequiredNamespaces();
|
|
119
|
+
const result: CacheDescriptor[] = [];
|
|
120
|
+
languages.forEach((language) => {
|
|
121
|
+
namespaces.forEach((namespace) => {
|
|
122
|
+
if (!cache.exists({ language, namespace }, true)) {
|
|
123
|
+
result.push({ language, namespace });
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function isLoaded(ns?: FallbackNSTranslation) {
|
|
131
|
+
const language = state.getLanguage();
|
|
132
|
+
if (!language) {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
const languages = state.getFallbackLangs(language);
|
|
136
|
+
const namespaces =
|
|
137
|
+
ns !== undefined ? getFallbackArray(ns) : state.getRequiredNamespaces();
|
|
138
|
+
const result: CacheDescriptor[] = [];
|
|
139
|
+
languages.forEach((language) => {
|
|
140
|
+
namespaces.forEach((namespace) => {
|
|
141
|
+
if (!cache.exists({ language, namespace })) {
|
|
142
|
+
result.push({ language, namespace });
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
return result.length === 0;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function loadRequiredRecords(lang?: string, ns?: FallbackNSTranslation) {
|
|
150
|
+
const descriptors = getRequiredRecords(lang, ns);
|
|
151
|
+
if (descriptors.length) {
|
|
152
|
+
return valueOrPromise(loadRecords(descriptors), () => {});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async function changeLanguage(language: string) {
|
|
157
|
+
if (
|
|
158
|
+
state.getPendingLanguage() === language &&
|
|
159
|
+
state.getLanguage() === language
|
|
160
|
+
) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
state.setPendingLanguage(language);
|
|
164
|
+
|
|
165
|
+
if (state.isRunning()) {
|
|
166
|
+
await loadRequiredRecords(language);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (language === state.getPendingLanguage()) {
|
|
170
|
+
// there might be parallel language change
|
|
171
|
+
// we only want to apply latest
|
|
172
|
+
state.setLanguage(language);
|
|
173
|
+
pluginService.setStoredLanguage(language);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function getTranslationNs({
|
|
178
|
+
key,
|
|
179
|
+
ns,
|
|
180
|
+
}: Pick<TranslatePropsInternal, 'key' | 'ns'>) {
|
|
181
|
+
const namespaces =
|
|
182
|
+
ns !== undefined ? getFallbackArray(ns) : state.getFallbackNamespaces();
|
|
183
|
+
const languages = state.getFallbackLangs();
|
|
184
|
+
return cache.getTranslationNs(namespaces, languages, key);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function getTranslation({
|
|
188
|
+
key,
|
|
189
|
+
ns,
|
|
190
|
+
}: Pick<TranslatePropsInternal, 'key' | 'ns'>) {
|
|
191
|
+
const namespaces =
|
|
192
|
+
ns !== undefined ? getFallbackArray(ns) : state.getFallbackNamespaces();
|
|
193
|
+
const languages = state.getFallbackLangs();
|
|
194
|
+
return cache.getTranslationFallback(namespaces, languages, key);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function loadInitial() {
|
|
198
|
+
const data = valueOrPromise(initializeLanguage(), () => {
|
|
199
|
+
// fail if there is no language
|
|
200
|
+
return loadRequiredRecords();
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
if (isPromise(data)) {
|
|
204
|
+
state.setInitialLoading(true);
|
|
205
|
+
fetchingObserver.notify();
|
|
206
|
+
loadingObserver.notify();
|
|
207
|
+
return Promise.resolve(data).then(() => {
|
|
208
|
+
state.setInitialLoading(false);
|
|
209
|
+
fetchingObserver.notify();
|
|
210
|
+
loadingObserver.notify();
|
|
211
|
+
events.onInitialLoaded.emit();
|
|
212
|
+
});
|
|
213
|
+
} else {
|
|
214
|
+
events.onInitialLoaded.emit();
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function initializeLanguage() {
|
|
219
|
+
const existingLanguage = state.getLanguage();
|
|
220
|
+
if (existingLanguage) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (!state.getInitialOptions().defaultLanguage) {
|
|
224
|
+
throw new Error(missingOptionError('defaultLanguage'));
|
|
225
|
+
}
|
|
226
|
+
const languageOrPromise = pluginService.getInitialLanguage();
|
|
227
|
+
return valueOrPromise(languageOrPromise, (lang) => {
|
|
228
|
+
const language =
|
|
229
|
+
(lang as string | undefined) ||
|
|
230
|
+
state.getInitialOptions().defaultLanguage;
|
|
231
|
+
language && state.setLanguage(language);
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async function loadRecord(descriptor: CacheDescriptor) {
|
|
236
|
+
return (await loadRecords([descriptor]))[0];
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function loadRecords(descriptors: CacheDescriptor[]) {
|
|
240
|
+
return cache.loadRecords(descriptors, isDev());
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const checkCorrectConfiguration = () => {
|
|
244
|
+
const languageDetector = pluginService.getLanguageDetector();
|
|
245
|
+
if (languageDetector) {
|
|
246
|
+
const availableLanguages = state.getAvailableLanguages();
|
|
247
|
+
if (!availableLanguages) {
|
|
248
|
+
throw new Error(missingOptionError('availableLanguages'));
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
if (!state.getLanguage() && !state.getInitialOptions().defaultLanguage) {
|
|
252
|
+
if (languageDetector) {
|
|
253
|
+
throw new Error(missingOptionError('defaultLanguage'));
|
|
254
|
+
} else {
|
|
255
|
+
throw new Error(missingOptionError('language'));
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
function run() {
|
|
261
|
+
let result: Promise<void> | undefined = undefined;
|
|
262
|
+
checkCorrectConfiguration();
|
|
263
|
+
if (!state.isRunning()) {
|
|
264
|
+
if (isDev()) {
|
|
265
|
+
cache.invalidate();
|
|
266
|
+
}
|
|
267
|
+
state.setRunning(true);
|
|
268
|
+
pluginService.run(isDev());
|
|
269
|
+
result = loadInitial();
|
|
270
|
+
}
|
|
271
|
+
return Promise.resolve(result);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function stop() {
|
|
275
|
+
if (state.isRunning()) {
|
|
276
|
+
pluginService.stop();
|
|
277
|
+
state.setRunning(false);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const t: TFnType = (...args) => {
|
|
282
|
+
// @ts-ignore
|
|
283
|
+
const params = getTranslateParams(...args);
|
|
284
|
+
const translation = getTranslation(params);
|
|
285
|
+
return pluginService.formatTranslation({ ...params, translation });
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
return Object.freeze({
|
|
289
|
+
...events,
|
|
290
|
+
...state,
|
|
291
|
+
...pluginService,
|
|
292
|
+
...cache,
|
|
293
|
+
init,
|
|
294
|
+
changeLanguage,
|
|
295
|
+
getTranslation,
|
|
296
|
+
changeTranslation,
|
|
297
|
+
addActiveNs,
|
|
298
|
+
loadRequiredRecords,
|
|
299
|
+
loadRecords,
|
|
300
|
+
loadRecord,
|
|
301
|
+
isLoading,
|
|
302
|
+
isLoaded,
|
|
303
|
+
t,
|
|
304
|
+
isDev,
|
|
305
|
+
run,
|
|
306
|
+
stop,
|
|
307
|
+
});
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
export type StateServiceType = ReturnType<typeof Controller>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Listener, ListenerHandler } from '../../types';
|
|
2
|
+
|
|
3
|
+
export const EventEmitter = <T>() => {
|
|
4
|
+
let handlers: ListenerHandler<T>[] = [];
|
|
5
|
+
|
|
6
|
+
const listen = (handler: ListenerHandler<T>): Listener => {
|
|
7
|
+
const handlerWrapper: ListenerHandler<T> = (e) => {
|
|
8
|
+
handler(e);
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
handlers.push(handlerWrapper);
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
unsubscribe: () => {
|
|
15
|
+
handlers = handlers.filter((i) => handlerWrapper !== i);
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const emit = (data: T) => {
|
|
21
|
+
handlers.forEach((handler) => handler({ value: data }));
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
return Object.freeze({ listen, emit });
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type EventEmitterType<T> = ReturnType<typeof EventEmitter<T>>;
|