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.
@@ -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
- [`&#39`]: `'`,
35
- [`&#34`]: `"`,
36
- [`&#8220`]: `“`,
37
- [`&#8221`]: `”`,
38
- [`&#169`]: `©`,
39
- [`&#174`]: `®`,
40
- [`&#8364`]: `€`,
41
- [`&#163`]: `£`,
42
- [`&#8482`]: `™`,
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
- const defaultLang = this.getDefaultLang();
51
- const langKey = (cookies.lang || defaultLang).toLowerCase();
52
- const foundLang = this.getLanguages().find((l) => l.lang === langKey);
53
- return foundLang === undefined ? defaultLang : foundLang.lang;
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
- const lang = typeof langOrReq === 'string' ? langOrReq : this.getLang(langOrReq.cookies);
57
- text = text.trim();
58
- if (text.length === 1) {
59
- return text;
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 codeLength = first.indexOf(';') + 1;
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
- throw new Error(`Cant recognize character code="${code}"\n for text=${text}\n\n`);
69
- // return text
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
- // text = text.replace(code, codes[code])
73
- if (replaceCount++ > 1000) {
74
- throw new Error(`Replace Count > 1000!!! character code="${code}"\n for text=${text}\n\n`);
75
- // return text
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
- const localCached = this.checkLocalCache(text, lang);
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
- const lang = this.getLang(cookies);
100
- const srcLang = this.getSourceLang();
101
- const dom = (0, dist_1.parseFromString)(html);
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
- promises.push(
133
- this.translateText(lang, value)
134
- .then((translatedText) => {
135
- node.text = meta.prefix + translatedText + meta.suffix;
136
- })
137
- .catch((err) => {
138
- node.text = text;
139
- if (this.opt.log) this.opt.log(err, node);
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 err; // TODO: Find out if surpressing is better
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
- return;
458
+ this.logError(wrappedError, 'translateHtml');
459
+ if (!this.surpressErrors) {
460
+ throw wrappedError;
461
+ }
462
+ return html;
146
463
  }
147
- // const hasChildren = node.childNodes !== undefined
148
- for (const child of node.childNodes || []) {
149
- this.translateHtmlRec(lang, child, promises, skipTranslate);
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
- const res = await this.sql.query(
154
- `
155
- INSERT INTO translations
156
- (\`key\`, \`lang\`, \`value\`)
157
- VALUES
158
- (:key, :lang, :value)
159
- ON DUPLICATE KEY UPDATE value=:value
160
- `,
161
- { key, lang, value: data.value },
162
- );
163
- if (res.affectedRows) {
164
- this.insertLocalCache(key, lang, data);
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
- const hasLocal = !this.localCache[lang].hasOwnProperty(key);
170
- return hasLocal ? false : this.localCache[lang][key];
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
- this.localCache[lang][key] = data;
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
- Object.keys(this.localCache).map((k) => {
177
- this.localCache[k] = {};
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
- const results = await this.sql.query(
182
- `SELECT \`value\` FROM translations WHERE \`lang\`=? AND \`key\`=CONVERT(? USING utf8mb3)`,
183
- [lang, key],
184
- );
185
- if (results.length > 0) {
186
- // api.query(`UPDATE translations SET last_used=CURRENT_TIMESTAMP WHERE \`lang\`=? AND \`key\`=?`, [lang, key])
187
- // .catch(err => console.error(err))
188
- const { value } = results[0];
189
- this.insertLocalCache(key, lang, { value });
190
- return { value };
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
- await this.sql.query(`INSERT IGNORE INTO translations (\`key\`, \`lang\`, \`value\`) VALUES (?, ?, ?)`, [
196
- key,
197
- lang,
198
- data.value,
199
- ]);
200
- }
201
- fetchLang(lang) {
202
- return this.sql.query(
203
- `SELECT \`key\`, \`lang\`, \`value\`, \`verified\`, \`created_at\` FROM translations WHERE \`lang\`=?`,
204
- [lang],
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
- const output = {};
209
- const allTranslations = await this.fetchAll();
210
- allTranslations.map((translation) => {
211
- const { key } = translation;
212
- if (!output.hasOwnProperty(key)) {
213
- output[key] = [];
214
- }
215
- output[key].push(translation);
216
- });
217
- return output;
218
- }
219
- fetchAll() {
220
- return this.sql.query(`SELECT \`key\`, \`lang\`, \`value\`, \`verified\`, \`created_at\` FROM translations`);
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;