@tolgee/web 4.9.3-rc.04b22e6.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 (206) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +31 -0
  3. package/dist/tolgee-backend-fetch.cjs.min.js +2 -0
  4. package/dist/tolgee-backend-fetch.cjs.min.js.map +1 -0
  5. package/dist/tolgee-backend-fetch.esm.min.mjs +2 -0
  6. package/dist/tolgee-backend-fetch.esm.min.mjs.map +1 -0
  7. package/dist/tolgee-backend-fetch.umd.min.js +2 -0
  8. package/dist/tolgee-backend-fetch.umd.min.js.map +1 -0
  9. package/dist/tolgee-context-ui.cjs.min.js +143 -0
  10. package/dist/tolgee-context-ui.cjs.min.js.map +1 -0
  11. package/dist/tolgee-context-ui.esm.min.mjs +143 -0
  12. package/dist/tolgee-context-ui.esm.min.mjs.map +1 -0
  13. package/dist/tolgee-context-ui.umd.min.js +143 -0
  14. package/dist/tolgee-context-ui.umd.min.js.map +1 -0
  15. package/dist/tolgee-in-context-tools.cjs.min.js +143 -0
  16. package/dist/tolgee-in-context-tools.cjs.min.js.map +1 -0
  17. package/dist/tolgee-in-context-tools.esm.min.mjs +143 -0
  18. package/dist/tolgee-in-context-tools.esm.min.mjs.map +1 -0
  19. package/dist/tolgee-in-context-tools.umd.min.js +143 -0
  20. package/dist/tolgee-in-context-tools.umd.min.js.map +1 -0
  21. package/dist/tolgee-invisible-observer.cjs.min.js +2 -0
  22. package/dist/tolgee-invisible-observer.cjs.min.js.map +1 -0
  23. package/dist/tolgee-invisible-observer.esm.min.mjs +2 -0
  24. package/dist/tolgee-invisible-observer.esm.min.mjs.map +1 -0
  25. package/dist/tolgee-invisible-observer.umd.min.js +2 -0
  26. package/dist/tolgee-invisible-observer.umd.min.js.map +1 -0
  27. package/dist/tolgee-language-detector.cjs.min.js +2 -0
  28. package/dist/tolgee-language-detector.cjs.min.js.map +1 -0
  29. package/dist/tolgee-language-detector.esm.min.mjs +2 -0
  30. package/dist/tolgee-language-detector.esm.min.mjs.map +1 -0
  31. package/dist/tolgee-language-detector.umd.min.js +2 -0
  32. package/dist/tolgee-language-detector.umd.min.js.map +1 -0
  33. package/dist/tolgee-language-storage.cjs.min.js +2 -0
  34. package/dist/tolgee-language-storage.cjs.min.js.map +1 -0
  35. package/dist/tolgee-language-storage.esm.min.mjs +2 -0
  36. package/dist/tolgee-language-storage.esm.min.mjs.map +1 -0
  37. package/dist/tolgee-language-storage.umd.min.js +2 -0
  38. package/dist/tolgee-language-storage.umd.min.js.map +1 -0
  39. package/dist/tolgee-text-observer.cjs.min.js +2 -0
  40. package/dist/tolgee-text-observer.cjs.min.js.map +1 -0
  41. package/dist/tolgee-text-observer.esm.min.mjs +2 -0
  42. package/dist/tolgee-text-observer.esm.min.mjs.map +1 -0
  43. package/dist/tolgee-text-observer.umd.min.js +2 -0
  44. package/dist/tolgee-text-observer.umd.min.js.map +1 -0
  45. package/dist/tolgee-web-tolgee.cjs.min.js +2 -0
  46. package/dist/tolgee-web-tolgee.cjs.min.js.map +1 -0
  47. package/dist/tolgee-web-tolgee.esm.min.mjs +2 -0
  48. package/dist/tolgee-web-tolgee.esm.min.mjs.map +1 -0
  49. package/dist/tolgee-web-tolgee.umd.min.js +2 -0
  50. package/dist/tolgee-web-tolgee.umd.min.js.map +1 -0
  51. package/dist/tolgee-web.cjs.js +27079 -0
  52. package/dist/tolgee-web.cjs.js.map +1 -0
  53. package/dist/tolgee-web.cjs.min.js +143 -0
  54. package/dist/tolgee-web.cjs.min.js.map +1 -0
  55. package/dist/tolgee-web.esm.js +27055 -0
  56. package/dist/tolgee-web.esm.js.map +1 -0
  57. package/dist/tolgee-web.esm.min.mjs +143 -0
  58. package/dist/tolgee-web.esm.min.mjs.map +1 -0
  59. package/dist/tolgee-web.umd.js +27085 -0
  60. package/dist/tolgee-web.umd.js.map +1 -0
  61. package/dist/tolgee-web.umd.min.js +143 -0
  62. package/dist/tolgee-web.umd.min.js.map +1 -0
  63. package/lib/BackendFetch.d.ts +4 -0
  64. package/lib/BrowserExtensionPlugin/BrowserExtensionPlugin.d.ts +8 -0
  65. package/lib/BrowserExtensionPlugin/constants.d.ts +3 -0
  66. package/lib/BrowserExtensionPlugin/loadInContextLib.d.ts +1 -0
  67. package/lib/ContextUi.d.ts +2 -0
  68. package/lib/DevBackend.d.ts +2 -0
  69. package/lib/DevTools.d.ts +3 -0
  70. package/lib/InContextTools.d.ts +2 -0
  71. package/lib/InvisibleObserver.d.ts +2 -0
  72. package/lib/LanguageDetector.d.ts +3 -0
  73. package/lib/LanguageStorage.d.ts +3 -0
  74. package/lib/TextObserver.d.ts +2 -0
  75. package/lib/WebTolgee.d.ts +3 -0
  76. package/lib/constants.d.ts +5 -0
  77. package/lib/index.d.ts +5 -0
  78. package/lib/observers/general/DomHelper.d.ts +4 -0
  79. package/lib/observers/general/ElementHighlighter.d.ts +10 -0
  80. package/lib/observers/general/ElementMeta.d.ts +3 -0
  81. package/lib/observers/general/ElementRegistry.d.ts +11 -0
  82. package/lib/observers/general/ElementStore.d.ts +10 -0
  83. package/lib/observers/general/GeneralObserver.d.ts +12 -0
  84. package/lib/observers/general/MouseEventHandler.d.ts +13 -0
  85. package/lib/observers/general/NodeHandler.d.ts +6 -0
  86. package/lib/observers/general/helpers.d.ts +7 -0
  87. package/lib/observers/invisible/InvisibleWrapper.d.ts +2 -0
  88. package/lib/observers/invisible/ValueMemory.d.ts +5 -0
  89. package/lib/observers/invisible/encoderPolyfill.d.ts +8 -0
  90. package/lib/observers/invisible/secret.d.ts +6 -0
  91. package/lib/observers/text/TextWrapper.d.ts +8 -0
  92. package/lib/observers/text/helpers.d.ts +3 -0
  93. package/lib/tools/decodeApiKey.d.ts +1 -0
  94. package/lib/tools/extension.d.ts +28 -0
  95. package/lib/typedIndex.d.ts +11 -0
  96. package/lib/types.d.ts +28 -0
  97. package/lib/ui/KeyContextMenu/KeyContextMenu.d.ts +19 -0
  98. package/lib/ui/KeyDialog/KeyDialog.d.ts +23 -0
  99. package/lib/ui/KeyDialog/KeyForm.d.ts +2 -0
  100. package/lib/ui/KeyDialog/LanguageSelect.d.ts +2 -0
  101. package/lib/ui/KeyDialog/NewWindow.d.ts +2 -0
  102. package/lib/ui/KeyDialog/NsSelect.d.ts +9 -0
  103. package/lib/ui/KeyDialog/ScreenshotGallery/ExtensionPrompt.d.ts +6 -0
  104. package/lib/ui/KeyDialog/ScreenshotGallery/ScreenshotDetail.d.ts +8 -0
  105. package/lib/ui/KeyDialog/ScreenshotGallery/ScreenshotDropzone.d.ts +6 -0
  106. package/lib/ui/KeyDialog/ScreenshotGallery/ScreenshotGallery.d.ts +2 -0
  107. package/lib/ui/KeyDialog/ScreenshotGallery/ScreenshotThumbnail.d.ts +8 -0
  108. package/lib/ui/KeyDialog/ScreenshotGallery/utils.d.ts +3 -0
  109. package/lib/ui/KeyDialog/TranslationDialog.d.ts +2 -0
  110. package/lib/ui/KeyDialog/TranslationDialogContextProvider.d.ts +128 -0
  111. package/lib/ui/KeyDialog/TranslationDialogWrapper.d.ts +2 -0
  112. package/lib/ui/KeyDialog/TranslationFields.d.ts +2 -0
  113. package/lib/ui/KeyDialog/languageHelpers.d.ts +12 -0
  114. package/lib/ui/KeyDialog/tools.d.ts +3 -0
  115. package/lib/ui/ThemeProvider.d.ts +2 -0
  116. package/lib/ui/client/QueryProvider.d.ts +12 -0
  117. package/lib/ui/client/apiSchema.generated.d.ts +3283 -0
  118. package/lib/ui/client/client.d.ts +5 -0
  119. package/lib/ui/client/types.d.ts +58 -0
  120. package/lib/ui/client/useQueryApi.d.ts +84 -0
  121. package/lib/ui/common/BodyEnd.d.ts +13 -0
  122. package/lib/ui/common/FieldTitle.d.ts +2 -0
  123. package/lib/ui/common/LoadingButton.d.ts +7 -0
  124. package/lib/ui/index.d.ts +13 -0
  125. package/lib/ui/tools/createProvider.d.ts +5 -0
  126. package/lib/ui/tools/isLanguagePermitted.d.ts +1 -0
  127. package/lib/ui/tools/sleep.d.ts +1 -0
  128. package/package.json +91 -0
  129. package/src/BackendFetch.ts +64 -0
  130. package/src/BrowserExtensionPlugin/BrowserExtensionPlugin.ts +98 -0
  131. package/src/BrowserExtensionPlugin/constants.ts +3 -0
  132. package/src/BrowserExtensionPlugin/loadInContextLib.ts +32 -0
  133. package/src/ContextUi.ts +7 -0
  134. package/src/DevBackend.ts +30 -0
  135. package/src/DevTools.ts +9 -0
  136. package/src/InContextTools.ts +20 -0
  137. package/src/InvisibleObserver.ts +16 -0
  138. package/src/LanguageDetector.test.ts +19 -0
  139. package/src/LanguageDetector.ts +32 -0
  140. package/src/LanguageStorage.ts +23 -0
  141. package/src/TextObserver.ts +45 -0
  142. package/src/WebTolgee.ts +8 -0
  143. package/src/__test__/browser.extension.test.ts +70 -0
  144. package/src/__test__/observer.test.ts +13 -0
  145. package/src/__test__/testObserver.ts +106 -0
  146. package/src/__test__/testRetranslate.ts +47 -0
  147. package/src/constants.ts +12 -0
  148. package/src/index.ts +8 -0
  149. package/src/observers/general/DomHelper.ts +46 -0
  150. package/src/observers/general/ElementHighlighter.ts +72 -0
  151. package/src/observers/general/ElementMeta.ts +17 -0
  152. package/src/observers/general/ElementRegistry.ts +159 -0
  153. package/src/observers/general/ElementStore.ts +34 -0
  154. package/src/observers/general/GeneralObserver.ts +133 -0
  155. package/src/observers/general/MouseEventHandler.ts +199 -0
  156. package/src/observers/general/NodeHandler.ts +39 -0
  157. package/src/observers/general/helpers.ts +65 -0
  158. package/src/observers/invisible/InvisibleWrapper.test.ts +17 -0
  159. package/src/observers/invisible/InvisibleWrapper.ts +96 -0
  160. package/src/observers/invisible/ValueMemory.test.ts +25 -0
  161. package/src/observers/invisible/ValueMemory.ts +20 -0
  162. package/src/observers/invisible/encoderPolyfill.ts +96 -0
  163. package/src/observers/invisible/secret.test.ts +61 -0
  164. package/src/observers/invisible/secret.ts +68 -0
  165. package/src/observers/text/TextWrapper.ts +258 -0
  166. package/src/observers/text/helpers.ts +56 -0
  167. package/src/tools/decodeApiKey.test.ts +14 -0
  168. package/src/tools/decodeApiKey.ts +74 -0
  169. package/src/tools/extension.test.ts +159 -0
  170. package/src/tools/extension.ts +119 -0
  171. package/src/typedIndex.ts +13 -0
  172. package/src/types.ts +33 -0
  173. package/src/ui/KeyContextMenu/KeyContextMenu.test.ts +51 -0
  174. package/src/ui/KeyContextMenu/KeyContextMenu.tsx +108 -0
  175. package/src/ui/KeyDialog/KeyDialog.tsx +67 -0
  176. package/src/ui/KeyDialog/KeyForm.tsx +208 -0
  177. package/src/ui/KeyDialog/LanguageSelect.tsx +78 -0
  178. package/src/ui/KeyDialog/NewWindow.tsx +106 -0
  179. package/src/ui/KeyDialog/NsSelect.tsx +67 -0
  180. package/src/ui/KeyDialog/ScreenshotGallery/ExtensionPrompt.tsx +97 -0
  181. package/src/ui/KeyDialog/ScreenshotGallery/ScreenshotDetail.tsx +33 -0
  182. package/src/ui/KeyDialog/ScreenshotGallery/ScreenshotDropzone.tsx +138 -0
  183. package/src/ui/KeyDialog/ScreenshotGallery/ScreenshotGallery.tsx +240 -0
  184. package/src/ui/KeyDialog/ScreenshotGallery/ScreenshotThumbnail.tsx +113 -0
  185. package/src/ui/KeyDialog/ScreenshotGallery/utils.ts +17 -0
  186. package/src/ui/KeyDialog/TranslationDialog.tsx +14 -0
  187. package/src/ui/KeyDialog/TranslationDialogContextProvider.tsx +464 -0
  188. package/src/ui/KeyDialog/TranslationDialogWrapper.tsx +44 -0
  189. package/src/ui/KeyDialog/TranslationFields.tsx +113 -0
  190. package/src/ui/KeyDialog/languageHelpers.ts +18 -0
  191. package/src/ui/KeyDialog/tools.ts +30 -0
  192. package/src/ui/ThemeProvider.tsx +71 -0
  193. package/src/ui/client/QueryProvider.tsx +38 -0
  194. package/src/ui/client/apiSchema.generated.ts +3281 -0
  195. package/src/ui/client/client.ts +155 -0
  196. package/src/ui/client/types.ts +113 -0
  197. package/src/ui/client/useQueryApi.ts +121 -0
  198. package/src/ui/common/BodyEnd.tsx +44 -0
  199. package/src/ui/common/FieldTitle.tsx +9 -0
  200. package/src/ui/common/LoadingButton.tsx +45 -0
  201. package/src/ui/index.ts +88 -0
  202. package/src/ui/screenshots/ScreenshotPreview.tsx +18 -0
  203. package/src/ui/tools/createProvider.tsx +54 -0
  204. package/src/ui/tools/isLanguagePermitted.ts +14 -0
  205. package/src/ui/tools/sleep.ts +2 -0
  206. package/types/index.d.ts +9 -0
@@ -0,0 +1,45 @@
1
+ import type { ObserverInterface, TolgeePlugin } from '@tolgee/core';
2
+ import { GeneralObserver } from './observers/general/GeneralObserver';
3
+ import { setNodeText } from './observers/general/helpers';
4
+ import { TextWrapper } from './observers/text/TextWrapper';
5
+
6
+ const TextObserverCreator =
7
+ (): ObserverInterface =>
8
+ ({ translate, onClick, options }) => {
9
+ const wrapper = TextWrapper({
10
+ inputPrefix: options.inputPrefix,
11
+ inputSuffix: options.inputSuffix,
12
+ translate,
13
+ });
14
+ const { wrap, unwrap, stop, forEachElement, highlight, run } =
15
+ GeneralObserver(wrapper, options, onClick);
16
+
17
+ const retranslate = () => {
18
+ forEachElement((_, elMeta) => {
19
+ for (const [node, nodeMeta] of elMeta.nodes.entries()) {
20
+ if (nodeMeta.keyAttributeOnly) {
21
+ return;
22
+ }
23
+ const result = wrapper.unwrap(nodeMeta.oldTextContent);
24
+ if (result) {
25
+ setNodeText(node, result.text);
26
+ }
27
+ }
28
+ });
29
+ };
30
+
31
+ return {
32
+ wrap,
33
+ unwrap,
34
+ stop,
35
+ run,
36
+ retranslate,
37
+ highlight,
38
+ outputNotFormattable: true,
39
+ };
40
+ };
41
+
42
+ export const TextObserver = (): TolgeePlugin => (tolgee, tools) => {
43
+ tools.setObserver(TextObserverCreator());
44
+ return tolgee;
45
+ };
@@ -0,0 +1,8 @@
1
+ import { Options, Tolgee as TolgeeCore, TolgeeInstance } from '@tolgee/core';
2
+ import { BrowserExtensionPlugin } from './BrowserExtensionPlugin/BrowserExtensionPlugin';
3
+
4
+ export const Tolgee = (options?: Partial<Options>): TolgeeInstance => {
5
+ return TolgeeCore(options).use(BrowserExtensionPlugin());
6
+ };
7
+
8
+ export { TolgeeCore };
@@ -0,0 +1,70 @@
1
+ const handshakerUpdate = jest.fn(() => Promise.resolve());
2
+ const Handshaker = jest.fn(() => ({ update: handshakerUpdate }));
3
+ const loadInContextLib = jest.fn(() => Promise.resolve(() => {}));
4
+
5
+ jest.mock('../tools/extension', () => ({
6
+ Handshaker,
7
+ }));
8
+
9
+ import {
10
+ IN_CONTEXT_EXPORT_NAME,
11
+ IN_CONTEXT_FILE,
12
+ IN_CONTEXT_UMD_NAME,
13
+ } from '../BrowserExtensionPlugin/constants';
14
+
15
+ jest.mock('../BrowserExtensionPlugin/loadInContextLib', () => ({
16
+ loadInContextLib,
17
+ }));
18
+
19
+ import { Tolgee } from '@tolgee/core';
20
+ import { BrowserExtensionPlugin } from '../typedIndex';
21
+ import {
22
+ API_KEY_LOCAL_STORAGE,
23
+ API_URL_LOCAL_STORAGE,
24
+ } from '../BrowserExtensionPlugin/BrowserExtensionPlugin';
25
+ import fs from 'fs/promises';
26
+ import path from 'path';
27
+
28
+ describe('compatibility with browser extension', () => {
29
+ afterEach(() => {
30
+ sessionStorage.clear();
31
+ });
32
+
33
+ it('sends correct data to extension', async () => {
34
+ const tolgee = Tolgee({ language: 'en', apiUrl: 'test' });
35
+ tolgee.use(BrowserExtensionPlugin());
36
+ await tolgee.run();
37
+ expect(handshakerUpdate).toBeCalledTimes(1);
38
+ expect(handshakerUpdate).toBeCalledWith({
39
+ config: {
40
+ apiKey: '',
41
+ apiUrl: 'test',
42
+ },
43
+ mode: 'production',
44
+ uiPresent: true,
45
+ uiVersion: undefined,
46
+ });
47
+ });
48
+
49
+ it('loads in-context lib if session storage is set', async () => {
50
+ sessionStorage.setItem(API_KEY_LOCAL_STORAGE, 'test');
51
+ sessionStorage.setItem(API_URL_LOCAL_STORAGE, 'test');
52
+
53
+ const tolgee = Tolgee({ language: 'en' });
54
+ tolgee.use(BrowserExtensionPlugin());
55
+ await tolgee.run();
56
+
57
+ expect(loadInContextLib).toBeCalledTimes(1);
58
+ });
59
+
60
+ it('builded module is valid', async () => {
61
+ // this test works only after build
62
+ const fileContent = await fs.readFile(
63
+ path.join(__dirname, `../../dist/${IN_CONTEXT_FILE}`)
64
+ );
65
+ expect(fileContent.toString().includes(IN_CONTEXT_UMD_NAME)).toBeTruthy();
66
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
67
+ const module = await import(`../../dist/${IN_CONTEXT_FILE}`);
68
+ expect(typeof module[IN_CONTEXT_EXPORT_NAME]).toEqual('function');
69
+ });
70
+ });
@@ -0,0 +1,13 @@
1
+ import { InvisibleObserver } from '../InvisibleObserver';
2
+ import { TextObserver } from '../TextObserver';
3
+ import { testObserver } from './testObserver';
4
+ import { testRetranslate } from './testRetranslate';
5
+
6
+ describe('invisble observer', () => {
7
+ testObserver(InvisibleObserver());
8
+ });
9
+
10
+ describe('text observer', () => {
11
+ testObserver(TextObserver());
12
+ testRetranslate(TextObserver());
13
+ });
@@ -0,0 +1,106 @@
1
+ import {
2
+ Tolgee,
3
+ TolgeeInstance,
4
+ TolgeePlugin,
5
+ TOLGEE_ATTRIBUTE_NAME,
6
+ TOLGEE_WRAPPED_ONLY_DATA_ATTRIBUTE,
7
+ } from '@tolgee/core';
8
+ import { screen, waitFor } from '@testing-library/dom';
9
+
10
+ export const testObserver = (plugin: TolgeePlugin) => {
11
+ describe('observer', () => {
12
+ let tolgee: TolgeeInstance;
13
+
14
+ beforeEach(() => {
15
+ tolgee = Tolgee()
16
+ .use(plugin)
17
+ .init({
18
+ language: 'en',
19
+ staticData: { en: { hello: 'world' } },
20
+ });
21
+ tolgee.run();
22
+ });
23
+
24
+ afterEach(() => {
25
+ tolgee.stop();
26
+ });
27
+
28
+ it('runs observer', async () => {
29
+ document.body.innerHTML = `
30
+ <span data-testid="translation">${tolgee.t({ key: 'hello' })}</span>
31
+ `;
32
+
33
+ await waitFor(() => {
34
+ expect(screen.queryByTestId('translation')?.textContent).toEqual(
35
+ 'world'
36
+ );
37
+ });
38
+ });
39
+
40
+ it('runs observer', async () => {
41
+ document.body.innerHTML = `
42
+ <span data-testid="translation">${tolgee.t({ key: 'hello' })}</span>
43
+ `;
44
+
45
+ await waitFor(() => {
46
+ expect(screen.queryByTestId('translation')?.textContent).toEqual(
47
+ 'world'
48
+ );
49
+ });
50
+ });
51
+
52
+ it('ignoring works', async () => {
53
+ document.body.innerHTML = `
54
+ <span data-testid="ignoredTranslation" data-tolgee-restricted="true">
55
+ ${tolgee.t({ key: 'hello' })}
56
+ <span data-testid="ignoredInside">${tolgee.t({ key: 'hello' })}</span>
57
+ </span>
58
+ <span data-testid="translation">${tolgee.t({ key: 'hello' })}</span>
59
+ `;
60
+
61
+ await waitFor(() => {
62
+ expect(
63
+ screen
64
+ .queryByTestId('translation')
65
+ ?.getAttribute(TOLGEE_ATTRIBUTE_NAME)
66
+ ).not.toBeFalsy();
67
+ expect(
68
+ screen
69
+ .queryByTestId('ignoredTranslation')
70
+ ?.getAttribute(TOLGEE_ATTRIBUTE_NAME)
71
+ ).toBeFalsy();
72
+ expect(
73
+ screen
74
+ .queryByTestId('ignoredInside')
75
+ ?.getAttribute(TOLGEE_ATTRIBUTE_NAME)
76
+ ).toBeFalsy();
77
+ });
78
+ });
79
+
80
+ it('key only attribute', async () => {
81
+ document.body.innerHTML = `
82
+ <span data-testid="translation" ${TOLGEE_WRAPPED_ONLY_DATA_ATTRIBUTE}="hello">Hello</span>
83
+ `;
84
+
85
+ await waitFor(() => {
86
+ expect(
87
+ screen
88
+ .queryByTestId('translation')
89
+ ?.getAttribute(TOLGEE_ATTRIBUTE_NAME)
90
+ ).not.toBeFalsy();
91
+ });
92
+
93
+ screen
94
+ .queryByTestId('translation')!
95
+ .removeAttribute(TOLGEE_WRAPPED_ONLY_DATA_ATTRIBUTE);
96
+
97
+ await waitFor(() => {
98
+ expect(
99
+ screen
100
+ .queryByTestId('translation')
101
+ ?.getAttribute(TOLGEE_ATTRIBUTE_NAME)
102
+ ).toBeFalsy();
103
+ });
104
+ });
105
+ });
106
+ };
@@ -0,0 +1,47 @@
1
+ import { screen, waitFor } from '@testing-library/dom';
2
+ import {
3
+ Tolgee,
4
+ TolgeeInstance,
5
+ TolgeePlugin,
6
+ TOLGEE_ATTRIBUTE_NAME,
7
+ } from '@tolgee/core';
8
+
9
+ export const testRetranslate = (plugin: TolgeePlugin) => {
10
+ describe('retranslate', () => {
11
+ let tolgee: TolgeeInstance;
12
+
13
+ beforeEach(async () => {
14
+ tolgee = Tolgee()
15
+ .use(plugin)
16
+ .init({
17
+ language: 'en',
18
+ staticData: { en: { hello: 'world' }, es: { hello: 'mundo' } },
19
+ });
20
+ await tolgee.run();
21
+ });
22
+
23
+ afterEach(() => {
24
+ tolgee.stop();
25
+ });
26
+
27
+ it('change translation on language change', async () => {
28
+ document.body.innerHTML = `
29
+ <span data-testid="translation">${tolgee.t({ key: 'hello' })}</span>
30
+ `;
31
+
32
+ await waitFor(() => {
33
+ const element = screen.queryByTestId('translation');
34
+ expect(element?.textContent).toEqual('world');
35
+ expect(element?.getAttribute(TOLGEE_ATTRIBUTE_NAME)).not.toBeFalsy();
36
+ });
37
+
38
+ await tolgee.changeLanguage('es');
39
+
40
+ await waitFor(() => {
41
+ expect(screen.queryByTestId('translation')?.textContent).toEqual(
42
+ 'mundo'
43
+ );
44
+ });
45
+ });
46
+ });
47
+ };
@@ -0,0 +1,12 @@
1
+ // needs to be same as in @tolgee/core package
2
+ export const DEVTOOLS_ID = '__tolgee_dev_tools';
3
+
4
+ export const DEVTOOLS_Z_INDEX = 2147483000;
5
+
6
+ export const CHROME_EXTENSION_LINK =
7
+ 'https://chrome.google.com/webstore/detail/tolgee-tools/hacnbapajkkfohnonhbmegojnddagfnj';
8
+
9
+ export const PREFERRED_LANGUAGES_LOCAL_STORAGE_KEY =
10
+ '__tolgee_preferredLanguages';
11
+
12
+ export const MAX_LANGUAGES_SELECTED = 5;
package/src/index.ts ADDED
@@ -0,0 +1,8 @@
1
+ export * from './ContextUi';
2
+ export * from './DevTools';
3
+ export * from './InContextTools';
4
+ export * from './typedIndex';
5
+ export {
6
+ PREFERRED_LANGUAGES_LOCAL_STORAGE_KEY,
7
+ DEVTOOLS_ID,
8
+ } from './constants';
@@ -0,0 +1,46 @@
1
+ import { ObserverOptions } from '@tolgee/core';
2
+
3
+ export const DomHelper = (options: ObserverOptions) => {
4
+ function getParentElement(node: Node): Element | undefined {
5
+ if (node.parentElement) {
6
+ return node.parentElement;
7
+ }
8
+ if ((node as Attr).ownerElement) {
9
+ return (node as Attr).ownerElement || undefined;
10
+ }
11
+ }
12
+
13
+ function getSuitableParent(node: Node): Element {
14
+ const domParent = getParentElement(node);
15
+
16
+ if (domParent === undefined) {
17
+ // eslint-disable-next-line no-console
18
+ console.error(node);
19
+ throw new Error('No suitable parent found for node above.');
20
+ }
21
+
22
+ if (!options.passToParent) {
23
+ return domParent;
24
+ }
25
+
26
+ if (Array.isArray(options.passToParent)) {
27
+ const tagNameEquals = (elementTagName: string) =>
28
+ domParent.tagName.toLowerCase() === elementTagName.toLowerCase();
29
+ if (options.passToParent.findIndex(tagNameEquals) === -1) {
30
+ return domParent;
31
+ }
32
+ }
33
+
34
+ if (typeof options.passToParent === 'function') {
35
+ if (!options.passToParent(domParent)) {
36
+ return domParent;
37
+ }
38
+ }
39
+
40
+ return getSuitableParent(domParent);
41
+ }
42
+
43
+ return Object.freeze({
44
+ getSuitableParent,
45
+ });
46
+ };
@@ -0,0 +1,72 @@
1
+ import { ElementMeta, TOLGEE_HIGHLIGHTER_CLASS } from '@tolgee/core';
2
+ import { TolgeeElement } from '../../types';
3
+
4
+ const HIGHLIGHTER_BASE_STYLE: Partial<CSSStyleDeclaration> = {
5
+ pointerEvents: 'none',
6
+ position: 'fixed',
7
+ boxSizing: 'content-box',
8
+ zIndex: String(Number.MAX_SAFE_INTEGER),
9
+ contain: 'layout',
10
+ display: 'block',
11
+ borderStyle: 'solid',
12
+ borderRadius: '4px',
13
+ };
14
+
15
+ type Props = {
16
+ highlightColor: string;
17
+ highlightWidth: number;
18
+ };
19
+
20
+ export const ElementHighlighter = ({
21
+ highlightColor,
22
+ highlightWidth,
23
+ }: Props) => {
24
+ function initHighlightFunction(
25
+ element: TolgeeElement,
26
+ elementMeta: ElementMeta
27
+ ) {
28
+ elementMeta.highlight = () => {
29
+ if (!element.isConnected) {
30
+ return;
31
+ }
32
+ let highlightEl = elementMeta.highlightEl;
33
+ if (!highlightEl) {
34
+ highlightEl = document.createElement('div');
35
+ highlightEl.classList.add(TOLGEE_HIGHLIGHTER_CLASS);
36
+ Object.entries(HIGHLIGHTER_BASE_STYLE).forEach(([key, value]) => {
37
+ // @ts-ignore
38
+ highlightEl!.style[key] = value;
39
+ });
40
+ highlightEl.style.borderColor = highlightColor;
41
+
42
+ elementMeta.highlightEl = highlightEl;
43
+ document.body.appendChild(highlightEl);
44
+ }
45
+
46
+ const shape = element.getBoundingClientRect();
47
+
48
+ highlightEl.style.borderWidth = highlightWidth + 'px';
49
+ highlightEl.style.top = shape.top - highlightWidth + 'px';
50
+ highlightEl.style.left = shape.left - highlightWidth + 'px';
51
+ highlightEl.style.width = shape.width + 'px';
52
+ highlightEl.style.height = shape.height + 'px';
53
+ };
54
+ }
55
+
56
+ function initUnhighlightFunction(
57
+ element: TolgeeElement,
58
+ elementMeta: ElementMeta
59
+ ) {
60
+ elementMeta.unhighlight = () => {
61
+ elementMeta.highlightEl?.remove();
62
+ elementMeta.highlightEl = undefined;
63
+ };
64
+ }
65
+
66
+ function initHighlighter(element: TolgeeElement, elementMeta: ElementMeta) {
67
+ initHighlightFunction(element, elementMeta);
68
+ initUnhighlightFunction(element, elementMeta);
69
+ }
70
+
71
+ return Object.freeze({ initHighlighter });
72
+ };
@@ -0,0 +1,17 @@
1
+ import type { ElementMeta, KeyAndParams, NodeMeta } from '@tolgee/core';
2
+
3
+ export function initElementMeta(): ElementMeta {
4
+ return {
5
+ nodes: new Map(),
6
+ };
7
+ }
8
+
9
+ export function initNodeMeta(
10
+ oldTextContent: string,
11
+ keys: KeyAndParams[]
12
+ ): NodeMeta {
13
+ return {
14
+ oldTextContent,
15
+ keys,
16
+ };
17
+ }
@@ -0,0 +1,159 @@
1
+ import {
2
+ FallbackNSTranslation,
3
+ getFallback,
4
+ RESTRICTED_ASCENDANT_ATTRIBUTE,
5
+ TOLGEE_ATTRIBUTE_NAME,
6
+ } from '@tolgee/core';
7
+ import {
8
+ ElementMeta,
9
+ KeyWithDefault,
10
+ NodeMeta,
11
+ TranslationOnClick,
12
+ ObserverOptions,
13
+ } from '@tolgee/core';
14
+ import { TolgeeElement } from '../../types';
15
+
16
+ import { ElementHighlighter } from './ElementHighlighter';
17
+ import { initElementMeta } from './ElementMeta';
18
+ import { ElementStore } from './ElementStore';
19
+ import { compareDescriptors, nodeContains } from './helpers';
20
+ import { MouseEventHandler } from './MouseEventHandler';
21
+
22
+ export const ElementRegistry = (
23
+ options: ObserverOptions,
24
+ onClick: TranslationOnClick
25
+ ) => {
26
+ const elementStore = ElementStore();
27
+ const elementHighlighter = ElementHighlighter({
28
+ highlightColor: options.highlightColor,
29
+ highlightWidth: options.highlightWidth,
30
+ });
31
+ const eventHandler = MouseEventHandler({
32
+ highlightKeys: options.highlightKeys,
33
+ elementStore,
34
+ onClick: (event, el) => {
35
+ const meta = elementStore.get(el)!;
36
+ onClick(event, {
37
+ el,
38
+ meta,
39
+ keysAndDefaults: getKeysAndDefaults(meta),
40
+ });
41
+ },
42
+ });
43
+
44
+ function register(element: Element, node: Node, nodeMeta: NodeMeta) {
45
+ if (isRestricted(element)) {
46
+ return;
47
+ }
48
+ const tolgeeElement = element as TolgeeElement;
49
+ let elementMeta = elementStore.get(tolgeeElement);
50
+ if (!elementMeta) {
51
+ elementMeta = initElementMeta();
52
+ elementStore.set(tolgeeElement, elementMeta);
53
+ tolgeeElement.setAttribute(TOLGEE_ATTRIBUTE_NAME, 'true');
54
+ }
55
+ elementMeta.nodes.set(node, nodeMeta);
56
+ elementHighlighter.initHighlighter(tolgeeElement, elementMeta);
57
+ }
58
+
59
+ function run(mouseHighlight: boolean) {
60
+ if (mouseHighlight) {
61
+ eventHandler.run();
62
+ }
63
+ }
64
+
65
+ function stop() {
66
+ eventHandler.stop();
67
+ }
68
+
69
+ function isRestricted(element: Element) {
70
+ const restrictedElements = options.restrictedElements;
71
+ return (
72
+ restrictedElements.indexOf(element.tagName.toLowerCase()) !== -1 ||
73
+ element.closest(`[${RESTRICTED_ASCENDANT_ATTRIBUTE}]`) !== null
74
+ );
75
+ }
76
+
77
+ function refreshAll() {
78
+ elementStore.forEachElement((element, meta) => {
79
+ if (meta.preventClean) {
80
+ return;
81
+ }
82
+ cleanElementInactiveNodes(meta);
83
+ if (meta.nodes.size === 0) {
84
+ cleanElement(element, meta);
85
+ }
86
+ });
87
+ }
88
+
89
+ function findAll(key?: string, ns?: FallbackNSTranslation) {
90
+ const result: ElementMeta[] = [];
91
+ elementStore.forEachElement((_, meta) => {
92
+ for (const nodeMeta of meta.nodes.values()) {
93
+ const fits = nodeMeta.keys.find((val) =>
94
+ compareDescriptors(
95
+ { key, ns: getFallback(ns) },
96
+ { key: val.key, ns: getFallback(val.ns) }
97
+ )
98
+ );
99
+ if (fits) {
100
+ result.push(meta);
101
+ break;
102
+ }
103
+ }
104
+ });
105
+ return result;
106
+ }
107
+
108
+ function cleanElementInactiveNodes(meta: ElementMeta) {
109
+ meta.nodes = new Map(getActiveNodes(meta));
110
+ }
111
+
112
+ function getTargetElement() {
113
+ return options.targetElement || document.body;
114
+ }
115
+
116
+ function* getActiveNodes(meta: ElementMeta) {
117
+ for (const [node, nodeMeta] of meta.nodes.entries()) {
118
+ if (nodeContains(getTargetElement(), node)) {
119
+ yield [node, nodeMeta] as const;
120
+ }
121
+ }
122
+ }
123
+
124
+ function cleanElement(element: TolgeeElement, meta: ElementMeta) {
125
+ if (meta.highlightEl) {
126
+ meta.unhighlight?.();
127
+ }
128
+ element.removeAttribute(TOLGEE_ATTRIBUTE_NAME);
129
+ elementStore.remove(element);
130
+ }
131
+
132
+ function getKeyOptions(meta: ElementMeta): KeyWithDefault[] {
133
+ const nodes = Array.from(meta.nodes.values());
134
+ return nodes.reduce(
135
+ (acc, curr) => [
136
+ ...acc,
137
+ ...curr.keys.map((k) => ({
138
+ key: k.key,
139
+ defaultValue: k.defaultValue,
140
+ ns: k.ns,
141
+ })),
142
+ ],
143
+ [] as KeyWithDefault[]
144
+ );
145
+ }
146
+
147
+ function getKeysAndDefaults(meta: ElementMeta): KeyWithDefault[] {
148
+ return getKeyOptions(meta);
149
+ }
150
+
151
+ return Object.freeze({
152
+ register,
153
+ forEachElement: elementStore.forEachElement,
154
+ findAll,
155
+ refreshAll,
156
+ run,
157
+ stop,
158
+ });
159
+ };
@@ -0,0 +1,34 @@
1
+ import type { ElementMeta } from '@tolgee/core';
2
+ import { TolgeeElement } from '../../types';
3
+
4
+ export type RegistredElementsMap = Map<TolgeeElement, ElementMeta>;
5
+
6
+ export const ElementStore = () => {
7
+ const registredElements: RegistredElementsMap = new Map();
8
+
9
+ function set(el: TolgeeElement, meta: ElementMeta) {
10
+ registredElements.set(el, meta);
11
+ }
12
+
13
+ function get(el: TolgeeElement | undefined) {
14
+ return el && registredElements.get(el);
15
+ }
16
+
17
+ function remove(el: TolgeeElement) {
18
+ return registredElements.delete(el);
19
+ }
20
+
21
+ function forEachElement(
22
+ callback: (el: TolgeeElement, meta: ElementMeta) => void
23
+ ) {
24
+ registredElements.forEach((value, key) => callback(key, value));
25
+ }
26
+ return Object.freeze({
27
+ set,
28
+ get,
29
+ remove,
30
+ forEachElement,
31
+ });
32
+ };
33
+
34
+ export type ElementStoreType = ReturnType<typeof ElementStore>;