@tolgee/core 4.7.0 → 4.7.2
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 +2 -4
- package/dist/tolgee.cjs.js.map +1 -1
- package/dist/tolgee.cjs.min.js +1 -1
- package/dist/tolgee.cjs.min.js.map +1 -1
- package/dist/{tolgee.esm.js → tolgee.esm.min.mjs} +2 -2
- package/dist/tolgee.esm.min.mjs.map +1 -0
- package/dist/tolgee.esm.mjs +5690 -0
- package/dist/tolgee.esm.mjs.map +1 -0
- package/dist/tolgee.umd.js +2 -4
- 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/package.json +10 -9
- package/src/Constants/Global.ts +9 -0
- package/src/Constants/ModifierKey.ts +6 -0
- package/src/Errors/ApiHttpError.ts +8 -0
- package/src/Observer.test.ts +119 -0
- package/src/Observer.ts +68 -0
- package/src/Properties.test.ts +150 -0
- package/src/Properties.ts +112 -0
- package/src/Tolgee.test.ts +473 -0
- package/src/Tolgee.ts +335 -0
- package/src/TolgeeConfig.test.ts +21 -0
- package/src/TolgeeConfig.ts +134 -0
- package/src/__integration/FormatterIcu.test.ts +80 -0
- package/src/__integration/FormatterMissing.ts +54 -0
- package/src/__integration/Tolgee.test.ts +90 -0
- package/src/__integration/TolgeeInvisible.test.ts +145 -0
- package/src/__integration/mockTranslations.ts +6 -0
- package/src/__integration/testConfig.ts +16 -0
- package/src/__testFixtures/classMock.ts +11 -0
- package/src/__testFixtures/createElement.ts +43 -0
- package/src/__testFixtures/createTestDom.ts +25 -0
- package/src/__testFixtures/mocked.ts +25 -0
- package/src/__testFixtures/setupAfterEnv.ts +34 -0
- package/src/helpers/NodeHelper.ts +90 -0
- package/src/helpers/TextHelper.test.ts +62 -0
- package/src/helpers/TextHelper.ts +58 -0
- package/src/helpers/commonTypes.ts +8 -0
- package/src/helpers/encoderPolyfill.ts +96 -0
- package/src/helpers/secret.test.ts +61 -0
- package/src/helpers/secret.ts +68 -0
- package/src/helpers/sleep.ts +2 -0
- package/src/highlighter/HighlightFunctionsInitializer.test.ts +40 -0
- package/src/highlighter/HighlightFunctionsInitializer.ts +61 -0
- package/src/highlighter/MouseEventHandler.test.ts +151 -0
- package/src/highlighter/MouseEventHandler.ts +191 -0
- package/src/highlighter/TranslationHighlighter.test.ts +177 -0
- package/src/highlighter/TranslationHighlighter.ts +113 -0
- package/src/index.ts +10 -0
- package/src/internal.ts +2 -0
- package/src/modules/IcuFormatter.ts +17 -0
- package/src/modules/index.ts +1 -0
- package/src/services/ApiHttpService.ts +85 -0
- package/src/services/CoreService.test.ts +142 -0
- package/src/services/CoreService.ts +76 -0
- package/src/services/DependencyService.test.ts +51 -0
- package/src/services/DependencyService.ts +116 -0
- package/src/services/ElementRegistrar.test.ts +131 -0
- package/src/services/ElementRegistrar.ts +108 -0
- package/src/services/EventEmitter.ts +52 -0
- package/src/services/EventService.ts +14 -0
- package/src/services/ModuleService.ts +14 -0
- package/src/services/ScreenshotService.ts +31 -0
- package/src/services/Subscription.ts +7 -0
- package/src/services/TextService.test.ts +88 -0
- package/src/services/TextService.ts +82 -0
- package/src/services/TranslationService.test.ts +358 -0
- package/src/services/TranslationService.ts +417 -0
- package/src/services/__mocks__/CoreService.ts +17 -0
- package/src/toolsManager/Messages.test.ts +79 -0
- package/src/toolsManager/Messages.ts +60 -0
- package/src/toolsManager/PluginManager.test.ts +108 -0
- package/src/toolsManager/PluginManager.ts +129 -0
- package/src/types/DTOs.ts +25 -0
- package/src/types/apiSchema.generated.ts +6208 -0
- package/src/types.ts +146 -0
- package/src/wrappers/AbstractWrapper.ts +14 -0
- package/src/wrappers/NodeHandler.ts +143 -0
- package/src/wrappers/WrappedHandler.ts +28 -0
- package/src/wrappers/invisible/AttributeHandler.ts +23 -0
- package/src/wrappers/invisible/Coder.ts +65 -0
- package/src/wrappers/invisible/ContentHandler.ts +15 -0
- package/src/wrappers/invisible/CoreHandler.ts +17 -0
- package/src/wrappers/invisible/InvisibleWrapper.ts +59 -0
- package/src/wrappers/invisible/ValueMemory.test.ts +25 -0
- package/src/wrappers/invisible/ValueMemory.ts +16 -0
- package/src/wrappers/text/AttributeHandler.test.ts +117 -0
- package/src/wrappers/text/AttributeHandler.ts +25 -0
- package/src/wrappers/text/Coder.test.ts +298 -0
- package/src/wrappers/text/Coder.ts +202 -0
- package/src/wrappers/text/ContentHandler.test.ts +185 -0
- package/src/wrappers/text/ContentHandler.ts +21 -0
- package/src/wrappers/text/CoreHandler.test.ts +106 -0
- package/src/wrappers/text/CoreHandler.ts +45 -0
- package/src/wrappers/text/TextWrapper.ts +69 -0
- package/dist/tolgee.esm.js.map +0 -1
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { NodeHandler } from '../NodeHandler';
|
|
2
|
+
import { Properties } from '../../Properties';
|
|
3
|
+
import { NodeHelper } from '../../helpers/NodeHelper';
|
|
4
|
+
|
|
5
|
+
export class AttributeHandler {
|
|
6
|
+
constructor(
|
|
7
|
+
private properties: Properties,
|
|
8
|
+
private nodeHandler: NodeHandler
|
|
9
|
+
) {}
|
|
10
|
+
|
|
11
|
+
async handle(node: Element) {
|
|
12
|
+
const inputPrefix = this.properties.config.inputPrefix;
|
|
13
|
+
const inputSuffix = this.properties.config.inputSuffix;
|
|
14
|
+
|
|
15
|
+
for (const [tag, attributes] of Object.entries(
|
|
16
|
+
this.properties.config.tagAttributes
|
|
17
|
+
)) {
|
|
18
|
+
for (const attribute of attributes) {
|
|
19
|
+
const expression = `descendant-or-self::${tag}/@${attribute}[contains(., '${inputPrefix}') and contains(., '${inputSuffix}')]`;
|
|
20
|
+
const nodes: Array<Attr | Text> = NodeHelper.evaluate(expression, node);
|
|
21
|
+
await this.nodeHandler.handleNodes(nodes);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
jest.dontMock('./Coder');
|
|
2
|
+
jest.dontMock('../../services/DependencyService');
|
|
3
|
+
jest.dontMock('../../helpers/TextHelper');
|
|
4
|
+
jest.dontMock('../../services/TextService');
|
|
5
|
+
jest.dontMock('../../services/ModuleService');
|
|
6
|
+
jest.dontMock('../../modules/IcuFormatter');
|
|
7
|
+
|
|
8
|
+
import { DependencyService } from '../../services/DependencyService';
|
|
9
|
+
import { Coder } from './Coder';
|
|
10
|
+
import { getMockedInstance } from '@testFixtures/mocked';
|
|
11
|
+
import { TextService } from '../../services/TextService';
|
|
12
|
+
import { Properties } from '../../Properties';
|
|
13
|
+
import { TranslationService } from '../../services/TranslationService';
|
|
14
|
+
import { IcuFormatter } from '../../modules/IcuFormatter';
|
|
15
|
+
|
|
16
|
+
describe('Coder', () => {
|
|
17
|
+
let mockedTranslationReturn = '';
|
|
18
|
+
let textService: TextService;
|
|
19
|
+
let coder: Coder;
|
|
20
|
+
|
|
21
|
+
const getTranslationMock = jest.fn(async () => {
|
|
22
|
+
return mockedTranslationReturn;
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const getFromCacheOrCallbackMock = jest.fn(() => {
|
|
26
|
+
return mockedTranslationReturn;
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
beforeEach(async () => {
|
|
30
|
+
const depStore = new DependencyService();
|
|
31
|
+
depStore.moduleService.addModule(IcuFormatter);
|
|
32
|
+
depStore.run();
|
|
33
|
+
textService = depStore.textService;
|
|
34
|
+
mockedTranslationReturn = 'Dummy translated text {param1} {param2}';
|
|
35
|
+
|
|
36
|
+
coder = new Coder(depStore.properties, textService);
|
|
37
|
+
|
|
38
|
+
getMockedInstance(Properties).config = {
|
|
39
|
+
inputPrefix: '{{',
|
|
40
|
+
inputSuffix: '}}',
|
|
41
|
+
restrictedElements: [],
|
|
42
|
+
tagAttributes: {
|
|
43
|
+
'*': ['aria-label'],
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
getMockedInstance(TranslationService).getTranslation = getTranslationMock;
|
|
47
|
+
|
|
48
|
+
getMockedInstance(TranslationService).getFromCacheOrFallback =
|
|
49
|
+
getFromCacheOrCallbackMock;
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
afterEach(async () => {
|
|
53
|
+
jest.clearAllMocks();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('replace function replaces occurrences correctly', async () => {
|
|
57
|
+
const text =
|
|
58
|
+
'This is text with params {{text:param1:hello,param2:hello 2 as well}}. Text continues';
|
|
59
|
+
const replaced = await coder.unwrap(text);
|
|
60
|
+
expect(replaced.text).toEqual(
|
|
61
|
+
'This is text with params Dummy translated text hello hello 2 as well. Text continues'
|
|
62
|
+
);
|
|
63
|
+
expect(replaced.keys[0].key).toEqual('text');
|
|
64
|
+
expect(replaced.keys[0].params['param1']).toEqual('hello');
|
|
65
|
+
expect(replaced.keys[0].params['param2']).toEqual('hello 2 as well');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('replace function does not replace when prefix is escaped', async () => {
|
|
69
|
+
const text = 'This is text: {{text:param1:aaaa,param2:aaaa}} \\{{text}}';
|
|
70
|
+
const replaced = await coder.unwrap(text);
|
|
71
|
+
expect(replaced.text).toEqual(
|
|
72
|
+
'This is text: Dummy translated text aaaa aaaa {{text}}'
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("it doesn't affect not related backslashes", async () => {
|
|
77
|
+
const text =
|
|
78
|
+
'\\This is \\text: {{text:param1:aaaa,param2:aaaa}} \\{{text}}, see? \\';
|
|
79
|
+
const replaced = await coder.unwrap(text);
|
|
80
|
+
expect(replaced.text).toEqual(
|
|
81
|
+
'\\This is \\text: Dummy translated text aaaa aaaa {{text}}, see? \\'
|
|
82
|
+
);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('correctly parses default value', async () => {
|
|
86
|
+
const text =
|
|
87
|
+
'\\This is \\text: {{text,This is my default value.\\:look!:param1:aaaa,param2:aaaa}} \\{{text}}, see? \\';
|
|
88
|
+
await coder.unwrap(text);
|
|
89
|
+
expect(getFromCacheOrCallbackMock).toHaveBeenCalledWith(
|
|
90
|
+
'text',
|
|
91
|
+
undefined,
|
|
92
|
+
'This is my default value.:look!'
|
|
93
|
+
);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('replace function does not translate when params have escaped , or :', async () => {
|
|
97
|
+
const text =
|
|
98
|
+
'This is text: {{text:param1:param 1 with\\,and \\:.,param2:hello \\:}}. Text continues';
|
|
99
|
+
const replaced = await coder.unwrap(text);
|
|
100
|
+
expect(replaced.text).toEqual(
|
|
101
|
+
'This is text: Dummy translated text param 1 with,and :. hello :. Text continues'
|
|
102
|
+
);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('replace function does not translate when params have escaped \\', async () => {
|
|
106
|
+
const text =
|
|
107
|
+
'This is text: {{text:param1:param 1 with\\\\ and again \\\\,param2:hello \\\\}}. Text continues';
|
|
108
|
+
const replaced = await coder.unwrap(text);
|
|
109
|
+
expect(replaced.text).toEqual(
|
|
110
|
+
'This is text: Dummy translated text param 1 with\\ and again \\ hello \\. Text continues'
|
|
111
|
+
);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test('replace function works with new lines in key', async () => {
|
|
115
|
+
mockedTranslationReturn = 'yep';
|
|
116
|
+
const text = 'This is text: {{text\nwith\nnew\nlines}}. Text continues';
|
|
117
|
+
await coder.unwrap(text);
|
|
118
|
+
expect(getFromCacheOrCallbackMock).toHaveBeenCalledWith(
|
|
119
|
+
'text\nwith\nnew\nlines',
|
|
120
|
+
undefined,
|
|
121
|
+
undefined
|
|
122
|
+
);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test('works with escaped strings in params', async () => {
|
|
126
|
+
const text = 'Text: {{text\nwith\nnew\nlines:hello:w\\,or\\:ld}}.';
|
|
127
|
+
mockedTranslationReturn = 'translated {hello}';
|
|
128
|
+
const result = await coder.unwrap(text);
|
|
129
|
+
expect(result.keys[0].params['hello']).toEqual('w,or:ld');
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe('Different key occurrences', () => {
|
|
133
|
+
beforeEach(() => {
|
|
134
|
+
getMockedInstance(TranslationService).getFromCacheOrFallback = jest.fn(
|
|
135
|
+
() => {
|
|
136
|
+
return 'translated';
|
|
137
|
+
}
|
|
138
|
+
);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test('longer text will be handled correctly', async () => {
|
|
142
|
+
const text =
|
|
143
|
+
'This is standard text to translate: {{text}}. This is another one: {{text}}.\n' +
|
|
144
|
+
'There is an text with slash before, but escaped: \\\\{{text}}\n' +
|
|
145
|
+
'This is one with escaped prefix, to it shall not translate this one: \\{{aaa}}\n' +
|
|
146
|
+
'There is another text: {{text}}\n' +
|
|
147
|
+
'There are two texts to translate next to each other: {{text}}{{text}}. And after it text continues: {{text}}.\n' +
|
|
148
|
+
'This is an end, finally.\n';
|
|
149
|
+
|
|
150
|
+
const replaced = await coder.unwrap(text);
|
|
151
|
+
|
|
152
|
+
expect(replaced.text).toEqual(
|
|
153
|
+
'This is standard text to translate: translated. This is another one: translated.\n' +
|
|
154
|
+
'There is an text with slash before, but escaped: \\translated\n' +
|
|
155
|
+
'This is one with escaped prefix, to it shall not translate this one: {{aaa}}\n' +
|
|
156
|
+
'There is another text: translated\n' +
|
|
157
|
+
'There are two texts to translate next to each other: translatedtranslated. And after it text continues: translated.\n' +
|
|
158
|
+
'This is an end, finally.\n'
|
|
159
|
+
);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test('will translate correctly when the text begins with key', async () => {
|
|
163
|
+
const text = '{{text}}, text continues';
|
|
164
|
+
const replaced = await coder.unwrap(text);
|
|
165
|
+
expect(replaced.text).toEqual('translated, text continues');
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test('will not translate when there is nothing to translate', async () => {
|
|
169
|
+
const text = '\\{{text}}, text continues';
|
|
170
|
+
const replaced = await coder.unwrap(text);
|
|
171
|
+
expect(replaced).toEqual(undefined);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test('will not translate when the text begins with escaped key', async () => {
|
|
175
|
+
const text = '\\{{text}}, text continues {{other text}}';
|
|
176
|
+
const replaced = await coder.unwrap(text);
|
|
177
|
+
expect(replaced.text).toEqual('{{text}}, text continues translated');
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test('will translate when the text begins with escaped escape character', async () => {
|
|
181
|
+
const text = '\\\\{{text}}, text continues {{other text}}';
|
|
182
|
+
const replaced = await coder.unwrap(text);
|
|
183
|
+
expect(replaced.text).toEqual('\\translated, text continues translated');
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test('will translate when the text begins with escaped escape character, what is escaped', async () => {
|
|
187
|
+
const text = '\\\\\\{{text}}, text continues {{other text}}';
|
|
188
|
+
const replaced = await coder.unwrap(text);
|
|
189
|
+
expect(replaced.text).toEqual('\\\\{{text}}, text continues translated');
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
describe('wrap function', () => {
|
|
194
|
+
test('will correctly wrap text without params', () => {
|
|
195
|
+
expect(coder.wrap('text')).toEqual('{{text}}');
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test('will correctly wrap text with , and :', () => {
|
|
199
|
+
expect(coder.wrap('text, other text: yes!')).toEqual(
|
|
200
|
+
'{{text\\, other text\\: yes!}}'
|
|
201
|
+
);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test('will correctly wrap text with ,, : and \\', () => {
|
|
205
|
+
expect(coder.wrap('text, other text: \\ yes!')).toEqual(
|
|
206
|
+
'{{text\\, other text\\: \\\\ yes!}}'
|
|
207
|
+
);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test('will correctly wrap text with parameters', () => {
|
|
211
|
+
expect(
|
|
212
|
+
coder.wrap('text', {
|
|
213
|
+
param1: 'the param value',
|
|
214
|
+
param2: '2nd value',
|
|
215
|
+
})
|
|
216
|
+
).toEqual('{{text:param1:the param value,param2:2nd value}}');
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
test('will correctly wrap text with parameters containing : an ,', () => {
|
|
220
|
+
expect(
|
|
221
|
+
coder.wrap('text', {
|
|
222
|
+
param1: 'the param value: value, value2 or value3',
|
|
223
|
+
param2: '2nd value',
|
|
224
|
+
})
|
|
225
|
+
).toEqual(
|
|
226
|
+
'{{text:param1:the param value\\: value\\, value2 or value3,param2:2nd value}}'
|
|
227
|
+
);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
test('it will correctly replace wrapped text', async () => {
|
|
231
|
+
getMockedInstance(TranslationService).getFromCacheOrFallback = jest.fn(
|
|
232
|
+
() => {
|
|
233
|
+
return 'xxx {param1} {param2} xxx';
|
|
234
|
+
}
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
const wrapped = coder.wrap(
|
|
238
|
+
'An longer key with some strange ,: ,: \\ characters',
|
|
239
|
+
{ param1: ',:,:', param2: '....\\...,' }
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
expect((await coder.unwrap(wrapped)).text).toEqual(
|
|
243
|
+
'xxx ,:,: ....\\..., xxx'
|
|
244
|
+
);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
test('will correctly convert param to string', () => {
|
|
248
|
+
const wrapped = coder.wrap('key', { param1: 1 });
|
|
249
|
+
expect(wrapped).toEqual('{{key:param1:1}}');
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
test('will correctly replace number parameters', async () => {
|
|
253
|
+
getMockedInstance(TranslationService).getFromCacheOrFallback = jest.fn(
|
|
254
|
+
() => {
|
|
255
|
+
return 'xxx {param1, number} {param2, number} xxx';
|
|
256
|
+
}
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
const wrapped = coder.wrap('key', { param1: 1, param2: 145.5 });
|
|
260
|
+
expect((await coder.unwrap(wrapped)).text).toEqual('xxx 1 145.5 xxx');
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
test('correctly wraps default value', async () => {
|
|
264
|
+
const wrapped = coder.wrap(
|
|
265
|
+
'key',
|
|
266
|
+
{ param1: 1, param2: 'Yes,yes,yes:yes' },
|
|
267
|
+
'Look: What a beautiful default\nvalue,' +
|
|
268
|
+
' translating will be such an experience.'
|
|
269
|
+
);
|
|
270
|
+
expect(wrapped).toEqual(
|
|
271
|
+
'{{key,Look\\: What a beautiful default\n' +
|
|
272
|
+
'value\\, translating will be such an experience.' +
|
|
273
|
+
':param1:1,param2:Yes\\,yes\\,yes\\:yes}}'
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
await coder.unwrap(wrapped);
|
|
277
|
+
expect(getFromCacheOrCallbackMock).toBeCalledWith(
|
|
278
|
+
'key',
|
|
279
|
+
undefined,
|
|
280
|
+
'Look: What a beautiful default\nvalue, translating will be such an experience.'
|
|
281
|
+
);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
test('will correctly replace bigint parameter', async () => {
|
|
285
|
+
getMockedInstance(TranslationService).getFromCacheOrFallback = jest.fn(
|
|
286
|
+
() => {
|
|
287
|
+
return 'xxx {param1, number} {param2, number} xxx';
|
|
288
|
+
}
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
const wrapped = coder.wrap('key', {
|
|
292
|
+
param1: 1,
|
|
293
|
+
param2: BigInt(129374),
|
|
294
|
+
});
|
|
295
|
+
expect((await coder.unwrap(wrapped)).text).toEqual('xxx 1 129,374 xxx');
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
});
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { TextHelper } from '../../helpers/TextHelper';
|
|
2
|
+
import { Properties } from '../../Properties';
|
|
3
|
+
import { TextService } from '../../services/TextService';
|
|
4
|
+
import {
|
|
5
|
+
KeyAndParams,
|
|
6
|
+
KeyAndParamsTags,
|
|
7
|
+
TranslatedWithMetadataTags,
|
|
8
|
+
TranslationParams,
|
|
9
|
+
Unwrapped,
|
|
10
|
+
} from '../../types';
|
|
11
|
+
|
|
12
|
+
export class Coder {
|
|
13
|
+
constructor(
|
|
14
|
+
private properties: Properties,
|
|
15
|
+
private textService: TextService
|
|
16
|
+
) {}
|
|
17
|
+
|
|
18
|
+
private get rawUnWrapRegex(): string {
|
|
19
|
+
const escapedPrefix = this.escapeForRegExp(
|
|
20
|
+
this.properties.config.inputPrefix
|
|
21
|
+
);
|
|
22
|
+
const escapedSuffix = this.escapeForRegExp(
|
|
23
|
+
this.properties.config.inputSuffix
|
|
24
|
+
);
|
|
25
|
+
return `(\\\\?)(${escapedPrefix}(.*?)${escapedSuffix})`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
private static parseUnwrapped(unwrappedString: string): KeyAndParams {
|
|
29
|
+
let escaped = false;
|
|
30
|
+
let actual = '';
|
|
31
|
+
let paramName = '';
|
|
32
|
+
let readingState: 'KEY' | 'DEFAULT_VALUE' | 'PARAM_NAME' | 'PARAM_VALUE' =
|
|
33
|
+
'KEY';
|
|
34
|
+
|
|
35
|
+
const result = {
|
|
36
|
+
key: '',
|
|
37
|
+
params: {},
|
|
38
|
+
defaultValue: undefined as string | undefined,
|
|
39
|
+
} as KeyAndParams;
|
|
40
|
+
|
|
41
|
+
for (const char of unwrappedString) {
|
|
42
|
+
if (char === '\\' && !escaped) {
|
|
43
|
+
escaped = true;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (escaped) {
|
|
47
|
+
escaped = false;
|
|
48
|
+
actual += char;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (readingState === 'KEY' && char === ',') {
|
|
52
|
+
readingState = 'DEFAULT_VALUE';
|
|
53
|
+
result.key = actual;
|
|
54
|
+
actual = '';
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (readingState === 'KEY' && char === ':') {
|
|
59
|
+
readingState = 'PARAM_NAME';
|
|
60
|
+
result.key = actual;
|
|
61
|
+
actual = '';
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (readingState === 'DEFAULT_VALUE' && char === ':') {
|
|
66
|
+
readingState = 'PARAM_NAME';
|
|
67
|
+
result.defaultValue = actual;
|
|
68
|
+
actual = '';
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (readingState === 'PARAM_NAME' && char === ':') {
|
|
73
|
+
readingState = 'PARAM_VALUE';
|
|
74
|
+
paramName = actual;
|
|
75
|
+
actual = '';
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (readingState === 'PARAM_VALUE' && char === ',') {
|
|
80
|
+
readingState = 'PARAM_NAME';
|
|
81
|
+
result.params[paramName] = actual;
|
|
82
|
+
actual = '';
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
actual += char;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (readingState === 'KEY') {
|
|
89
|
+
result.key = actual;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (readingState === 'DEFAULT_VALUE') {
|
|
93
|
+
result.defaultValue = actual;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (readingState === 'PARAM_VALUE') {
|
|
97
|
+
result.params[paramName] = actual;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return result;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
unwrap(text: string): Unwrapped {
|
|
104
|
+
const matchRegexp = new RegExp(this.rawUnWrapRegex, 'gs');
|
|
105
|
+
|
|
106
|
+
const keysAndParams: KeyAndParamsTags<any>[] = [];
|
|
107
|
+
|
|
108
|
+
let matched = false;
|
|
109
|
+
|
|
110
|
+
let match;
|
|
111
|
+
let start = 0;
|
|
112
|
+
let result = '';
|
|
113
|
+
while ((match = matchRegexp.exec(text)) !== null) {
|
|
114
|
+
let pre = match[1] as string;
|
|
115
|
+
const [fullMatch, _, wrapped, unwrapped] = match as [
|
|
116
|
+
string,
|
|
117
|
+
string,
|
|
118
|
+
string,
|
|
119
|
+
string
|
|
120
|
+
];
|
|
121
|
+
const { index, input } = match;
|
|
122
|
+
result += input.substr(start, index - start);
|
|
123
|
+
start = index + fullMatch.length;
|
|
124
|
+
if (pre === '\\') {
|
|
125
|
+
if (!TextHelper.isCharEscaped(index, text)) {
|
|
126
|
+
result += wrapped;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
pre = '';
|
|
130
|
+
}
|
|
131
|
+
const translated = this.getTranslatedWithMetadata(unwrapped);
|
|
132
|
+
keysAndParams.push({
|
|
133
|
+
key: translated.key,
|
|
134
|
+
params: translated.params,
|
|
135
|
+
defaultValue: translated.defaultValue,
|
|
136
|
+
});
|
|
137
|
+
matched = true;
|
|
138
|
+
result += pre + translated.translated;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
result += text.substring(start);
|
|
142
|
+
|
|
143
|
+
if (matched) {
|
|
144
|
+
return { text: result, keys: keysAndParams };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
public wrap(
|
|
151
|
+
key: string,
|
|
152
|
+
params: TranslationParams = {},
|
|
153
|
+
defaultValue: string | undefined = undefined
|
|
154
|
+
): string {
|
|
155
|
+
let paramString = Object.entries(params)
|
|
156
|
+
.map(
|
|
157
|
+
([name, value]) =>
|
|
158
|
+
`${this.escapeParam(name)}:${this.escapeParam(value as string)}`
|
|
159
|
+
)
|
|
160
|
+
.join(',');
|
|
161
|
+
paramString = paramString.length ? `:${paramString}` : '';
|
|
162
|
+
|
|
163
|
+
const defaultString =
|
|
164
|
+
defaultValue !== undefined ? `,${this.escapeParam(defaultValue)}` : '';
|
|
165
|
+
|
|
166
|
+
return `${this.properties.config.inputPrefix}${this.escapeParam(
|
|
167
|
+
key
|
|
168
|
+
)}${defaultString}${paramString}${this.properties.config.inputSuffix}`;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
private getTranslatedWithMetadata(
|
|
172
|
+
text: string
|
|
173
|
+
): TranslatedWithMetadataTags<any> {
|
|
174
|
+
const { key, params, defaultValue } = Coder.parseUnwrapped(text);
|
|
175
|
+
const translated = this.textService.instant(
|
|
176
|
+
key,
|
|
177
|
+
params,
|
|
178
|
+
undefined,
|
|
179
|
+
false,
|
|
180
|
+
defaultValue
|
|
181
|
+
);
|
|
182
|
+
return { translated, key, params, defaultValue };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
private readonly escapeForRegExp = (string: string) => {
|
|
186
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
private readonly escapeParam = (param: any) => {
|
|
190
|
+
if (typeof param === 'string') {
|
|
191
|
+
return param.replace(/[,:\\]/gs, '\\$&');
|
|
192
|
+
}
|
|
193
|
+
if (typeof param === 'number' || typeof param === 'bigint') {
|
|
194
|
+
return param.toString();
|
|
195
|
+
}
|
|
196
|
+
// eslint-disable-next-line no-console
|
|
197
|
+
console.warn(
|
|
198
|
+
`Parameters of type "${typeof param}" are not supported in "text" wrapper mode.`
|
|
199
|
+
);
|
|
200
|
+
return param;
|
|
201
|
+
};
|
|
202
|
+
}
|