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