@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,293 @@
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);
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
+ const response = await fetch(`${endpoint.url}/health`, {
218
+ method: 'GET',
219
+ signal: controller.signal,
220
+ });
221
+ const isHealthy = response.ok;
222
+ endpoint.isHealthy = isHealthy;
223
+ endpoint.lastHealthCheck = new Date();
224
+ if (isHealthy) {
225
+ endpoint.consecutiveFailures = 0;
226
+ // Reset the circuit breaker so subsequent calls don't fail immediately
227
+ const cb = this.circuitBreakers.get(endpoint.name);
228
+ if (cb) {
229
+ cb.reset();
230
+ logger_1.engineLogger.info('Circuit breaker reset after healthy health check', { endpoint: endpoint.name });
231
+ }
232
+ }
233
+ results[endpoint.name] = isHealthy;
234
+ }
235
+ catch {
236
+ endpoint.isHealthy = false;
237
+ endpoint.lastHealthCheck = new Date();
238
+ results[endpoint.name] = false;
239
+ }
240
+ finally {
241
+ clearTimeout(timeout);
242
+ }
243
+ }
244
+ return results;
245
+ }
246
+ /**
247
+ * Start periodic health checks
248
+ */
249
+ startHealthChecks() {
250
+ if (this.healthCheckInterval)
251
+ return;
252
+ this.healthCheckInterval = setInterval(() => this.healthCheck(), this.config.healthCheckInterval);
253
+ // Initial health check
254
+ this.healthCheck();
255
+ }
256
+ /**
257
+ * Stop periodic health checks
258
+ */
259
+ stopHealthChecks() {
260
+ if (this.healthCheckInterval) {
261
+ clearInterval(this.healthCheckInterval);
262
+ this.healthCheckInterval = null;
263
+ }
264
+ }
265
+ /**
266
+ * Get current status of all endpoints
267
+ */
268
+ getStatus() {
269
+ return this.config.endpoints.map(e => ({
270
+ name: e.name,
271
+ url: e.url.substring(0, 50) + (e.url.length > 50 ? '...' : ''),
272
+ isHealthy: e.isHealthy,
273
+ failures: e.consecutiveFailures,
274
+ }));
275
+ }
276
+ }
277
+ // Singleton instance
278
+ // Created on first use, not at import: the constructor reads TRANSLATOR_*
279
+ // env vars and starts health checks, and importing @babelize/engine must not
280
+ // do either — a host may load its env (dotenv, test setup) after the import.
281
+ let instance = null;
282
+ function getTranslatorLoadBalancer() {
283
+ if (!instance)
284
+ instance = new TranslatorLoadBalancer();
285
+ return instance;
286
+ }
287
+ exports.translatorLoadBalancer = new Proxy({}, {
288
+ get(_target, prop) {
289
+ const lb = getTranslatorLoadBalancer();
290
+ const value = Reflect.get(lb, prop, lb);
291
+ return typeof value === 'function' ? value.bind(lb) : value;
292
+ },
293
+ });
@@ -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;
@@ -0,0 +1,126 @@
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.DATA_KEYS = exports.UI_COPY_KEYS = void 0;
37
+ exports.isMultiWordCopy = isMultiWordCopy;
38
+ exports.looksLikeCopy = looksLikeCopy;
39
+ exports.isUiCopyForName = isUiCopyForName;
40
+ exports.isUiCopyLiteral = isUiCopyLiteral;
41
+ const t = __importStar(require("@babel/types"));
42
+ /**
43
+ * UI copy vs program data in object / array / variable initializers.
44
+ *
45
+ * `const user = { name: "Ada" }` is data: translating it rewrites a user's
46
+ * name (`{ name: t("page.ada") }`). `[{ label: "Home", href: "/" }]` holds UI
47
+ * copy under `label` and data under `href`. A string in an initializer is
48
+ * only extracted when its key says it's copy, or (without a telling key) when
49
+ * it looks like copy: a sentence, a Title Case phrase or a capitalized word.
50
+ * Both lists match the key's last camelCase word (`buttonLabel` → label,
51
+ * `firstName` → name) or the whole key, case-insensitively, and can be
52
+ * extended by callers.
53
+ */
54
+ exports.UI_COPY_KEYS = new Set([
55
+ 'label', 'title', 'description', 'placeholder', 'text', 'message', 'heading', 'subheading', 'headline', 'subtitle',
56
+ 'tooltip', 'cta', 'alt', 'arialabel', 'caption', 'hint', 'help', 'helptext', 'summary', 'body', 'question',
57
+ 'answer', 'notice', 'error', 'success', 'warning', 'confirm', 'prompt', 'greeting', 'tagline', 'blurb', 'excerpt',
58
+ ]);
59
+ exports.DATA_KEYS = new Set([
60
+ 'name', 'id', 'key', 'email', 'username', 'slug', 'url', 'href', 'src', 'path', 'type', 'value', 'code', 'locale',
61
+ 'lang', 'language', 'classname', 'class', 'variant', 'color', 'colour', 'icon', 'date', 'currency', 'size', 'status',
62
+ 'role', 'method', 'format', 'mode', 'theme', 'target', 'rel', 'route', 'to', 'link', 'ref', 'uuid', 'token',
63
+ 'phone', 'address', 'image', 'avatar', 'file', 'filename', 'extension', 'mime', 'kind', 'timezone', 'country',
64
+ 'handle', 'domain', 'host', 'endpoint', 'version', 'sku', 'hash', 'secret', 'password', 'field', 'event', 'action',
65
+ 'user', 'author', 'owner', 'customer', 'company', 'brand', 'city', 'state', 'zip', 'region',
66
+ ]);
67
+ function words(name) {
68
+ return name.split(/(?=[A-Z])|[-_\s]/).filter(Boolean).map((w) => w.toLowerCase());
69
+ }
70
+ /** Two or more words with letters: reads like copy, not an identifier. */
71
+ function isMultiWordCopy(text) {
72
+ return text.trim().split(/\s+/).filter((w) => /\p{L}/u.test(w)).length >= 2;
73
+ }
74
+ /**
75
+ * Copy without a key to say so: a sentence or Title Case phrase, or a single
76
+ * capitalized word ("Overview", "Settings"). Data looks different: lowercase
77
+ * tokens ("active"), kebab/snake/camelCase, ALLCAPS, or anything with digits,
78
+ * underscores, slashes or dots in a single token.
79
+ */
80
+ function looksLikeCopy(text) {
81
+ const trimmed = text.trim();
82
+ if (isMultiWordCopy(trimmed))
83
+ return true;
84
+ return /^\p{Lu}\p{Ll}+[!?.:…]?$/u.test(trimmed);
85
+ }
86
+ /** Whether a string stored under `name` (a key or a variable) is UI copy. */
87
+ function isUiCopyForName(name, text) {
88
+ if (name) {
89
+ const parts = words(name);
90
+ const last = parts[parts.length - 1] ?? '';
91
+ const full = name.toLowerCase().replace(/[-_]/g, '');
92
+ if (exports.UI_COPY_KEYS.has(last) || exports.UI_COPY_KEYS.has(full))
93
+ return true;
94
+ if (exports.DATA_KEYS.has(last) || exports.DATA_KEYS.has(full))
95
+ return false;
96
+ }
97
+ return looksLikeCopy(text);
98
+ }
99
+ function keyName(key) {
100
+ if (t.isIdentifier(key))
101
+ return key.name;
102
+ if (t.isStringLiteral(key))
103
+ return key.value;
104
+ return null;
105
+ }
106
+ /**
107
+ * For a string literal in an object property value, array element or
108
+ * variable/assignment initializer: is it UI copy? Other positions (JSX,
109
+ * returns, conditionals, …) aren't initializers and are left as they are.
110
+ */
111
+ function isUiCopyLiteral(node, parent, text) {
112
+ if (!parent)
113
+ return true;
114
+ if (t.isObjectProperty(parent) && parent.value === node)
115
+ return isUiCopyForName(parent.computed ? null : keyName(parent.key), text);
116
+ if (t.isVariableDeclarator(parent) && parent.init === node)
117
+ return isUiCopyForName(t.isIdentifier(parent.id) ? parent.id.name : null, text);
118
+ if (t.isAssignmentExpression(parent) && parent.right === node) {
119
+ const left = parent.left;
120
+ const name = t.isIdentifier(left) ? left.name : t.isMemberExpression(left) && !left.computed && t.isIdentifier(left.property) ? left.property.name : null;
121
+ return isUiCopyForName(name, text);
122
+ }
123
+ if (t.isArrayExpression(parent))
124
+ return looksLikeCopy(text);
125
+ return true;
126
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Remove stale locale files from the workspace that are NOT in the target locale list.
3
+ *
4
+ * When a project is re-run with changed languages, old locale files from previous
5
+ * runs persist because `git reset --hard` only resets tracked files and generated
6
+ * locale files are untracked. This function cleans them up.
7
+ *
8
+ * @param workspacePath - Path to the workspace directory
9
+ * @param targetLocales - Current list of target locale codes (e.g., ['fr', 'de'])
10
+ * @param sourceLocale - Source locale code (e.g., 'en') — always preserved
11
+ * @param localeFormat - Output format ('json' | 'po' | 'arb')
12
+ * @param publicDir - Public directory relative to workspace (e.g., 'public')
13
+ */
14
+ export declare function removeStaleLocales(workspacePath: string, targetLocales: string[], sourceLocale: string, localeFormat?: string, publicDir?: string): Promise<void>;
@@ -0,0 +1,89 @@
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.removeStaleLocales = removeStaleLocales;
37
+ const fs = __importStar(require("fs-extra"));
38
+ const path = __importStar(require("path"));
39
+ const file_utils_1 = require("./file-utils");
40
+ const logger_1 = require("./logger");
41
+ /**
42
+ * Remove stale locale files from the workspace that are NOT in the target locale list.
43
+ *
44
+ * When a project is re-run with changed languages, old locale files from previous
45
+ * runs persist because `git reset --hard` only resets tracked files and generated
46
+ * locale files are untracked. This function cleans them up.
47
+ *
48
+ * @param workspacePath - Path to the workspace directory
49
+ * @param targetLocales - Current list of target locale codes (e.g., ['fr', 'de'])
50
+ * @param sourceLocale - Source locale code (e.g., 'en') — always preserved
51
+ * @param localeFormat - Output format ('json' | 'po' | 'arb')
52
+ * @param publicDir - Public directory relative to workspace (e.g., 'public')
53
+ */
54
+ async function removeStaleLocales(workspacePath, targetLocales, sourceLocale, localeFormat = 'json', publicDir = 'public') {
55
+ // Django uses locale/<lang>/LC_MESSAGES/ — skip stale locale removal for PO format
56
+ if (localeFormat === 'po')
57
+ return;
58
+ const localesDir = path.join(workspacePath, publicDir, 'locales');
59
+ if (!(await fs.pathExists(localesDir))) {
60
+ return;
61
+ }
62
+ const ext = (0, file_utils_1.localeExtension)(localeFormat);
63
+ const allowedLocales = new Set([sourceLocale, ...targetLocales]);
64
+ try {
65
+ const entries = await fs.readdir(localesDir);
66
+ let removedCount = 0;
67
+ for (const entry of entries) {
68
+ if (!entry.endsWith(ext))
69
+ continue;
70
+ const localeCode = entry.replace(ext, '');
71
+ if (!allowedLocales.has(localeCode)) {
72
+ const filePath = path.join(localesDir, entry);
73
+ await fs.remove(filePath);
74
+ removedCount++;
75
+ logger_1.engineLogger.debug('Removed stale locale file', { file: entry, localeCode });
76
+ }
77
+ }
78
+ if (removedCount > 0) {
79
+ logger_1.engineLogger.info('Cleaned up stale locale files', {
80
+ removedCount,
81
+ keptLocales: [...allowedLocales],
82
+ workspacePath,
83
+ });
84
+ }
85
+ }
86
+ catch (err) {
87
+ logger_1.engineLogger.warn('Failed to clean up stale locale files', { error: err, workspacePath });
88
+ }
89
+ }
package/package.json CHANGED
@@ -1,6 +1,66 @@
1
1
  {
2
2
  "name": "@babelize/engine",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Babelize translation pipeline (extract, keygen, translate, inject). Infrastructure is injected through provider/store interfaces.",
5
+ "keywords": [
6
+ "babelize",
7
+ "i18n",
8
+ "localization",
9
+ "translation",
10
+ "ast"
11
+ ],
12
+ "license": "Apache-2.0",
13
+ "homepage": "https://babelize.co",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/babelize/babelize-dashboard.git"
17
+ },
18
+ "main": "./dist/index.js",
19
+ "types": "./dist/index.d.ts",
20
+ "files": [
21
+ "dist",
22
+ "schemas"
23
+ ],
24
+ "publishConfig": {
25
+ "access": "public"
26
+ },
27
+ "scripts": {
28
+ "build": "rm -rf dist && tsc -p tsconfig.build.json",
29
+ "typecheck": "tsc --noEmit",
30
+ "lint": "eslint src",
31
+ "test": "jest",
32
+ "prepublishOnly": "npm run build && npm test && npm run typecheck"
33
+ },
34
+ "dependencies": {
35
+ "@babel/generator": "^7.28.5",
36
+ "@babel/parser": "^7.28.5",
37
+ "@babel/traverse": "^7.28.5",
38
+ "@babel/types": "^7.29.8",
39
+ "@babelize/types": "^0.1.1",
40
+ "@vue/compiler-dom": "^3.5",
41
+ "@vue/compiler-sfc": "^3.5",
42
+ "diff": "^8.0.4",
43
+ "fast-glob": "^3.3.3",
44
+ "fs-extra": "^11.3.3",
45
+ "gettext-parser": "^9.1.1",
46
+ "magic-string": "^0.30",
47
+ "node-html-parser": "^8.0.4",
48
+ "zod": "^3.22.4"
49
+ },
50
+ "devDependencies": {
51
+ "@eslint/js": "^9.39.2",
52
+ "@jest/globals": "^30.3.0",
53
+ "@types/babel__generator": "^7.27.0",
54
+ "@types/babel__traverse": "^7.28.0",
55
+ "@types/fs-extra": "^11.0.4",
56
+ "@types/jest": "^30.0.0",
57
+ "@types/node": "^25.0.3",
58
+ "ajv": "^8.17.1",
59
+ "eslint": "^9.39.2",
60
+ "intl-messageformat": "^10.7.16",
61
+ "jest": "^30.2.0",
62
+ "ts-jest": "^29.4.6",
63
+ "typescript": "^5.9.3",
64
+ "typescript-eslint": "^8.53.1"
65
+ }
66
+ }