@babelize/engine 0.0.0-stage → 0.1.2

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 (270) 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/detection/stack.d.ts +42 -0
  11. package/dist/detection/stack.js +277 -0
  12. package/dist/django/catalog.d.ts +92 -0
  13. package/dist/django/catalog.js +677 -0
  14. package/dist/django/extract.d.ts +20 -0
  15. package/dist/django/extract.js +79 -0
  16. package/dist/django/inject-file.d.ts +6 -0
  17. package/dist/django/inject-file.js +52 -0
  18. package/dist/django/inject.d.ts +11 -0
  19. package/dist/django/inject.js +205 -0
  20. package/dist/django/keys.d.ts +21 -0
  21. package/dist/django/keys.js +63 -0
  22. package/dist/django/locale-names.d.ts +13 -0
  23. package/dist/django/locale-names.js +60 -0
  24. package/dist/django/mo-file.d.ts +6 -0
  25. package/dist/django/mo-file.js +60 -0
  26. package/dist/django/plural-forms.d.ts +34 -0
  27. package/dist/django/plural-forms.js +216 -0
  28. package/dist/django/po-file.d.ts +38 -0
  29. package/dist/django/po-file.js +273 -0
  30. package/dist/django/python-extract.d.ts +34 -0
  31. package/dist/django/python-extract.js +619 -0
  32. package/dist/django/python-lexer.d.ts +65 -0
  33. package/dist/django/python-lexer.js +398 -0
  34. package/dist/django/template-extract.d.ts +32 -0
  35. package/dist/django/template-extract.js +680 -0
  36. package/dist/django/types.d.ts +49 -0
  37. package/dist/django/types.js +15 -0
  38. package/dist/index.d.ts +59 -0
  39. package/dist/index.js +140 -0
  40. package/dist/injection/InjectionStrategy.d.ts +62 -0
  41. package/dist/injection/InjectionStrategy.js +2 -0
  42. package/dist/injection/StrategyFactory.d.ts +4 -0
  43. package/dist/injection/StrategyFactory.js +29 -0
  44. package/dist/injection/html/html-injector.d.ts +11 -0
  45. package/dist/injection/html/html-injector.js +173 -0
  46. package/dist/injection/strategies/NextIntlStrategy.d.ts +25 -0
  47. package/dist/injection/strategies/NextIntlStrategy.js +141 -0
  48. package/dist/injection/strategies/ReactI18nextStrategy.d.ts +19 -0
  49. package/dist/injection/strategies/ReactI18nextStrategy.js +116 -0
  50. package/dist/injection/strategies/VanillaStrategy.d.ts +11 -0
  51. package/dist/injection/strategies/VanillaStrategy.js +73 -0
  52. package/dist/injection/strategies/VueI18nStrategy.d.ts +11 -0
  53. package/dist/injection/strategies/VueI18nStrategy.js +84 -0
  54. package/dist/local/formatter.d.ts +26 -0
  55. package/dist/local/formatter.js +105 -0
  56. package/dist/local/git.d.ts +23 -0
  57. package/dist/local/git.js +136 -0
  58. package/dist/local/hosted-provider.d.ts +68 -0
  59. package/dist/local/hosted-provider.js +215 -0
  60. package/dist/local/index.d.ts +23 -0
  61. package/dist/local/index.js +75 -0
  62. package/dist/local/provider.d.ts +56 -0
  63. package/dist/local/provider.js +119 -0
  64. package/dist/local/pseudo-provider.d.ts +18 -0
  65. package/dist/local/pseudo-provider.js +208 -0
  66. package/dist/local/report.d.ts +177 -0
  67. package/dist/local/report.js +186 -0
  68. package/dist/local/translate-core.d.ts +218 -0
  69. package/dist/local/translate-core.js +499 -0
  70. package/dist/local/translate-messages.d.ts +49 -0
  71. package/dist/local/translate-messages.js +89 -0
  72. package/dist/local/workspace-copy.d.ts +91 -0
  73. package/dist/local/workspace-copy.js +355 -0
  74. package/dist/modules/extractor/extract.d.ts +20 -0
  75. package/dist/modules/extractor/extract.js +709 -0
  76. package/dist/modules/extractor/index.d.ts +2 -0
  77. package/dist/modules/extractor/index.js +63 -0
  78. package/dist/modules/finalization/django-finalize.d.ts +28 -0
  79. package/dist/modules/finalization/django-finalize.js +412 -0
  80. package/dist/modules/finalization/finalize.d.ts +38 -0
  81. package/dist/modules/finalization/finalize.js +745 -0
  82. package/dist/modules/finalization/index.d.ts +1 -0
  83. package/dist/modules/finalization/index.js +5 -0
  84. package/dist/modules/finalization/nextjs-finalize.d.ts +40 -0
  85. package/dist/modules/finalization/nextjs-finalize.js +328 -0
  86. package/dist/modules/finalization/static-runtime.d.ts +9 -0
  87. package/dist/modules/finalization/static-runtime.js +261 -0
  88. package/dist/modules/finalization/writes.d.ts +29 -0
  89. package/dist/modules/finalization/writes.js +78 -0
  90. package/dist/modules/format-injection/index.d.ts +19 -0
  91. package/dist/modules/format-injection/index.js +273 -0
  92. package/dist/modules/format-injection/inject-formats.d.ts +31 -0
  93. package/dist/modules/format-injection/inject-formats.js +208 -0
  94. package/dist/modules/glossary/index.d.ts +2 -0
  95. package/dist/modules/glossary/index.js +16 -0
  96. package/dist/modules/glossary/rules.d.ts +81 -0
  97. package/dist/modules/glossary/rules.js +244 -0
  98. package/dist/modules/glossary/types.d.ts +22 -0
  99. package/dist/modules/glossary/types.js +2 -0
  100. package/dist/modules/injection/index.d.ts +1 -0
  101. package/dist/modules/injection/index.js +6 -0
  102. package/dist/modules/injection/inject.d.ts +29 -0
  103. package/dist/modules/injection/inject.js +1050 -0
  104. package/dist/modules/keygen/index.d.ts +1 -0
  105. package/dist/modules/keygen/index.js +11 -0
  106. package/dist/modules/keygen/keygen.d.ts +4 -0
  107. package/dist/modules/keygen/keygen.js +123 -0
  108. package/dist/modules/keygen/locale-data.d.ts +7 -0
  109. package/dist/modules/keygen/locale-data.js +73 -0
  110. package/dist/modules/keygen/public.d.ts +2 -0
  111. package/dist/modules/keygen/public.js +12 -0
  112. package/dist/modules/locale-builder/index.d.ts +1 -0
  113. package/dist/modules/locale-builder/index.js +7 -0
  114. package/dist/modules/locale-builder/locales.d.ts +13 -0
  115. package/dist/modules/locale-builder/locales.js +130 -0
  116. package/dist/modules/lockfile/hasher.d.ts +7 -0
  117. package/dist/modules/lockfile/hasher.js +13 -0
  118. package/dist/modules/lockfile/index.d.ts +3 -0
  119. package/dist/modules/lockfile/index.js +22 -0
  120. package/dist/modules/lockfile/lockfile-manager.d.ts +99 -0
  121. package/dist/modules/lockfile/lockfile-manager.js +497 -0
  122. package/dist/modules/lockfile/schema.d.ts +196 -0
  123. package/dist/modules/lockfile/schema.js +56 -0
  124. package/dist/modules/parser/index.d.ts +2 -0
  125. package/dist/modules/parser/index.js +9 -0
  126. package/dist/modules/summary/index.d.ts +1 -0
  127. package/dist/modules/summary/index.js +7 -0
  128. package/dist/modules/summary/summary.d.ts +9 -0
  129. package/dist/modules/summary/summary.js +195 -0
  130. package/dist/modules/sync-detector/index.d.ts +2 -0
  131. package/dist/modules/sync-detector/index.js +7 -0
  132. package/dist/modules/sync-detector/reporter.d.ts +3 -0
  133. package/dist/modules/sync-detector/reporter.js +16 -0
  134. package/dist/modules/sync-detector/sync-detector.d.ts +39 -0
  135. package/dist/modules/sync-detector/sync-detector.js +374 -0
  136. package/dist/modules/translator/batch-optimizer.d.ts +12 -0
  137. package/dist/modules/translator/batch-optimizer.js +30 -0
  138. package/dist/modules/translator/client/byok.provider.d.ts +64 -0
  139. package/dist/modules/translator/client/byok.provider.js +319 -0
  140. package/dist/modules/translator/client/direct-translator.client.d.ts +12 -0
  141. package/dist/modules/translator/client/direct-translator.client.js +71 -0
  142. package/dist/modules/translator/client/gemini.provider.d.ts +1 -0
  143. package/dist/modules/translator/client/gemini.provider.js +110 -0
  144. package/dist/modules/translator/client/groq.provider.d.ts +1 -0
  145. package/dist/modules/translator/client/groq.provider.js +111 -0
  146. package/dist/modules/translator/client/request.types.d.ts +1 -0
  147. package/dist/modules/translator/client/request.types.js +2 -0
  148. package/dist/modules/translator/client/retry.d.ts +29 -0
  149. package/dist/modules/translator/client/retry.js +94 -0
  150. package/dist/modules/translator/client/test-utils/mock.client.d.ts +31 -0
  151. package/dist/modules/translator/client/test-utils/mock.client.js +59 -0
  152. package/dist/modules/translator/client/translator.interface.d.ts +1 -0
  153. package/dist/modules/translator/client/translator.interface.js +2 -0
  154. package/dist/modules/translator/icu-branches.d.ts +138 -0
  155. package/dist/modules/translator/icu-branches.js +393 -0
  156. package/dist/modules/translator/language-codes/errors.d.ts +10 -0
  157. package/dist/modules/translator/language-codes/errors.js +18 -0
  158. package/dist/modules/translator/language-codes/flores-map.d.ts +1 -0
  159. package/dist/modules/translator/language-codes/flores-map.js +107 -0
  160. package/dist/modules/translator/language-codes/index.d.ts +11 -0
  161. package/dist/modules/translator/language-codes/index.js +24 -0
  162. package/dist/modules/translator/language-codes/language-mapper.d.ts +25 -0
  163. package/dist/modules/translator/language-codes/language-mapper.js +132 -0
  164. package/dist/modules/translator/language-codes/rtl-languages.d.ts +2 -0
  165. package/dist/modules/translator/language-codes/rtl-languages.js +9 -0
  166. package/dist/modules/translator/memory/memory-matcher.d.ts +8 -0
  167. package/dist/modules/translator/memory/memory-matcher.js +218 -0
  168. package/dist/modules/translator/memory/types.d.ts +22 -0
  169. package/dist/modules/translator/memory/types.js +2 -0
  170. package/dist/modules/translator/translate.d.ts +74 -0
  171. package/dist/modules/translator/translate.js +1047 -0
  172. package/dist/modules/vue/migrate.d.ts +13 -0
  173. package/dist/modules/vue/migrate.js +40 -0
  174. package/dist/modules/vue/script-inject.d.ts +16 -0
  175. package/dist/modules/vue/script-inject.js +115 -0
  176. package/dist/modules/vue/sfc.d.ts +11 -0
  177. package/dist/modules/vue/sfc.js +51 -0
  178. package/dist/modules/vue/template-rewrite.d.ts +25 -0
  179. package/dist/modules/vue/template-rewrite.js +76 -0
  180. package/dist/modules/vue/template-scan.d.ts +29 -0
  181. package/dist/modules/vue/template-scan.js +264 -0
  182. package/dist/modules/workspace/index.d.ts +1 -0
  183. package/dist/modules/workspace/index.js +5 -0
  184. package/dist/modules/workspace/validate.d.ts +13 -0
  185. package/dist/modules/workspace/validate.js +333 -0
  186. package/dist/orchestrator.d.ts +13 -0
  187. package/dist/orchestrator.js +727 -0
  188. package/dist/providers/default-translation-provider.d.ts +20 -0
  189. package/dist/providers/default-translation-provider.js +30 -0
  190. package/dist/providers/index.d.ts +2 -0
  191. package/dist/providers/index.js +21 -0
  192. package/dist/providers/types.d.ts +153 -0
  193. package/dist/providers/types.js +2 -0
  194. package/dist/qa/check-project.d.ts +50 -0
  195. package/dist/qa/check-project.js +193 -0
  196. package/dist/qa/gettext-files.d.ts +15 -0
  197. package/dist/qa/gettext-files.js +115 -0
  198. package/dist/qa/locale-files.d.ts +44 -0
  199. package/dist/qa/locale-files.js +159 -0
  200. package/dist/qa/locale-qa.d.ts +61 -0
  201. package/dist/qa/locale-qa.js +342 -0
  202. package/dist/qa/review.d.ts +43 -0
  203. package/dist/qa/review.js +175 -0
  204. package/dist/runtime/config.d.ts +8 -0
  205. package/dist/runtime/config.js +37 -0
  206. package/dist/runtime/index.d.ts +3 -0
  207. package/dist/runtime/index.js +8 -0
  208. package/dist/runtime/types.d.ts +427 -0
  209. package/dist/runtime/types.js +38 -0
  210. package/dist/types.d.ts +134 -0
  211. package/dist/types.js +12 -0
  212. package/dist/utils/ast-helpers.d.ts +57 -0
  213. package/dist/utils/ast-helpers.js +608 -0
  214. package/dist/utils/cache-keys.d.ts +8 -0
  215. package/dist/utils/cache-keys.js +18 -0
  216. package/dist/utils/circuit-breaker.d.ts +43 -0
  217. package/dist/utils/circuit-breaker.js +119 -0
  218. package/dist/utils/concurrent-map.d.ts +4 -0
  219. package/dist/utils/concurrent-map.js +21 -0
  220. package/dist/utils/detect-format.d.ts +5 -0
  221. package/dist/utils/detect-format.js +125 -0
  222. package/dist/utils/extraction-options.d.ts +29 -0
  223. package/dist/utils/extraction-options.js +33 -0
  224. package/dist/utils/file-utils.d.ts +33 -0
  225. package/dist/utils/file-utils.js +267 -0
  226. package/dist/utils/glossary-match.d.ts +9 -0
  227. package/dist/utils/glossary-match.js +25 -0
  228. package/dist/utils/html-extractor.d.ts +8 -0
  229. package/dist/utils/html-extractor.js +317 -0
  230. package/dist/utils/i18next-icu.d.ts +38 -0
  231. package/dist/utils/i18next-icu.js +159 -0
  232. package/dist/utils/icu-message.d.ts +65 -0
  233. package/dist/utils/icu-message.js +346 -0
  234. package/dist/utils/icu-spans.d.ts +10 -0
  235. package/dist/utils/icu-spans.js +126 -0
  236. package/dist/utils/jsx-message.d.ts +93 -0
  237. package/dist/utils/jsx-message.js +628 -0
  238. package/dist/utils/key-usage.d.ts +35 -0
  239. package/dist/utils/key-usage.js +247 -0
  240. package/dist/utils/logger.d.ts +24 -0
  241. package/dist/utils/logger.js +33 -0
  242. package/dist/utils/metadata.d.ts +82 -0
  243. package/dist/utils/metadata.js +261 -0
  244. package/dist/utils/module-constants.d.ts +30 -0
  245. package/dist/utils/module-constants.js +265 -0
  246. package/dist/utils/not-injected.d.ts +6 -0
  247. package/dist/utils/not-injected.js +38 -0
  248. package/dist/utils/placeholder-protection.d.ts +74 -0
  249. package/dist/utils/placeholder-protection.js +181 -0
  250. package/dist/utils/pseudo-markers.d.ts +9 -0
  251. package/dist/utils/pseudo-markers.js +16 -0
  252. package/dist/utils/string-utils.d.ts +52 -0
  253. package/dist/utils/string-utils.js +270 -0
  254. package/dist/utils/translation-quality.d.ts +34 -0
  255. package/dist/utils/translation-quality.js +156 -0
  256. package/dist/utils/translation-sanitizer.d.ts +11 -0
  257. package/dist/utils/translation-sanitizer.js +71 -0
  258. package/dist/utils/translator-caller.d.ts +32 -0
  259. package/dist/utils/translator-caller.js +217 -0
  260. package/dist/utils/translator-load-balancer.d.ts +73 -0
  261. package/dist/utils/translator-load-balancer.js +297 -0
  262. package/dist/utils/ui-copy.d.ts +32 -0
  263. package/dist/utils/ui-copy.js +126 -0
  264. package/dist/utils/untranslated.d.ts +15 -0
  265. package/dist/utils/untranslated.js +34 -0
  266. package/dist/utils/workspace.d.ts +14 -0
  267. package/dist/utils/workspace.js +89 -0
  268. package/package.json +64 -4
  269. package/schemas/translate-report.schema.json +297 -0
  270. package/README.md +0 -3
@@ -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 {};
@@ -0,0 +1,297 @@
1
+ "use strict";
2
+ /**
3
+ * Translator Load Balancer
4
+ *
5
+ * Manages multiple translator endpoints with:
6
+ * - Health checking
7
+ * - Round-robin load balancing
8
+ * - Automatic failover on errors
9
+ * - Circuit breaker per endpoint
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.translatorLoadBalancer = void 0;
13
+ exports.getTranslatorLoadBalancer = getTranslatorLoadBalancer;
14
+ const circuit_breaker_1 = require("./circuit-breaker");
15
+ const logger_1 = require("./logger");
16
+ const DEFAULT_CONFIG = {
17
+ endpoints: [],
18
+ healthCheckInterval: 60000, // 1 minute
19
+ healthCheckTimeout: 10000, // 10 seconds
20
+ maxConsecutiveFailures: 3,
21
+ };
22
+ class TranslatorLoadBalancer {
23
+ constructor() {
24
+ this.circuitBreakers = new Map();
25
+ this.healthCheckInterval = null;
26
+ this.currentEndpointIndex = 0;
27
+ this.config = { ...DEFAULT_CONFIG };
28
+ this.initializeEndpoints();
29
+ if (this.config.endpoints.length > 0) {
30
+ setTimeout(() => this.startHealthChecks(), 0).unref?.();
31
+ }
32
+ }
33
+ /**
34
+ * Initialize endpoints from environment variable
35
+ * Format: url1|name1,url2|name2 or just url1,url2
36
+ */
37
+ initializeEndpoints() {
38
+ const translatorUrl = process.env.TRANSLATOR_URL;
39
+ const backupUrl = process.env.TRANSLATOR_BACKUP_URL;
40
+ const endpoints = [];
41
+ // Primary endpoint
42
+ if (translatorUrl) {
43
+ endpoints.push({
44
+ url: translatorUrl,
45
+ name: 'primary',
46
+ priority: 1,
47
+ isHealthy: true,
48
+ lastHealthCheck: null,
49
+ consecutiveFailures: 0,
50
+ });
51
+ }
52
+ // Backup endpoint
53
+ if (backupUrl) {
54
+ endpoints.push({
55
+ url: backupUrl,
56
+ name: 'backup',
57
+ priority: 2,
58
+ isHealthy: true,
59
+ lastHealthCheck: null,
60
+ consecutiveFailures: 0,
61
+ });
62
+ }
63
+ // Parse additional endpoints from TRANSLATOR_ENDPOINTS env var
64
+ // Format: url1|name1,url2|name2
65
+ const additionalEndpoints = process.env.TRANSLATOR_ENDPOINTS;
66
+ if (additionalEndpoints) {
67
+ additionalEndpoints.split(',').forEach((entry, index) => {
68
+ const [url, name] = entry.trim().split('|');
69
+ if (url) {
70
+ endpoints.push({
71
+ url: url.trim(),
72
+ name: name?.trim() || `endpoint-${index + 3}`,
73
+ priority: index + 3,
74
+ isHealthy: true,
75
+ lastHealthCheck: null,
76
+ consecutiveFailures: 0,
77
+ });
78
+ }
79
+ });
80
+ }
81
+ this.config.endpoints = endpoints;
82
+ // Create circuit breaker for each endpoint
83
+ endpoints.forEach(endpoint => {
84
+ this.circuitBreakers.set(endpoint.name, (0, circuit_breaker_1.getCircuitBreaker)(`translator-${endpoint.name}`, {
85
+ failureThreshold: 5,
86
+ resetTimeout: 30000,
87
+ halfOpenMaxCalls: 3,
88
+ }));
89
+ });
90
+ logger_1.engineLogger.info('Translator load balancer initialized', {
91
+ endpointCount: endpoints.length,
92
+ endpoints: endpoints.map(e => ({ name: e.name, url: e.url.substring(0, 50) })),
93
+ });
94
+ }
95
+ /**
96
+ * Get the next healthy endpoint using weighted round-robin
97
+ */
98
+ getNextEndpoint() {
99
+ const healthyEndpoints = this.config.endpoints
100
+ .filter(e => e.isHealthy)
101
+ .sort((a, b) => a.priority - b.priority);
102
+ if (healthyEndpoints.length === 0) {
103
+ // All endpoints unhealthy, try primary anyway
104
+ if (this.config.endpoints.length > 0) {
105
+ logger_1.engineLogger.warn('All translator endpoints unhealthy, attempting primary');
106
+ return this.config.endpoints[0];
107
+ }
108
+ return null;
109
+ }
110
+ // Round-robin among healthy endpoints
111
+ const endpoint = healthyEndpoints[this.currentEndpointIndex % healthyEndpoints.length];
112
+ this.currentEndpointIndex++;
113
+ return endpoint;
114
+ }
115
+ /**
116
+ * Mark an endpoint as failed
117
+ */
118
+ markEndpointFailed(endpoint) {
119
+ endpoint.consecutiveFailures++;
120
+ if (endpoint.consecutiveFailures >= this.config.maxConsecutiveFailures) {
121
+ endpoint.isHealthy = false;
122
+ logger_1.engineLogger.warn('Translator endpoint marked unhealthy', {
123
+ name: endpoint.name,
124
+ failures: endpoint.consecutiveFailures,
125
+ });
126
+ }
127
+ }
128
+ /**
129
+ * Mark an endpoint as successful
130
+ */
131
+ markEndpointSuccess(endpoint) {
132
+ if (!endpoint.isHealthy || endpoint.consecutiveFailures > 0) {
133
+ logger_1.engineLogger.info('Translator endpoint recovered', { name: endpoint.name });
134
+ }
135
+ endpoint.consecutiveFailures = 0;
136
+ endpoint.isHealthy = true;
137
+ }
138
+ /**
139
+ * Execute a translation request with automatic failover and exponential backoff
140
+ */
141
+ async executeWithFailover(requestFn) {
142
+ const maxAttempts = parseInt(process.env.TRANSLATOR_RETRY_ATTEMPTS || '2');
143
+ const triedEndpoints = [];
144
+ let lastError = null;
145
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
146
+ const endpoint = this.getNextEndpoint();
147
+ if (!endpoint) {
148
+ // No endpoints available
149
+ break;
150
+ }
151
+ if (triedEndpoints.includes(endpoint.name)) {
152
+ // If we've already tried this endpoint, allow retry only when
153
+ // it's the only endpoint available (otherwise cycle to next)
154
+ const healthyEndpoints = this.config.endpoints.filter(e => e.isHealthy);
155
+ if (healthyEndpoints.length > 1) {
156
+ continue; // Skip to next round-robin pick
157
+ }
158
+ // Single endpoint — retrying it is our only option
159
+ }
160
+ if (!triedEndpoints.includes(endpoint.name)) {
161
+ triedEndpoints.push(endpoint.name);
162
+ }
163
+ const circuitBreaker = this.circuitBreakers.get(endpoint.name);
164
+ try {
165
+ const result = await (circuitBreaker
166
+ ? circuitBreaker.execute(() => requestFn(endpoint.url))
167
+ : requestFn(endpoint.url));
168
+ this.markEndpointSuccess(endpoint);
169
+ return result;
170
+ }
171
+ catch (error) {
172
+ lastError = error instanceof Error ? error : new Error(String(error));
173
+ this.markEndpointFailed(endpoint);
174
+ logger_1.engineLogger.warn('Translator endpoint failed, trying fallback', {
175
+ endpoint: endpoint.name,
176
+ error: lastError.message,
177
+ attempt: attempt + 1,
178
+ maxAttempts,
179
+ });
180
+ // Exponential backoff with jitter before next attempt
181
+ // Only delay if we have more attempts remaining
182
+ if (attempt < maxAttempts - 1) {
183
+ const baseDelay = Math.min(200 * Math.pow(2, attempt), 3000); // Max 3s
184
+ const jitter = Math.random() * 200; // 0-200ms jitter
185
+ const delay = baseDelay + jitter;
186
+ logger_1.engineLogger.info('Waiting before retry', {
187
+ delayMs: Math.round(delay),
188
+ nextAttempt: attempt + 2
189
+ });
190
+ await this.sleep(delay);
191
+ }
192
+ }
193
+ }
194
+ const endpointsTried = triedEndpoints.length > 0
195
+ ? `tried endpoints: [${triedEndpoints.join(', ')}]`
196
+ : 'no endpoints available';
197
+ throw lastError
198
+ ? new Error(`Translator request failed after ${maxAttempts} attempts (${endpointsTried}): ${lastError.message}`)
199
+ : new Error(`All translator endpoints failed (${endpointsTried})`);
200
+ }
201
+ /**
202
+ * Sleep utility
203
+ */
204
+ sleep(ms) {
205
+ return new Promise(resolve => setTimeout(resolve, ms));
206
+ }
207
+ /**
208
+ * Perform health check on all endpoints
209
+ */
210
+ async healthCheck() {
211
+ const results = {};
212
+ for (const endpoint of this.config.endpoints) {
213
+ let timeout;
214
+ try {
215
+ const controller = new AbortController();
216
+ timeout = setTimeout(() => controller.abort(), this.config.healthCheckTimeout);
217
+ timeout.unref?.();
218
+ const response = await fetch(`${endpoint.url}/health`, {
219
+ method: 'GET',
220
+ signal: controller.signal,
221
+ });
222
+ const isHealthy = response.ok;
223
+ endpoint.isHealthy = isHealthy;
224
+ endpoint.lastHealthCheck = new Date();
225
+ if (isHealthy) {
226
+ endpoint.consecutiveFailures = 0;
227
+ // Reset the circuit breaker so subsequent calls don't fail immediately
228
+ const cb = this.circuitBreakers.get(endpoint.name);
229
+ if (cb) {
230
+ cb.reset();
231
+ logger_1.engineLogger.info('Circuit breaker reset after healthy health check', { endpoint: endpoint.name });
232
+ }
233
+ }
234
+ results[endpoint.name] = isHealthy;
235
+ }
236
+ catch {
237
+ endpoint.isHealthy = false;
238
+ endpoint.lastHealthCheck = new Date();
239
+ results[endpoint.name] = false;
240
+ }
241
+ finally {
242
+ clearTimeout(timeout);
243
+ }
244
+ }
245
+ return results;
246
+ }
247
+ /**
248
+ * Start periodic health checks
249
+ */
250
+ startHealthChecks() {
251
+ if (this.healthCheckInterval)
252
+ return;
253
+ this.healthCheckInterval = setInterval(() => this.healthCheck(), this.config.healthCheckInterval);
254
+ // Background monitoring must not keep the process alive: short-lived
255
+ // hosts (scripts, the local runner) would otherwise never exit.
256
+ this.healthCheckInterval.unref?.();
257
+ // Initial health check
258
+ this.healthCheck();
259
+ }
260
+ /**
261
+ * Stop periodic health checks
262
+ */
263
+ stopHealthChecks() {
264
+ if (this.healthCheckInterval) {
265
+ clearInterval(this.healthCheckInterval);
266
+ this.healthCheckInterval = null;
267
+ }
268
+ }
269
+ /**
270
+ * Get current status of all endpoints
271
+ */
272
+ getStatus() {
273
+ return this.config.endpoints.map(e => ({
274
+ name: e.name,
275
+ url: e.url.substring(0, 50) + (e.url.length > 50 ? '...' : ''),
276
+ isHealthy: e.isHealthy,
277
+ failures: e.consecutiveFailures,
278
+ }));
279
+ }
280
+ }
281
+ // Singleton instance
282
+ // Created on first use, not at import: the constructor reads TRANSLATOR_*
283
+ // env vars and starts health checks, and importing @babelize/engine must not
284
+ // do either — a host may load its env (dotenv, test setup) after the import.
285
+ let instance = null;
286
+ function getTranslatorLoadBalancer() {
287
+ if (!instance)
288
+ instance = new TranslatorLoadBalancer();
289
+ return instance;
290
+ }
291
+ exports.translatorLoadBalancer = new Proxy({}, {
292
+ get(_target, prop) {
293
+ const lb = getTranslatorLoadBalancer();
294
+ const value = Reflect.get(lb, prop, lb);
295
+ return typeof value === 'function' ? value.bind(lb) : value;
296
+ },
297
+ });
@@ -0,0 +1,32 @@
1
+ import * as t from '@babel/types';
2
+ /**
3
+ * UI copy vs program data in object / array / variable initializers.
4
+ *
5
+ * `const user = { name: "Ada" }` is data: translating it rewrites a user's
6
+ * name (`{ name: t("page.ada") }`). `[{ label: "Home", href: "/" }]` holds UI
7
+ * copy under `label` and data under `href`. A string in an initializer is
8
+ * only extracted when its key says it's copy, or (without a telling key) when
9
+ * it looks like copy: a sentence, a Title Case phrase or a capitalized word.
10
+ * Both lists match the key's last camelCase word (`buttonLabel` → label,
11
+ * `firstName` → name) or the whole key, case-insensitively, and can be
12
+ * extended by callers.
13
+ */
14
+ export declare const UI_COPY_KEYS: Set<string>;
15
+ export declare const DATA_KEYS: Set<string>;
16
+ /** Two or more words with letters: reads like copy, not an identifier. */
17
+ export declare function isMultiWordCopy(text: string): boolean;
18
+ /**
19
+ * Copy without a key to say so: a sentence or Title Case phrase, or a single
20
+ * capitalized word ("Overview", "Settings"). Data looks different: lowercase
21
+ * tokens ("active"), kebab/snake/camelCase, ALLCAPS, or anything with digits,
22
+ * underscores, slashes or dots in a single token.
23
+ */
24
+ export declare function looksLikeCopy(text: string): boolean;
25
+ /** Whether a string stored under `name` (a key or a variable) is UI copy. */
26
+ export declare function isUiCopyForName(name: string | null, text: string): boolean;
27
+ /**
28
+ * For a string literal in an object property value, array element or
29
+ * variable/assignment initializer: is it UI copy? Other positions (JSX,
30
+ * returns, conditionals, …) aren't initializers and are left as they are.
31
+ */
32
+ export declare function isUiCopyLiteral(node: t.Node, parent: t.Node | null | undefined, text: string): boolean;