ztechno_core 0.0.65 → 0.0.66
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/lib/crypto_service.d.ts +1 -1
- package/lib/engine_base.d.ts +2 -2
- package/lib/index.d.ts +3 -13
- package/lib/index.js +33 -3
- package/lib/mail_service.d.ts +72 -3
- package/lib/mail_service.js +81 -0
- package/lib/orm/mail_blacklist_orm.d.ts +21 -0
- package/lib/orm/mail_blacklist_orm.js +68 -0
- package/lib/orm/orm.d.ts +8 -0
- package/lib/orm/orm.js +26 -0
- package/lib/sql_service.d.ts +1 -1
- package/lib/sql_service.js +3 -3
- package/lib/translate_service.d.ts +8 -2
- package/lib/translate_service.js +602 -143
- package/lib/typings/index.d.ts +4 -0
- package/lib/typings/index.js +36 -0
- package/lib/typings/mail_types.d.ts +31 -2
- package/lib/typings/translate_types.d.ts +22 -2
- package/lib/typings/translate_types.js +44 -0
- package/lib/user_service.d.ts +3 -3
- package/package.json +1 -1
package/lib/translate_service.js
CHANGED
|
@@ -6,6 +6,7 @@ var __importDefault =
|
|
|
6
6
|
};
|
|
7
7
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
8
8
|
exports.ZTranslateService = void 0;
|
|
9
|
+
const typings_1 = require('./typings');
|
|
9
10
|
const dist_1 = require('./vendor/dom-parser/dist');
|
|
10
11
|
const translate_1 = __importDefault(require('translate'));
|
|
11
12
|
class ZTranslateService {
|
|
@@ -30,194 +31,652 @@ class ZTranslateService {
|
|
|
30
31
|
this.opt = opt;
|
|
31
32
|
this.localCache = {};
|
|
32
33
|
this.surpressErrors = true;
|
|
34
|
+
this.maxRetries = 3;
|
|
35
|
+
this.retryDelay = 1000;
|
|
36
|
+
this.fallbackText = '?';
|
|
33
37
|
this.codes = {
|
|
34
|
-
|
|
35
|
-
[`&#
|
|
36
|
-
[`&#
|
|
37
|
-
[`&#
|
|
38
|
-
[`&#
|
|
39
|
-
[`&#
|
|
40
|
-
[`&#
|
|
41
|
-
[`&#
|
|
42
|
-
[`&#
|
|
38
|
+
// Quotes and apostrophes
|
|
39
|
+
[`'`]: `'`,
|
|
40
|
+
[`"`]: `"`,
|
|
41
|
+
[`“`]: `"`,
|
|
42
|
+
[`”`]: `"`,
|
|
43
|
+
[`‘`]: `'`,
|
|
44
|
+
[`’`]: `'`,
|
|
45
|
+
[`‚`]: `‚`,
|
|
46
|
+
[`„`]: `„`,
|
|
47
|
+
[`«`]: `«`,
|
|
48
|
+
[`»`]: `»`,
|
|
49
|
+
[`‹`]: `‹`,
|
|
50
|
+
[`›`]: `›`,
|
|
51
|
+
// Currency symbols
|
|
52
|
+
[`©`]: `©`,
|
|
53
|
+
[`®`]: `®`,
|
|
54
|
+
[`€`]: `€`,
|
|
55
|
+
[`£`]: `£`,
|
|
56
|
+
[`¥`]: `¥`,
|
|
57
|
+
[`¢`]: `¢`,
|
|
58
|
+
[`™`]: `™`,
|
|
59
|
+
[`$`]: `$`,
|
|
60
|
+
// Mathematical and special symbols
|
|
61
|
+
[`–`]: `–`,
|
|
62
|
+
[`—`]: `—`,
|
|
63
|
+
[`…`]: `…`,
|
|
64
|
+
[`•`]: `•`,
|
|
65
|
+
[`→`]: `→`,
|
|
66
|
+
[`←`]: `←`,
|
|
67
|
+
[`↑`]: `↑`,
|
|
68
|
+
[`↓`]: `↓`,
|
|
69
|
+
[`×`]: `×`,
|
|
70
|
+
[`÷`]: `÷`,
|
|
71
|
+
[`±`]: `±`,
|
|
72
|
+
[`≤`]: `≤`,
|
|
73
|
+
[`≥`]: `≥`,
|
|
74
|
+
[`≠`]: `≠`,
|
|
75
|
+
[`∞`]: `∞`,
|
|
76
|
+
[`°`]: `°`,
|
|
77
|
+
[`‰`]: `‰`,
|
|
78
|
+
[`†`]: `†`,
|
|
79
|
+
[`‡`]: `‡`,
|
|
80
|
+
[`§`]: `§`,
|
|
81
|
+
[`¶`]: `¶`,
|
|
82
|
+
// Accented characters
|
|
83
|
+
[`À`]: `À`,
|
|
84
|
+
[`Á`]: `Á`,
|
|
85
|
+
[`Â`]: `Â`,
|
|
86
|
+
[`Ã`]: `Ã`,
|
|
87
|
+
[`Ä`]: `Ä`,
|
|
88
|
+
[`Å`]: `Å`,
|
|
89
|
+
[`Æ`]: `Æ`,
|
|
90
|
+
[`Ç`]: `Ç`,
|
|
91
|
+
[`È`]: `È`,
|
|
92
|
+
[`É`]: `É`,
|
|
93
|
+
[`Ê`]: `Ê`,
|
|
94
|
+
[`Ë`]: `Ë`,
|
|
95
|
+
[`Ì`]: `Ì`,
|
|
96
|
+
[`Í`]: `Í`,
|
|
97
|
+
[`Î`]: `Î`,
|
|
98
|
+
[`Ï`]: `Ï`,
|
|
99
|
+
[`Ð`]: `Ð`,
|
|
100
|
+
[`Ñ`]: `Ñ`,
|
|
101
|
+
[`Ò`]: `Ò`,
|
|
102
|
+
[`Ó`]: `Ó`,
|
|
103
|
+
[`Ô`]: `Ô`,
|
|
104
|
+
[`Õ`]: `Õ`,
|
|
105
|
+
[`Ö`]: `Ö`,
|
|
106
|
+
[`Ø`]: `Ø`,
|
|
107
|
+
[`Ù`]: `Ù`,
|
|
108
|
+
[`Ú`]: `Ú`,
|
|
109
|
+
[`Û`]: `Û`,
|
|
110
|
+
[`Ü`]: `Ü`,
|
|
111
|
+
[`Ý`]: `Ý`,
|
|
112
|
+
[`Þ`]: `Þ`,
|
|
113
|
+
[`ß`]: `ß`,
|
|
114
|
+
[`à`]: `à`,
|
|
115
|
+
[`á`]: `á`,
|
|
116
|
+
[`â`]: `â`,
|
|
117
|
+
[`ã`]: `ã`,
|
|
118
|
+
[`ä`]: `ä`,
|
|
119
|
+
[`å`]: `å`,
|
|
120
|
+
[`æ`]: `æ`,
|
|
121
|
+
[`ç`]: `ç`,
|
|
122
|
+
[`è`]: `è`,
|
|
123
|
+
[`é`]: `é`,
|
|
124
|
+
[`ê`]: `ê`,
|
|
125
|
+
[`ë`]: `ë`,
|
|
126
|
+
[`ì`]: `ì`,
|
|
127
|
+
[`í`]: `í`,
|
|
128
|
+
[`î`]: `î`,
|
|
129
|
+
[`ï`]: `ï`,
|
|
130
|
+
[`ð`]: `ð`,
|
|
131
|
+
[`ñ`]: `ñ`,
|
|
132
|
+
[`ò`]: `ò`,
|
|
133
|
+
[`ó`]: `ó`,
|
|
134
|
+
[`ô`]: `ô`,
|
|
135
|
+
[`õ`]: `õ`,
|
|
136
|
+
[`ö`]: `ö`,
|
|
137
|
+
[`ø`]: `ø`,
|
|
138
|
+
[`ù`]: `ù`,
|
|
139
|
+
[`ú`]: `ú`,
|
|
140
|
+
[`û`]: `û`,
|
|
141
|
+
[`ü`]: `ü`,
|
|
142
|
+
[`ý`]: `ý`,
|
|
143
|
+
[`þ`]: `þ`,
|
|
144
|
+
[`ÿ`]: `ÿ`,
|
|
145
|
+
// Common spaces and breaks
|
|
146
|
+
[` `]: ` `,
|
|
147
|
+
[`­`]: ``,
|
|
148
|
+
[`​`]: ``,
|
|
149
|
+
// Punctuation
|
|
150
|
+
[`¡`]: `¡`,
|
|
151
|
+
[`¿`]: `¿`,
|
|
152
|
+
[`·`]: `·`,
|
|
153
|
+
[`¸`]: `¸`,
|
|
154
|
+
// Fractions
|
|
155
|
+
[`¼`]: `¼`,
|
|
156
|
+
[`½`]: `½`,
|
|
157
|
+
[`¾`]: `¾`,
|
|
158
|
+
[`⅓`]: `⅓`,
|
|
159
|
+
[`⅔`]: `⅔`,
|
|
160
|
+
[`⅕`]: `⅕`,
|
|
161
|
+
[`⅖`]: `⅖`,
|
|
162
|
+
[`⅗`]: `⅗`,
|
|
163
|
+
[`⅘`]: `⅘`,
|
|
164
|
+
[`⅙`]: `⅙`,
|
|
165
|
+
[`⅚`]: `⅚`,
|
|
166
|
+
[`⅛`]: `⅛`,
|
|
167
|
+
[`⅜`]: `⅜`,
|
|
168
|
+
[`⅝`]: `⅝`,
|
|
169
|
+
[`⅞`]: `⅞`,
|
|
170
|
+
// Greek letters (common ones)
|
|
171
|
+
[`α`]: `α`,
|
|
172
|
+
[`β`]: `β`,
|
|
173
|
+
[`γ`]: `γ`,
|
|
174
|
+
[`δ`]: `δ`,
|
|
175
|
+
[`ε`]: `ε`,
|
|
176
|
+
[`ζ`]: `ζ`,
|
|
177
|
+
[`η`]: `η`,
|
|
178
|
+
[`θ`]: `θ`,
|
|
179
|
+
[`ι`]: `ι`,
|
|
180
|
+
[`κ`]: `κ`,
|
|
181
|
+
[`λ`]: `λ`,
|
|
182
|
+
[`μ`]: `μ`,
|
|
183
|
+
[`ν`]: `ν`,
|
|
184
|
+
[`ξ`]: `ξ`,
|
|
185
|
+
[`ο`]: `ο`,
|
|
186
|
+
[`π`]: `π`,
|
|
187
|
+
[`ρ`]: `ρ`,
|
|
188
|
+
[`σ`]: `σ`,
|
|
189
|
+
[`τ`]: `τ`,
|
|
190
|
+
[`υ`]: `υ`,
|
|
191
|
+
[`φ`]: `φ`,
|
|
192
|
+
[`χ`]: `χ`,
|
|
193
|
+
[`ψ`]: `ψ`,
|
|
194
|
+
[`ω`]: `ω`,
|
|
195
|
+
// Uppercase Greek letters
|
|
196
|
+
[`Α`]: `Α`,
|
|
197
|
+
[`Β`]: `Β`,
|
|
198
|
+
[`Γ`]: `Γ`,
|
|
199
|
+
[`Δ`]: `Δ`,
|
|
200
|
+
[`Ε`]: `Ε`,
|
|
201
|
+
[`Ζ`]: `Ζ`,
|
|
202
|
+
[`Η`]: `Η`,
|
|
203
|
+
[`Θ`]: `Θ`,
|
|
204
|
+
[`Ι`]: `Ι`,
|
|
205
|
+
[`Κ`]: `Κ`,
|
|
206
|
+
[`Λ`]: `Λ`,
|
|
207
|
+
[`Μ`]: `Μ`,
|
|
208
|
+
[`Ν`]: `Ν`,
|
|
209
|
+
[`Ξ`]: `Ξ`,
|
|
210
|
+
[`Ο`]: `Ο`,
|
|
211
|
+
[`Π`]: `Π`,
|
|
212
|
+
[`Ρ`]: `Ρ`,
|
|
213
|
+
[`Σ`]: `Σ`,
|
|
214
|
+
[`Τ`]: `Τ`,
|
|
215
|
+
[`Υ`]: `Υ`,
|
|
216
|
+
[`Φ`]: `Φ`,
|
|
217
|
+
[`Χ`]: `Χ`,
|
|
218
|
+
[`Ψ`]: `Ψ`,
|
|
219
|
+
[`Ω`]: `Ω`,
|
|
220
|
+
// Additional common symbols
|
|
221
|
+
[`₺`]: `₪`,
|
|
222
|
+
[`₽`]: `₽`,
|
|
223
|
+
[`₹`]: `₹`,
|
|
224
|
+
[`¤`]: `¤`,
|
|
225
|
+
[`¦`]: `¦`,
|
|
226
|
+
[`¨`]: `¨`,
|
|
227
|
+
[`ª`]: `ª`,
|
|
228
|
+
[`¬`]: `¬`,
|
|
229
|
+
[`¯`]: `¯`,
|
|
230
|
+
[`²`]: `²`,
|
|
231
|
+
[`³`]: `³`,
|
|
232
|
+
[`¹`]: `¹`,
|
|
233
|
+
[`º`]: `º`,
|
|
234
|
+
// Card suits and misc symbols
|
|
235
|
+
[`♠`]: `♠`,
|
|
236
|
+
[`♣`]: `♣`,
|
|
237
|
+
[`♥`]: `♥`,
|
|
238
|
+
[`♦`]: `♦`,
|
|
239
|
+
[`★`]: `★`,
|
|
240
|
+
[`☆`]: `☆`,
|
|
241
|
+
[`☎`]: `☎`,
|
|
242
|
+
[`☕`]: `☕`,
|
|
243
|
+
[`☺`]: `☺`,
|
|
244
|
+
[`☻`]: `☻`,
|
|
245
|
+
[`☼`]: `☼`,
|
|
246
|
+
[`♀`]: `♀`,
|
|
247
|
+
[`♂`]: `♂`,
|
|
248
|
+
[`❤`]: `❤`,
|
|
43
249
|
};
|
|
250
|
+
if (!opt.googleApiKey) {
|
|
251
|
+
throw new typings_1.ValidationError('googleApiKey', opt.googleApiKey);
|
|
252
|
+
}
|
|
253
|
+
if (!opt.sqlService) {
|
|
254
|
+
throw new typings_1.ValidationError('sqlService', opt.sqlService);
|
|
255
|
+
}
|
|
44
256
|
translate_1.default.key = opt.googleApiKey;
|
|
45
257
|
this.surpressErrors = opt.surpressErrors ?? true;
|
|
258
|
+
this.maxRetries = opt.maxRetries ?? 3;
|
|
259
|
+
this.retryDelay = opt.retryDelay ?? 1000;
|
|
260
|
+
this.fallbackText = opt.fallbackText ?? '?';
|
|
46
261
|
this.getLanguages().map((lang) => (this.localCache[lang.lang] = {}));
|
|
47
262
|
setInterval(() => this.clearLocalCache(), 1000 * 60 * 60); // Every Hour
|
|
48
263
|
}
|
|
49
264
|
getLang(cookies) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
265
|
+
try {
|
|
266
|
+
const defaultLang = this.getDefaultLang();
|
|
267
|
+
const langKey = (cookies?.lang || defaultLang).toLowerCase();
|
|
268
|
+
const foundLang = this.getLanguages().find((l) => l.lang === langKey);
|
|
269
|
+
return foundLang === undefined ? defaultLang : foundLang.lang;
|
|
270
|
+
} catch (error) {
|
|
271
|
+
this.logError(new typings_1.ValidationError('cookies', cookies), 'getLang');
|
|
272
|
+
return this.getDefaultLang();
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
async sleep(ms) {
|
|
276
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
277
|
+
}
|
|
278
|
+
logError(error, context) {
|
|
279
|
+
if (this.opt.log) {
|
|
280
|
+
this.opt.log(error, { context, timestamp: new Date().toISOString() });
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
async retryOperation(operation, operationName, maxRetries = this.maxRetries) {
|
|
284
|
+
let lastError;
|
|
285
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
286
|
+
try {
|
|
287
|
+
return await operation();
|
|
288
|
+
} catch (error) {
|
|
289
|
+
lastError = error;
|
|
290
|
+
this.logError(error, `${operationName} - Attempt ${attempt}/${maxRetries}`);
|
|
291
|
+
if (attempt < maxRetries) {
|
|
292
|
+
await this.sleep(this.retryDelay * attempt); // Exponential backoff
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
throw lastError;
|
|
54
297
|
}
|
|
55
298
|
async translateText(langOrReq, text) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
299
|
+
try {
|
|
300
|
+
// Input validation
|
|
301
|
+
if (!text || typeof text !== 'string') {
|
|
302
|
+
throw new typings_1.ValidationError('text', text);
|
|
303
|
+
}
|
|
304
|
+
const lang = typeof langOrReq === 'string' ? langOrReq : this.getLang(langOrReq.cookies);
|
|
305
|
+
text = text.trim();
|
|
306
|
+
if (text.length === 0) {
|
|
307
|
+
return text;
|
|
308
|
+
}
|
|
309
|
+
if (text.length === 1) {
|
|
310
|
+
return text;
|
|
311
|
+
}
|
|
312
|
+
// Process HTML entities with better error handling
|
|
313
|
+
text = await this.processHtmlEntities(text);
|
|
314
|
+
// Check local cache
|
|
315
|
+
const localCached = this.checkLocalCache(text, lang);
|
|
316
|
+
if (localCached !== false) {
|
|
317
|
+
return localCached.value;
|
|
318
|
+
}
|
|
319
|
+
// Check remote cache
|
|
320
|
+
const remoteCached = await this.fetch(text, lang);
|
|
321
|
+
if (remoteCached !== false) {
|
|
322
|
+
return remoteCached.value;
|
|
323
|
+
}
|
|
324
|
+
// Perform translation with retry logic
|
|
325
|
+
let result;
|
|
326
|
+
try {
|
|
327
|
+
result = await this.retryOperation(async () => {
|
|
328
|
+
return await (0, translate_1.default)(text, {
|
|
329
|
+
from: this.getSourceLang(),
|
|
330
|
+
to: lang,
|
|
331
|
+
});
|
|
332
|
+
}, 'translateText');
|
|
333
|
+
} catch (err) {
|
|
334
|
+
const translationError = new typings_1.ApiTranslationError(err, text, lang);
|
|
335
|
+
this.logError(translationError, 'translateText');
|
|
336
|
+
if (!this.surpressErrors) {
|
|
337
|
+
throw translationError;
|
|
338
|
+
}
|
|
339
|
+
result = this.fallbackText;
|
|
340
|
+
}
|
|
341
|
+
// Save translation to cache
|
|
342
|
+
try {
|
|
343
|
+
await this.insert(text, lang, { value: result });
|
|
344
|
+
} catch (err) {
|
|
345
|
+
this.logError(new typings_1.DatabaseError(err, 'insert translation'), 'translateText');
|
|
346
|
+
// Don't throw here, translation still succeeded
|
|
347
|
+
}
|
|
348
|
+
return result;
|
|
349
|
+
} catch (error) {
|
|
350
|
+
if (error instanceof typings_1.TranslateError) {
|
|
351
|
+
throw error;
|
|
352
|
+
}
|
|
353
|
+
const wrappedError = new typings_1.TranslateError(
|
|
354
|
+
`Unexpected error in translateText: ${error.message}`,
|
|
355
|
+
'UNEXPECTED_ERROR',
|
|
356
|
+
{ originalError: error },
|
|
357
|
+
);
|
|
358
|
+
this.logError(wrappedError, 'translateText');
|
|
359
|
+
if (!this.surpressErrors) {
|
|
360
|
+
throw wrappedError;
|
|
361
|
+
}
|
|
362
|
+
return this.fallbackText;
|
|
60
363
|
}
|
|
364
|
+
}
|
|
365
|
+
async processHtmlEntities(text) {
|
|
61
366
|
let replaceCount = 0;
|
|
367
|
+
const maxReplacements = 1000;
|
|
62
368
|
while (text.includes('&#')) {
|
|
63
369
|
const codeIndexStart = text.indexOf('&#');
|
|
64
370
|
const first = text.substring(codeIndexStart);
|
|
65
|
-
const
|
|
371
|
+
const semicolonIndex = first.indexOf(';');
|
|
372
|
+
if (semicolonIndex === -1) {
|
|
373
|
+
// No closing semicolon found, break to avoid infinite loop
|
|
374
|
+
break;
|
|
375
|
+
}
|
|
376
|
+
const codeLength = semicolonIndex + 1;
|
|
66
377
|
const code = first.substring(0, codeLength);
|
|
67
378
|
if (this.codes[code] === undefined) {
|
|
68
|
-
|
|
69
|
-
|
|
379
|
+
const entityError = new typings_1.HtmlEntityError(code, text);
|
|
380
|
+
this.logError(entityError, 'processHtmlEntities');
|
|
381
|
+
if (!this.surpressErrors) {
|
|
382
|
+
throw entityError;
|
|
383
|
+
}
|
|
384
|
+
// Skip this entity and continue
|
|
385
|
+
text = text.substring(0, codeIndexStart) + code + text.substring(codeIndexStart + codeLength);
|
|
386
|
+
break;
|
|
70
387
|
}
|
|
71
388
|
text = text.substring(0, codeIndexStart) + this.codes[code] + text.substring(codeIndexStart + codeLength);
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
389
|
+
if (replaceCount++ > maxReplacements) {
|
|
390
|
+
const loopError = new typings_1.TranslateError(
|
|
391
|
+
`HTML entity replacement exceeded maximum count (${maxReplacements})`,
|
|
392
|
+
'MAX_REPLACEMENTS_EXCEEDED',
|
|
393
|
+
{ code, text, replaceCount },
|
|
394
|
+
);
|
|
395
|
+
this.logError(loopError, 'processHtmlEntities');
|
|
396
|
+
if (!this.surpressErrors) {
|
|
397
|
+
throw loopError;
|
|
398
|
+
}
|
|
399
|
+
break;
|
|
76
400
|
}
|
|
77
401
|
}
|
|
78
|
-
|
|
79
|
-
if (localCached !== false) {
|
|
80
|
-
return localCached.value;
|
|
81
|
-
}
|
|
82
|
-
const remoteCached = await this.fetch(text, lang);
|
|
83
|
-
if (remoteCached !== false) {
|
|
84
|
-
return remoteCached.value;
|
|
85
|
-
}
|
|
86
|
-
let result;
|
|
87
|
-
try {
|
|
88
|
-
result = await (0, translate_1.default)(text, {
|
|
89
|
-
from: this.getSourceLang(),
|
|
90
|
-
to: lang,
|
|
91
|
-
});
|
|
92
|
-
} catch (err) {
|
|
93
|
-
result = '?';
|
|
94
|
-
}
|
|
95
|
-
await this.insert(text, lang, { value: result });
|
|
96
|
-
return result;
|
|
402
|
+
return text;
|
|
97
403
|
}
|
|
98
404
|
async translateHtml(html, cookies) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const htmlNodes = dom.getElementsByTagName('html');
|
|
103
|
-
const mainNodes = dom.getElementsByTagName('main');
|
|
104
|
-
const isView = htmlNodes.length === 0;
|
|
105
|
-
const domNode = isView ? mainNodes[0] : htmlNodes[0];
|
|
106
|
-
if (lang !== srcLang) {
|
|
107
|
-
const node = isView ? domNode : domNode.getElementsByTagName('body')[0];
|
|
108
|
-
const promises = [];
|
|
109
|
-
this.translateHtmlRec(lang, node, promises);
|
|
110
|
-
await Promise.all(promises);
|
|
111
|
-
}
|
|
112
|
-
const output = domNode ? domNode.outerHTML : html;
|
|
113
|
-
return output.startsWith(`<!DOCTYPE html>`) ? output : `<!DOCTYPE html>\r\n${output}`;
|
|
114
|
-
}
|
|
115
|
-
translateHtmlRec(lang, node, promises, skipTranslate = false) {
|
|
116
|
-
if (this.opt.verbose) this.opt.verbose(node.nodeName, node);
|
|
117
|
-
if (node.getAttribute('notranslate') != null) {
|
|
118
|
-
skipTranslate = true;
|
|
119
|
-
}
|
|
120
|
-
if (node.nodeName === '#text') {
|
|
121
|
-
const nodeText = node;
|
|
122
|
-
const text = nodeText.text.replace(/[\r|\n|\r\n]+/g, ' ').replace(/\s\s+/g, ' ');
|
|
123
|
-
const value = text.trim();
|
|
124
|
-
const meta = {
|
|
125
|
-
prefix: genSpaces(text.length - text.trimStart().length),
|
|
126
|
-
suffix: genSpaces(text.length - text.trimEnd().length),
|
|
127
|
-
};
|
|
128
|
-
if (skipTranslate === true || text.length === 0 || !strContainsLetters(text)) {
|
|
129
|
-
node.text = meta.prefix + text + meta.suffix;
|
|
130
|
-
return;
|
|
405
|
+
try {
|
|
406
|
+
if (!html || typeof html !== 'string') {
|
|
407
|
+
throw new typings_1.ValidationError('html', html);
|
|
131
408
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
409
|
+
if (!cookies) {
|
|
410
|
+
throw new typings_1.ValidationError('cookies', cookies);
|
|
411
|
+
}
|
|
412
|
+
const lang = this.getLang(cookies);
|
|
413
|
+
const srcLang = this.getSourceLang();
|
|
414
|
+
let dom;
|
|
415
|
+
try {
|
|
416
|
+
dom = (0, dist_1.parseFromString)(html);
|
|
417
|
+
} catch (error) {
|
|
418
|
+
const parseError = new typings_1.TranslateError('Failed to parse HTML', 'HTML_PARSE_ERROR', { error, html });
|
|
419
|
+
this.logError(parseError, 'translateHtml');
|
|
420
|
+
if (!this.surpressErrors) {
|
|
421
|
+
throw parseError;
|
|
422
|
+
}
|
|
423
|
+
return html;
|
|
424
|
+
}
|
|
425
|
+
const htmlNodes = dom.getElementsByTagName('html');
|
|
426
|
+
const mainNodes = dom.getElementsByTagName('main');
|
|
427
|
+
const isView = htmlNodes.length === 0;
|
|
428
|
+
const domNode = isView ? mainNodes[0] : htmlNodes[0];
|
|
429
|
+
if (lang !== srcLang && domNode) {
|
|
430
|
+
const node = isView ? domNode : domNode.getElementsByTagName('body')[0];
|
|
431
|
+
if (node) {
|
|
432
|
+
const promises = [];
|
|
433
|
+
this.translateHtmlRec(lang, node, promises);
|
|
434
|
+
try {
|
|
435
|
+
await Promise.all(promises);
|
|
436
|
+
} catch (error) {
|
|
437
|
+
this.logError(
|
|
438
|
+
new typings_1.TranslateError('Failed to translate HTML nodes', 'HTML_TRANSLATION_ERROR', { error }),
|
|
439
|
+
'translateHtml',
|
|
440
|
+
);
|
|
140
441
|
if (!this.surpressErrors) {
|
|
141
|
-
throw
|
|
442
|
+
throw error;
|
|
142
443
|
}
|
|
143
|
-
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
const output = domNode ? domNode.outerHTML : html;
|
|
448
|
+
return output.startsWith(`<!DOCTYPE html>`) ? output : `<!DOCTYPE html>\r\n${output}`;
|
|
449
|
+
} catch (error) {
|
|
450
|
+
if (error instanceof typings_1.TranslateError) {
|
|
451
|
+
throw error;
|
|
452
|
+
}
|
|
453
|
+
const wrappedError = new typings_1.TranslateError(
|
|
454
|
+
`Unexpected error in translateHtml: ${error.message}`,
|
|
455
|
+
'UNEXPECTED_ERROR',
|
|
456
|
+
{ originalError: error },
|
|
144
457
|
);
|
|
145
|
-
|
|
458
|
+
this.logError(wrappedError, 'translateHtml');
|
|
459
|
+
if (!this.surpressErrors) {
|
|
460
|
+
throw wrappedError;
|
|
461
|
+
}
|
|
462
|
+
return html;
|
|
146
463
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
464
|
+
}
|
|
465
|
+
translateHtmlRec(lang, node, promises, skipTranslate = false) {
|
|
466
|
+
try {
|
|
467
|
+
if (this.opt.verbose) this.opt.verbose(node.nodeName, node);
|
|
468
|
+
if (node.getAttribute && node.getAttribute('notranslate') != null) {
|
|
469
|
+
skipTranslate = true;
|
|
470
|
+
}
|
|
471
|
+
// Skip HTML comments
|
|
472
|
+
if (node.nodeName === '#comment') {
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
if (node.nodeName === '#text') {
|
|
476
|
+
const nodeText = node;
|
|
477
|
+
const text = nodeText.text.replace(/[\r|\n|\r\n]+/g, ' ').replace(/\s\s+/g, ' ');
|
|
478
|
+
const value = text.trim();
|
|
479
|
+
const meta = {
|
|
480
|
+
prefix: genSpaces(text.length - text.trimStart().length),
|
|
481
|
+
suffix: genSpaces(text.length - text.trimEnd().length),
|
|
482
|
+
};
|
|
483
|
+
if (skipTranslate === true || text.length === 0 || !strContainsLetters(text)) {
|
|
484
|
+
node.text = meta.prefix + text + meta.suffix;
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
promises.push(
|
|
488
|
+
this.translateText(lang, value)
|
|
489
|
+
.then((translatedText) => {
|
|
490
|
+
node.text = meta.prefix + translatedText + meta.suffix;
|
|
491
|
+
})
|
|
492
|
+
.catch((err) => {
|
|
493
|
+
node.text = text;
|
|
494
|
+
this.logError(err, 'translateHtmlRec');
|
|
495
|
+
if (!this.surpressErrors) {
|
|
496
|
+
throw err;
|
|
497
|
+
}
|
|
498
|
+
}),
|
|
499
|
+
);
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
// Process child nodes safely
|
|
503
|
+
if (node.childNodes && Array.isArray(node.childNodes)) {
|
|
504
|
+
for (const child of node.childNodes) {
|
|
505
|
+
this.translateHtmlRec(lang, child, promises, skipTranslate);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
} catch (error) {
|
|
509
|
+
this.logError(
|
|
510
|
+
new typings_1.TranslateError(`Error processing HTML node: ${error.message}`, 'HTML_NODE_ERROR', {
|
|
511
|
+
error,
|
|
512
|
+
nodeName: node?.nodeName,
|
|
513
|
+
}),
|
|
514
|
+
'translateHtmlRec',
|
|
515
|
+
);
|
|
516
|
+
if (!this.surpressErrors) {
|
|
517
|
+
throw error;
|
|
518
|
+
}
|
|
150
519
|
}
|
|
151
520
|
}
|
|
152
521
|
async update(key, lang, data) {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
522
|
+
try {
|
|
523
|
+
if (!key || !lang || !data) {
|
|
524
|
+
throw new typings_1.ValidationError('update parameters', { key, lang, data });
|
|
525
|
+
}
|
|
526
|
+
const res = await this.retryOperation(async () => {
|
|
527
|
+
return await this.sql.query(
|
|
528
|
+
`
|
|
529
|
+
INSERT INTO translations
|
|
530
|
+
(\`key\`, \`lang\`, \`value\`)
|
|
531
|
+
VALUES
|
|
532
|
+
(:key, :lang, :value)
|
|
533
|
+
ON DUPLICATE KEY UPDATE value=:value
|
|
534
|
+
`,
|
|
535
|
+
{ key, lang, value: data.value },
|
|
536
|
+
);
|
|
537
|
+
}, 'update translation');
|
|
538
|
+
if (res.affectedRows) {
|
|
539
|
+
this.insertLocalCache(key, lang, data);
|
|
540
|
+
}
|
|
541
|
+
return res;
|
|
542
|
+
} catch (error) {
|
|
543
|
+
const dbError = new typings_1.DatabaseError(error, 'update translation');
|
|
544
|
+
this.logError(dbError, 'update');
|
|
545
|
+
throw dbError;
|
|
165
546
|
}
|
|
166
|
-
return res;
|
|
167
547
|
}
|
|
168
548
|
checkLocalCache(key, lang) {
|
|
169
|
-
|
|
170
|
-
|
|
549
|
+
try {
|
|
550
|
+
if (!key || !lang || !this.localCache[lang]) {
|
|
551
|
+
return false;
|
|
552
|
+
}
|
|
553
|
+
const hasLocal = !this.localCache[lang].hasOwnProperty(key);
|
|
554
|
+
return hasLocal ? false : this.localCache[lang][key];
|
|
555
|
+
} catch (error) {
|
|
556
|
+
this.logError(
|
|
557
|
+
new typings_1.TranslateError('Local cache check failed', 'CACHE_ERROR', { error, key, lang }),
|
|
558
|
+
'checkLocalCache',
|
|
559
|
+
);
|
|
560
|
+
return false;
|
|
561
|
+
}
|
|
171
562
|
}
|
|
172
563
|
insertLocalCache(key, lang, data) {
|
|
173
|
-
|
|
564
|
+
try {
|
|
565
|
+
if (!key || !lang || !data) {
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
if (!this.localCache[lang]) {
|
|
569
|
+
this.localCache[lang] = {};
|
|
570
|
+
}
|
|
571
|
+
this.localCache[lang][key] = data;
|
|
572
|
+
} catch (error) {
|
|
573
|
+
this.logError(
|
|
574
|
+
new typings_1.TranslateError('Local cache insertion failed', 'CACHE_ERROR', { error, key, lang }),
|
|
575
|
+
'insertLocalCache',
|
|
576
|
+
);
|
|
577
|
+
}
|
|
174
578
|
}
|
|
175
579
|
clearLocalCache() {
|
|
176
|
-
|
|
177
|
-
this.localCache
|
|
178
|
-
|
|
580
|
+
try {
|
|
581
|
+
Object.keys(this.localCache).map((k) => {
|
|
582
|
+
this.localCache[k] = {};
|
|
583
|
+
});
|
|
584
|
+
} catch (error) {
|
|
585
|
+
this.logError(
|
|
586
|
+
new typings_1.TranslateError('Failed to clear local cache', 'CACHE_ERROR', { error }),
|
|
587
|
+
'clearLocalCache',
|
|
588
|
+
);
|
|
589
|
+
}
|
|
179
590
|
}
|
|
180
591
|
async fetch(key, lang) {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
592
|
+
try {
|
|
593
|
+
if (!key || !lang) {
|
|
594
|
+
return false;
|
|
595
|
+
}
|
|
596
|
+
const results = await this.retryOperation(async () => {
|
|
597
|
+
return await this.sql.query(
|
|
598
|
+
`SELECT \`value\` FROM translations WHERE \`lang\`=? AND \`key\`=CONVERT(? USING utf8mb3)`,
|
|
599
|
+
[lang, key],
|
|
600
|
+
);
|
|
601
|
+
}, 'fetch translation');
|
|
602
|
+
if (results.length > 0) {
|
|
603
|
+
const { value } = results[0];
|
|
604
|
+
const data = { value };
|
|
605
|
+
this.insertLocalCache(key, lang, data);
|
|
606
|
+
return data;
|
|
607
|
+
}
|
|
608
|
+
return false;
|
|
609
|
+
} catch (error) {
|
|
610
|
+
const dbError = new typings_1.DatabaseError(error, 'fetch translation');
|
|
611
|
+
this.logError(dbError, 'fetch');
|
|
612
|
+
return false; // Don't throw, let translation proceed
|
|
191
613
|
}
|
|
192
|
-
return false;
|
|
193
614
|
}
|
|
194
615
|
async insert(key, lang, data) {
|
|
195
|
-
|
|
196
|
-
key
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
616
|
+
try {
|
|
617
|
+
if (!key || !lang || !data) {
|
|
618
|
+
throw new typings_1.ValidationError('insert parameters', { key, lang, data });
|
|
619
|
+
}
|
|
620
|
+
await this.retryOperation(async () => {
|
|
621
|
+
return await this.sql.query(`INSERT IGNORE INTO translations (\`key\`, \`lang\`, \`value\`) VALUES (?, ?, ?)`, [
|
|
622
|
+
key,
|
|
623
|
+
lang,
|
|
624
|
+
data.value,
|
|
625
|
+
]);
|
|
626
|
+
}, 'insert translation');
|
|
627
|
+
} catch (error) {
|
|
628
|
+
const dbError = new typings_1.DatabaseError(error, 'insert translation');
|
|
629
|
+
this.logError(dbError, 'insert');
|
|
630
|
+
throw dbError;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
async fetchLang(lang) {
|
|
634
|
+
try {
|
|
635
|
+
if (!lang) {
|
|
636
|
+
throw new typings_1.ValidationError('lang', lang);
|
|
637
|
+
}
|
|
638
|
+
return await this.retryOperation(async () => {
|
|
639
|
+
return await this.sql.query(
|
|
640
|
+
`SELECT \`key\`, \`lang\`, \`value\`, \`verified\`, \`created_at\` FROM translations WHERE \`lang\`=?`,
|
|
641
|
+
[lang],
|
|
642
|
+
);
|
|
643
|
+
}, 'fetchLang');
|
|
644
|
+
} catch (error) {
|
|
645
|
+
const dbError = new typings_1.DatabaseError(error, 'fetch language translations');
|
|
646
|
+
this.logError(dbError, 'fetchLang');
|
|
647
|
+
throw dbError;
|
|
648
|
+
}
|
|
206
649
|
}
|
|
207
650
|
async fetchAllGrouped() {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
output
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
651
|
+
try {
|
|
652
|
+
const output = {};
|
|
653
|
+
const allTranslations = await this.fetchAll();
|
|
654
|
+
allTranslations.forEach((translation) => {
|
|
655
|
+
const { key } = translation;
|
|
656
|
+
if (!output.hasOwnProperty(key)) {
|
|
657
|
+
output[key] = [];
|
|
658
|
+
}
|
|
659
|
+
output[key].push(translation);
|
|
660
|
+
});
|
|
661
|
+
return output;
|
|
662
|
+
} catch (error) {
|
|
663
|
+
const dbError = new typings_1.DatabaseError(error, 'fetch all grouped translations');
|
|
664
|
+
this.logError(dbError, 'fetchAllGrouped');
|
|
665
|
+
throw dbError;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
async fetchAll() {
|
|
669
|
+
try {
|
|
670
|
+
return await this.retryOperation(async () => {
|
|
671
|
+
return await this.sql.query(
|
|
672
|
+
`SELECT \`key\`, \`lang\`, \`value\`, \`verified\`, \`created_at\` FROM translations`,
|
|
673
|
+
);
|
|
674
|
+
}, 'fetchAll');
|
|
675
|
+
} catch (error) {
|
|
676
|
+
const dbError = new typings_1.DatabaseError(error, 'fetch all translations');
|
|
677
|
+
this.logError(dbError, 'fetchAll');
|
|
678
|
+
throw dbError;
|
|
679
|
+
}
|
|
221
680
|
}
|
|
222
681
|
}
|
|
223
682
|
exports.ZTranslateService = ZTranslateService;
|