@umituz/web-localization 1.1.7 → 1.1.9
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/domain/entities/translation.entity.d.ts +1 -0
- package/dist/domain/interfaces/translation-service.interface.d.ts +1 -2
- package/dist/infrastructure/constants/index.d.ts +2 -0
- package/dist/infrastructure/constants/index.js +3 -0
- package/dist/infrastructure/services/cli.service.d.ts +1 -0
- package/dist/infrastructure/services/cli.service.js +147 -55
- package/dist/infrastructure/services/google-translate.service.d.ts +45 -3
- package/dist/infrastructure/services/google-translate.service.js +324 -108
- package/dist/infrastructure/utils/file.util.d.ts +27 -1
- package/dist/infrastructure/utils/file.util.js +150 -11
- package/dist/infrastructure/utils/rate-limit.util.d.ts +37 -4
- package/dist/infrastructure/utils/rate-limit.util.js +109 -10
- package/dist/infrastructure/utils/text-validator.util.d.ts +1 -1
- package/dist/infrastructure/utils/text-validator.util.js +4 -4
- package/dist/integrations/i18n.setup.d.ts +43 -5
- package/dist/integrations/i18n.setup.js +133 -6
- package/dist/scripts/cli.js +6 -1
- package/package.json +2 -2
|
@@ -1,29 +1,127 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Google Translate Service
|
|
3
|
-
* @description Main translation service using Google Translate API
|
|
2
|
+
* Google Translate Service with Performance Optimizations
|
|
3
|
+
* @description Main translation service using Google Translate API with caching and pooling
|
|
4
4
|
*/
|
|
5
5
|
import { RateLimiter } from "../utils/rate-limit.util.js";
|
|
6
6
|
import { shouldSkipWord, needsTranslation, isValidText, } from "../utils/text-validator.util.js";
|
|
7
|
-
import { GOOGLE_TRANSLATE_API_URL, DEFAULT_MIN_DELAY, DEFAULT_TIMEOUT, } from "../constants/index.js";
|
|
7
|
+
import { GOOGLE_TRANSLATE_API_URL, DEFAULT_MIN_DELAY, DEFAULT_TIMEOUT, DEFAULT_MAX_RETRIES, TRANSLATION_BATCH_SIZE, TRANSLATION_CONCURRENCY_LIMIT, } from "../constants/index.js";
|
|
8
|
+
/**
|
|
9
|
+
* Object pool for reusing Map instances
|
|
10
|
+
*/
|
|
11
|
+
class MapPool {
|
|
12
|
+
pool = [];
|
|
13
|
+
maxPoolSize = 10;
|
|
14
|
+
acquire() {
|
|
15
|
+
return this.pool.pop() || new Map();
|
|
16
|
+
}
|
|
17
|
+
release(map) {
|
|
18
|
+
if (map.size === 0 && this.pool.length < this.maxPoolSize) {
|
|
19
|
+
this.pool.push(map);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
clear() {
|
|
23
|
+
this.pool.length = 0;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
8
26
|
class GoogleTranslateService {
|
|
9
27
|
config = null;
|
|
10
|
-
|
|
28
|
+
_rateLimiter = null;
|
|
29
|
+
// Translation cache with LRU-style eviction
|
|
30
|
+
translationCache = new Map();
|
|
31
|
+
maxCacheSize = 1000;
|
|
32
|
+
cacheTTL = 1000 * 60 * 60; // 1 hour
|
|
33
|
+
// Object pools
|
|
34
|
+
mapPool = new MapPool();
|
|
35
|
+
// Performance tracking
|
|
36
|
+
activeRequests = 0;
|
|
37
|
+
maxConcurrentRequests = 20;
|
|
11
38
|
initialize(config) {
|
|
12
39
|
this.config = {
|
|
13
40
|
minDelay: DEFAULT_MIN_DELAY,
|
|
14
41
|
timeout: DEFAULT_TIMEOUT,
|
|
15
42
|
...config,
|
|
16
43
|
};
|
|
17
|
-
this.
|
|
44
|
+
this._rateLimiter = new RateLimiter(this.config.minDelay);
|
|
18
45
|
}
|
|
19
46
|
isInitialized() {
|
|
20
|
-
return this.config !== null && this.
|
|
47
|
+
return this.config !== null && this._rateLimiter !== null;
|
|
48
|
+
}
|
|
49
|
+
get rateLimiter() {
|
|
50
|
+
if (!this._rateLimiter) {
|
|
51
|
+
throw new Error("RateLimiter not initialized");
|
|
52
|
+
}
|
|
53
|
+
return this._rateLimiter;
|
|
21
54
|
}
|
|
22
55
|
ensureInitialized() {
|
|
23
56
|
if (!this.isInitialized()) {
|
|
24
57
|
throw new Error("GoogleTranslateService is not initialized. Call initialize() first.");
|
|
25
58
|
}
|
|
26
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Generate cache key for translation
|
|
62
|
+
*/
|
|
63
|
+
getCacheKey(text, targetLang, sourceLang) {
|
|
64
|
+
return `${sourceLang}|${targetLang}|${text}`;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Get translation from cache
|
|
68
|
+
*/
|
|
69
|
+
getFromCache(text, targetLang, sourceLang) {
|
|
70
|
+
const key = this.getCacheKey(text, targetLang, sourceLang);
|
|
71
|
+
const entry = this.translationCache.get(key);
|
|
72
|
+
if (!entry)
|
|
73
|
+
return null;
|
|
74
|
+
// Check if entry has expired
|
|
75
|
+
const now = Date.now();
|
|
76
|
+
if (now - entry.timestamp > this.cacheTTL) {
|
|
77
|
+
this.translationCache.delete(key);
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
// Update access count and timestamp for LRU
|
|
81
|
+
entry.accessCount++;
|
|
82
|
+
entry.timestamp = now;
|
|
83
|
+
return entry.translation;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Store translation in cache with automatic eviction
|
|
87
|
+
*/
|
|
88
|
+
storeInCache(text, targetLang, sourceLang, translation) {
|
|
89
|
+
// If cache is full, remove least recently used entries
|
|
90
|
+
if (this.translationCache.size >= this.maxCacheSize) {
|
|
91
|
+
let oldestKey = null;
|
|
92
|
+
let oldestTime = Infinity;
|
|
93
|
+
for (const [key, entry] of this.translationCache) {
|
|
94
|
+
if (entry.timestamp < oldestTime) {
|
|
95
|
+
oldestTime = entry.timestamp;
|
|
96
|
+
oldestKey = key;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (oldestKey) {
|
|
100
|
+
this.translationCache.delete(oldestKey);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const key = this.getCacheKey(text, targetLang, sourceLang);
|
|
104
|
+
this.translationCache.set(key, {
|
|
105
|
+
translation,
|
|
106
|
+
timestamp: Date.now(),
|
|
107
|
+
accessCount: 1,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Clear translation cache
|
|
112
|
+
*/
|
|
113
|
+
clearCache() {
|
|
114
|
+
this.translationCache.clear();
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Get cache statistics
|
|
118
|
+
*/
|
|
119
|
+
getCacheStats() {
|
|
120
|
+
return {
|
|
121
|
+
size: this.translationCache.size,
|
|
122
|
+
maxSize: this.maxCacheSize,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
27
125
|
async translate(request) {
|
|
28
126
|
this.ensureInitialized();
|
|
29
127
|
const { text, targetLanguage, sourceLanguage = "en" } = request;
|
|
@@ -46,9 +144,28 @@ class GoogleTranslateService {
|
|
|
46
144
|
error: "Invalid target language",
|
|
47
145
|
};
|
|
48
146
|
}
|
|
49
|
-
|
|
147
|
+
// Check cache first
|
|
148
|
+
const cachedTranslation = this.getFromCache(text, targetLanguage, sourceLanguage);
|
|
149
|
+
if (cachedTranslation !== null) {
|
|
150
|
+
return {
|
|
151
|
+
originalText: text,
|
|
152
|
+
translatedText: cachedTranslation,
|
|
153
|
+
sourceLanguage,
|
|
154
|
+
targetLanguage,
|
|
155
|
+
success: true,
|
|
156
|
+
cached: true,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
// Wait for rate limit slot with normal priority
|
|
160
|
+
await this.rateLimiter.waitForSlot(5);
|
|
50
161
|
try {
|
|
162
|
+
const startTime = Date.now();
|
|
51
163
|
const translatedText = await this.callTranslateAPI(text, targetLanguage, sourceLanguage);
|
|
164
|
+
const responseTime = Date.now() - startTime;
|
|
165
|
+
// Record response time for dynamic rate adjustment
|
|
166
|
+
this.rateLimiter.recordResponseTime(responseTime);
|
|
167
|
+
// Store in cache
|
|
168
|
+
this.storeInCache(text, targetLanguage, sourceLanguage, translatedText);
|
|
52
169
|
return {
|
|
53
170
|
originalText: text,
|
|
54
171
|
translatedText,
|
|
@@ -80,33 +197,65 @@ class GoogleTranslateService {
|
|
|
80
197
|
if (!Array.isArray(requests) || requests.length === 0) {
|
|
81
198
|
return stats;
|
|
82
199
|
}
|
|
83
|
-
//
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
for (let i = 0; i < requests.length; i
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
200
|
+
// Filter out requests that can be served from cache
|
|
201
|
+
const uncachedRequests = [];
|
|
202
|
+
const cacheIndexMap = new Map(); // Maps original index to cached translation
|
|
203
|
+
for (let i = 0; i < requests.length; i++) {
|
|
204
|
+
const request = requests[i];
|
|
205
|
+
const cachedTranslation = this.getFromCache(request.text, request.targetLanguage, request.sourceLanguage || "en");
|
|
206
|
+
if (cachedTranslation !== null) {
|
|
207
|
+
// Serve from cache
|
|
208
|
+
cacheIndexMap.set(i, cachedTranslation);
|
|
209
|
+
stats.successCount++;
|
|
210
|
+
stats.translatedKeys.push({
|
|
211
|
+
key: request.text,
|
|
212
|
+
from: request.text,
|
|
213
|
+
to: cachedTranslation,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
uncachedRequests.push(request);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
stats.skippedCount = cacheIndexMap.size;
|
|
221
|
+
// Process uncached requests with controlled parallelism
|
|
222
|
+
if (uncachedRequests.length > 0) {
|
|
223
|
+
const chunks = [];
|
|
224
|
+
for (let i = 0; i < uncachedRequests.length; i += TRANSLATION_CONCURRENCY_LIMIT) {
|
|
225
|
+
chunks.push(uncachedRequests.slice(i, i + TRANSLATION_CONCURRENCY_LIMIT));
|
|
226
|
+
}
|
|
227
|
+
for (const chunk of chunks) {
|
|
228
|
+
const results = await Promise.allSettled(chunk.map(async (request) => {
|
|
229
|
+
await this.rateLimiter.waitForSlot(5);
|
|
230
|
+
const startTime = Date.now();
|
|
231
|
+
const result = await this.callTranslateAPI(request.text, request.targetLanguage, request.sourceLanguage || "en");
|
|
232
|
+
const responseTime = Date.now() - startTime;
|
|
233
|
+
// Record response time for dynamic rate adjustment
|
|
234
|
+
this.rateLimiter.recordResponseTime(responseTime);
|
|
235
|
+
return result;
|
|
236
|
+
}));
|
|
237
|
+
for (let i = 0; i < chunk.length; i++) {
|
|
238
|
+
const request = chunk[i];
|
|
239
|
+
const result = results[i];
|
|
240
|
+
if (result.status === "fulfilled") {
|
|
241
|
+
const translatedText = result.value;
|
|
242
|
+
if (translatedText && translatedText !== request.text) {
|
|
243
|
+
stats.successCount++;
|
|
244
|
+
stats.translatedKeys.push({
|
|
245
|
+
key: request.text,
|
|
246
|
+
from: request.text,
|
|
247
|
+
to: translatedText,
|
|
248
|
+
});
|
|
249
|
+
// Cache the successful translation
|
|
250
|
+
this.storeInCache(request.text, request.targetLanguage, request.sourceLanguage || "en", translatedText);
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
stats.skippedCount++;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
stats.failureCount++;
|
|
258
|
+
}
|
|
110
259
|
}
|
|
111
260
|
}
|
|
112
261
|
}
|
|
@@ -125,21 +274,30 @@ class GoogleTranslateService {
|
|
|
125
274
|
return;
|
|
126
275
|
if (!targetLanguage || targetLanguage.trim().length === 0)
|
|
127
276
|
return;
|
|
128
|
-
|
|
277
|
+
// First pass: collect all texts to translate (flattens nested structure)
|
|
129
278
|
const textsToTranslate = [];
|
|
279
|
+
const nestedObjects = [];
|
|
280
|
+
// Collect texts and nested objects
|
|
281
|
+
const keys = Object.keys(sourceObject);
|
|
130
282
|
for (const key of keys) {
|
|
131
283
|
const enValue = sourceObject[key];
|
|
132
284
|
const targetValue = targetObject[key];
|
|
133
285
|
const currentPath = path ? `${path}.${key}` : key;
|
|
134
286
|
if (typeof enValue === "object" && enValue !== null) {
|
|
287
|
+
// Prepare nested object for processing
|
|
135
288
|
if (!targetObject[key] || typeof targetObject[key] !== "object") {
|
|
136
289
|
targetObject[key] = {};
|
|
137
290
|
}
|
|
138
|
-
|
|
291
|
+
nestedObjects.push({
|
|
292
|
+
key,
|
|
293
|
+
sourceObj: enValue,
|
|
294
|
+
targetObj: targetObject[key],
|
|
295
|
+
currentPath,
|
|
296
|
+
});
|
|
139
297
|
}
|
|
140
298
|
else if (typeof enValue === "string") {
|
|
141
299
|
stats.totalCount++;
|
|
142
|
-
if (force || needsTranslation(targetValue
|
|
300
|
+
if (force || needsTranslation(targetValue)) {
|
|
143
301
|
textsToTranslate.push({ key, enValue, currentPath });
|
|
144
302
|
}
|
|
145
303
|
else {
|
|
@@ -147,97 +305,155 @@ class GoogleTranslateService {
|
|
|
147
305
|
}
|
|
148
306
|
}
|
|
149
307
|
}
|
|
308
|
+
// Process nested objects recursively
|
|
309
|
+
for (const nested of nestedObjects) {
|
|
310
|
+
await this.translateObject(nested.sourceObj, nested.targetObj, targetLanguage, nested.currentPath, stats, onTranslate, force);
|
|
311
|
+
}
|
|
312
|
+
// Process texts in batches
|
|
150
313
|
if (textsToTranslate.length > 0) {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
const
|
|
154
|
-
const results = await this.translateBatch(batch.map(item => ({
|
|
314
|
+
for (let i = 0; i < textsToTranslate.length; i += TRANSLATION_BATCH_SIZE) {
|
|
315
|
+
const batch = textsToTranslate.slice(i, i + TRANSLATION_BATCH_SIZE);
|
|
316
|
+
const results = await this.translateBatch(batch.map((item) => ({
|
|
155
317
|
text: item.enValue,
|
|
156
318
|
targetLanguage,
|
|
157
319
|
})));
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
stats.successCount++;
|
|
165
|
-
if (onTranslate)
|
|
166
|
-
onTranslate(currentPath, enValue, translatedItem.to);
|
|
167
|
-
resultIndex++;
|
|
320
|
+
// Use object pool for map
|
|
321
|
+
const translationMap = this.mapPool.acquire();
|
|
322
|
+
try {
|
|
323
|
+
// Create a map for quick lookup of translations
|
|
324
|
+
for (const item of results.translatedKeys) {
|
|
325
|
+
translationMap.set(item.from, item.to);
|
|
168
326
|
}
|
|
169
|
-
|
|
170
|
-
|
|
327
|
+
for (let j = 0; j < batch.length; j++) {
|
|
328
|
+
const { key, enValue, currentPath } = batch[j];
|
|
329
|
+
const translatedText = translationMap.get(enValue);
|
|
330
|
+
if (translatedText && translatedText !== enValue) {
|
|
331
|
+
targetObject[key] = translatedText;
|
|
332
|
+
stats.successCount++;
|
|
333
|
+
if (onTranslate)
|
|
334
|
+
onTranslate(currentPath, enValue, translatedText);
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
stats.failureCount++;
|
|
338
|
+
}
|
|
171
339
|
}
|
|
172
340
|
}
|
|
341
|
+
finally {
|
|
342
|
+
// Clear and release map back to pool
|
|
343
|
+
translationMap.clear();
|
|
344
|
+
this.mapPool.release(translationMap);
|
|
345
|
+
}
|
|
173
346
|
}
|
|
174
347
|
}
|
|
175
348
|
}
|
|
176
|
-
async callTranslateAPI(text, targetLanguage, sourceLanguage, retries =
|
|
177
|
-
//
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
counter++;
|
|
185
|
-
return placeholder;
|
|
186
|
-
});
|
|
187
|
-
const timeout = this.config?.timeout || DEFAULT_TIMEOUT;
|
|
188
|
-
const encodedText = encodeURIComponent(safeText);
|
|
189
|
-
const url = `${GOOGLE_TRANSLATE_API_URL}?client=gtx&sl=${sourceLanguage}&tl=${targetLanguage}&dt=t&q=${encodedText}`;
|
|
190
|
-
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
191
|
-
const controller = new AbortController();
|
|
192
|
-
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
349
|
+
async callTranslateAPI(text, targetLanguage, sourceLanguage, retries = DEFAULT_MAX_RETRIES, backoffMs = 2000) {
|
|
350
|
+
// Increment active requests counter
|
|
351
|
+
this.activeRequests++;
|
|
352
|
+
try {
|
|
353
|
+
// 1. Variable Protection (Extract {{variables}})
|
|
354
|
+
// Use object pool for map to reduce GC pressure
|
|
355
|
+
const varMap = this.mapPool.acquire();
|
|
356
|
+
let counter = 0;
|
|
193
357
|
try {
|
|
194
|
-
|
|
195
|
-
|
|
358
|
+
// Find all {{something}} patterns
|
|
359
|
+
// Use more specific pattern to avoid false matches
|
|
360
|
+
const safeText = text.replace(/\{\{([^}]+)\}\}/g, (match) => {
|
|
361
|
+
const placeholder = `__VAR${counter}__`;
|
|
362
|
+
varMap.set(placeholder, match);
|
|
363
|
+
counter++;
|
|
364
|
+
return placeholder;
|
|
196
365
|
});
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
366
|
+
const timeout = this.config?.timeout || DEFAULT_TIMEOUT;
|
|
367
|
+
const encodedText = encodeURIComponent(safeText);
|
|
368
|
+
const url = `${GOOGLE_TRANSLATE_API_URL}?client=gtx&sl=${sourceLanguage}&tl=${targetLanguage}&dt=t&q=${encodedText}`;
|
|
369
|
+
for (let attempt = 0; attempt < retries; attempt++) {
|
|
370
|
+
const controller = new AbortController();
|
|
371
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
372
|
+
try {
|
|
373
|
+
const response = await fetch(url, {
|
|
374
|
+
signal: controller.signal,
|
|
375
|
+
});
|
|
376
|
+
if (!response.ok) {
|
|
377
|
+
if (response.status === 429 || response.status >= 500) {
|
|
378
|
+
if (attempt < retries - 1) {
|
|
379
|
+
clearTimeout(timeoutId);
|
|
380
|
+
// Exponential backoff
|
|
381
|
+
const delay = backoffMs * Math.pow(2, attempt);
|
|
382
|
+
await this.sleep(delay);
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
throw new Error(`API request failed: ${response.status}`);
|
|
387
|
+
}
|
|
388
|
+
const data = await response.json();
|
|
389
|
+
let translatedStr = safeText;
|
|
390
|
+
if (Array.isArray(data) &&
|
|
391
|
+
data.length > 0 &&
|
|
392
|
+
Array.isArray(data[0]) &&
|
|
393
|
+
data[0].length > 0 &&
|
|
394
|
+
typeof data[0][0][0] === "string") {
|
|
395
|
+
translatedStr = data[0].map((item) => item[0]).join('');
|
|
205
396
|
}
|
|
397
|
+
// 2. Re-inject Variables
|
|
398
|
+
if (varMap.size > 0) {
|
|
399
|
+
// Sometimes Google adds spaces, like __VAR0__ -> __ VAR0 __
|
|
400
|
+
// Use pre-compiled regex patterns for better performance
|
|
401
|
+
for (const [placeholder, originalVar] of varMap.entries()) {
|
|
402
|
+
// Escape special regex characters in placeholder
|
|
403
|
+
const escapedPlaceholder = placeholder.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
404
|
+
// Allow optional spaces between each character
|
|
405
|
+
const regex = new RegExp(escapedPlaceholder.split('').join('\\s*'), 'g');
|
|
406
|
+
translatedStr = translatedStr.replace(regex, originalVar);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
return translatedStr;
|
|
206
410
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
}
|
|
218
|
-
// 2. Re-inject Variables
|
|
219
|
-
if (varMap.size > 0) {
|
|
220
|
-
// Sometimes Google adds spaces, like _VAR0_ -> _ VAR0 _
|
|
221
|
-
for (const [placeholder, originalVar] of varMap.entries()) {
|
|
222
|
-
const regex = new RegExp(placeholder.split('').join('\\s*'), 'g');
|
|
223
|
-
translatedStr = translatedStr.replace(regex, originalVar);
|
|
411
|
+
catch (error) {
|
|
412
|
+
clearTimeout(timeoutId);
|
|
413
|
+
if (attempt === retries - 1) {
|
|
414
|
+
throw error;
|
|
415
|
+
}
|
|
416
|
+
const delay = backoffMs * Math.pow(2, attempt);
|
|
417
|
+
await this.sleep(delay);
|
|
418
|
+
}
|
|
419
|
+
finally {
|
|
420
|
+
clearTimeout(timeoutId);
|
|
224
421
|
}
|
|
225
422
|
}
|
|
226
|
-
return
|
|
227
|
-
}
|
|
228
|
-
catch (error) {
|
|
229
|
-
clearTimeout(timeoutId);
|
|
230
|
-
if (attempt === retries) {
|
|
231
|
-
throw error;
|
|
232
|
-
}
|
|
233
|
-
const delay = backoffMs * Math.pow(2, attempt);
|
|
234
|
-
await new Promise(resolve => setTimeout(resolve, delay));
|
|
423
|
+
return text;
|
|
235
424
|
}
|
|
236
425
|
finally {
|
|
237
|
-
|
|
426
|
+
// Clear and release map back to pool
|
|
427
|
+
varMap.clear();
|
|
428
|
+
this.mapPool.release(varMap);
|
|
238
429
|
}
|
|
239
430
|
}
|
|
240
|
-
|
|
431
|
+
finally {
|
|
432
|
+
// Decrement active requests counter
|
|
433
|
+
this.activeRequests--;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Optimized sleep function
|
|
438
|
+
*/
|
|
439
|
+
sleep(ms) {
|
|
440
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Get number of active requests
|
|
444
|
+
*/
|
|
445
|
+
getActiveRequestCount() {
|
|
446
|
+
return this.activeRequests;
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Clean up resources
|
|
450
|
+
*/
|
|
451
|
+
dispose() {
|
|
452
|
+
this.clearCache();
|
|
453
|
+
this.mapPool.clear();
|
|
454
|
+
if (this._rateLimiter) {
|
|
455
|
+
this._rateLimiter.clear();
|
|
456
|
+
}
|
|
241
457
|
}
|
|
242
458
|
}
|
|
243
459
|
export const googleTranslateService = new GoogleTranslateService();
|
|
@@ -1,9 +1,35 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Parses a TypeScript file containing an object export
|
|
3
|
-
* @description
|
|
3
|
+
* @description Improved parser with caching and better error handling
|
|
4
|
+
* @security Note: Uses Function constructor - only use with trusted local files
|
|
4
5
|
*/
|
|
5
6
|
export declare function parseTypeScriptFile(filePath: string): Record<string, unknown>;
|
|
6
7
|
/**
|
|
7
8
|
* Generates a TypeScript file content from an object
|
|
9
|
+
* @description Optimized content generation with proper escaping
|
|
8
10
|
*/
|
|
9
11
|
export declare function generateTypeScriptContent(obj: Record<string, unknown>, langCode?: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Clear file content cache
|
|
14
|
+
* @description Call this when you know files have been modified externally
|
|
15
|
+
*/
|
|
16
|
+
export declare function clearFileCache(): void;
|
|
17
|
+
/**
|
|
18
|
+
* Get cache statistics
|
|
19
|
+
*/
|
|
20
|
+
export declare function getFileCacheStats(): {
|
|
21
|
+
size: number;
|
|
22
|
+
maxSize: number;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Check if a file exists (async version for better performance)
|
|
26
|
+
*/
|
|
27
|
+
export declare function fileExists(filePath: string): Promise<boolean>;
|
|
28
|
+
/**
|
|
29
|
+
* Read file content asynchronously (for better performance in non-blocking scenarios)
|
|
30
|
+
*/
|
|
31
|
+
export declare function readFileAsync(filePath: string): Promise<string | null>;
|
|
32
|
+
/**
|
|
33
|
+
* Write file content asynchronously (for better performance in non-blocking scenarios)
|
|
34
|
+
*/
|
|
35
|
+
export declare function writeFileAsync(filePath: string, content: string): Promise<void>;
|