@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,461 @@
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.LocalTranslateError = exports.BETA_LOCAL_FRAMEWORKS = exports.SUPPORTED_LOCAL_FRAMEWORKS = exports.LOCAL_LOCKFILE = exports.LOCAL_PROJECT_ID = void 0;
37
+ exports.parseLocales = parseLocales;
38
+ exports.validateLocales = validateLocales;
39
+ exports.sentence = sentence;
40
+ exports.isEngineLogNoise = isEngineLogNoise;
41
+ exports.buildLocaleReports = buildLocaleReports;
42
+ exports.runLocalTranslate = runLocalTranslate;
43
+ const node_child_process_1 = require("node:child_process");
44
+ const promises_1 = require("node:fs/promises");
45
+ const path = __importStar(require("node:path"));
46
+ const node_util_1 = require("node:util");
47
+ const language_codes_1 = require("../modules/translator/language-codes");
48
+ const lockfile_1 = require("../modules/lockfile");
49
+ const orchestrator_1 = require("../orchestrator");
50
+ const logger_1 = require("../utils/logger");
51
+ const workspace_1 = require("../modules/workspace");
52
+ const types_1 = require("@babelize/types");
53
+ const git_1 = require("./git");
54
+ const formatter_1 = require("./formatter");
55
+ const report_1 = require("./report");
56
+ const workspace_copy_1 = require("./workspace-copy");
57
+ const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
58
+ /** Lockfile scope for local runs: `.babelize/local/babelize-lock.json` in the project root. */
59
+ exports.LOCAL_PROJECT_ID = 'local';
60
+ exports.LOCAL_LOCKFILE = `.babelize/${exports.LOCAL_PROJECT_ID}/babelize-lock.json`;
61
+ exports.SUPPORTED_LOCAL_FRAMEWORKS = ['react', 'nextjs'];
62
+ exports.BETA_LOCAL_FRAMEWORKS = ['vue', 'static'];
63
+ class LocalTranslateError extends Error {
64
+ constructor(message, code,
65
+ /** Files left changed in the project (only for a failed, un-rolled-back apply). */
66
+ changedFiles = []) {
67
+ super(message);
68
+ this.code = code;
69
+ this.changedFiles = changedFiles;
70
+ this.name = 'LocalTranslateError';
71
+ }
72
+ }
73
+ exports.LocalTranslateError = LocalTranslateError;
74
+ /** Normalise `--to fr,de , ja` into a deduped list without the source locale. */
75
+ function parseLocales(raw, sourceLocale) {
76
+ const parts = (Array.isArray(raw) ? raw : (raw ?? '').split(','))
77
+ .flatMap((p) => p.split(','))
78
+ .map((p) => p.trim())
79
+ .filter(Boolean);
80
+ return [...new Set(parts)].filter((l) => l !== sourceLocale);
81
+ }
82
+ function validateLocales(sourceLocale, targetLocales) {
83
+ const bad = [sourceLocale, ...targetLocales].filter((l) => !(0, language_codes_1.isSupported)(l));
84
+ if (bad.length > 0) {
85
+ throw new LocalTranslateError(`Unsupported locale code${bad.length > 1 ? 's' : ''}: ${bad.join(', ')}. Use ISO codes like fr, de, ja, pt-BR, zh-CN.`, 'invalid_locale');
86
+ }
87
+ if (targetLocales.length === 0) {
88
+ throw new LocalTranslateError('No target locales given. Pass --to, e.g. --to fr,de.', 'invalid_locale');
89
+ }
90
+ }
91
+ /** Tracked + untracked-but-not-ignored files under `dir`, relative to it. */
92
+ async function listProjectFiles(dir) {
93
+ const { stdout } = await execFileAsync('git', ['ls-files', '-z', '--cached', '--others', '--exclude-standard', '--', '.'], { cwd: dir, maxBuffer: 256 * 1024 * 1024 });
94
+ return stdout.split('\0').filter(Boolean);
95
+ }
96
+ /** Locales this project was already translated into: locale files plus lockfile entries. */
97
+ async function existingTargetLocales(workspaceDir, publicDir, localeFormat, sourceLocale) {
98
+ const found = new Set();
99
+ if (localeFormat !== 'po') {
100
+ const ext = localeFormat === 'arb' ? '.arb' : '.json';
101
+ for (const entry of await (0, promises_1.readdir)(path.join(workspaceDir, publicDir, 'locales')).catch(() => [])) {
102
+ if (entry.endsWith(ext))
103
+ found.add(entry.slice(0, -ext.length));
104
+ }
105
+ }
106
+ try {
107
+ const lock = JSON.parse(await (0, promises_1.readFile)(path.join(workspaceDir, exports.LOCAL_LOCKFILE), 'utf8'));
108
+ for (const entry of Object.values(lock.keys ?? {})) {
109
+ for (const locale of Object.keys(entry.languages ?? {}))
110
+ found.add(locale);
111
+ }
112
+ }
113
+ catch {
114
+ // No lockfile yet.
115
+ }
116
+ found.delete(sourceLocale);
117
+ return [...found].filter((l) => (0, language_codes_1.isSupported)(l)).sort();
118
+ }
119
+ /**
120
+ * Engine log lines that are wrong or redundant for a local run: the
121
+ * TRANSLATOR_URL check doesn't know about BYOK/injected providers, and the
122
+ * "run your install" hint is already a finalization note in the summary.
123
+ */
124
+ /** `msg` as one sentence: trailing dots/spaces trimmed, exactly one period added. */
125
+ function sentence(msg) {
126
+ const trimmed = msg.trim().replace(/[.\s]+$/, '');
127
+ return /[!?]$/.test(trimmed) ? trimmed : `${trimmed}.`;
128
+ }
129
+ const ENGINE_LOG_NOISE = [
130
+ /TRANSLATOR_URL environment variable is not set/,
131
+ /TRANSLATOR_URL not configured/,
132
+ /^New dependencies added\. Please run your package manager install/,
133
+ // Reported in full in the summary (failures / not-wired strings).
134
+ /^Some strings could not be translated/,
135
+ /^Extracted strings not wired into code/,
136
+ ];
137
+ function isEngineLogNoise(message) {
138
+ return ENGINE_LOG_NOISE.some((re) => re.test(message));
139
+ }
140
+ async function readJsonIfExists(file) {
141
+ try {
142
+ return JSON.parse(await (0, promises_1.readFile)(file, 'utf8'));
143
+ }
144
+ catch {
145
+ return null;
146
+ }
147
+ }
148
+ function flattenData(data, prefix = '', out = {}) {
149
+ if (data && typeof data === 'object') {
150
+ for (const [k, v] of Object.entries(data)) {
151
+ const key = prefix ? `${prefix}.${k}` : k;
152
+ if (v && typeof v === 'object')
153
+ flattenData(v, key, out);
154
+ else if (typeof v === 'string')
155
+ out[key] = v;
156
+ }
157
+ }
158
+ return out;
159
+ }
160
+ function sortKeysDeep(value) {
161
+ if (!value || typeof value !== 'object' || Array.isArray(value))
162
+ return value;
163
+ return Object.fromEntries(Object.keys(value)
164
+ .sort()
165
+ .map((k) => [k, sortKeysDeep(value[k])]));
166
+ }
167
+ function frameworkStatusOf(framework) {
168
+ if (exports.SUPPORTED_LOCAL_FRAMEWORKS.includes(framework))
169
+ return 'supported';
170
+ if (exports.BETA_LOCAL_FRAMEWORKS.includes(framework))
171
+ return 'beta';
172
+ return null;
173
+ }
174
+ function stripMeta(flat) {
175
+ const out = {};
176
+ for (const [k, v] of Object.entries(flat ?? {})) {
177
+ if (k === '_meta' || k.startsWith('_meta.'))
178
+ continue;
179
+ out[k] = v;
180
+ }
181
+ return out;
182
+ }
183
+ /** Text that is only digits, symbols or whitespace is legitimately identical in every locale. */
184
+ function hasLetters(text) {
185
+ return /\p{L}/u.test(text);
186
+ }
187
+ function buildLocaleReports(targetLocales, source, targets, failures = [], metadata = {}) {
188
+ const src = stripMeta(source);
189
+ const keys = Object.keys(src);
190
+ return targetLocales.map((locale) => {
191
+ const target = stripMeta(targets[locale]);
192
+ const missing = [];
193
+ const sameAsSource = [];
194
+ let translated = 0;
195
+ for (const key of keys) {
196
+ const value = target[key];
197
+ if (value === undefined || value === '') {
198
+ missing.push(key);
199
+ continue;
200
+ }
201
+ translated++;
202
+ if (value === src[key] && hasLetters(src[key]))
203
+ sameAsSource.push(key);
204
+ }
205
+ const failed = failures
206
+ .filter((f) => f.locale === locale)
207
+ .map((f) => ({ key: f.key, reason: f.reason, message: f.message, retryable: f.retryable }));
208
+ const qa = [];
209
+ for (const [key, meta] of Object.entries(metadata[locale] ?? {})) {
210
+ const warnings = meta?.qa;
211
+ if (Array.isArray(warnings) && warnings.length > 0)
212
+ qa.push({ key, warnings: warnings.map(String) });
213
+ }
214
+ return { locale, total: keys.length, translated, missing, failed, qa, sameAsSource };
215
+ });
216
+ }
217
+ function describeEngineFailure(error, byok) {
218
+ if (byok && /translator service (is )?(unavailable|not responding)/i.test(error)) {
219
+ const name = types_1.PROVIDER_DEFINITIONS[byok.provider].name;
220
+ return new LocalTranslateError(`Could not reach ${name} with your API key (invalid key, no access, or network error). Nothing was changed.`, 'translator_unreachable');
221
+ }
222
+ return new LocalTranslateError(`Translation failed: ${sentence(error)} Nothing was changed.`, 'engine_failed');
223
+ }
224
+ function hostedFailure(err) {
225
+ return new LocalTranslateError(`${sentence(err.message)} Nothing was changed.`, err.code === 'hosted_credits' ? 'hosted_credits' : 'hosted_auth');
226
+ }
227
+ /**
228
+ * Run the Babelize engine on a temp copy of `projectDir` and, unless
229
+ * `dryRun`, apply the result back to the working tree. The project is never
230
+ * touched until the engine has finished successfully and every target locale
231
+ * has translations.
232
+ */
233
+ async function runLocalTranslate(opts, deps = {}) {
234
+ // Engine logs go to this run only (not the process-wide logger), so
235
+ // concurrent runs in one process (the MCP server) keep separate logs.
236
+ const engineLog = [];
237
+ const state = { collecting: false };
238
+ const log = (level) => (message) => {
239
+ if (state.collecting && !isEngineLogNoise(message))
240
+ engineLog.push({ level, message });
241
+ };
242
+ const logger = { debug: () => { }, info: () => { }, warn: log('warn'), error: log('error') };
243
+ return (0, logger_1.runWithEngineLogger)(logger, () => runScoped(opts, deps, engineLog, state));
244
+ }
245
+ async function runScoped(opts, deps, engineLog, state) {
246
+ const startedAt = Date.now();
247
+ const projectDir = path.resolve(opts.projectDir);
248
+ try {
249
+ await (0, git_1.assertGitRepo)(projectDir);
250
+ // A dry run writes nothing, so it is safe on a dirty tree.
251
+ if (!opts.dryRun)
252
+ await (0, git_1.assertCleanTree)(projectDir, opts.force);
253
+ }
254
+ catch (err) {
255
+ if (err instanceof git_1.GitError)
256
+ throw new LocalTranslateError(err.message, err.code);
257
+ throw err;
258
+ }
259
+ validateLocales(opts.sourceLocale, opts.targetLocales);
260
+ const translator = deps.hosted ?? deps.translator;
261
+ if (!opts.byok && !translator) {
262
+ throw new LocalTranslateError('No translation provider configured.', 'no_translator');
263
+ }
264
+ const files = await listProjectFiles(projectDir);
265
+ const temp = await (0, workspace_copy_1.createTempCopy)(projectDir, { files, tmpRoot: deps.tmpRoot });
266
+ try {
267
+ // Framework detection runs on the copy (it creates .babelize/ as a side effect).
268
+ let framework;
269
+ let publicDir = 'public';
270
+ let localeFormat = 'json';
271
+ try {
272
+ const { workspace } = await (0, workspace_1.validateWorkspace)({
273
+ workspacePath: temp.dir,
274
+ inputType: 'zip',
275
+ sourceLocale: opts.sourceLocale,
276
+ targetLocales: opts.targetLocales,
277
+ confidenceThreshold: 80,
278
+ dryRun: true,
279
+ });
280
+ framework = workspace.framework;
281
+ publicDir = workspace.publicDir;
282
+ localeFormat = workspace.localeFormat;
283
+ }
284
+ catch (err) {
285
+ throw new LocalTranslateError(err.message, 'unsupported_framework');
286
+ }
287
+ const frameworkStatus = frameworkStatusOf(framework);
288
+ if (!frameworkStatus) {
289
+ throw new LocalTranslateError(`Detected framework "${framework}", which \`babelize translate\` does not support. Supported: React and Next.js (Vue and static HTML in beta).`, 'unsupported_framework');
290
+ }
291
+ deps.onFramework?.(framework, frameworkStatus);
292
+ // The engine deletes locale files that aren't in its target list, so
293
+ // `--to ja` after `--to fr,de` would silently drop fr and de. Unless the
294
+ // user asked to replace the set, earlier locales stay in it; their
295
+ // translations come from the lockfile, so only new strings cost tokens.
296
+ const earlier = await existingTargetLocales(temp.dir, publicDir, localeFormat, opts.sourceLocale);
297
+ const keptLocales = opts.replace ? [] : earlier.filter((l) => !opts.targetLocales.includes(l));
298
+ const droppedLocales = opts.replace ? earlier.filter((l) => !opts.targetLocales.includes(l)) : [];
299
+ const targetLocales = [...opts.targetLocales, ...keptLocales];
300
+ const before = await (0, workspace_copy_1.snapshot)(temp.dir);
301
+ state.collecting = true;
302
+ const runEngine = deps.runEngine ?? orchestrator_1.runEngineV2;
303
+ const result = await runEngine({
304
+ workspacePath: temp.dir,
305
+ inputType: 'zip',
306
+ framework: framework,
307
+ sourceLocale: opts.sourceLocale,
308
+ targetLocales: [...targetLocales],
309
+ confidenceThreshold: 80,
310
+ dryRun: false,
311
+ projectId: exports.LOCAL_PROJECT_ID,
312
+ // The lockfile lives at <dataDir>/.babelize/local/. Pointing it at the
313
+ // copy means the project's committed lockfile is read (it was copied)
314
+ // and the updated one comes back with the other changes — only on success.
315
+ dataDir: temp.dir,
316
+ byok: translator ? undefined : opts.byok,
317
+ providers: translator ? { translator } : undefined,
318
+ installDependencies: false,
319
+ onProgress: (message, step, status) => deps.onProgress?.(message, step, status),
320
+ });
321
+ const hostedFatal = deps.hosted?.fatalError;
322
+ if (!result.success) {
323
+ if (hostedFatal)
324
+ throw hostedFailure(hostedFatal);
325
+ throw describeEngineFailure(result.error ?? 'unknown engine error', translator ? undefined : opts.byok);
326
+ }
327
+ // --replace: forget dropped locales in the lockfile too, or a later run
328
+ // would rebuild their files from it.
329
+ for (const locale of droppedLocales)
330
+ await (0, lockfile_1.removeLocaleFromLockfile)(exports.LOCAL_PROJECT_ID, locale, temp.dir);
331
+ const source = stripMeta(result.results?.source);
332
+ const targets = result.results?.targets ?? {};
333
+ const translationFailures = result.translationFailures ?? [];
334
+ const metadata = (result.results?.metadata ?? {});
335
+ const locales = buildLocaleReports(targetLocales, source, targets, translationFailures, metadata);
336
+ // A locale with source keys but zero translations (nothing, or only copies
337
+ // of the source text) is a failed run, not a partial one: refuse to write
338
+ // it rather than ship an untranslated language as if it were done.
339
+ const empty = locales.filter((l) => l.total > 0 && l.translated - l.sameAsSource.length === 0);
340
+ if (empty.length > 0) {
341
+ if (hostedFatal)
342
+ throw hostedFailure(hostedFatal);
343
+ throw new LocalTranslateError(`No translations were produced for ${empty.map((l) => l.locale).join(', ')}. Nothing was changed.`, 'incomplete_translation');
344
+ }
345
+ // The engine's result carries each target locale's complete data (fresh
346
+ // translations merged with lockfile-cached ones); its on-disk files can
347
+ // hold only this run's new keys. Write the complete data, exactly as the
348
+ // cloud backend stages locale files for a PR.
349
+ const lost = [];
350
+ for (const locale of result.locales) {
351
+ if (locale.locale === opts.sourceLocale)
352
+ continue;
353
+ const rel = locale.path.split(path.sep).join('/');
354
+ const previous = await readJsonIfExists(path.join(projectDir, rel));
355
+ const next = flattenData(locale.data);
356
+ const failedHere = new Set(translationFailures.filter((f) => f.locale === locale.locale).map((f) => f.key));
357
+ for (const [key, value] of Object.entries(flattenData(previous ?? {}))) {
358
+ // A key whose re-translation failed is reported as a failure instead.
359
+ if (key.startsWith('_meta') || !value || failedHere.has(key))
360
+ continue;
361
+ if (!next[key])
362
+ lost.push(`${locale.locale}: ${key}`);
363
+ }
364
+ const abs = path.resolve(temp.dir, rel);
365
+ if (path.relative(temp.dir, abs).startsWith('..'))
366
+ continue;
367
+ await (0, promises_1.mkdir)(path.dirname(abs), { recursive: true });
368
+ await (0, promises_1.writeFile)(abs, `${JSON.stringify(sortKeysDeep(locale.data), null, 2)}\n`);
369
+ }
370
+ // The QA report (.babelize/local/report.json) stays out of git; the
371
+ // lockfile next to it is committed. Shipped with the run's other changes.
372
+ await (0, report_1.ensureReportGitignored)(temp.dir);
373
+ // Never ship a run that would delete translations the project already has.
374
+ if (lost.length > 0) {
375
+ throw new LocalTranslateError(`This run would remove ${lost.length} existing translation(s) (${lost.slice(0, 5).join(', ')}${lost.length > 5 ? ', …' : ''}). Nothing was changed.`, 'incomplete_translation');
376
+ }
377
+ let changes = (0, workspace_copy_1.diffSnapshots)(before, await (0, workspace_copy_1.snapshot)(temp.dir));
378
+ const formatter = (await (0, formatter_1.formatChangedFiles)(projectDir, temp.dir, [...changes.added, ...changes.modified])) ?? undefined;
379
+ if (formatter?.ok)
380
+ changes = (0, workspace_copy_1.diffSnapshots)(before, await (0, workspace_copy_1.snapshot)(temp.dir));
381
+ const diff = opts.dryRun ? await (0, workspace_copy_1.renderUnifiedDiff)(changes, projectDir, temp.dir) : undefined;
382
+ // Before apply: the project still holds the old content.
383
+ const diffStat = await (0, workspace_copy_1.diffStats)(changes, projectDir, temp.dir);
384
+ if (!opts.dryRun && (0, workspace_copy_1.changeCount)(changes) > 0) {
385
+ const conflicts = await (0, workspace_copy_1.findConflicts)(changes, before, projectDir);
386
+ if (conflicts.length > 0) {
387
+ throw new LocalTranslateError(`These files changed in your project while translate was running (or exist but weren't part of the copy), so nothing was applied:\n` +
388
+ conflicts.map((f) => ` ${f}`).join('\n') +
389
+ '\nCommit or discard those edits, then run translate again.', 'project_changed');
390
+ }
391
+ try {
392
+ await (0, workspace_copy_1.applyChanges)(changes, temp.dir, projectDir, deps.applyIo);
393
+ }
394
+ catch (err) {
395
+ if (!(err instanceof workspace_copy_1.ApplyError))
396
+ throw err;
397
+ if (err.rolledBack) {
398
+ throw new LocalTranslateError(`${sentence(err.message)} Nothing was changed.`, 'apply_failed');
399
+ }
400
+ throw new LocalTranslateError(`${err.message} These files are still changed:\n` +
401
+ err.changedFiles.map((f) => ` ${f}`).join('\n') +
402
+ `\nRestore them with: git checkout -- ${err.changedFiles.join(' ')} (and delete any of them that are new).`, 'apply_failed', err.changedFiles);
403
+ }
404
+ }
405
+ // Partial run cut short by the key or the balance: keep what was
406
+ // translated (it was paid for) and say why the rest is missing.
407
+ if (hostedFatal)
408
+ engineLog.unshift({ level: 'error', message: hostedFatal.message });
409
+ const s = result.stats;
410
+ const provider = deps.hosted
411
+ ? { provider: 'babelize', name: 'Babelize hosted', model: deps.hosted.model }
412
+ : !translator && opts.byok
413
+ ? {
414
+ provider: opts.byok.provider,
415
+ name: types_1.PROVIDER_DEFINITIONS[opts.byok.provider].name,
416
+ model: opts.byok.model ?? types_1.PROVIDER_DEFINITIONS[opts.byok.provider].defaultModel,
417
+ }
418
+ : deps.translatorInfo;
419
+ return {
420
+ framework,
421
+ frameworkStatus,
422
+ provider,
423
+ sourceLocale: opts.sourceLocale,
424
+ targetLocales,
425
+ keptLocales,
426
+ droppedLocales,
427
+ unusedKeys: result.keyUsage?.unused ?? [],
428
+ notInjected: result.notInjected ?? [],
429
+ dynamicKeyFiles: result.keyUsage?.dynamicFiles ?? [],
430
+ dryRun: opts.dryRun,
431
+ applied: !opts.dryRun && (0, workspace_copy_1.changeCount)(changes) > 0,
432
+ changes,
433
+ diff,
434
+ diffStat,
435
+ stats: {
436
+ filesScanned: s.filesScanned,
437
+ stringsExtracted: s.stringsExtracted,
438
+ keysExtracted: s.keysGenerated,
439
+ injectionsApplied: s.injectionsApplied,
440
+ keysFromLockfile: s.translationsFromLockfile ?? 0,
441
+ keysSentToTranslator: s.lockfileKeysTranslated ?? 0,
442
+ translatedThisRun: s.translatedThisRun,
443
+ },
444
+ locales,
445
+ translationFailures,
446
+ warnings: result.warnings.map((w) => ({ type: w.type, file: w.file, line: w.line, message: w.message })),
447
+ notes: result.notes ?? [],
448
+ formatter,
449
+ engineLog,
450
+ lockfile: exports.LOCAL_LOCKFILE,
451
+ lockfileIgnored: await (0, git_1.isGitIgnored)(projectDir, exports.LOCAL_LOCKFILE),
452
+ metadata,
453
+ results: { source, targets: Object.fromEntries(Object.entries(targets).map(([l, t]) => [l, stripMeta(t)])) },
454
+ startedAt,
455
+ durationMs: Date.now() - startedAt,
456
+ };
457
+ }
458
+ finally {
459
+ await temp.cleanup();
460
+ }
461
+ }
@@ -0,0 +1,49 @@
1
+ import type { ByokProvider } from '@babelize/types';
2
+ import type { TranslationFailure, TranslationProvider } from '../providers/types';
3
+ /**
4
+ * Translate a flat key → text map with the engine's translate phase, without
5
+ * a project: the same placeholder protection, ICU/plural handling, glossary,
6
+ * validation and repair pass as `babelize translate`, but no extraction or
7
+ * injection and nothing written anywhere. Used by the golden-set quality run.
8
+ */
9
+ export interface TranslateMessagesOptions {
10
+ sourceLocale: string;
11
+ targetLocales: string[];
12
+ /** Flat key → source text. Keys must not contain dots. */
13
+ messages: Record<string, string>;
14
+ /** The user's LLM key. Required unless `translator` is given. */
15
+ byok?: {
16
+ provider: ByokProvider;
17
+ apiKey: string;
18
+ model?: string;
19
+ };
20
+ translator?: TranslationProvider;
21
+ glossary?: {
22
+ neverTranslate?: string[];
23
+ alwaysTranslateAs?: Array<{
24
+ term: string;
25
+ translations: Record<string, string>;
26
+ }>;
27
+ };
28
+ /**
29
+ * The app's runtime formats ICU (next-intl, i18next-icu): count
30
+ * placeholders may become plurals. Default true.
31
+ */
32
+ targetPlurals?: boolean;
33
+ onProgress?: (message: string) => void;
34
+ /** Engine warnings/errors (silent by default). */
35
+ onLog?: (level: 'warn' | 'error', message: string) => void;
36
+ }
37
+ export interface TranslateMessagesResult {
38
+ /** locale → key → translation (keys that failed are absent). */
39
+ targets: Record<string, Record<string, string>>;
40
+ /** locale → key → engine metadata (`qa` warnings, confidence, …). */
41
+ metadata: Record<string, Record<string, unknown>>;
42
+ failures: TranslationFailure[];
43
+ /** Locales that failed as a whole. */
44
+ errors: Array<{
45
+ locale: string;
46
+ error: string;
47
+ }>;
48
+ }
49
+ export declare function translateMessages(opts: TranslateMessagesOptions): Promise<TranslateMessagesResult>;
@@ -0,0 +1,91 @@
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.translateMessages = translateMessages;
37
+ const promises_1 = require("node:fs/promises");
38
+ const node_os_1 = require("node:os");
39
+ const path = __importStar(require("node:path"));
40
+ const keygen_1 = require("../modules/keygen");
41
+ const translate_1 = require("../modules/translator/translate");
42
+ const logger_1 = require("../utils/logger");
43
+ async function translateMessages(opts) {
44
+ const logger = {
45
+ debug: () => { },
46
+ info: () => { },
47
+ warn: (message) => opts.onLog?.('warn', message),
48
+ error: (message) => opts.onLog?.('error', message),
49
+ };
50
+ return (0, logger_1.runWithEngineLogger)(logger, () => run(opts));
51
+ }
52
+ async function run(opts) {
53
+ if (!opts.byok && !opts.translator)
54
+ throw new Error('translateMessages needs a byok key or a translator.');
55
+ const dir = await (0, promises_1.mkdtemp)(path.join((0, node_os_1.tmpdir)(), 'babelize-messages-'));
56
+ try {
57
+ // The translate phase reads glossary rules from .babelize/config.json when there is no project id.
58
+ const customTranslations = {};
59
+ for (const term of opts.glossary?.neverTranslate ?? [])
60
+ customTranslations[term] = { neverTranslate: true };
61
+ for (const e of opts.glossary?.alwaysTranslateAs ?? [])
62
+ customTranslations[e.term] = { translations: e.translations };
63
+ await (0, promises_1.mkdir)(path.join(dir, '.babelize'), { recursive: true });
64
+ await (0, promises_1.writeFile)(path.join(dir, '.babelize', 'config.json'), JSON.stringify({ customTranslations }));
65
+ const workspace = {
66
+ path: dir,
67
+ framework: 'react',
68
+ sourceDirs: [],
69
+ hasLocalesDir: false,
70
+ existingLocales: [],
71
+ publicDir: 'public',
72
+ localeFormat: 'json',
73
+ };
74
+ const result = await (0, translate_1.translateLocales)(workspace, { locale: opts.sourceLocale, path: '', data: { ...opts.messages }, isNew: false }, opts.targetLocales, false, true, // dry run: nothing written
75
+ undefined, opts.onProgress, undefined, undefined, opts.translator ? undefined : opts.byok, undefined, undefined, opts.translator ? { translator: opts.translator } : {}, { targetPlurals: opts.targetPlurals ?? true });
76
+ const targets = {};
77
+ for (const file of result.translation.locales) {
78
+ const flat = (0, keygen_1.flattenLocaleData)(file.data);
79
+ targets[file.locale] = Object.fromEntries(Object.entries(flat).filter(([k]) => k !== '_meta' && !k.startsWith('_meta.')));
80
+ }
81
+ return {
82
+ targets,
83
+ metadata: result.metadata,
84
+ failures: result.translation.failures,
85
+ errors: result.translation.errors,
86
+ };
87
+ }
88
+ finally {
89
+ await (0, promises_1.rm)(dir, { recursive: true, force: true });
90
+ }
91
+ }