@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,270 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SKIP_TAGS = void 0;
4
+ exports.normalizeString = normalizeString;
5
+ exports.toCamelCase = toCamelCase;
6
+ exports.inferNamespace = inferNamespace;
7
+ exports.generateUniqueKey = generateUniqueKey;
8
+ exports.truncateForKey = truncateForKey;
9
+ exports.isTranslatableText = isTranslatableText;
10
+ exports.extractPlaceholders = extractPlaceholders;
11
+ exports.protectPlaceholders = protectPlaceholders;
12
+ exports.restorePlaceholders = restorePlaceholders;
13
+ exports.isLikelyPlural = isLikelyPlural;
14
+ exports.hasICUSyntax = hasICUSyntax;
15
+ exports.escapeRegex = escapeRegex;
16
+ exports.hashString = hashString;
17
+ exports.matchesCurrencyPattern = matchesCurrencyPattern;
18
+ exports.matchesDatePattern = matchesDatePattern;
19
+ exports.containsPII = containsPII;
20
+ exports.extractBalancedTag = extractBalancedTag;
21
+ const node_crypto_1 = require("node:crypto");
22
+ /**
23
+ * Engine v2 string utilities.
24
+ *
25
+ * Copied from v1 to preserve keygen/normalization behavior.
26
+ */
27
+ function normalizeString(text) {
28
+ return text
29
+ .toLowerCase()
30
+ .replace(/[^\p{L}\p{N}\s']/gu, ' ')
31
+ .replace(/\s+/g, ' ')
32
+ .trim();
33
+ }
34
+ function toCamelCase(text) {
35
+ const normalized = normalizeString(text);
36
+ const words = normalized.split(' ').filter(Boolean);
37
+ if (words.length === 0)
38
+ return 'untitled';
39
+ const result = words
40
+ .map((word, index) => {
41
+ word = word.replace(/'/g, '');
42
+ if (index === 0)
43
+ return word.toLowerCase();
44
+ return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
45
+ })
46
+ .join('');
47
+ if (!/[a-zA-Z]/.test(result))
48
+ return 'untitled';
49
+ return result;
50
+ }
51
+ function inferNamespace(filePath) {
52
+ const normalizedPath = filePath.replace(/\\/g, '/');
53
+ const pathWithoutExt = normalizedPath.replace(/\.(ts|tsx|js|jsx)$/, '');
54
+ const parts = pathWithoutExt.split('/').filter(Boolean);
55
+ const prefixesToRemove = ['src', 'app', 'pages', 'components', 'features', 'modules'];
56
+ while (parts.length > 0 && prefixesToRemove.includes(parts[0].toLowerCase())) {
57
+ parts.shift();
58
+ }
59
+ const suffixesToRemove = ['index', 'page', 'component', 'container', 'view'];
60
+ while (parts.length > 1 && suffixesToRemove.includes(parts[parts.length - 1].toLowerCase())) {
61
+ parts.pop();
62
+ }
63
+ if (parts.length === 0)
64
+ return 'common';
65
+ const namespace = parts[parts.length - 1]
66
+ .replace(/([A-Z])/g, (match, p1, offset) => (offset > 0 ? '-' + p1 : p1))
67
+ .toLowerCase()
68
+ .replace(/^-/, '');
69
+ return namespace.replace(/[^a-z0-9-]/g, '') || 'common';
70
+ }
71
+ function generateUniqueKey(baseKey, existingKeys) {
72
+ if (!existingKeys.has(baseKey))
73
+ return baseKey;
74
+ let counter = 2;
75
+ let uniqueKey = `${baseKey}_${counter}`;
76
+ while (existingKeys.has(uniqueKey)) {
77
+ counter++;
78
+ uniqueKey = `${baseKey}_${counter}`;
79
+ }
80
+ return uniqueKey;
81
+ }
82
+ function truncateForKey(text, maxWords = 5) {
83
+ const words = text.split(/\s+/).filter(Boolean);
84
+ if (words.length <= maxWords)
85
+ return text;
86
+ return words.slice(0, maxWords).join(' ');
87
+ }
88
+ function isTranslatableText(text) {
89
+ const trimmed = text.trim();
90
+ if (!trimmed)
91
+ return false;
92
+ if (/^\s*$/.test(trimmed))
93
+ return false;
94
+ if (/^\d+$/.test(trimmed))
95
+ return false;
96
+ if (/^[^\p{L}\p{N}\s]+$/u.test(trimmed))
97
+ return false;
98
+ if (trimmed.length < 2)
99
+ return false;
100
+ if (/^\{.*\}$/.test(trimmed))
101
+ return false;
102
+ if (/^(https?:\/\/|\/|\.\/|\.\.\/)/.test(trimmed))
103
+ return false;
104
+ if (/^[.#][a-zA-Z]/.test(trimmed))
105
+ return false;
106
+ if (/^\p{Ll}[\p{L}\p{N}_]*$/u.test(trimmed) && trimmed.length > 20)
107
+ return false;
108
+ return /[\p{L}]/u.test(trimmed);
109
+ }
110
+ function extractPlaceholders(text) {
111
+ // Use a more specific regex that only matches simple variable names to avoid greedy ICU matching
112
+ // or use non-greedy matching if we expect alphanumeric+underscore names.
113
+ const matches = text.match(/\{(\w+)\}/g);
114
+ if (!matches)
115
+ return [];
116
+ return matches.map((m) => m.slice(1, -1));
117
+ }
118
+ /**
119
+ * Protect placeholders by replacing them with AI-safe tokens before translation
120
+ */
121
+ function protectPlaceholders(text) {
122
+ // If it's complex ICU syntax, we don't protect simple placeholders as they might be part of the ICU structure
123
+ // This avoids destroying nested braces like {count, plural, one {# item} other {# items}}
124
+ if (hasICUSyntax(text)) {
125
+ return { protectedText: text, vars: [] };
126
+ }
127
+ const vars = [];
128
+ let protectedText = text;
129
+ // Refined regex to only match simple alphanumeric variable names in braces
130
+ const regex = /\{(\w+)\}/g;
131
+ let i = 0;
132
+ protectedText = text.replace(regex, (match, varName) => {
133
+ vars.push(varName);
134
+ const token = `[#VAR_${i}#]`;
135
+ i++;
136
+ return token;
137
+ });
138
+ return { protectedText, vars };
139
+ }
140
+ /**
141
+ * Restore placeholders from AI-safe tokens after translation
142
+ * Returns a diagnostic result to detect dropped variables
143
+ */
144
+ function restorePlaceholders(text, vars) {
145
+ let restored = text;
146
+ let restoredCount = 0;
147
+ const missingTokens = [];
148
+ for (let i = 0; i < vars.length; i++) {
149
+ const regex = new RegExp(`\\[\\s*#\\s*VAR_${i}\\s*#\\s*\\]`, 'gi');
150
+ const before = restored;
151
+ restored = restored.replace(regex, `{${vars[i]}}`);
152
+ if (restored !== before) {
153
+ restoredCount++;
154
+ }
155
+ else {
156
+ missingTokens.push(`[#VAR_${i}#]`);
157
+ }
158
+ }
159
+ return {
160
+ restoredText: restored,
161
+ restoredCount,
162
+ expectedCount: vars.length,
163
+ missingTokens
164
+ };
165
+ }
166
+ /**
167
+ * Check if text text and associated variables likely represent a pluralized string
168
+ * (e.g. "count files", "Total {length}")
169
+ */
170
+ function isLikelyPlural(text, variables = []) {
171
+ const lowerText = text.toLowerCase().trim();
172
+ // Exclude question strings — plural generation produces nonsensical variants
173
+ // for interrogative sentences (e.g. "Do you support tracked shipping?").
174
+ if (/^(do|does|is|are|can|will|would|have|has|did|should|could|was|were)\s/i.test(lowerText))
175
+ return false;
176
+ // Exclude single-brand terms (e.g. "GIFTIFY")
177
+ if (/^[A-Z]{3,}$/.test(text.trim()))
178
+ return false;
179
+ const pluralVars = ['count', 'length', 'total', 'num', 'size'];
180
+ if (variables.some((v) => pluralVars.includes(v.toLowerCase())))
181
+ return true;
182
+ if (/\{\s*(count|length|total|num|size)\s*\}/.test(lowerText))
183
+ return true;
184
+ // Digit + plural noun pattern — exclude longer title-like phrases
185
+ // (5+ words, e.g. "15 reasons naive automation fails") to reduce false positives
186
+ if (/\b\d+\s+[a-z]+s\b/.test(lowerText)) {
187
+ const wordCount = lowerText.split(/\s+/).length;
188
+ if (wordCount >= 5)
189
+ return false;
190
+ return true;
191
+ }
192
+ return false;
193
+ }
194
+ function hasICUSyntax(text) {
195
+ return /\{[^}]+,\s*(plural|select|selectordinal)\s*,/.test(text);
196
+ }
197
+ function escapeRegex(text) {
198
+ return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
199
+ }
200
+ function hashString(text) {
201
+ return (0, node_crypto_1.createHash)('sha256').update(text, 'utf8').digest('hex').slice(0, 16);
202
+ }
203
+ function matchesCurrencyPattern(text) {
204
+ return /[€$£¥]/.test(text) || /\d[.,]\d{2}\b/.test(text) || /\b\d{1,3}(,\d{3})+\b/.test(text);
205
+ }
206
+ function matchesDatePattern(text) {
207
+ return (/(0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])[- /.](19|20)\d\d/.test(text) ||
208
+ /\b(19|20)\d\d[- /.](0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])\b/.test(text));
209
+ }
210
+ function containsPII(text) {
211
+ const PATTERNS = {
212
+ email: /[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*/,
213
+ ipv4: /\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b/,
214
+ creditCard: /\b(?:\d[ -]*?){13,16}\b/,
215
+ secretToken: /\b(?:sk|pk|key|secret|token|auth|password)[-_=]\w{12,}\b/i,
216
+ hexKey: /\b[a-f0-9]{32,64}\b/i,
217
+ phone: /\b(?:\+\d{1,4}[- ]?)?\(?\d{2,5}\)?[- ]?\d{3,5}[- ]?\d{3,5}\b/,
218
+ };
219
+ return Object.values(PATTERNS).some((pattern) => pattern.test(text));
220
+ }
221
+ exports.SKIP_TAGS = new Set(['script', 'style', 'noscript', 'code', 'pre', 'svg']);
222
+ /**
223
+ * Extract content between balanced opening and closing tags, handling nested tags.
224
+ * Finds the first `<tagName>` and its matching `</tagName>`, correctly counting
225
+ * nested `<tagName ...>` elements.
226
+ */
227
+ function extractBalancedTag(content, tagName, allowAttrs = false) {
228
+ const openPattern = allowAttrs ? `<${tagName}` : `<${tagName}>`;
229
+ const closeTag = `</${tagName}>`;
230
+ const startIdx = content.indexOf(openPattern);
231
+ if (startIdx === -1)
232
+ return null;
233
+ // For allowAttrs=true, find the end of the opening tag
234
+ let afterOpen;
235
+ let attrs = '';
236
+ if (allowAttrs) {
237
+ const gtIdx = content.indexOf('>', startIdx);
238
+ if (gtIdx === -1)
239
+ return null;
240
+ afterOpen = gtIdx + 1;
241
+ attrs = content.slice(startIdx + tagName.length + 1, gtIdx).trim();
242
+ }
243
+ else {
244
+ afterOpen = startIdx + openPattern.length;
245
+ }
246
+ let depth = 1;
247
+ let i = afterOpen;
248
+ while (i < content.length && depth > 0) {
249
+ const nextOpen = content.indexOf(`<${tagName}`, i);
250
+ const nextClose = content.indexOf(closeTag, i);
251
+ if (nextClose === -1)
252
+ return null;
253
+ if (nextOpen !== -1 && nextOpen < nextClose) {
254
+ depth++;
255
+ i = nextOpen + tagName.length + 1;
256
+ }
257
+ else {
258
+ depth--;
259
+ if (depth === 0) {
260
+ return {
261
+ fullMatch: content.slice(startIdx, nextClose + closeTag.length),
262
+ body: content.slice(afterOpen, nextClose),
263
+ attrs,
264
+ };
265
+ }
266
+ i = nextClose + closeTag.length;
267
+ }
268
+ }
269
+ return null;
270
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Translation Quality Validation
3
+ *
4
+ * Validates translation quality and detects potential issues
5
+ */
6
+ export interface QualityIssue {
7
+ type: 'missing_placeholder' | 'extra_placeholder' | 'empty_translation' | 'suspicious_length' | 'untranslated_text' | 'html_tag_mismatch' | 'placeholder_order_mismatch';
8
+ severity: 'error' | 'warning';
9
+ message: string;
10
+ original: string;
11
+ translated: string;
12
+ }
13
+ export interface QualityResult {
14
+ isValid: boolean;
15
+ score: number;
16
+ issues: QualityIssue[];
17
+ }
18
+ /**
19
+ * Validate translation quality
20
+ */
21
+ export declare function validateTranslationQuality(original: string, translated: string): QualityResult;
22
+ /**
23
+ * Validate batch of translations
24
+ */
25
+ export declare function validateBatchQuality(entries: Array<{
26
+ key: string;
27
+ original: string;
28
+ translated: string;
29
+ }>): {
30
+ valid: number;
31
+ invalid: number;
32
+ averageScore: number;
33
+ issues: QualityIssue[];
34
+ };
@@ -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,11 @@
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
+ source?: string;
10
+ }): string;
11
+ export declare function validateRawTokens(aiOutput: string, expectedCount: number): boolean;
@@ -0,0 +1,71 @@
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(
6
+ // Sentence-initial echoes come back capitalized ("Eng_Latn Produktkatalog").
7
+ '\\b(?:__)?[A-Za-z][a-z]{2}_[A-Z][a-z]{3,4}(?:__)?\\b', 'g');
8
+ /**
9
+ * Collapse phrase-level stutter ("Il a Il a" -> "Il a"). NLLB occasionally
10
+ * repeats a short fragment twice after language tokens are stripped. Only
11
+ * collapse when BOTH halves contain >=2 words and match case-insensitively —
12
+ * this never touches legitimate one-word doublings like "no no" or "la la".
13
+ */
14
+ function collapsePhraseStutter(text) {
15
+ const words = text.split(/\s+/);
16
+ // The whole output is one phrase of >=2 words repeated 2+ times ("Il a Il a",
17
+ // "Product catalog Product catalog Product catalog").
18
+ for (let size = 2; size <= words.length / 2; size++) {
19
+ if (words.length % size !== 0)
20
+ continue;
21
+ const first = words.slice(0, size).map((w) => w.toLowerCase());
22
+ let repeated = true;
23
+ for (let i = size; i < words.length && repeated; i++) {
24
+ if (words[i].toLowerCase() !== first[i % size])
25
+ repeated = false;
26
+ }
27
+ if (repeated)
28
+ return words.slice(0, size).join(' ');
29
+ }
30
+ return text;
31
+ }
32
+ /**
33
+ * Clean up translator output. Script injection is stripped for every
34
+ * provider; the NLLB-specific repairs (language-code echoes, unknown-token
35
+ * marks, whitespace and stutter collapsing) only run for NLLB output, since on
36
+ * LLM output they would rewrite legitimate text.
37
+ */
38
+ function sanitizeTranslation(text, options = {}) {
39
+ if (!text)
40
+ return text;
41
+ let clean = text.replace(/<script\b[^>]*>([\s\S]*?)<\/script>/gim, '');
42
+ clean = clean.replace(/javascript:/gim, 'java_script:');
43
+ clean = clean.replace(/\s+on[a-z]+\s*=\s*['"][^'"]*['"]/gim, '');
44
+ if (options.nllb === false)
45
+ return clean;
46
+ // Strip FLORES-200 language-code artifacts (eng_Latn, __fra_Latn__, ...)
47
+ // anywhere in the output — NLLB echoes these at the end/middle of short
48
+ // fragments. Mirrors the fix in apps/translator/src/translator.rs.
49
+ clean = clean.replace(NLLB_ARTIFACT_PATTERN, '');
50
+ // Strip U+2047 (DOUBLE QUESTION MARK) — NLLB's unknown-token marker, emitted
51
+ // for tokens it can't translate (e.g. punctuation-only fragments). Never
52
+ // legitimate translated text.
53
+ clean = clean.replace(/\u2047/g, '');
54
+ clean = clean.replace(/ +/g, ' ').trim();
55
+ clean = collapsePhraseStutter(clean);
56
+ // A one-word source doubled in the output ("Product" → "Produkt Produkt").
57
+ if (options.source && !/\s/.test(options.source.trim())) {
58
+ const words = clean.split(/\s+/);
59
+ if (words.length > 1 && words.every((w) => w.toLowerCase() === words[0].toLowerCase()))
60
+ clean = words[0];
61
+ }
62
+ return clean;
63
+ }
64
+ function validateRawTokens(aiOutput, expectedCount) {
65
+ for (let i = 0; i < expectedCount; i++) {
66
+ const tokenRegex = new RegExp(`\\[\\s*#\\s*VAR_${i}\\s*#\\s*\\]`, 'gi');
67
+ if (!tokenRegex.test(aiOutput))
68
+ return false;
69
+ }
70
+ return true;
71
+ }
@@ -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>;