@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,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,13 @@
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
+ }
8
+ export declare function resolveSettingsPath(workspacePath: string): Promise<string | null>;
9
+ export declare function finalizeDjangoSettings(workspacePath: string, options?: {
10
+ targetLocales?: string[];
11
+ sourceLocale?: string;
12
+ dryRun?: boolean;
13
+ }): Promise<DjangoFinalizeResult>;
@@ -0,0 +1,275 @@
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.finalizeDjangoSettings = finalizeDjangoSettings;
41
+ const path = __importStar(require("path"));
42
+ const fast_glob_1 = __importDefault(require("fast-glob"));
43
+ const file_utils_1 = require("../../utils/file-utils");
44
+ const logger_1 = require("../../utils/logger");
45
+ /**
46
+ * Django settings.py auto-configuration.
47
+ *
48
+ * Translating a Django project produces .po files under locale/<lang>/LC_MESSAGES/,
49
+ * but Django will not load them unless settings.py opts in: USE_I18N, LocaleMiddleware
50
+ * in the right position, LOCALE_PATHS pointing at the locale directory, and LANGUAGES
51
+ * listing what was translated. Without this the run "succeeds" and the site stays in
52
+ * English, which looks like a translation bug rather than a missing setting.
53
+ *
54
+ * settings.py is Python, and we have no Python parser here, so every edit is
55
+ * deliberately conservative:
56
+ *
57
+ * - Nothing is ever reordered or rewritten. We append settings that are absent and
58
+ * insert exactly one middleware line.
59
+ * - A setting the user has already written is left alone, even when its value differs
60
+ * from what we would have chosen. Their file, their call.
61
+ * - Anything we cannot do safely becomes a warning rather than a guess.
62
+ *
63
+ * Re-running is a no-op: every step checks for what it is about to add.
64
+ */
65
+ const LOCALE_MIDDLEWARE = 'django.middleware.locale.LocaleMiddleware';
66
+ const SESSION_MIDDLEWARE = 'django.contrib.sessions.middleware.SessionMiddleware';
67
+ const COMMON_MIDDLEWARE = 'django.middleware.common.CommonMiddleware';
68
+ const MANAGED_BLOCK_HEADER = '# --- Added by Babelize ---';
69
+ /** Is this settings file using pathlib (Django >= 3.1) or os.path? */
70
+ function usesPathlibBaseDir(source) {
71
+ return /BASE_DIR\s*=\s*Path\s*\(/.test(source);
72
+ }
73
+ /**
74
+ * Does this setting already exist as a top-level assignment?
75
+ *
76
+ * Anchored to the start of a line so a mention inside a comment, a string, or a
77
+ * nested dict does not count as a definition.
78
+ */
79
+ function hasSetting(source, name) {
80
+ return new RegExp(`^${name}\\s*=`, 'm').test(source);
81
+ }
82
+ /**
83
+ * Find settings.py by reading DJANGO_SETTINGS_MODULE out of manage.py.
84
+ *
85
+ * That is the project's own declaration of which settings module is real, which
86
+ * matters when a repo carries several (settings/base.py, settings/prod.py, or a
87
+ * second vendored Django app).
88
+ */
89
+ async function resolveViaManagePy(workspacePath) {
90
+ const managePaths = await (0, fast_glob_1.default)('**/manage.py', {
91
+ cwd: workspacePath,
92
+ onlyFiles: true,
93
+ deep: 3,
94
+ ignore: ['**/node_modules/**', '**/.venv/**', '**/venv/**', '**/site-packages/**'],
95
+ });
96
+ for (const rel of managePaths) {
97
+ const source = await (0, file_utils_1.readFile)(path.join(workspacePath, rel)).catch(() => '');
98
+ // os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
99
+ const match = source.match(/DJANGO_SETTINGS_MODULE['"]\s*,\s*['"]([\w.]+)['"]/);
100
+ if (!match)
101
+ continue;
102
+ const moduleParts = match[1].split('.');
103
+ const manageDir = path.dirname(path.join(workspacePath, rel));
104
+ // A settings module is either a file (pkg/settings.py) or a package
105
+ // (pkg/settings/__init__.py). Try both, relative to manage.py.
106
+ const candidates = [
107
+ path.join(manageDir, ...moduleParts) + '.py',
108
+ path.join(manageDir, ...moduleParts, '__init__.py'),
109
+ ];
110
+ for (const candidate of candidates) {
111
+ if (await (0, file_utils_1.pathExists)(candidate))
112
+ return candidate;
113
+ }
114
+ logger_1.engineLogger.warn('DJANGO_SETTINGS_MODULE did not resolve to a file', {
115
+ module: match[1],
116
+ managePath: rel,
117
+ });
118
+ }
119
+ return null;
120
+ }
121
+ /** Fall back to the shallowest settings.py in the tree. */
122
+ async function resolveByGlob(workspacePath) {
123
+ const found = await (0, fast_glob_1.default)('**/settings.py', {
124
+ cwd: workspacePath,
125
+ onlyFiles: true,
126
+ deep: 4,
127
+ ignore: ['**/node_modules/**', '**/.venv/**', '**/venv/**', '**/site-packages/**'],
128
+ });
129
+ if (found.length === 0)
130
+ return null;
131
+ // Shallowest wins: a vendored or example settings.py is usually deeper than
132
+ // the project's own.
133
+ const shallowest = found.sort((a, b) => a.split('/').length - b.split('/').length || a.localeCompare(b))[0];
134
+ return path.join(workspacePath, shallowest);
135
+ }
136
+ async function resolveSettingsPath(workspacePath) {
137
+ return (await resolveViaManagePy(workspacePath)) ?? (await resolveByGlob(workspacePath));
138
+ }
139
+ /**
140
+ * Insert LocaleMiddleware into the MIDDLEWARE list.
141
+ *
142
+ * Django requires it after SessionMiddleware (it reads the session for the active
143
+ * language) and before CommonMiddleware (which performs locale-aware URL redirects).
144
+ * Getting the order wrong fails quietly — language selection simply stops working.
145
+ */
146
+ function insertLocaleMiddleware(source) {
147
+ if (source.includes(LOCALE_MIDDLEWARE)) {
148
+ return { source };
149
+ }
150
+ const middlewareMatch = source.match(/^MIDDLEWARE\s*=\s*\[/m);
151
+ if (!middlewareMatch) {
152
+ return {
153
+ source,
154
+ warning: 'Could not find a MIDDLEWARE list in settings.py — add ' +
155
+ `'${LOCALE_MIDDLEWARE}' after SessionMiddleware and before CommonMiddleware yourself.`,
156
+ };
157
+ }
158
+ const listStart = middlewareMatch.index + middlewareMatch[0].length;
159
+ const listEnd = source.indexOf(']', listStart);
160
+ if (listEnd === -1) {
161
+ return { source, warning: 'MIDDLEWARE list in settings.py is not closed — left unchanged.' };
162
+ }
163
+ const list = source.slice(listStart, listEnd);
164
+ const lines = list.split('\n');
165
+ // Match the indentation and quote style already in the file.
166
+ const entryLine = lines.find((l) => l.trim().startsWith("'") || l.trim().startsWith('"'));
167
+ const indent = entryLine?.match(/^(\s*)/)?.[1] ?? ' ';
168
+ const quote = entryLine?.trim().startsWith('"') ? '"' : "'";
169
+ const newEntry = `${indent}${quote}${LOCALE_MIDDLEWARE}${quote},`;
170
+ const sessionIdx = lines.findIndex((l) => l.includes(SESSION_MIDDLEWARE));
171
+ const commonIdx = lines.findIndex((l) => l.includes(COMMON_MIDDLEWARE));
172
+ let insertAt;
173
+ if (sessionIdx !== -1) {
174
+ insertAt = sessionIdx + 1;
175
+ }
176
+ else if (commonIdx !== -1) {
177
+ insertAt = commonIdx;
178
+ }
179
+ else {
180
+ // Neither neighbour present. Append rather than guess a position.
181
+ insertAt = lines.length - 1;
182
+ }
183
+ // Appending after Session must still land before Common.
184
+ if (commonIdx !== -1 && insertAt > commonIdx) {
185
+ insertAt = commonIdx;
186
+ }
187
+ lines.splice(insertAt, 0, newEntry);
188
+ const updated = source.slice(0, listStart) + lines.join('\n') + source.slice(listEnd);
189
+ const position = sessionIdx !== -1 ? 'after SessionMiddleware' : commonIdx !== -1 ? 'before CommonMiddleware' : 'at the end of MIDDLEWARE';
190
+ return { source: updated, change: `Added LocaleMiddleware ${position}` };
191
+ }
192
+ async function finalizeDjangoSettings(workspacePath, options = {}) {
193
+ const { targetLocales = [], sourceLocale = 'en', dryRun = false } = options;
194
+ const changes = [];
195
+ const warnings = [];
196
+ const settingsPath = await resolveSettingsPath(workspacePath);
197
+ if (!settingsPath) {
198
+ warnings.push('Could not find settings.py — configure USE_I18N, LocaleMiddleware, LOCALE_PATHS and LANGUAGES manually.');
199
+ return { settingsPath: null, changes, warnings };
200
+ }
201
+ const original = await (0, file_utils_1.readFile)(settingsPath).catch(() => null);
202
+ if (original === null) {
203
+ warnings.push(`Found ${path.basename(settingsPath)} but could not read it — left unchanged.`);
204
+ return { settingsPath, changes, warnings };
205
+ }
206
+ let source = original;
207
+ // ── USE_I18N ────────────────────────────────────────────────
208
+ // Only flip an explicit False. Anything else the user wrote stays.
209
+ if (hasSetting(source, 'USE_I18N')) {
210
+ const falseMatch = source.match(/^USE_I18N\s*=\s*False\s*$/m);
211
+ if (falseMatch) {
212
+ source = source.replace(/^USE_I18N\s*=\s*False\s*$/m, 'USE_I18N = True');
213
+ changes.push('Set USE_I18N = True (was False)');
214
+ }
215
+ }
216
+ else {
217
+ source = appendSetting(source, 'USE_I18N = True');
218
+ changes.push('Added USE_I18N = True');
219
+ }
220
+ // ── LocaleMiddleware ────────────────────────────────────────
221
+ const middlewareResult = insertLocaleMiddleware(source);
222
+ source = middlewareResult.source;
223
+ if (middlewareResult.change)
224
+ changes.push(middlewareResult.change);
225
+ if (middlewareResult.warning)
226
+ warnings.push(middlewareResult.warning);
227
+ // ── LOCALE_PATHS ────────────────────────────────────────────
228
+ // Must match where the locale builder writes: <workspace>/locale/<lang>/LC_MESSAGES/
229
+ if (!hasSetting(source, 'LOCALE_PATHS')) {
230
+ const value = usesPathlibBaseDir(source)
231
+ ? "LOCALE_PATHS = [BASE_DIR / 'locale']"
232
+ : "LOCALE_PATHS = [os.path.join(BASE_DIR, 'locale')]";
233
+ source = appendSetting(source, value);
234
+ changes.push('Added LOCALE_PATHS');
235
+ }
236
+ // ── LANGUAGE_CODE ───────────────────────────────────────────
237
+ // Only added when missing; an existing value is a deliberate choice.
238
+ if (!hasSetting(source, 'LANGUAGE_CODE')) {
239
+ source = appendSetting(source, `LANGUAGE_CODE = '${sourceLocale}'`);
240
+ changes.push(`Added LANGUAGE_CODE = '${sourceLocale}'`);
241
+ }
242
+ // ── LANGUAGES ───────────────────────────────────────────────
243
+ if (!hasSetting(source, 'LANGUAGES') && targetLocales.length > 0) {
244
+ const all = [sourceLocale, ...targetLocales].filter((l, i, arr) => l && arr.indexOf(l) === i);
245
+ const entries = all.map((l) => ` ('${l}', '${l}'),`).join('\n');
246
+ source = appendSetting(source, `LANGUAGES = [\n${entries}\n]`);
247
+ changes.push(`Added LANGUAGES (${all.length} locales)`);
248
+ }
249
+ if (changes.length === 0) {
250
+ logger_1.engineLogger.info('Django settings already configured for i18n', { settingsPath });
251
+ return { settingsPath, changes, warnings };
252
+ }
253
+ if (!dryRun) {
254
+ await (0, file_utils_1.writeFile)(settingsPath, source);
255
+ }
256
+ logger_1.engineLogger.info('Django settings finalized', {
257
+ settingsPath,
258
+ changes,
259
+ dryRun,
260
+ });
261
+ return { settingsPath, changes, warnings };
262
+ }
263
+ /**
264
+ * Append a setting under a single Babelize block at the end of the file.
265
+ *
266
+ * Appending keeps every existing line exactly where the author put it — the
267
+ * alternative, inserting near related settings, means moving code around a file
268
+ * we cannot parse.
269
+ */
270
+ function appendSetting(source, statement) {
271
+ const withBlock = source.includes(MANAGED_BLOCK_HEADER)
272
+ ? source
273
+ : `${source.trimEnd()}\n\n${MANAGED_BLOCK_HEADER}\n`;
274
+ return `${withBlock.trimEnd()}\n${statement}\n`;
275
+ }
@@ -0,0 +1,36 @@
1
+ import type { WorkspaceInfo } from '../../runtime/types';
2
+ interface PackageJson {
3
+ dependencies?: Record<string, string>;
4
+ devDependencies?: Record<string, string>;
5
+ [key: string]: unknown;
6
+ }
7
+ export declare function finalizeProject(workspace: WorkspaceInfo, dryRun: boolean, options?: {
8
+ targetLocales?: string[];
9
+ sourceLocale?: string;
10
+ /**
11
+ * Run the package manager after adding missing i18n dependencies.
12
+ * Default true. Local runs (CLI on a temp copy) turn it off: installing
13
+ * into a throwaway copy is slow, runs install scripts, and the user's own
14
+ * install is what actually matters.
15
+ */
16
+ installDependencies?: boolean;
17
+ }): Promise<{
18
+ finalization: {
19
+ dependenciesInvalid: boolean;
20
+ configFileCreated: boolean;
21
+ entryPointModified: boolean;
22
+ packagesInstalled: boolean;
23
+ warnings: string[];
24
+ };
25
+ }>;
26
+ /**
27
+ * The i18n runtime the engine wires a project to, and which of its packages
28
+ * package.json doesn't list yet. Same choice as finalizeProject: Next.js gets
29
+ * next-intl unless it already uses react-i18next.
30
+ */
31
+ export declare function i18nRuntimeFor(framework: string, pkg: PackageJson | null | undefined): {
32
+ library: string;
33
+ required: string[];
34
+ missing: string[];
35
+ };
36
+ export {};