@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,2 @@
1
+ import type { BabelizeConfig, ExtractedString } from '../../types';
2
+ export declare function extract(config: BabelizeConfig): Promise<ExtractedString[]>;
@@ -0,0 +1,63 @@
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.extract = extract;
37
+ const path = __importStar(require("path"));
38
+ const file_utils_1 = require("../../utils/file-utils");
39
+ const types_1 = require("../../runtime/types");
40
+ const extract_1 = require("./extract");
41
+ async function extract(config) {
42
+ // For v2 bootstrapping we reuse existing phase1 extractor logic to guarantee
43
+ // identical behavior, while we later refactor adapters + parser in place.
44
+ const workspacePath = config.workspacePath;
45
+ const sourceDir = config.sourceDir ?? 'apps/frontend/app';
46
+ const dirPath = path.join(workspacePath, sourceDir);
47
+ const files = await (0, file_utils_1.findFiles)(dirPath, types_1.SUPPORTED_EXTENSIONS, types_1.EXCLUDED_PATTERNS);
48
+ const all = [];
49
+ for (const filePath of files) {
50
+ const rel = (0, file_utils_1.getRelativePath)(workspacePath, filePath);
51
+ const content = await (0, file_utils_1.readFile)(filePath);
52
+ const { strings } = await (0, extract_1.extractStringsFromFile)(content, rel);
53
+ all.push(...strings.map((s) => ({
54
+ id: s.id,
55
+ text: s.text,
56
+ filePath: s.file,
57
+ line: s.line,
58
+ column: s.column,
59
+ context: s.context,
60
+ })));
61
+ }
62
+ return all;
63
+ }
@@ -0,0 +1,28 @@
1
+ export interface DjangoFinalizeResult {
2
+ /** Absolute path to the settings file, or null when none could be resolved. */
3
+ settingsPath: string | null;
4
+ /** Human-readable description of each edit applied. */
5
+ changes: string[];
6
+ warnings: string[];
7
+ /** Settings file content before and after, when it changed. */
8
+ original?: string;
9
+ updated?: string;
10
+ }
11
+ export declare function resolveSettingsPath(workspacePath: string): Promise<string | null>;
12
+ /**
13
+ * The locale directory Django reads (LOCALE_PATHS[0]), relative to the
14
+ * workspace, '/'-separated. Falls back to <BASE_DIR>/locale — what finalize
15
+ * adds — when LOCALE_PATHS is absent or not a plain BASE_DIR path.
16
+ */
17
+ export declare function resolveDjangoLocaleDir(workspacePath: string, djangoRoot: string): Promise<string>;
18
+ /** First LOCALE_PATHS entry as a path relative to BASE_DIR, if it is one. */
19
+ export declare function parseLocalePaths(source: string): string | null;
20
+ export declare function finalizeDjangoSettings(workspacePath: string, options?: {
21
+ targetLocales?: string[];
22
+ sourceLocale?: string;
23
+ dryRun?: boolean;
24
+ /** Django project root (BASE_DIR), relative to the workspace. */
25
+ djangoRoot?: string;
26
+ /** Locale directory the catalogues were written to, relative to the workspace. */
27
+ localeDir?: string;
28
+ }): Promise<DjangoFinalizeResult>;
@@ -0,0 +1,412 @@
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
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.resolveSettingsPath = resolveSettingsPath;
40
+ exports.resolveDjangoLocaleDir = resolveDjangoLocaleDir;
41
+ exports.parseLocalePaths = parseLocalePaths;
42
+ exports.finalizeDjangoSettings = finalizeDjangoSettings;
43
+ const path = __importStar(require("path"));
44
+ const fast_glob_1 = __importDefault(require("fast-glob"));
45
+ const file_utils_1 = require("../../utils/file-utils");
46
+ const logger_1 = require("../../utils/logger");
47
+ const locale_names_1 = require("../../django/locale-names");
48
+ /**
49
+ * Django settings.py auto-configuration.
50
+ *
51
+ * Translating a Django project produces .po files under locale/<lang>/LC_MESSAGES/,
52
+ * but Django will not load them unless settings.py opts in: USE_I18N, LocaleMiddleware
53
+ * in the right position, LOCALE_PATHS pointing at the locale directory, and LANGUAGES
54
+ * listing what was translated. Without this the run "succeeds" and the site stays in
55
+ * English, which looks like a translation bug rather than a missing setting.
56
+ *
57
+ * settings.py is Python, and we have no Python parser here, so every edit is
58
+ * deliberately conservative:
59
+ *
60
+ * - Nothing is ever reordered or rewritten. We append settings that are absent and
61
+ * insert exactly one middleware line.
62
+ * - A setting the user has already written is left alone, even when its value differs
63
+ * from what we would have chosen. Their file, their call.
64
+ * - Anything we cannot do safely becomes a warning rather than a guess.
65
+ *
66
+ * Re-running is a no-op: every step checks for what it is about to add.
67
+ */
68
+ const LOCALE_MIDDLEWARE = 'django.middleware.locale.LocaleMiddleware';
69
+ const SESSION_MIDDLEWARE = 'django.contrib.sessions.middleware.SessionMiddleware';
70
+ const COMMON_MIDDLEWARE = 'django.middleware.common.CommonMiddleware';
71
+ const MANAGED_BLOCK_HEADER = '# --- Added by Babelize ---';
72
+ /** Is this settings file using pathlib (Django >= 3.1) or os.path? */
73
+ function usesPathlibBaseDir(source) {
74
+ return /BASE_DIR\s*=\s*Path\s*\(/.test(source);
75
+ }
76
+ /**
77
+ * Does this setting already exist as a top-level assignment?
78
+ *
79
+ * Anchored to the start of a line so a mention inside a comment, a string, or a
80
+ * nested dict does not count as a definition.
81
+ */
82
+ function hasSetting(source, name) {
83
+ return new RegExp(`^${name}\\s*=`, 'm').test(source);
84
+ }
85
+ /**
86
+ * Find settings.py by reading DJANGO_SETTINGS_MODULE out of manage.py.
87
+ *
88
+ * That is the project's own declaration of which settings module is real, which
89
+ * matters when a repo carries several (settings/base.py, settings/prod.py, or a
90
+ * second vendored Django app).
91
+ */
92
+ async function resolveViaManagePy(workspacePath) {
93
+ const managePaths = await (0, fast_glob_1.default)('**/manage.py', {
94
+ cwd: workspacePath,
95
+ onlyFiles: true,
96
+ deep: 3,
97
+ ignore: ['**/node_modules/**', '**/.venv/**', '**/venv/**', '**/site-packages/**'],
98
+ });
99
+ for (const rel of managePaths) {
100
+ const source = await (0, file_utils_1.readFile)(path.join(workspacePath, rel)).catch(() => '');
101
+ // os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
102
+ const match = source.match(/DJANGO_SETTINGS_MODULE['"]\s*,\s*['"]([\w.]+)['"]/);
103
+ if (!match)
104
+ continue;
105
+ const moduleParts = match[1].split('.');
106
+ const manageDir = path.dirname(path.join(workspacePath, rel));
107
+ // A settings module is either a file (pkg/settings.py) or a package
108
+ // (pkg/settings/__init__.py). Try both, relative to manage.py.
109
+ const candidates = [
110
+ path.join(manageDir, ...moduleParts) + '.py',
111
+ path.join(manageDir, ...moduleParts, '__init__.py'),
112
+ ];
113
+ for (const candidate of candidates) {
114
+ if (await (0, file_utils_1.pathExists)(candidate))
115
+ return candidate;
116
+ }
117
+ logger_1.engineLogger.warn('DJANGO_SETTINGS_MODULE did not resolve to a file', {
118
+ module: match[1],
119
+ managePath: rel,
120
+ });
121
+ }
122
+ return null;
123
+ }
124
+ /** Fall back to the shallowest settings.py in the tree. */
125
+ async function resolveByGlob(workspacePath) {
126
+ const found = await (0, fast_glob_1.default)('**/settings.py', {
127
+ cwd: workspacePath,
128
+ onlyFiles: true,
129
+ deep: 4,
130
+ ignore: ['**/node_modules/**', '**/.venv/**', '**/venv/**', '**/site-packages/**'],
131
+ });
132
+ if (found.length === 0)
133
+ return null;
134
+ // Shallowest wins: a vendored or example settings.py is usually deeper than
135
+ // the project's own.
136
+ const shallowest = found.sort((a, b) => a.split('/').length - b.split('/').length || a.localeCompare(b))[0];
137
+ return path.join(workspacePath, shallowest);
138
+ }
139
+ async function resolveSettingsPath(workspacePath) {
140
+ const resolved = (await resolveViaManagePy(workspacePath)) ?? (await resolveByGlob(workspacePath));
141
+ if (!resolved)
142
+ return null;
143
+ // settings/dev.py with `from .base import *`: the shared settings live in base.py,
144
+ // and editing only dev.py would leave production without i18n.
145
+ const source = await (0, file_utils_1.readFile)(resolved).catch(() => '');
146
+ if (!/^MIDDLEWARE\s*=/m.test(source)) {
147
+ for (const m of source.matchAll(/^from\s+\.(\w+)\s+import\s+\*/gm)) {
148
+ const sibling = path.join(path.dirname(resolved), `${m[1]}.py`);
149
+ const siblingSource = await (0, file_utils_1.readFile)(sibling).catch(() => '');
150
+ if (/^MIDDLEWARE\s*=/m.test(siblingSource))
151
+ return sibling;
152
+ }
153
+ }
154
+ return resolved;
155
+ }
156
+ /**
157
+ * The locale directory Django reads (LOCALE_PATHS[0]), relative to the
158
+ * workspace, '/'-separated. Falls back to <BASE_DIR>/locale — what finalize
159
+ * adds — when LOCALE_PATHS is absent or not a plain BASE_DIR path.
160
+ */
161
+ async function resolveDjangoLocaleDir(workspacePath, djangoRoot) {
162
+ const fallback = path.posix.join(djangoRoot, 'locale');
163
+ const settingsPath = await resolveSettingsPath(workspacePath);
164
+ if (!settingsPath)
165
+ return fallback;
166
+ const source = await (0, file_utils_1.readFile)(settingsPath).catch(() => '');
167
+ const rel = parseLocalePaths(source);
168
+ return rel ? path.posix.normalize(path.posix.join(djangoRoot, rel)) : fallback;
169
+ }
170
+ /** First LOCALE_PATHS entry as a path relative to BASE_DIR, if it is one. */
171
+ function parseLocalePaths(source) {
172
+ const m = /^LOCALE_PATHS\s*=\s*[[(]/m.exec(source);
173
+ if (!m)
174
+ return null;
175
+ // First element of the list: up to the first comma outside brackets.
176
+ let depth = 0;
177
+ let first = '';
178
+ for (let i = m.index + m[0].length; i < source.length; i++) {
179
+ const ch = source[i];
180
+ if (ch === '(' || ch === '[')
181
+ depth++;
182
+ else if (ch === ')' || ch === ']') {
183
+ if (depth === 0)
184
+ break;
185
+ depth--;
186
+ }
187
+ else if (ch === ',' && depth === 0)
188
+ break;
189
+ first += ch;
190
+ }
191
+ first = first.trim();
192
+ const strings = (t) => [...t.matchAll(/['"]([^'"]+)['"]/g)].map((x) => x[1]);
193
+ // BASE_DIR / 'a' / 'b' str(BASE_DIR / 'a')
194
+ if (/^(?:str\()?\s*BASE_DIR\s*\//.test(first))
195
+ return path.posix.join(...strings(first));
196
+ // os.path.join(BASE_DIR, 'a', 'b') BASE_DIR.joinpath('a') Path(BASE_DIR, 'a')
197
+ if (/^(?:os\.path\.join|Path)\(\s*BASE_DIR\s*,/.test(first) || /^BASE_DIR\.joinpath\(/.test(first)) {
198
+ const parts = strings(first);
199
+ return parts.length ? path.posix.join(...parts) : null;
200
+ }
201
+ return null;
202
+ }
203
+ /** LANGUAGES entries for the source and target locales: ('pt-br', 'Brazilian Portuguese'). */
204
+ function languageEntries(locales) {
205
+ const seen = new Set();
206
+ const out = [];
207
+ for (const l of locales) {
208
+ const code = (0, locale_names_1.djangoLanguageCode)(l);
209
+ if (!l || seen.has(code))
210
+ continue;
211
+ seen.add(code);
212
+ out.push({ code, name: (0, locale_names_1.languageName)(l) });
213
+ }
214
+ return out;
215
+ }
216
+ /** Language codes listed in a LANGUAGES setting. */
217
+ function listedLanguages(source) {
218
+ const m = /^LANGUAGES\s*=\s*[[(]/m.exec(source);
219
+ if (!m)
220
+ return null;
221
+ let depth = 0;
222
+ let end = -1;
223
+ for (let i = m.index + m[0].length - 1; i < source.length; i++) {
224
+ const ch = source[i];
225
+ if (ch === '[' || ch === '(')
226
+ depth++;
227
+ else if (ch === ']' || ch === ')') {
228
+ depth--;
229
+ if (depth === 0) {
230
+ end = i + 1;
231
+ break;
232
+ }
233
+ }
234
+ }
235
+ if (end === -1)
236
+ return null;
237
+ const body = source.slice(m.index, end);
238
+ const codes = [...body.matchAll(/[[(,]\s*\(\s*['"]([\w-]+)['"]\s*,/g)].map((x) => x[1].toLowerCase());
239
+ return { codes, start: m.index, end };
240
+ }
241
+ /**
242
+ * Insert LocaleMiddleware into the MIDDLEWARE list.
243
+ *
244
+ * Django requires it after SessionMiddleware (it reads the session for the active
245
+ * language) and before CommonMiddleware (which performs locale-aware URL redirects).
246
+ * Getting the order wrong fails quietly — language selection simply stops working.
247
+ */
248
+ function insertLocaleMiddleware(source) {
249
+ if (source.includes(LOCALE_MIDDLEWARE)) {
250
+ return { source };
251
+ }
252
+ const middlewareMatch = source.match(/^MIDDLEWARE\s*=\s*\[/m);
253
+ if (!middlewareMatch) {
254
+ return {
255
+ source,
256
+ warning: 'Could not find a MIDDLEWARE list in settings.py — add ' +
257
+ `'${LOCALE_MIDDLEWARE}' after SessionMiddleware and before CommonMiddleware yourself.`,
258
+ };
259
+ }
260
+ const listStart = middlewareMatch.index + middlewareMatch[0].length;
261
+ const listEnd = source.indexOf(']', listStart);
262
+ if (listEnd === -1) {
263
+ return { source, warning: 'MIDDLEWARE list in settings.py is not closed — left unchanged.' };
264
+ }
265
+ const list = source.slice(listStart, listEnd);
266
+ const lines = list.split('\n');
267
+ // Match the indentation and quote style already in the file.
268
+ const entryLine = lines.find((l) => l.trim().startsWith("'") || l.trim().startsWith('"'));
269
+ const indent = entryLine?.match(/^(\s*)/)?.[1] ?? ' ';
270
+ const quote = entryLine?.trim().startsWith('"') ? '"' : "'";
271
+ const newEntry = `${indent}${quote}${LOCALE_MIDDLEWARE}${quote},${source.includes('\r\n') ? '\r' : ''}`;
272
+ const sessionIdx = lines.findIndex((l) => l.includes(SESSION_MIDDLEWARE));
273
+ const commonIdx = lines.findIndex((l) => l.includes(COMMON_MIDDLEWARE));
274
+ let insertAt;
275
+ if (sessionIdx !== -1) {
276
+ insertAt = sessionIdx + 1;
277
+ }
278
+ else if (commonIdx !== -1) {
279
+ insertAt = commonIdx;
280
+ }
281
+ else {
282
+ // Neither neighbour present. Append rather than guess a position.
283
+ insertAt = lines.length - 1;
284
+ }
285
+ // Appending after Session must still land before Common.
286
+ if (commonIdx !== -1 && insertAt > commonIdx) {
287
+ insertAt = commonIdx;
288
+ }
289
+ lines.splice(insertAt, 0, newEntry);
290
+ const updated = source.slice(0, listStart) + lines.join('\n') + source.slice(listEnd);
291
+ const position = sessionIdx !== -1 ? 'after SessionMiddleware' : commonIdx !== -1 ? 'before CommonMiddleware' : 'at the end of MIDDLEWARE';
292
+ return { source: updated, change: `Added LocaleMiddleware ${position}` };
293
+ }
294
+ async function finalizeDjangoSettings(workspacePath, options = {}) {
295
+ const { targetLocales = [], sourceLocale = 'en', dryRun = false } = options;
296
+ const djangoRoot = options.djangoRoot ?? '.';
297
+ const localeRel = path.posix.relative(djangoRoot === '.' ? '' : djangoRoot, options.localeDir ?? path.posix.join(djangoRoot, 'locale')) || 'locale';
298
+ const changes = [];
299
+ const warnings = [];
300
+ const settingsPath = await resolveSettingsPath(workspacePath);
301
+ if (!settingsPath) {
302
+ warnings.push('Could not find settings.py — configure USE_I18N, LocaleMiddleware, LOCALE_PATHS and LANGUAGES manually.');
303
+ return { settingsPath: null, changes, warnings };
304
+ }
305
+ const original = await (0, file_utils_1.readFile)(settingsPath).catch(() => null);
306
+ if (original === null) {
307
+ warnings.push(`Found ${path.basename(settingsPath)} but could not read it — left unchanged.`);
308
+ return { settingsPath, changes, warnings };
309
+ }
310
+ let source = original;
311
+ const nl = original.includes('\r\n') ? '\r\n' : '\n';
312
+ // ── USE_I18N ────────────────────────────────────────────────
313
+ // Only flip an explicit False. Anything else the user wrote stays.
314
+ if (hasSetting(source, 'USE_I18N')) {
315
+ const falseRe = /^USE_I18N[ \t]*=[ \t]*False\b/m;
316
+ if (falseRe.test(source)) {
317
+ source = source.replace(falseRe, 'USE_I18N = True');
318
+ changes.push('Set USE_I18N = True (was False)');
319
+ }
320
+ }
321
+ else {
322
+ source = appendSetting(source, 'USE_I18N = True');
323
+ changes.push('Added USE_I18N = True');
324
+ }
325
+ // ── LocaleMiddleware ────────────────────────────────────────
326
+ const middlewareResult = insertLocaleMiddleware(source);
327
+ source = middlewareResult.source;
328
+ if (middlewareResult.change)
329
+ changes.push(middlewareResult.change);
330
+ if (middlewareResult.warning)
331
+ warnings.push(middlewareResult.warning);
332
+ // ── LOCALE_PATHS ────────────────────────────────────────────
333
+ // Must match where the locale builder writes: <workspace>/locale/<lang>/LC_MESSAGES/
334
+ if (!hasSetting(source, 'LOCALE_PATHS')) {
335
+ const segments = localeRel.split('/').filter(Boolean);
336
+ const value = usesPathlibBaseDir(source)
337
+ ? `LOCALE_PATHS = [BASE_DIR / ${segments.map((p) => `'${p}'`).join(' / ')}]`
338
+ : `LOCALE_PATHS = [os.path.join(BASE_DIR, ${segments.map((p) => `'${p}'`).join(', ')})]`;
339
+ source = appendSetting(source, value, nl);
340
+ changes.push('Added LOCALE_PATHS');
341
+ }
342
+ else {
343
+ const configured = parseLocalePaths(source);
344
+ if (configured === null) {
345
+ warnings.push(`LOCALE_PATHS in ${path.basename(settingsPath)} isn't a plain BASE_DIR path — make sure it includes ${localeRel}/ (where the .po files were written).`);
346
+ }
347
+ }
348
+ // ── LANGUAGE_CODE ───────────────────────────────────────────
349
+ // Only added when missing; an existing value is a deliberate choice.
350
+ if (!hasSetting(source, 'LANGUAGE_CODE')) {
351
+ source = appendSetting(source, `LANGUAGE_CODE = '${(0, locale_names_1.djangoLanguageCode)(sourceLocale)}'`, nl);
352
+ changes.push(`Added LANGUAGE_CODE = '${(0, locale_names_1.djangoLanguageCode)(sourceLocale)}'`);
353
+ }
354
+ // ── LANGUAGES ───────────────────────────────────────────────
355
+ // Django only activates languages listed here. Ours live in the Babelize
356
+ // block and grow with new target locales; a list the user wrote is theirs —
357
+ // we only point out what it is missing.
358
+ const wanted = languageEntries([sourceLocale, ...targetLocales]);
359
+ const listed = listedLanguages(source);
360
+ if (!listed && targetLocales.length > 0) {
361
+ const entries = wanted.map((e) => ` ('${e.code}', '${e.name.replace(/'/g, "\\'")}'),`).join(nl);
362
+ source = appendSetting(source, `LANGUAGES = [${nl}${entries}${nl}]`, nl);
363
+ changes.push(`Added LANGUAGES (${wanted.length} locales)`);
364
+ }
365
+ else if (listed) {
366
+ const missing = wanted.filter((e) => !listed.codes.includes(e.code) && !listed.codes.includes(e.code.split('-')[0]));
367
+ const managedAt = source.indexOf(MANAGED_BLOCK_HEADER);
368
+ if (missing.length > 0 && managedAt !== -1 && listed.start > managedAt) {
369
+ const body = source.slice(listed.start, listed.end);
370
+ const close = body.lastIndexOf(']');
371
+ if (close !== -1) {
372
+ const indent = /\n([ \t]+)\(/.exec(body)?.[1] ?? ' ';
373
+ const before = body.slice(0, close).replace(/\s*$/, '');
374
+ const sep = before.endsWith(',') || before.endsWith('[') ? '' : ',';
375
+ const added = missing.map((e) => `${indent}('${e.code}', '${e.name.replace(/'/g, "\\'")}'),`).join(nl);
376
+ const next = `${before}${sep}${nl}${added}${nl}${body.slice(close)}`;
377
+ source = source.slice(0, listed.start) + next + source.slice(listed.end);
378
+ changes.push(`Added ${missing.map((e) => e.code).join(', ')} to LANGUAGES`);
379
+ }
380
+ }
381
+ else if (missing.length > 0) {
382
+ warnings.push(`LANGUAGES in ${path.basename(settingsPath)} doesn't list ${missing.map((e) => `'${e.code}'`).join(', ')} — ` +
383
+ 'Django will not switch to those languages until you add them.');
384
+ }
385
+ }
386
+ if (changes.length === 0) {
387
+ logger_1.engineLogger.info('Django settings already configured for i18n', { settingsPath });
388
+ return { settingsPath, changes, warnings };
389
+ }
390
+ if (!dryRun) {
391
+ await (0, file_utils_1.writeFile)(settingsPath, source);
392
+ }
393
+ logger_1.engineLogger.info('Django settings finalized', {
394
+ settingsPath,
395
+ changes,
396
+ dryRun,
397
+ });
398
+ return { settingsPath, changes, warnings, original, updated: source };
399
+ }
400
+ /**
401
+ * Append a setting under a single Babelize block at the end of the file.
402
+ *
403
+ * Appending keeps every existing line exactly where the author put it — the
404
+ * alternative, inserting near related settings, means moving code around a file
405
+ * we cannot parse.
406
+ */
407
+ function appendSetting(source, statement, nl = '\n') {
408
+ const withBlock = source.includes(MANAGED_BLOCK_HEADER)
409
+ ? source
410
+ : `${source.trimEnd()}${nl}${nl}${MANAGED_BLOCK_HEADER}${nl}`;
411
+ return `${withBlock.trimEnd()}${nl}${statement}${nl}`;
412
+ }
@@ -0,0 +1,38 @@
1
+ import type { WorkspaceInfo } from '../../runtime/types';
2
+ import { type FinalizationChange } from './writes';
3
+ interface PackageJson {
4
+ dependencies?: Record<string, string>;
5
+ devDependencies?: Record<string, string>;
6
+ [key: string]: unknown;
7
+ }
8
+ export declare function finalizeProject(workspace: WorkspaceInfo, dryRun: boolean, options?: {
9
+ targetLocales?: string[];
10
+ sourceLocale?: string;
11
+ /**
12
+ * Run the package manager after adding missing i18n dependencies.
13
+ * Default true. Local runs (CLI on a temp copy) turn it off: installing
14
+ * into a throwaway copy is slow, runs install scripts, and the user's own
15
+ * install is what actually matters.
16
+ */
17
+ installDependencies?: boolean;
18
+ }): Promise<{
19
+ finalization: {
20
+ dependenciesInvalid: boolean;
21
+ configFileCreated: boolean;
22
+ entryPointModified: boolean;
23
+ packagesInstalled: boolean;
24
+ warnings: string[];
25
+ changedFiles: FinalizationChange[];
26
+ };
27
+ }>;
28
+ /**
29
+ * The i18n runtime the engine wires a project to, and which of its packages
30
+ * package.json doesn't list yet. Same choice as finalizeProject: Next.js gets
31
+ * next-intl unless it already uses react-i18next.
32
+ */
33
+ export declare function i18nRuntimeFor(framework: string, pkg: PackageJson | null | undefined): {
34
+ library: string;
35
+ required: string[];
36
+ missing: string[];
37
+ };
38
+ export {};