@djangocfg/llm 2.1.164

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +181 -0
  2. package/dist/index.cjs +1164 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.d.cts +164 -0
  5. package/dist/index.d.ts +164 -0
  6. package/dist/index.mjs +1128 -0
  7. package/dist/index.mjs.map +1 -0
  8. package/dist/providers/index.cjs +317 -0
  9. package/dist/providers/index.cjs.map +1 -0
  10. package/dist/providers/index.d.cts +30 -0
  11. package/dist/providers/index.d.ts +30 -0
  12. package/dist/providers/index.mjs +304 -0
  13. package/dist/providers/index.mjs.map +1 -0
  14. package/dist/sdkrouter-D8GMBmTi.d.ts +171 -0
  15. package/dist/sdkrouter-hlQlVd0v.d.cts +171 -0
  16. package/dist/text-utils-DoYqMIr6.d.ts +289 -0
  17. package/dist/text-utils-VXWN-8Oq.d.cts +289 -0
  18. package/dist/translator/index.cjs +794 -0
  19. package/dist/translator/index.cjs.map +1 -0
  20. package/dist/translator/index.d.cts +24 -0
  21. package/dist/translator/index.d.ts +24 -0
  22. package/dist/translator/index.mjs +769 -0
  23. package/dist/translator/index.mjs.map +1 -0
  24. package/dist/types-D6lazgm1.d.cts +59 -0
  25. package/dist/types-D6lazgm1.d.ts +59 -0
  26. package/package.json +82 -0
  27. package/src/client.ts +119 -0
  28. package/src/index.ts +70 -0
  29. package/src/providers/anthropic.ts +98 -0
  30. package/src/providers/base.ts +90 -0
  31. package/src/providers/index.ts +15 -0
  32. package/src/providers/openai.ts +73 -0
  33. package/src/providers/sdkrouter.ts +279 -0
  34. package/src/translator/cache.ts +237 -0
  35. package/src/translator/index.ts +55 -0
  36. package/src/translator/json-translator.ts +408 -0
  37. package/src/translator/prompts.ts +90 -0
  38. package/src/translator/text-utils.ts +148 -0
  39. package/src/translator/types.ts +112 -0
  40. package/src/translator/validator.ts +181 -0
  41. package/src/types.ts +85 -0
  42. package/src/utils/env.ts +67 -0
  43. package/src/utils/index.ts +2 -0
  44. package/src/utils/json.ts +44 -0
  45. package/src/utils/schema.ts +153 -0
@@ -0,0 +1,794 @@
1
+ 'use strict';
2
+
3
+ var crypto = require('crypto');
4
+
5
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
6
+
7
+ var crypto__default = /*#__PURE__*/_interopDefault(crypto);
8
+
9
+ var __defProp = Object.defineProperty;
10
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
11
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
12
+
13
+ // src/translator/text-utils.ts
14
+ function isTechnicalContent(text) {
15
+ const trimmed = text.trim();
16
+ if (!trimmed) return true;
17
+ if (/^(https?:\/\/|\/\/|www\.)/i.test(trimmed)) return true;
18
+ if (/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmed)) return true;
19
+ if (/^\/[a-zA-Z]/.test(trimmed)) return true;
20
+ if (/\.(js|ts|tsx|jsx|json|css|scss|html|md|py|go|rs)$/i.test(trimmed)) return true;
21
+ if (/^[\d.,]+%?$/.test(trimmed)) return true;
22
+ if (/^[A-Z][A-Z0-9_]*$/.test(trimmed)) return true;
23
+ if (/^(\{[^}]+\}|\{\{[^}]+\}\}|%[sd]|\$\d+)$/.test(trimmed)) return true;
24
+ if (/^[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]+$/.test(trimmed)) return true;
25
+ return false;
26
+ }
27
+ function containsCJK(text) {
28
+ return /[\u4e00-\u9fff\u3040-\u309f\u30a0-\u30ff\uac00-\ud7af]/.test(text);
29
+ }
30
+ function containsCyrillic(text) {
31
+ return /[\u0400-\u04ff]/.test(text);
32
+ }
33
+ function containsArabic(text) {
34
+ return /[\u0600-\u06ff]/.test(text);
35
+ }
36
+ function detectScript(text) {
37
+ if (containsCJK(text)) return "cjk";
38
+ if (containsCyrillic(text)) return "cyrillic";
39
+ if (containsArabic(text)) return "arabic";
40
+ if (/[a-zA-Z]/.test(text)) return "latin";
41
+ return "unknown";
42
+ }
43
+ function extractPlaceholders(text) {
44
+ const patterns = [
45
+ /\{[^}]+\}/g,
46
+ // {name}
47
+ /\{\{[^}]+\}\}/g,
48
+ // {{name}}
49
+ /%[sd]/g,
50
+ // %s, %d
51
+ /\$\d+/g
52
+ // $1, $2
53
+ ];
54
+ const placeholders = [];
55
+ for (const pattern of patterns) {
56
+ const matches = text.match(pattern);
57
+ if (matches) {
58
+ placeholders.push(...matches);
59
+ }
60
+ }
61
+ return [...new Set(placeholders)];
62
+ }
63
+ function validatePlaceholders(original, translated) {
64
+ const originalPlaceholders = extractPlaceholders(original);
65
+ const translatedPlaceholders = extractPlaceholders(translated);
66
+ const missing = originalPlaceholders.filter(
67
+ (p) => !translatedPlaceholders.includes(p)
68
+ );
69
+ const extra = translatedPlaceholders.filter(
70
+ (p) => !originalPlaceholders.includes(p)
71
+ );
72
+ return {
73
+ valid: missing.length === 0 && extra.length === 0,
74
+ missing,
75
+ extra
76
+ };
77
+ }
78
+ function extractTranslatableValues(obj, path = "") {
79
+ const values = [];
80
+ if (typeof obj === "string") {
81
+ if (!isTechnicalContent(obj)) {
82
+ values.push({ path, value: obj });
83
+ }
84
+ } else if (Array.isArray(obj)) {
85
+ obj.forEach((item, index) => {
86
+ values.push(
87
+ ...extractTranslatableValues(item, path ? `${path}[${index}]` : `[${index}]`)
88
+ );
89
+ });
90
+ } else if (obj !== null && typeof obj === "object") {
91
+ for (const [key, value] of Object.entries(obj)) {
92
+ values.push(
93
+ ...extractTranslatableValues(value, path ? `${path}.${key}` : key)
94
+ );
95
+ }
96
+ }
97
+ return values;
98
+ }
99
+
100
+ // src/translator/validator.ts
101
+ function validateJsonKeys(original, translated, path = "") {
102
+ const errors = [];
103
+ if (typeof original !== typeof translated) {
104
+ errors.push(`Type mismatch at ${path || "root"}: expected ${typeof original}, got ${typeof translated}`);
105
+ return { valid: false, errors };
106
+ }
107
+ if (original === null || translated === null) {
108
+ if (original !== translated) {
109
+ errors.push(`Null mismatch at ${path || "root"}`);
110
+ }
111
+ return { valid: errors.length === 0, errors };
112
+ }
113
+ if (Array.isArray(original)) {
114
+ if (!Array.isArray(translated)) {
115
+ errors.push(`Expected array at ${path || "root"}`);
116
+ return { valid: false, errors };
117
+ }
118
+ if (original.length !== translated.length) {
119
+ errors.push(
120
+ `Array length mismatch at ${path || "root"}: expected ${original.length}, got ${translated.length}`
121
+ );
122
+ }
123
+ const minLen = Math.min(original.length, translated.length);
124
+ for (let i = 0; i < minLen; i++) {
125
+ const result = validateJsonKeys(
126
+ original[i],
127
+ translated[i],
128
+ `${path}[${i}]`
129
+ );
130
+ errors.push(...result.errors);
131
+ }
132
+ return { valid: errors.length === 0, errors };
133
+ }
134
+ if (typeof original === "object") {
135
+ if (typeof translated !== "object" || Array.isArray(translated)) {
136
+ errors.push(`Expected object at ${path || "root"}`);
137
+ return { valid: false, errors };
138
+ }
139
+ const origKeys = Object.keys(original);
140
+ const transKeys = Object.keys(translated);
141
+ for (const key of origKeys) {
142
+ if (!transKeys.includes(key)) {
143
+ errors.push(`Missing key: ${path ? `${path}.${key}` : key}`);
144
+ }
145
+ }
146
+ for (const key of transKeys) {
147
+ if (!origKeys.includes(key)) {
148
+ errors.push(
149
+ `Unexpected key: ${path ? `${path}.${key}` : key} (key was translated?)`
150
+ );
151
+ }
152
+ }
153
+ for (const key of origKeys) {
154
+ if (transKeys.includes(key)) {
155
+ const result = validateJsonKeys(
156
+ original[key],
157
+ translated[key],
158
+ path ? `${path}.${key}` : key
159
+ );
160
+ errors.push(...result.errors);
161
+ }
162
+ }
163
+ return { valid: errors.length === 0, errors };
164
+ }
165
+ return { valid: true, errors: [] };
166
+ }
167
+ function validatePlaceholders2(original, translated, path = "") {
168
+ const errors = [];
169
+ if (typeof original === "string" && typeof translated === "string") {
170
+ const origPlaceholders = extractPlaceholders(original);
171
+ const transPlaceholders = extractPlaceholders(translated);
172
+ for (const placeholder of origPlaceholders) {
173
+ if (!transPlaceholders.includes(placeholder)) {
174
+ errors.push(
175
+ `Missing placeholder "${placeholder}" at ${path || "root"}`
176
+ );
177
+ }
178
+ }
179
+ for (const placeholder of transPlaceholders) {
180
+ if (!origPlaceholders.includes(placeholder)) {
181
+ errors.push(
182
+ `Extra placeholder "${placeholder}" at ${path || "root"}`
183
+ );
184
+ }
185
+ }
186
+ } else if (Array.isArray(original) && Array.isArray(translated)) {
187
+ const minLen = Math.min(original.length, translated.length);
188
+ for (let i = 0; i < minLen; i++) {
189
+ const result = validatePlaceholders2(
190
+ original[i],
191
+ translated[i],
192
+ `${path}[${i}]`
193
+ );
194
+ errors.push(...result.errors);
195
+ }
196
+ } else if (typeof original === "object" && original !== null && typeof translated === "object" && translated !== null) {
197
+ for (const key of Object.keys(original)) {
198
+ if (key in translated) {
199
+ const result = validatePlaceholders2(
200
+ original[key],
201
+ translated[key],
202
+ path ? `${path}.${key}` : key
203
+ );
204
+ errors.push(...result.errors);
205
+ }
206
+ }
207
+ }
208
+ return { valid: errors.length === 0, errors };
209
+ }
210
+ function validateTranslation(original, translated) {
211
+ const keyResult = validateJsonKeys(original, translated);
212
+ const placeholderResult = validatePlaceholders2(original, translated);
213
+ return {
214
+ valid: keyResult.valid && placeholderResult.valid,
215
+ errors: [...keyResult.errors, ...placeholderResult.errors]
216
+ };
217
+ }
218
+
219
+ // src/utils/json.ts
220
+ function extractJson(text) {
221
+ let jsonStr = text.trim();
222
+ if (jsonStr.startsWith("```json")) {
223
+ jsonStr = jsonStr.slice(7);
224
+ } else if (jsonStr.startsWith("```")) {
225
+ jsonStr = jsonStr.slice(3);
226
+ }
227
+ if (jsonStr.endsWith("```")) {
228
+ jsonStr = jsonStr.slice(0, -3);
229
+ }
230
+ jsonStr = jsonStr.trim();
231
+ const jsonStart = jsonStr.search(/[\[{]/);
232
+ const jsonEndBracket = jsonStr.lastIndexOf("]");
233
+ const jsonEndBrace = jsonStr.lastIndexOf("}");
234
+ const jsonEnd = Math.max(jsonEndBracket, jsonEndBrace);
235
+ if (jsonStart !== -1 && jsonEnd !== -1) {
236
+ jsonStr = jsonStr.slice(jsonStart, jsonEnd + 1);
237
+ }
238
+ try {
239
+ return JSON.parse(jsonStr);
240
+ } catch (error) {
241
+ throw new Error(
242
+ `Failed to parse JSON from LLM response: ${error instanceof Error ? error.message : "Unknown error"}
243
+
244
+ Response:
245
+ ${text}`
246
+ );
247
+ }
248
+ }
249
+
250
+ // src/translator/types.ts
251
+ var LANGUAGE_NAMES = {
252
+ en: "English",
253
+ ru: "Russian",
254
+ ko: "Korean",
255
+ ja: "Japanese",
256
+ zh: "Chinese",
257
+ de: "German",
258
+ fr: "French",
259
+ es: "Spanish",
260
+ it: "Italian",
261
+ pt: "Portuguese",
262
+ "pt-BR": "Brazilian Portuguese",
263
+ ar: "Arabic",
264
+ nl: "Dutch",
265
+ tr: "Turkish",
266
+ pl: "Polish",
267
+ sv: "Swedish",
268
+ no: "Norwegian",
269
+ da: "Danish",
270
+ uk: "Ukrainian",
271
+ hi: "Hindi"
272
+ };
273
+
274
+ // src/translator/prompts.ts
275
+ function getLanguageName(code) {
276
+ return LANGUAGE_NAMES[code] ?? code;
277
+ }
278
+ function buildJsonTranslationPrompt(json, sourceLanguage, targetLanguage) {
279
+ const sourceName = getLanguageName(sourceLanguage);
280
+ const targetName = getLanguageName(targetLanguage);
281
+ return `Translate all string VALUES in this JSON from ${sourceName} to ${targetName}.
282
+
283
+ Rules:
284
+ - Translate ALL string values to ${targetName}
285
+ - Keep JSON keys unchanged (English)
286
+ - Skip: URLs, emails, numbers, "SKIP"
287
+ - Keep placeholders: {name}, {{var}}, %s
288
+
289
+ ${json}
290
+
291
+ Return ONLY the JSON with all values translated to ${targetName}:`;
292
+ }
293
+ function buildTextTranslationPrompt(text, sourceLanguage, targetLanguage) {
294
+ const sourceName = getLanguageName(sourceLanguage);
295
+ const targetName = getLanguageName(targetLanguage);
296
+ return `Translate from ${sourceName} to ${targetName}.
297
+
298
+ RULES:
299
+ 1. Translate ONLY the text provided
300
+ 2. Preserve formatting, numbers, URLs
301
+ 3. Keep placeholders like {name}, {{var}} unchanged
302
+ 4. Return ONLY the translation, no explanations
303
+
304
+ Text: ${text}
305
+
306
+ Translation:`;
307
+ }
308
+ function buildRetryPrompt(originalJson, wrongJson, errors, targetLanguage) {
309
+ const targetName = getLanguageName(targetLanguage);
310
+ return `Your previous translation had errors. Fix them.
311
+
312
+ ERRORS FOUND:
313
+ ${errors.map((e) => `- ${e}`).join("\n")}
314
+
315
+ ORIGINAL JSON:
316
+ ${originalJson}
317
+
318
+ YOUR WRONG TRANSLATION:
319
+ ${wrongJson}
320
+
321
+ FIX THE ERRORS. Remember:
322
+ - JSON keys must stay in English
323
+ - Only translate string values
324
+ - Preserve all placeholders
325
+
326
+ Return ONLY the corrected JSON translated to ${targetName}:`;
327
+ }
328
+ var TranslationCache = class {
329
+ constructor(maxMemorySize = 1e3, storage) {
330
+ this.maxMemorySize = maxMemorySize;
331
+ this.storage = storage;
332
+ __publicField(this, "memoryCache", /* @__PURE__ */ new Map());
333
+ __publicField(this, "cacheOrder", []);
334
+ __publicField(this, "hits", 0);
335
+ __publicField(this, "misses", 0);
336
+ }
337
+ /**
338
+ * Generate hash for text
339
+ */
340
+ getTextHash(text) {
341
+ return crypto__default.default.createHash("md5").update(text).digest("hex");
342
+ }
343
+ /**
344
+ * Get storage key for language pair
345
+ */
346
+ getStorageKey(sourceLang, targetLang) {
347
+ return `translator:${sourceLang}-${targetLang}`;
348
+ }
349
+ /**
350
+ * Load from persistent storage
351
+ */
352
+ loadFromStorage(sourceLang, targetLang) {
353
+ if (!this.storage) return {};
354
+ try {
355
+ const key = this.getStorageKey(sourceLang, targetLang);
356
+ const data = this.storage.getItem(key);
357
+ return data ? JSON.parse(data) : {};
358
+ } catch {
359
+ return {};
360
+ }
361
+ }
362
+ /**
363
+ * Save to persistent storage
364
+ */
365
+ saveToStorage(sourceLang, targetLang, cache) {
366
+ if (!this.storage) return;
367
+ try {
368
+ const key = this.getStorageKey(sourceLang, targetLang);
369
+ this.storage.setItem(key, JSON.stringify(cache));
370
+ } catch {
371
+ }
372
+ }
373
+ /**
374
+ * Evict oldest entries if memory is full
375
+ */
376
+ evictIfNeeded() {
377
+ while (this.memoryCache.size >= this.maxMemorySize && this.cacheOrder.length > 0) {
378
+ const oldestKey = this.cacheOrder.shift();
379
+ this.memoryCache.delete(oldestKey);
380
+ }
381
+ }
382
+ /**
383
+ * Get translation from cache
384
+ */
385
+ get(text, sourceLang, targetLang) {
386
+ const textHash = this.getTextHash(text);
387
+ const cacheKey = `${sourceLang}-${targetLang}:${textHash}`;
388
+ if (this.memoryCache.has(cacheKey)) {
389
+ this.hits++;
390
+ return this.memoryCache.get(cacheKey);
391
+ }
392
+ const fileCache = this.loadFromStorage(sourceLang, targetLang);
393
+ if (textHash in fileCache) {
394
+ const translation = fileCache[textHash];
395
+ this.evictIfNeeded();
396
+ this.memoryCache.set(cacheKey, translation);
397
+ this.cacheOrder.push(cacheKey);
398
+ this.hits++;
399
+ return translation;
400
+ }
401
+ this.misses++;
402
+ return void 0;
403
+ }
404
+ /**
405
+ * Store translation in cache
406
+ */
407
+ set(text, sourceLang, targetLang, translation) {
408
+ const textHash = this.getTextHash(text);
409
+ const cacheKey = `${sourceLang}-${targetLang}:${textHash}`;
410
+ this.evictIfNeeded();
411
+ this.memoryCache.set(cacheKey, translation);
412
+ if (!this.cacheOrder.includes(cacheKey)) {
413
+ this.cacheOrder.push(cacheKey);
414
+ }
415
+ const fileCache = this.loadFromStorage(sourceLang, targetLang);
416
+ fileCache[textHash] = translation;
417
+ this.saveToStorage(sourceLang, targetLang, fileCache);
418
+ }
419
+ /**
420
+ * Get multiple translations at once
421
+ */
422
+ getMany(texts, sourceLang, targetLang) {
423
+ const cached = /* @__PURE__ */ new Map();
424
+ const uncached = [];
425
+ for (const text of texts) {
426
+ const translation = this.get(text, sourceLang, targetLang);
427
+ if (translation !== void 0) {
428
+ cached.set(text, translation);
429
+ } else {
430
+ uncached.push(text);
431
+ }
432
+ }
433
+ return { cached, uncached };
434
+ }
435
+ /**
436
+ * Store multiple translations at once
437
+ */
438
+ setMany(translations, sourceLang, targetLang) {
439
+ for (const [text, translation] of translations) {
440
+ this.set(text, sourceLang, targetLang, translation);
441
+ }
442
+ }
443
+ /**
444
+ * Clear cache
445
+ */
446
+ clear(sourceLang, targetLang) {
447
+ if (sourceLang && targetLang) {
448
+ const prefix = `${sourceLang}-${targetLang}:`;
449
+ for (const key of [...this.memoryCache.keys()]) {
450
+ if (key.startsWith(prefix)) {
451
+ this.memoryCache.delete(key);
452
+ const idx = this.cacheOrder.indexOf(key);
453
+ if (idx !== -1) this.cacheOrder.splice(idx, 1);
454
+ }
455
+ }
456
+ if (this.storage) {
457
+ this.storage.removeItem(this.getStorageKey(sourceLang, targetLang));
458
+ }
459
+ } else {
460
+ this.memoryCache.clear();
461
+ this.cacheOrder = [];
462
+ this.hits = 0;
463
+ this.misses = 0;
464
+ }
465
+ }
466
+ /**
467
+ * Get cache statistics
468
+ */
469
+ getStats() {
470
+ const languagePairs = [];
471
+ const pairCounts = /* @__PURE__ */ new Map();
472
+ for (const key of this.memoryCache.keys()) {
473
+ const pair = key.split(":")[0];
474
+ pairCounts.set(pair, (pairCounts.get(pair) || 0) + 1);
475
+ }
476
+ for (const [pair, count] of pairCounts) {
477
+ languagePairs.push({ pair, translations: count });
478
+ }
479
+ return {
480
+ memorySize: this.memoryCache.size,
481
+ hits: this.hits,
482
+ misses: this.misses,
483
+ languagePairs
484
+ };
485
+ }
486
+ };
487
+ function createCache(maxMemorySize, storage) {
488
+ return new TranslationCache(maxMemorySize, storage);
489
+ }
490
+
491
+ // src/translator/json-translator.ts
492
+ var DEFAULT_TRANSLATION_MODEL = "openai/gpt-4o-mini";
493
+ var JsonTranslator = class {
494
+ constructor(client, cache, defaultModel) {
495
+ this.client = client;
496
+ __publicField(this, "cache");
497
+ __publicField(this, "defaultModel");
498
+ this.cache = cache ?? createCache();
499
+ this.defaultModel = defaultModel ?? DEFAULT_TRANSLATION_MODEL;
500
+ }
501
+ /**
502
+ * Detect language from text
503
+ */
504
+ detectLanguage(text) {
505
+ const script = detectScript(text);
506
+ switch (script) {
507
+ case "cjk":
508
+ return "zh";
509
+ case "cyrillic":
510
+ return "ru";
511
+ case "arabic":
512
+ return "ar";
513
+ default:
514
+ return "en";
515
+ }
516
+ }
517
+ /**
518
+ * Check if text needs translation
519
+ */
520
+ needsTranslation(text, sourceLang, targetLang) {
521
+ if (!text || !text.trim()) return false;
522
+ if (isTechnicalContent(text)) return false;
523
+ if (sourceLang === targetLang) return false;
524
+ return true;
525
+ }
526
+ /**
527
+ * Translate single text
528
+ */
529
+ async translateText(text, targetLanguage, options) {
530
+ if (!text || !text.trim()) return text;
531
+ const sourceLang = options?.sourceLanguage ?? "auto";
532
+ const actualSource = sourceLang === "auto" ? this.detectLanguage(text) : sourceLang;
533
+ if (!this.needsTranslation(text, actualSource, targetLanguage)) {
534
+ return text;
535
+ }
536
+ const cached = this.cache.get(text, actualSource, targetLanguage);
537
+ if (cached) return cached;
538
+ const response = await this.client.chat(
539
+ `Translate this text to ${targetLanguage}. Return ONLY the translation:
540
+
541
+ ${text}`,
542
+ {
543
+ ...options,
544
+ model: options?.model ?? this.defaultModel,
545
+ temperature: 0
546
+ }
547
+ );
548
+ const translated = response.content.trim();
549
+ this.cache.set(text, actualSource, targetLanguage, translated);
550
+ return translated;
551
+ }
552
+ /**
553
+ * Translate JSON object with smart text-level caching
554
+ *
555
+ * @example
556
+ * ```ts
557
+ * const translator = new JsonTranslator(llm)
558
+ * const result = await translator.translate(
559
+ * { title: 'Hello', items: ['World', 'Earth'] },
560
+ * 'ru'
561
+ * )
562
+ * // { data: { title: 'Привет', items: ['Мир', 'Земля'] }, valid: true, ... }
563
+ * ```
564
+ */
565
+ async translate(data, targetLanguage, options) {
566
+ const sourceLang = options?.sourceLanguage ?? "auto";
567
+ const translatableTexts = this.extractTranslatableTexts(data, sourceLang, targetLanguage);
568
+ if (translatableTexts.size === 0) {
569
+ return {
570
+ data,
571
+ valid: true,
572
+ errors: [],
573
+ retries: 0,
574
+ sourceLanguage: sourceLang,
575
+ targetLanguage
576
+ };
577
+ }
578
+ let actualSource = sourceLang;
579
+ if (sourceLang === "auto") {
580
+ const firstText = [...translatableTexts][0];
581
+ actualSource = this.detectLanguage(firstText);
582
+ }
583
+ const { cached, uncached } = this.cache.getMany(
584
+ [...translatableTexts],
585
+ actualSource,
586
+ targetLanguage
587
+ );
588
+ if (uncached.length === 0) {
589
+ return {
590
+ data: this.applyTranslations(data, cached),
591
+ valid: true,
592
+ errors: [],
593
+ retries: 0,
594
+ sourceLanguage: actualSource,
595
+ targetLanguage
596
+ };
597
+ }
598
+ const partialJson = this.createPartialJson(data, new Set(uncached));
599
+ const jsonStr = JSON.stringify(partialJson, null, 2);
600
+ try {
601
+ const prompt = buildJsonTranslationPrompt(jsonStr, actualSource, targetLanguage);
602
+ const response = await this.client.chat(prompt, {
603
+ ...options,
604
+ model: options?.model ?? this.defaultModel,
605
+ temperature: 0
606
+ });
607
+ const translatedPartial = extractJson(response.content);
608
+ const newTranslations = this.extractTranslationsByComparison(
609
+ partialJson,
610
+ translatedPartial,
611
+ uncached
612
+ );
613
+ this.cache.setMany(newTranslations, actualSource, targetLanguage);
614
+ const allTranslations = new Map([...cached, ...newTranslations]);
615
+ return {
616
+ data: this.applyTranslations(data, allTranslations),
617
+ valid: true,
618
+ errors: [],
619
+ retries: 0,
620
+ sourceLanguage: actualSource,
621
+ targetLanguage
622
+ };
623
+ } catch (error) {
624
+ const errorMsg = error instanceof Error ? error.message : String(error);
625
+ console.error("Translation failed:", errorMsg);
626
+ return {
627
+ data: cached.size > 0 ? this.applyTranslations(data, cached) : data,
628
+ valid: false,
629
+ errors: [errorMsg],
630
+ retries: 0,
631
+ sourceLanguage: actualSource,
632
+ targetLanguage
633
+ };
634
+ }
635
+ }
636
+ /**
637
+ * Translate to multiple languages in parallel
638
+ */
639
+ async translateToMany(data, targetLanguages, options) {
640
+ const results = /* @__PURE__ */ new Map();
641
+ const promises = targetLanguages.map(async (lang) => {
642
+ const result = await this.translate(data, lang, options);
643
+ return { lang, result };
644
+ });
645
+ const settled = await Promise.all(promises);
646
+ for (const { lang, result } of settled) {
647
+ results.set(lang, result);
648
+ }
649
+ return results;
650
+ }
651
+ /**
652
+ * Extract all translatable texts from object
653
+ */
654
+ extractTranslatableTexts(obj, sourceLang, targetLang) {
655
+ const texts = /* @__PURE__ */ new Set();
656
+ const extract = (item) => {
657
+ if (typeof item === "string") {
658
+ if (this.needsTranslation(item, sourceLang, targetLang)) {
659
+ texts.add(item);
660
+ }
661
+ } else if (Array.isArray(item)) {
662
+ for (const i of item) extract(i);
663
+ } else if (item !== null && typeof item === "object") {
664
+ for (const v of Object.values(item)) extract(v);
665
+ }
666
+ };
667
+ extract(obj);
668
+ return texts;
669
+ }
670
+ /**
671
+ * Apply translations to object
672
+ */
673
+ applyTranslations(obj, translations) {
674
+ const apply = (item) => {
675
+ if (typeof item === "string") {
676
+ return translations.get(item) ?? item;
677
+ } else if (Array.isArray(item)) {
678
+ return item.map(apply);
679
+ } else if (item !== null && typeof item === "object") {
680
+ const result = {};
681
+ for (const [k, v] of Object.entries(item)) {
682
+ result[k] = apply(v);
683
+ }
684
+ return result;
685
+ }
686
+ return item;
687
+ };
688
+ return apply(obj);
689
+ }
690
+ /**
691
+ * Create partial JSON with only texts that need translation
692
+ */
693
+ createPartialJson(data, textsToInclude) {
694
+ const filter = (obj) => {
695
+ if (typeof obj === "string") {
696
+ return textsToInclude.has(obj) ? obj : "SKIP";
697
+ } else if (Array.isArray(obj)) {
698
+ return obj.map(filter).filter((i) => this.hasTranslatable(i, textsToInclude));
699
+ } else if (obj !== null && typeof obj === "object") {
700
+ const result = {};
701
+ for (const [k, v] of Object.entries(obj)) {
702
+ const filtered = filter(v);
703
+ if (this.hasTranslatable(filtered, textsToInclude)) {
704
+ result[k] = filtered;
705
+ }
706
+ }
707
+ return result;
708
+ }
709
+ return obj;
710
+ };
711
+ return filter(data);
712
+ }
713
+ /**
714
+ * Check if object contains translatable text
715
+ */
716
+ hasTranslatable(obj, textsSet) {
717
+ if (typeof obj === "string") return textsSet.has(obj);
718
+ if (Array.isArray(obj)) return obj.some((i) => this.hasTranslatable(i, textsSet));
719
+ if (obj !== null && typeof obj === "object") {
720
+ return Object.values(obj).some((v) => this.hasTranslatable(v, textsSet));
721
+ }
722
+ return false;
723
+ }
724
+ /**
725
+ * Extract translations by comparing original and translated JSON
726
+ */
727
+ extractTranslationsByComparison(original, translated, uncachedTexts) {
728
+ const translations = /* @__PURE__ */ new Map();
729
+ const uncachedSet = new Set(uncachedTexts);
730
+ const compare = (orig, trans) => {
731
+ if (typeof orig === "string" && typeof trans === "string") {
732
+ if (uncachedSet.has(orig) && trans !== "SKIP" && trans.trim()) {
733
+ translations.set(orig, trans);
734
+ }
735
+ } else if (Array.isArray(orig) && Array.isArray(trans)) {
736
+ for (let i = 0; i < Math.min(orig.length, trans.length); i++) {
737
+ compare(orig[i], trans[i]);
738
+ }
739
+ } else if (orig !== null && typeof orig === "object" && trans !== null && typeof trans === "object") {
740
+ for (const key of Object.keys(orig)) {
741
+ if (key in trans) {
742
+ compare(
743
+ orig[key],
744
+ trans[key]
745
+ );
746
+ }
747
+ }
748
+ }
749
+ };
750
+ compare(original, translated);
751
+ return translations;
752
+ }
753
+ /**
754
+ * Get translation statistics
755
+ */
756
+ getStats() {
757
+ return this.cache.getStats();
758
+ }
759
+ /**
760
+ * Clear translation cache
761
+ */
762
+ clearCache(sourceLang, targetLang) {
763
+ this.cache.clear(sourceLang, targetLang);
764
+ }
765
+ };
766
+ function createTranslator(client, configOrCache) {
767
+ if (configOrCache instanceof TranslationCache) {
768
+ return new JsonTranslator(client, configOrCache);
769
+ }
770
+ return new JsonTranslator(client, configOrCache?.cache, configOrCache?.model);
771
+ }
772
+
773
+ exports.JsonTranslator = JsonTranslator;
774
+ exports.LANGUAGE_NAMES = LANGUAGE_NAMES;
775
+ exports.TranslationCache = TranslationCache;
776
+ exports.buildJsonTranslationPrompt = buildJsonTranslationPrompt;
777
+ exports.buildRetryPrompt = buildRetryPrompt;
778
+ exports.buildTextTranslationPrompt = buildTextTranslationPrompt;
779
+ exports.containsArabic = containsArabic;
780
+ exports.containsCJK = containsCJK;
781
+ exports.containsCyrillic = containsCyrillic;
782
+ exports.createCache = createCache;
783
+ exports.createTranslator = createTranslator;
784
+ exports.detectScript = detectScript;
785
+ exports.extractPlaceholders = extractPlaceholders;
786
+ exports.extractTranslatableValues = extractTranslatableValues;
787
+ exports.getLanguageName = getLanguageName;
788
+ exports.isTechnicalContent = isTechnicalContent;
789
+ exports.validateJsonKeys = validateJsonKeys;
790
+ exports.validatePlaceholders = validatePlaceholders2;
791
+ exports.validateTextPlaceholders = validatePlaceholders;
792
+ exports.validateTranslation = validateTranslation;
793
+ //# sourceMappingURL=index.cjs.map
794
+ //# sourceMappingURL=index.cjs.map