@babelize/engine 0.0.0-stage → 0.1.0

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 (246) hide show
  1. package/LICENSE +202 -0
  2. package/dist/BabelizeEngine.d.ts +12 -0
  3. package/dist/BabelizeEngine.js +47 -0
  4. package/dist/analysis/analyze.d.ts +106 -0
  5. package/dist/analysis/analyze.js +323 -0
  6. package/dist/config/env.d.ts +9 -0
  7. package/dist/config/env.js +16 -0
  8. package/dist/config.d.ts +74 -0
  9. package/dist/config.js +47 -0
  10. package/dist/index.d.ts +54 -0
  11. package/dist/index.js +126 -0
  12. package/dist/injection/InjectionStrategy.d.ts +62 -0
  13. package/dist/injection/InjectionStrategy.js +2 -0
  14. package/dist/injection/StrategyFactory.d.ts +4 -0
  15. package/dist/injection/StrategyFactory.js +29 -0
  16. package/dist/injection/django/django-template-injector.d.ts +29 -0
  17. package/dist/injection/django/django-template-injector.js +216 -0
  18. package/dist/injection/html/html-injector.d.ts +11 -0
  19. package/dist/injection/html/html-injector.js +173 -0
  20. package/dist/injection/strategies/NextIntlStrategy.d.ts +25 -0
  21. package/dist/injection/strategies/NextIntlStrategy.js +141 -0
  22. package/dist/injection/strategies/ReactI18nextStrategy.d.ts +19 -0
  23. package/dist/injection/strategies/ReactI18nextStrategy.js +116 -0
  24. package/dist/injection/strategies/VanillaStrategy.d.ts +11 -0
  25. package/dist/injection/strategies/VanillaStrategy.js +73 -0
  26. package/dist/injection/strategies/VueI18nStrategy.d.ts +11 -0
  27. package/dist/injection/strategies/VueI18nStrategy.js +84 -0
  28. package/dist/local/formatter.d.ts +26 -0
  29. package/dist/local/formatter.js +105 -0
  30. package/dist/local/git.d.ts +23 -0
  31. package/dist/local/git.js +136 -0
  32. package/dist/local/hosted-provider.d.ts +68 -0
  33. package/dist/local/hosted-provider.js +215 -0
  34. package/dist/local/index.d.ts +23 -0
  35. package/dist/local/index.js +75 -0
  36. package/dist/local/provider.d.ts +56 -0
  37. package/dist/local/provider.js +119 -0
  38. package/dist/local/pseudo-provider.d.ts +18 -0
  39. package/dist/local/pseudo-provider.js +207 -0
  40. package/dist/local/report.d.ts +177 -0
  41. package/dist/local/report.js +186 -0
  42. package/dist/local/translate-core.d.ts +215 -0
  43. package/dist/local/translate-core.js +461 -0
  44. package/dist/local/translate-messages.d.ts +49 -0
  45. package/dist/local/translate-messages.js +91 -0
  46. package/dist/local/workspace-copy.d.ts +91 -0
  47. package/dist/local/workspace-copy.js +355 -0
  48. package/dist/modules/extractor/extract.d.ts +20 -0
  49. package/dist/modules/extractor/extract.js +701 -0
  50. package/dist/modules/extractor/index.d.ts +2 -0
  51. package/dist/modules/extractor/index.js +63 -0
  52. package/dist/modules/finalization/django-finalize.d.ts +13 -0
  53. package/dist/modules/finalization/django-finalize.js +275 -0
  54. package/dist/modules/finalization/finalize.d.ts +36 -0
  55. package/dist/modules/finalization/finalize.js +717 -0
  56. package/dist/modules/finalization/index.d.ts +1 -0
  57. package/dist/modules/finalization/index.js +5 -0
  58. package/dist/modules/finalization/nextjs-finalize.d.ts +37 -0
  59. package/dist/modules/finalization/nextjs-finalize.js +329 -0
  60. package/dist/modules/finalization/static-runtime.d.ts +9 -0
  61. package/dist/modules/finalization/static-runtime.js +261 -0
  62. package/dist/modules/format-injection/index.d.ts +19 -0
  63. package/dist/modules/format-injection/index.js +272 -0
  64. package/dist/modules/format-injection/inject-formats.d.ts +31 -0
  65. package/dist/modules/format-injection/inject-formats.js +208 -0
  66. package/dist/modules/glossary/glossary.d.ts +9 -0
  67. package/dist/modules/glossary/glossary.js +27 -0
  68. package/dist/modules/glossary/index.d.ts +3 -0
  69. package/dist/modules/glossary/index.js +8 -0
  70. package/dist/modules/glossary/masker.d.ts +23 -0
  71. package/dist/modules/glossary/masker.js +56 -0
  72. package/dist/modules/glossary/types.d.ts +22 -0
  73. package/dist/modules/glossary/types.js +2 -0
  74. package/dist/modules/injection/index.d.ts +1 -0
  75. package/dist/modules/injection/index.js +6 -0
  76. package/dist/modules/injection/inject.d.ts +29 -0
  77. package/dist/modules/injection/inject.js +1066 -0
  78. package/dist/modules/keygen/index.d.ts +1 -0
  79. package/dist/modules/keygen/index.js +11 -0
  80. package/dist/modules/keygen/keygen.d.ts +4 -0
  81. package/dist/modules/keygen/keygen.js +116 -0
  82. package/dist/modules/keygen/locale-data.d.ts +7 -0
  83. package/dist/modules/keygen/locale-data.js +73 -0
  84. package/dist/modules/keygen/public.d.ts +2 -0
  85. package/dist/modules/keygen/public.js +12 -0
  86. package/dist/modules/locale-builder/index.d.ts +1 -0
  87. package/dist/modules/locale-builder/index.js +7 -0
  88. package/dist/modules/locale-builder/locales.d.ts +13 -0
  89. package/dist/modules/locale-builder/locales.js +107 -0
  90. package/dist/modules/locale-builder/po.d.ts +33 -0
  91. package/dist/modules/locale-builder/po.js +131 -0
  92. package/dist/modules/lockfile/hasher.d.ts +7 -0
  93. package/dist/modules/lockfile/hasher.js +13 -0
  94. package/dist/modules/lockfile/index.d.ts +3 -0
  95. package/dist/modules/lockfile/index.js +21 -0
  96. package/dist/modules/lockfile/lockfile-manager.d.ts +90 -0
  97. package/dist/modules/lockfile/lockfile-manager.js +482 -0
  98. package/dist/modules/lockfile/schema.d.ts +154 -0
  99. package/dist/modules/lockfile/schema.js +45 -0
  100. package/dist/modules/parser/index.d.ts +2 -0
  101. package/dist/modules/parser/index.js +9 -0
  102. package/dist/modules/summary/index.d.ts +1 -0
  103. package/dist/modules/summary/index.js +7 -0
  104. package/dist/modules/summary/summary.d.ts +9 -0
  105. package/dist/modules/summary/summary.js +195 -0
  106. package/dist/modules/sync-detector/index.d.ts +2 -0
  107. package/dist/modules/sync-detector/index.js +7 -0
  108. package/dist/modules/sync-detector/reporter.d.ts +3 -0
  109. package/dist/modules/sync-detector/reporter.js +16 -0
  110. package/dist/modules/sync-detector/sync-detector.d.ts +39 -0
  111. package/dist/modules/sync-detector/sync-detector.js +374 -0
  112. package/dist/modules/translator/batch-optimizer.d.ts +12 -0
  113. package/dist/modules/translator/batch-optimizer.js +30 -0
  114. package/dist/modules/translator/client/byok.provider.d.ts +64 -0
  115. package/dist/modules/translator/client/byok.provider.js +319 -0
  116. package/dist/modules/translator/client/direct-translator.client.d.ts +12 -0
  117. package/dist/modules/translator/client/direct-translator.client.js +71 -0
  118. package/dist/modules/translator/client/gemini.provider.d.ts +1 -0
  119. package/dist/modules/translator/client/gemini.provider.js +110 -0
  120. package/dist/modules/translator/client/groq.provider.d.ts +1 -0
  121. package/dist/modules/translator/client/groq.provider.js +111 -0
  122. package/dist/modules/translator/client/request.types.d.ts +1 -0
  123. package/dist/modules/translator/client/request.types.js +2 -0
  124. package/dist/modules/translator/client/retry.d.ts +29 -0
  125. package/dist/modules/translator/client/retry.js +94 -0
  126. package/dist/modules/translator/client/test-utils/mock.client.d.ts +31 -0
  127. package/dist/modules/translator/client/test-utils/mock.client.js +59 -0
  128. package/dist/modules/translator/client/translator.interface.d.ts +1 -0
  129. package/dist/modules/translator/client/translator.interface.js +2 -0
  130. package/dist/modules/translator/language-codes/errors.d.ts +10 -0
  131. package/dist/modules/translator/language-codes/errors.js +18 -0
  132. package/dist/modules/translator/language-codes/flores-map.d.ts +1 -0
  133. package/dist/modules/translator/language-codes/flores-map.js +107 -0
  134. package/dist/modules/translator/language-codes/index.d.ts +11 -0
  135. package/dist/modules/translator/language-codes/index.js +24 -0
  136. package/dist/modules/translator/language-codes/language-mapper.d.ts +25 -0
  137. package/dist/modules/translator/language-codes/language-mapper.js +132 -0
  138. package/dist/modules/translator/language-codes/rtl-languages.d.ts +2 -0
  139. package/dist/modules/translator/language-codes/rtl-languages.js +9 -0
  140. package/dist/modules/translator/memory/memory-matcher.d.ts +8 -0
  141. package/dist/modules/translator/memory/memory-matcher.js +218 -0
  142. package/dist/modules/translator/memory/types.d.ts +22 -0
  143. package/dist/modules/translator/memory/types.js +2 -0
  144. package/dist/modules/translator/translate.d.ts +38 -0
  145. package/dist/modules/translator/translate.js +932 -0
  146. package/dist/modules/translator/translator.d.ts +5 -0
  147. package/dist/modules/translator/translator.js +46 -0
  148. package/dist/modules/vue/migrate.d.ts +13 -0
  149. package/dist/modules/vue/migrate.js +40 -0
  150. package/dist/modules/vue/script-inject.d.ts +16 -0
  151. package/dist/modules/vue/script-inject.js +115 -0
  152. package/dist/modules/vue/sfc.d.ts +11 -0
  153. package/dist/modules/vue/sfc.js +51 -0
  154. package/dist/modules/vue/template-rewrite.d.ts +25 -0
  155. package/dist/modules/vue/template-rewrite.js +76 -0
  156. package/dist/modules/vue/template-scan.d.ts +29 -0
  157. package/dist/modules/vue/template-scan.js +264 -0
  158. package/dist/modules/workspace/index.d.ts +1 -0
  159. package/dist/modules/workspace/index.js +5 -0
  160. package/dist/modules/workspace/validate.d.ts +13 -0
  161. package/dist/modules/workspace/validate.js +298 -0
  162. package/dist/orchestrator.d.ts +8 -0
  163. package/dist/orchestrator.js +616 -0
  164. package/dist/providers/default-translation-provider.d.ts +20 -0
  165. package/dist/providers/default-translation-provider.js +30 -0
  166. package/dist/providers/index.d.ts +2 -0
  167. package/dist/providers/index.js +21 -0
  168. package/dist/providers/types.d.ts +145 -0
  169. package/dist/providers/types.js +2 -0
  170. package/dist/qa/check-project.d.ts +49 -0
  171. package/dist/qa/check-project.js +150 -0
  172. package/dist/qa/locale-files.d.ts +44 -0
  173. package/dist/qa/locale-files.js +159 -0
  174. package/dist/qa/locale-qa.d.ts +61 -0
  175. package/dist/qa/locale-qa.js +325 -0
  176. package/dist/qa/review.d.ts +43 -0
  177. package/dist/qa/review.js +175 -0
  178. package/dist/runtime/config.d.ts +8 -0
  179. package/dist/runtime/config.js +36 -0
  180. package/dist/runtime/index.d.ts +3 -0
  181. package/dist/runtime/index.js +8 -0
  182. package/dist/runtime/types.d.ts +363 -0
  183. package/dist/runtime/types.js +38 -0
  184. package/dist/types.d.ts +134 -0
  185. package/dist/types.js +12 -0
  186. package/dist/utils/ast-helpers.d.ts +57 -0
  187. package/dist/utils/ast-helpers.js +608 -0
  188. package/dist/utils/cache-keys.d.ts +8 -0
  189. package/dist/utils/cache-keys.js +18 -0
  190. package/dist/utils/circuit-breaker.d.ts +43 -0
  191. package/dist/utils/circuit-breaker.js +119 -0
  192. package/dist/utils/concurrent-map.d.ts +4 -0
  193. package/dist/utils/concurrent-map.js +21 -0
  194. package/dist/utils/detect-format.d.ts +5 -0
  195. package/dist/utils/detect-format.js +125 -0
  196. package/dist/utils/dtl-mask.d.ts +25 -0
  197. package/dist/utils/dtl-mask.js +58 -0
  198. package/dist/utils/extraction-options.d.ts +23 -0
  199. package/dist/utils/extraction-options.js +23 -0
  200. package/dist/utils/file-utils.d.ts +33 -0
  201. package/dist/utils/file-utils.js +262 -0
  202. package/dist/utils/glossary-match.d.ts +9 -0
  203. package/dist/utils/glossary-match.js +25 -0
  204. package/dist/utils/html-extractor.d.ts +8 -0
  205. package/dist/utils/html-extractor.js +317 -0
  206. package/dist/utils/i18next-icu.d.ts +38 -0
  207. package/dist/utils/i18next-icu.js +159 -0
  208. package/dist/utils/icu-message.d.ts +65 -0
  209. package/dist/utils/icu-message.js +346 -0
  210. package/dist/utils/icu-spans.d.ts +10 -0
  211. package/dist/utils/icu-spans.js +126 -0
  212. package/dist/utils/jsx-message.d.ts +93 -0
  213. package/dist/utils/jsx-message.js +621 -0
  214. package/dist/utils/key-usage.d.ts +35 -0
  215. package/dist/utils/key-usage.js +247 -0
  216. package/dist/utils/logger.d.ts +24 -0
  217. package/dist/utils/logger.js +33 -0
  218. package/dist/utils/metadata.d.ts +80 -0
  219. package/dist/utils/metadata.js +255 -0
  220. package/dist/utils/module-constants.d.ts +30 -0
  221. package/dist/utils/module-constants.js +265 -0
  222. package/dist/utils/not-injected.d.ts +6 -0
  223. package/dist/utils/not-injected.js +36 -0
  224. package/dist/utils/placeholder-protection.d.ts +57 -0
  225. package/dist/utils/placeholder-protection.js +136 -0
  226. package/dist/utils/pseudo-markers.d.ts +9 -0
  227. package/dist/utils/pseudo-markers.js +16 -0
  228. package/dist/utils/py-extractor.d.ts +14 -0
  229. package/dist/utils/py-extractor.js +285 -0
  230. package/dist/utils/string-utils.d.ts +52 -0
  231. package/dist/utils/string-utils.js +270 -0
  232. package/dist/utils/translation-quality.d.ts +34 -0
  233. package/dist/utils/translation-quality.js +156 -0
  234. package/dist/utils/translation-sanitizer.d.ts +10 -0
  235. package/dist/utils/translation-sanitizer.js +57 -0
  236. package/dist/utils/translator-caller.d.ts +32 -0
  237. package/dist/utils/translator-caller.js +217 -0
  238. package/dist/utils/translator-load-balancer.d.ts +73 -0
  239. package/dist/utils/translator-load-balancer.js +293 -0
  240. package/dist/utils/ui-copy.d.ts +32 -0
  241. package/dist/utils/ui-copy.js +126 -0
  242. package/dist/utils/workspace.d.ts +14 -0
  243. package/dist/utils/workspace.js +89 -0
  244. package/package.json +64 -4
  245. package/schemas/translate-report.schema.json +297 -0
  246. package/README.md +0 -3
@@ -0,0 +1,156 @@
1
+ "use strict";
2
+ /**
3
+ * Translation Quality Validation
4
+ *
5
+ * Validates translation quality and detects potential issues
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.validateTranslationQuality = validateTranslationQuality;
9
+ exports.validateBatchQuality = validateBatchQuality;
10
+ /**
11
+ * Validate translation quality
12
+ */
13
+ function validateTranslationQuality(original, translated) {
14
+ const issues = [];
15
+ let score = 100;
16
+ const trimmedOriginal = original.trim();
17
+ const trimmedTranslated = (translated || '').trim();
18
+ // Check for empty translation
19
+ if (trimmedTranslated.length === 0) {
20
+ issues.push({
21
+ type: 'empty_translation',
22
+ severity: 'error',
23
+ message: 'Translation is empty',
24
+ original,
25
+ translated,
26
+ });
27
+ score -= 100; // Complete failure
28
+ }
29
+ // Check for untranslated text (identical to source)
30
+ // Skip for very short strings (e.g. "OK", "Save") where source might equal translation
31
+ if (trimmedTranslated.length > 4 && trimmedOriginal.toLowerCase() === trimmedTranslated.toLowerCase()) {
32
+ issues.push({
33
+ type: 'untranslated_text',
34
+ severity: 'warning',
35
+ message: 'Translation is identical to source text',
36
+ original,
37
+ translated,
38
+ });
39
+ score -= 20;
40
+ }
41
+ // Check placeholder preservation
42
+ const originalPlaceholders = original.match(/\{[^}]+\}/g) || [];
43
+ const translatedPlaceholders = translated.match(/\{[^}]+\}/g) || [];
44
+ // Missing placeholders
45
+ const sortedOriginal = [...originalPlaceholders].sort();
46
+ const sortedTranslated = [...translatedPlaceholders].sort();
47
+ const missingPlaceholders = sortedOriginal.filter((p) => !sortedTranslated.includes(p));
48
+ if (missingPlaceholders.length > 0) {
49
+ issues.push({
50
+ type: 'missing_placeholder',
51
+ severity: 'error',
52
+ message: `Missing placeholders: ${missingPlaceholders.join(', ')}`,
53
+ original,
54
+ translated,
55
+ });
56
+ score -= 30 * missingPlaceholders.length;
57
+ }
58
+ // Extra placeholders
59
+ const extraPlaceholders = sortedTranslated.filter((p) => !sortedOriginal.includes(p));
60
+ if (extraPlaceholders.length > 0) {
61
+ issues.push({
62
+ type: 'extra_placeholder',
63
+ severity: 'warning',
64
+ message: `Extra placeholders found: ${extraPlaceholders.join(', ')}`,
65
+ original,
66
+ translated,
67
+ });
68
+ score -= 10 * extraPlaceholders.length;
69
+ }
70
+ // Check placeholder ordering
71
+ // If the order of placeholders is fundamentally different, it might indicate grammar corruption
72
+ if (originalPlaceholders.length > 1 && originalPlaceholders.length === translatedPlaceholders.length) {
73
+ if (originalPlaceholders.join(',') !== translatedPlaceholders.join(',')) {
74
+ // Check if they are just reordered (which might be valid for some languages)
75
+ // vs. duplicated or mangled. If sorted they match, it's just a reorder.
76
+ if (sortedOriginal.join(',') === sortedTranslated.join(',')) {
77
+ // Reordering is often valid (Subject-Object-Verb vs Subject-Verb-Object)
78
+ // but drastic jumps might be worth a warning in some engines.
79
+ // For now, we allow reordering but log it if we wanted to.
80
+ }
81
+ }
82
+ }
83
+ // Check HTML tag parity
84
+ const originalTags = original.match(/<[^>]+>/g) || [];
85
+ const translatedTags = translated.match(/<[^>]+>/g) || [];
86
+ if (originalTags.length !== translatedTags.length) {
87
+ issues.push({
88
+ type: 'html_tag_mismatch',
89
+ severity: 'error',
90
+ message: `HTML tag count mismatch: source has ${originalTags.length}, translation has ${translatedTags.length}`,
91
+ original,
92
+ translated,
93
+ });
94
+ score -= 25;
95
+ }
96
+ else {
97
+ // Even if count matches, check if tags themselves were mangled (e.g. <strong> becomes <fort>)
98
+ const sortedOrigTags = [...originalTags].map(t => t.toLowerCase().split(' ')[0]).sort();
99
+ const sortedTransTags = [...translatedTags].map(t => t.toLowerCase().split(' ')[0]).sort();
100
+ if (sortedOrigTags.join(',') !== sortedTransTags.join(',')) {
101
+ issues.push({
102
+ type: 'html_tag_mismatch',
103
+ severity: 'error',
104
+ message: 'HTML tags were altered or translated (e.g. <strong> became something else)',
105
+ original,
106
+ translated,
107
+ });
108
+ score -= 25;
109
+ }
110
+ }
111
+ // Check for suspicious length differences
112
+ const lengthRatio = trimmedTranslated.length / (trimmedOriginal.length || 1);
113
+ if (lengthRatio < 0.2 || lengthRatio > 5) {
114
+ issues.push({
115
+ type: 'suspicious_length',
116
+ severity: 'warning',
117
+ message: `Translation length is ${(lengthRatio * 100).toFixed(0)}% of original (suspicious)`,
118
+ original,
119
+ translated,
120
+ });
121
+ score -= 15;
122
+ }
123
+ // Ensure score doesn't go below 0
124
+ score = Math.max(0, score);
125
+ return {
126
+ isValid: issues.filter((i) => i.severity === 'error').length === 0,
127
+ score,
128
+ issues,
129
+ };
130
+ }
131
+ /**
132
+ * Validate batch of translations
133
+ */
134
+ function validateBatchQuality(entries) {
135
+ let valid = 0;
136
+ let invalid = 0;
137
+ let totalScore = 0;
138
+ const allIssues = [];
139
+ for (const entry of entries) {
140
+ const result = validateTranslationQuality(entry.original, entry.translated);
141
+ totalScore += result.score;
142
+ if (result.isValid) {
143
+ valid++;
144
+ }
145
+ else {
146
+ invalid++;
147
+ }
148
+ allIssues.push(...result.issues);
149
+ }
150
+ return {
151
+ valid,
152
+ invalid,
153
+ averageScore: entries.length > 0 ? totalScore / entries.length : 0,
154
+ issues: allIssues,
155
+ };
156
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Clean up translator output. Script injection is stripped for every
3
+ * provider; the NLLB-specific repairs (language-code echoes, unknown-token
4
+ * marks, whitespace and stutter collapsing) only run for NLLB output, since on
5
+ * LLM output they would rewrite legitimate text.
6
+ */
7
+ export declare function sanitizeTranslation(text: string, options?: {
8
+ nllb?: boolean;
9
+ }): string;
10
+ export declare function validateRawTokens(aiOutput: string, expectedCount: number): boolean;
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sanitizeTranslation = sanitizeTranslation;
4
+ exports.validateRawTokens = validateRawTokens;
5
+ const NLLB_ARTIFACT_PATTERN = new RegExp('\\b(?:__)?[a-z]{3}_[A-Z][a-z]{3,4}(?:__)?\\b', 'g');
6
+ /**
7
+ * Collapse phrase-level stutter ("Il a Il a" -> "Il a"). NLLB occasionally
8
+ * repeats a short fragment twice after language tokens are stripped. Only
9
+ * collapse when BOTH halves contain >=2 words and match case-insensitively —
10
+ * this never touches legitimate one-word doublings like "no no" or "la la".
11
+ */
12
+ function collapsePhraseStutter(text) {
13
+ const words = text.split(/\s+/);
14
+ if (words.length >= 4 && words.length % 2 === 0) {
15
+ const half = words.length / 2;
16
+ const first = words.slice(0, half);
17
+ const second = words.slice(half);
18
+ if (first.length >= 2 &&
19
+ first.every((w, i) => w.toLowerCase() === second[i].toLowerCase())) {
20
+ return first.join(' ');
21
+ }
22
+ }
23
+ return text;
24
+ }
25
+ /**
26
+ * Clean up translator output. Script injection is stripped for every
27
+ * provider; the NLLB-specific repairs (language-code echoes, unknown-token
28
+ * marks, whitespace and stutter collapsing) only run for NLLB output, since on
29
+ * LLM output they would rewrite legitimate text.
30
+ */
31
+ function sanitizeTranslation(text, options = {}) {
32
+ if (!text)
33
+ return text;
34
+ let clean = text.replace(/<script\b[^>]*>([\s\S]*?)<\/script>/gim, '');
35
+ clean = clean.replace(/javascript:/gim, 'java_script:');
36
+ clean = clean.replace(/\s+on[a-z]+\s*=\s*['"][^'"]*['"]/gim, '');
37
+ if (options.nllb === false)
38
+ return clean;
39
+ // Strip FLORES-200 language-code artifacts (eng_Latn, __fra_Latn__, ...)
40
+ // anywhere in the output — NLLB echoes these at the end/middle of short
41
+ // fragments. Mirrors the fix in apps/translator/src/translator.rs.
42
+ clean = clean.replace(NLLB_ARTIFACT_PATTERN, '');
43
+ // Strip U+2047 (DOUBLE QUESTION MARK) — NLLB's unknown-token marker, emitted
44
+ // for tokens it can't translate (e.g. punctuation-only fragments). Never
45
+ // legitimate translated text.
46
+ clean = clean.replace(/\u2047/g, '');
47
+ clean = clean.replace(/ +/g, ' ').trim();
48
+ return collapsePhraseStutter(clean);
49
+ }
50
+ function validateRawTokens(aiOutput, expectedCount) {
51
+ for (let i = 0; i < expectedCount; i++) {
52
+ const tokenRegex = new RegExp(`\\[\\s*#\\s*VAR_${i}\\s*#\\s*\\]`, 'gi');
53
+ if (!tokenRegex.test(aiOutput))
54
+ return false;
55
+ }
56
+ return true;
57
+ }
@@ -0,0 +1,32 @@
1
+ import type { TranslationEntry } from '../runtime/types';
2
+ import type { ByokProvider } from '@babelize/types';
3
+ /** Options only LLM (BYOK) providers use; the NLLB service ignores them. */
4
+ export interface LlmRequestOptions {
5
+ targetLocale?: string;
6
+ glossary?: Array<{
7
+ term: string;
8
+ translation: string;
9
+ }>;
10
+ hints?: Record<string, string>;
11
+ /** Aborts the request and any retries. */
12
+ signal?: AbortSignal;
13
+ }
14
+ /**
15
+ * Translate a batch. Returns translations only for entries the translator
16
+ * actually produced — never the source text in their place. Entries missing
17
+ * from the result are reported as failed keys by the caller.
18
+ */
19
+ export declare function callTranslator(entries: TranslationEntry[], sourceLang: string, targetLang: string, byokConfig?: {
20
+ provider: ByokProvider;
21
+ apiKey: string;
22
+ model?: string;
23
+ }, brandVoice?: string, llmOptions?: LlmRequestOptions): Promise<{
24
+ translations: TranslationEntry[];
25
+ metadata?: any;
26
+ }>;
27
+ export declare function checkTranslatorHealth(byokConfig?: {
28
+ provider: ByokProvider;
29
+ apiKey: string;
30
+ model?: string;
31
+ }): Promise<boolean>;
32
+ export declare function warmupTranslator(): Promise<boolean>;
@@ -0,0 +1,217 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.callTranslator = callTranslator;
37
+ exports.checkTranslatorHealth = checkTranslatorHealth;
38
+ exports.warmupTranslator = warmupTranslator;
39
+ const logger_1 = require("./logger");
40
+ const translation_quality_1 = require("./translation-quality");
41
+ const byok_provider_1 = require("../modules/translator/client/byok.provider");
42
+ /**
43
+ * Translate a batch. Returns translations only for entries the translator
44
+ * actually produced — never the source text in their place. Entries missing
45
+ * from the result are reported as failed keys by the caller.
46
+ */
47
+ async function callTranslator(entries, sourceLang, targetLang, byokConfig, brandVoice, llmOptions = {}) {
48
+ if (byokConfig) {
49
+ return callByokTranslator(entries, sourceLang, targetLang, byokConfig, brandVoice, llmOptions);
50
+ }
51
+ const outerSignal = llmOptions.signal;
52
+ const { translatorLoadBalancer } = await Promise.resolve().then(() => __importStar(require('./translator-load-balancer')));
53
+ let rustResult = null;
54
+ try {
55
+ rustResult = await translatorLoadBalancer.executeWithFailover(async (translatorUrl) => {
56
+ const response = await fetch(`${translatorUrl}/translate`, {
57
+ method: 'POST',
58
+ headers: { 'Content-Type': 'application/json' },
59
+ body: JSON.stringify({ sourceLang, targetLang, entries }),
60
+ signal: outerSignal ? AbortSignal.any([AbortSignal.timeout(300000), outerSignal]) : AbortSignal.timeout(300000),
61
+ });
62
+ if (!response.ok) {
63
+ throw new Error(`Translation API error: ${response.status} ${response.statusText}`);
64
+ }
65
+ const data = await response.json();
66
+ if (data.metadata) {
67
+ logger_1.engineLogger.info('Translation metadata', {
68
+ dictVersion: data.metadata.dictVersion,
69
+ processingTimeMs: data.metadata.processingTimeMs,
70
+ dictionaryHits: data.metadata.dictionaryHits,
71
+ modelTranslations: data.metadata.modelTranslations,
72
+ });
73
+ }
74
+ return { translations: data.translations || [], metadata: data.metadata };
75
+ });
76
+ }
77
+ catch (err) {
78
+ logger_1.engineLogger.warn('Rust translator unavailable, will try HF Inference API fallback', {
79
+ error: err instanceof Error ? err.message : String(err),
80
+ });
81
+ }
82
+ const needsFallback = rustResult === null || (rustResult.translations.length > 0 &&
83
+ rustResult.translations.every((t) => {
84
+ const orig = entries.find((e) => e.key === t.key)?.value || '';
85
+ if (orig.length <= 4)
86
+ return false;
87
+ const val = t.value || '';
88
+ if (orig.toLowerCase() === val.toLowerCase())
89
+ return true;
90
+ return /^\s*(?:[a-z]{3}_[A-Za-z]{4})\s+/.test(val);
91
+ }));
92
+ if (needsFallback) {
93
+ logger_1.engineLogger.warn('Rust translator returned untargeted translations or unavailable, falling back to HF Inference API');
94
+ try {
95
+ const hfResults = await callHuggingFaceNLLB(entries, sourceLang, targetLang);
96
+ if (hfResults.translations.length > 0) {
97
+ return { translations: hfResults.translations, metadata: { ...rustResult?.metadata, fallback: 'hf-inference-api' } };
98
+ }
99
+ }
100
+ catch (hfErr) {
101
+ logger_1.engineLogger.error('HF Inference API fallback also failed', { error: hfErr });
102
+ }
103
+ }
104
+ if (rustResult !== null && rustResult.translations.length > 0) {
105
+ return rustResult;
106
+ }
107
+ // Never pass the source text off as a translation: an empty result makes
108
+ // every key a reported failure, and the next run retries them.
109
+ logger_1.engineLogger.error(`Translator unavailable for ${sourceLang} → ${targetLang}; no translations produced`);
110
+ return { translations: [], metadata: { translatorUnavailable: true } };
111
+ }
112
+ async function callByokTranslator(entries, sourceLang, targetLang, byokConfig, brandVoice, llmOptions = {}) {
113
+ const client = new byok_provider_1.BYOKTranslatorClient(byokConfig);
114
+ const { signal, ...promptOptions } = llmOptions;
115
+ const hfRequest = {
116
+ entries: entries.map((e) => ({ key: e.key, value: e.value, context: e.parentContext, extractionContext: e.extractionContext })),
117
+ sourceLang,
118
+ targetLang,
119
+ brandVoice,
120
+ ...promptOptions,
121
+ };
122
+ const result = await client.translate(hfRequest, signal);
123
+ const byKey = new Map(result.translations.map((t) => [t.key, t]));
124
+ const translations = [];
125
+ for (const entry of entries) {
126
+ const t = byKey.get(entry.key);
127
+ if (!t)
128
+ continue;
129
+ const quality = (0, translation_quality_1.validateTranslationQuality)(entry.value, t.value);
130
+ translations.push({
131
+ key: entry.key,
132
+ value: t.value,
133
+ confidence: quality.isValid ? Math.max(0.5, quality.score / 100) : 0.3,
134
+ engine: byokConfig.provider,
135
+ warnings: quality.issues?.map((i) => `${i.type}:${i.severity}`) || [],
136
+ });
137
+ }
138
+ return { translations, metadata: result.metadata };
139
+ }
140
+ async function callHuggingFaceNLLB(entries, sourceLang, targetLang) {
141
+ const hfToken = process.env.HF_TOKEN || process.env.HF_API_TOKEN;
142
+ const model = 'facebook/nllb-200-distilled-600M';
143
+ const baseUrl = `https://api-inference.huggingface.co/models/${model}`;
144
+ const translations = [];
145
+ for (const entry of entries) {
146
+ const response = await fetch(baseUrl, {
147
+ method: 'POST',
148
+ headers: {
149
+ 'Content-Type': 'application/json',
150
+ ...(hfToken ? { Authorization: `Bearer ${hfToken}` } : {}),
151
+ },
152
+ body: JSON.stringify({
153
+ inputs: entry.value,
154
+ parameters: { src_lang: sourceLang, tgt_lang: targetLang },
155
+ }),
156
+ signal: AbortSignal.timeout(60000),
157
+ });
158
+ if (!response.ok) {
159
+ const body = await response.text().catch(() => '');
160
+ throw new Error(`HF Inference API error: ${response.status} — ${body.slice(0, 200)}`);
161
+ }
162
+ const data = await response.json();
163
+ const translatedText = data?.[0]?.translation_text;
164
+ if (!translatedText)
165
+ continue;
166
+ translations.push({
167
+ key: entry.key,
168
+ value: translatedText,
169
+ confidence: 0.9,
170
+ engine: 'hf-nllb-200',
171
+ });
172
+ }
173
+ return { translations };
174
+ }
175
+ async function checkTranslatorHealth(byokConfig) {
176
+ try {
177
+ if (byokConfig) {
178
+ const client = new byok_provider_1.BYOKTranslatorClient(byokConfig);
179
+ return await client.health();
180
+ }
181
+ const { DirectTranslatorClient } = await Promise.resolve().then(() => __importStar(require('../modules/translator/client/direct-translator.client')));
182
+ const client = new DirectTranslatorClient();
183
+ return await client.health();
184
+ }
185
+ catch {
186
+ return false;
187
+ }
188
+ }
189
+ async function warmupTranslator() {
190
+ logger_1.engineLogger.info('[Translator] Starting warmup...');
191
+ const MAX_ATTEMPTS = 3;
192
+ const RETRY_DELAY_MS = 15000;
193
+ for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
194
+ try {
195
+ logger_1.engineLogger.info(`[Translator] Warmup attempt ${attempt}/${MAX_ATTEMPTS}...`);
196
+ const { DirectTranslatorClient } = await Promise.resolve().then(() => __importStar(require('../modules/translator/client/direct-translator.client')));
197
+ const client = new DirectTranslatorClient();
198
+ const ok = await client.warmup();
199
+ if (ok) {
200
+ logger_1.engineLogger.info('[Translator] Warmup successful');
201
+ return true;
202
+ }
203
+ logger_1.engineLogger.warn(`[Translator] Warmup attempt ${attempt} failed`);
204
+ }
205
+ catch (err) {
206
+ logger_1.engineLogger.warn(`[Translator] Warmup attempt ${attempt} failed:`, {
207
+ error: err instanceof Error ? err.message : String(err),
208
+ });
209
+ }
210
+ if (attempt < MAX_ATTEMPTS) {
211
+ logger_1.engineLogger.info(`[Translator] Retrying warmup in ${RETRY_DELAY_MS / 1000}s...`);
212
+ await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY_MS));
213
+ }
214
+ }
215
+ logger_1.engineLogger.warn('[Translator] All warmup attempts failed — running cold');
216
+ return false;
217
+ }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Translator Load Balancer
3
+ *
4
+ * Manages multiple translator endpoints with:
5
+ * - Health checking
6
+ * - Round-robin load balancing
7
+ * - Automatic failover on errors
8
+ * - Circuit breaker per endpoint
9
+ */
10
+ export interface TranslatorEndpoint {
11
+ url: string;
12
+ name: string;
13
+ priority: number;
14
+ isHealthy: boolean;
15
+ lastHealthCheck: Date | null;
16
+ consecutiveFailures: number;
17
+ }
18
+ declare class TranslatorLoadBalancer {
19
+ private config;
20
+ private circuitBreakers;
21
+ private healthCheckInterval;
22
+ private currentEndpointIndex;
23
+ constructor();
24
+ /**
25
+ * Initialize endpoints from environment variable
26
+ * Format: url1|name1,url2|name2 or just url1,url2
27
+ */
28
+ private initializeEndpoints;
29
+ /**
30
+ * Get the next healthy endpoint using weighted round-robin
31
+ */
32
+ getNextEndpoint(): TranslatorEndpoint | null;
33
+ /**
34
+ * Mark an endpoint as failed
35
+ */
36
+ markEndpointFailed(endpoint: TranslatorEndpoint): void;
37
+ /**
38
+ * Mark an endpoint as successful
39
+ */
40
+ markEndpointSuccess(endpoint: TranslatorEndpoint): void;
41
+ /**
42
+ * Execute a translation request with automatic failover and exponential backoff
43
+ */
44
+ executeWithFailover<T>(requestFn: (url: string) => Promise<T>): Promise<T>;
45
+ /**
46
+ * Sleep utility
47
+ */
48
+ private sleep;
49
+ /**
50
+ * Perform health check on all endpoints
51
+ */
52
+ healthCheck(): Promise<Record<string, boolean>>;
53
+ /**
54
+ * Start periodic health checks
55
+ */
56
+ startHealthChecks(): void;
57
+ /**
58
+ * Stop periodic health checks
59
+ */
60
+ stopHealthChecks(): void;
61
+ /**
62
+ * Get current status of all endpoints
63
+ */
64
+ getStatus(): Array<{
65
+ name: string;
66
+ url: string;
67
+ isHealthy: boolean;
68
+ failures: number;
69
+ }>;
70
+ }
71
+ export declare function getTranslatorLoadBalancer(): TranslatorLoadBalancer;
72
+ export declare const translatorLoadBalancer: TranslatorLoadBalancer;
73
+ export {};