@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,265 @@
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.analyzeModuleConstants = analyzeModuleConstants;
37
+ const t = __importStar(require("@babel/types"));
38
+ const types_1 = require("../runtime/types");
39
+ const ast_helpers_1 = require("./ast-helpers");
40
+ const string_utils_1 = require("./string-utils");
41
+ const jsx_message_1 = require("./jsx-message");
42
+ const ui_copy_1 = require("./ui-copy");
43
+ const attr = (el, name) => el.openingElement.attributes.some((a) => t.isJSXAttribute(a) && t.isJSXIdentifier(a.name, { name }));
44
+ function positionOf(expr) {
45
+ const container = expr.parentPath;
46
+ if (!container?.isJSXExpressionContainer() || container.node.expression !== expr.node)
47
+ return 'other';
48
+ const holder = container.parentPath;
49
+ if (holder?.isJSXElement()) {
50
+ const tag = holder.node.openingElement.name;
51
+ // Form data, not display text: <option>{x}</option> without a value is
52
+ // the submitted value; <textarea>{x}</textarea> is the field's content.
53
+ if (t.isJSXIdentifier(tag, { name: 'option' }) && !attr(holder.node, 'value'))
54
+ return 'form';
55
+ if (t.isJSXIdentifier(tag, { name: 'textarea' }))
56
+ return 'form';
57
+ return 'render';
58
+ }
59
+ if (holder?.isJSXFragment())
60
+ return 'render';
61
+ if (holder?.isJSXAttribute() && t.isJSXIdentifier(holder.node.name)) {
62
+ if (holder.node.name.name === 'key')
63
+ return 'identity';
64
+ if (types_1.JSX_TRANSLATABLE_ATTRS.includes(holder.node.name.name))
65
+ return 'render';
66
+ }
67
+ return 'other';
68
+ }
69
+ const inComponent = (p) => (0, ast_helpers_1.isInsideComponent)(p) && !!(0, ast_helpers_1.findComponentBody)(p);
70
+ /** The render site is part of a JSX sentence (one message): the sentence pass owns it. */
71
+ function inSentence(p) {
72
+ let el = p.parentPath;
73
+ while (el && (el.isJSXExpressionContainer() || el.isJSXElement() || el.isJSXFragment())) {
74
+ if ((el.isJSXElement() || el.isJSXFragment()) && (0, jsx_message_1.buildJsxMessage)(el.node).kind === 'message')
75
+ return true;
76
+ el = el.parentPath;
77
+ }
78
+ return false;
79
+ }
80
+ function memberName(p) {
81
+ if (!p.isMemberExpression() || p.node.computed || !t.isIdentifier(p.node.property))
82
+ return null;
83
+ return p.node.property.name;
84
+ }
85
+ /** Plans for a file's module-level UI constants that are rendered in components. */
86
+ function analyzeModuleConstants(ast) {
87
+ const plans = [];
88
+ (0, ast_helpers_1.traverse)(ast, {
89
+ Program(program) {
90
+ for (const stmt of program.get('body')) {
91
+ const exported = stmt.isExportNamedDeclaration();
92
+ const decl = exported ? stmt.get('declaration') : stmt;
93
+ if (!decl?.isVariableDeclaration() || decl.node.kind !== 'const')
94
+ continue;
95
+ for (const d of decl.get('declarations')) {
96
+ const plan = planFor(d, exported);
97
+ if (plan)
98
+ plans.push(plan);
99
+ }
100
+ }
101
+ program.stop();
102
+ },
103
+ });
104
+ return plans;
105
+ }
106
+ function planFor(d, exported) {
107
+ if (!t.isIdentifier(d.node.id))
108
+ return null;
109
+ const name = d.node.id.name;
110
+ const init = d.get('init');
111
+ const isCopy = (s) => (0, string_utils_1.isTranslatableText)(s.trim());
112
+ // Per "field" ('' for an array of strings): the UI-copy literals that would
113
+ // become keys, every translatable literal (reported if not provable), and
114
+ // whether every value of the field is such a literal. A render site may
115
+ // only be wrapped in t() if all its values become keys — `["Overview",
116
+ // "FAQ"]` with only "Overview" keyed would render a missing key for FAQ.
117
+ const byField = new Map();
118
+ const translatable = new Map();
119
+ const incomplete = new Set();
120
+ const push = (map, field, lit) => map.set(field, [...(map.get(field) ?? []), lit]);
121
+ const consider = (field, value, uiCopy) => {
122
+ if (!value.isStringLiteral()) {
123
+ incomplete.add(field);
124
+ return;
125
+ }
126
+ if (isCopy(value.node.value))
127
+ push(translatable, field, value);
128
+ if (isCopy(value.node.value) && uiCopy(value.node.value))
129
+ push(byField, field, value);
130
+ else
131
+ incomplete.add(field);
132
+ };
133
+ let kind;
134
+ const objectFields = (objects) => {
135
+ const fields = new Set();
136
+ for (const obj of objects) {
137
+ for (const prop of obj.get('properties')) {
138
+ if (prop.isObjectProperty() && !prop.node.computed && t.isIdentifier(prop.node.key))
139
+ fields.add(prop.node.key.name);
140
+ }
141
+ }
142
+ for (const obj of objects) {
143
+ const props = new Map();
144
+ let opaque = false;
145
+ for (const prop of obj.get('properties')) {
146
+ if (prop.isObjectProperty() && !prop.node.computed && t.isIdentifier(prop.node.key))
147
+ props.set(prop.node.key.name, prop.get('value'));
148
+ else
149
+ opaque = true; // spread / computed keys: any field may come from there
150
+ }
151
+ for (const field of fields) {
152
+ const value = props.get(field);
153
+ if (!value || opaque)
154
+ incomplete.add(field);
155
+ if (value)
156
+ consider(field, value, (text) => (0, ui_copy_1.isUiCopyForName)(field, text));
157
+ }
158
+ }
159
+ };
160
+ if (init.isArrayExpression()) {
161
+ const elements = init.get('elements');
162
+ if (elements.length > 0 && elements.every((e) => e.isStringLiteral())) {
163
+ kind = 'strings';
164
+ for (const e of elements)
165
+ consider('', e, ui_copy_1.looksLikeCopy);
166
+ }
167
+ else if (elements.length > 0 && elements.every((e) => e.isObjectExpression())) {
168
+ kind = 'objects';
169
+ objectFields(elements);
170
+ }
171
+ else {
172
+ return null;
173
+ }
174
+ }
175
+ else if (init.isObjectExpression()) {
176
+ kind = 'object';
177
+ objectFields([init]);
178
+ }
179
+ else {
180
+ return null;
181
+ }
182
+ if (translatable.size === 0)
183
+ return null;
184
+ const binding = d.scope.getBinding(name);
185
+ if (!binding)
186
+ return null;
187
+ const sites = new Map();
188
+ const fail = [];
189
+ const site = (field, expr) => {
190
+ if (!translatable.has(field))
191
+ return; // nothing translatable rendered from it
192
+ const pos = positionOf(expr);
193
+ if (pos === 'render') {
194
+ if (!inComponent(expr))
195
+ fail.push('rendered outside a component');
196
+ else if (inSentence(expr))
197
+ fail.push('rendered inside a sentence');
198
+ else if (incomplete.has(field))
199
+ fail.push('not every value is a translatable string');
200
+ sites.set(field, [...(sites.get(field) ?? []), expr]);
201
+ }
202
+ else if (pos === 'form') {
203
+ // Shown, but also the submitted value: report rather than rewrite.
204
+ fail.push(`${expr.toString()} is form data (<option> without a value, or <textarea>)`);
205
+ sites.set(field, [...(sites.get(field) ?? []), expr]);
206
+ }
207
+ else if (pos === 'other') {
208
+ fail.push(`${expr.toString()} is used as data`);
209
+ }
210
+ };
211
+ if (exported)
212
+ fail.push('exported');
213
+ if (binding.constantViolations.length)
214
+ fail.push('reassigned');
215
+ for (const ref of binding.referencePaths) {
216
+ const parent = ref.parentPath;
217
+ const prop = memberName(parent);
218
+ if ((kind === 'strings' || kind === 'objects') && prop === 'map' && parent.parentPath?.isCallExpression() && parent.parentPath.node.callee === parent.node) {
219
+ const cb = parent.parentPath.get('arguments')[0];
220
+ const param = cb && (cb.isArrowFunctionExpression() || cb.isFunctionExpression()) ? cb.node.params[0] : undefined;
221
+ if (!cb || !t.isIdentifier(param)) {
222
+ fail.push(`${name}.map callback doesn't take a plain parameter`);
223
+ continue;
224
+ }
225
+ const item = cb.scope.getBinding(param.name);
226
+ if (!item || item.constantViolations.length) {
227
+ fail.push(`${name}.map item is reassigned`);
228
+ continue;
229
+ }
230
+ for (const use of item.referencePaths) {
231
+ if (kind === 'strings') {
232
+ site('', use);
233
+ continue;
234
+ }
235
+ const member = use.parentPath;
236
+ const field = memberName(member);
237
+ if (field === null || !member.isMemberExpression() || member.node.object !== use.node) {
238
+ fail.push(`${param.name} is used as a whole`);
239
+ }
240
+ else {
241
+ site(field, member);
242
+ }
243
+ }
244
+ continue;
245
+ }
246
+ if (kind === 'object' && prop !== null) {
247
+ site(prop, parent);
248
+ continue;
249
+ }
250
+ if (kind === 'strings' && parent.isMemberExpression() && parent.node.computed && t.isNumericLiteral(parent.node.property)) {
251
+ // TABS[0]: only that element is rendered; keep it simple and treat as one field.
252
+ site('', parent);
253
+ continue;
254
+ }
255
+ fail.push(`${name} is used as data`);
256
+ }
257
+ if (sites.size === 0)
258
+ return null; // never rendered: it's data
259
+ const provable = fail.length === 0;
260
+ // Provable: the keyed literals (all values of each rendered field).
261
+ // Otherwise: every translatable literal that is rendered, to report.
262
+ const literals = [...sites.keys()].flatMap((f) => (provable ? byField : translatable).get(f) ?? []);
263
+ const renderSites = [...sites.values()].flat();
264
+ return { name, provable, literals, renderSites, reason: fail[0] };
265
+ }
@@ -0,0 +1,6 @@
1
+ import type { InjectionResult, KeyGenerationResult, NotInjectedString } from '../runtime/types';
2
+ /**
3
+ * Keys generated this run whose string the injector didn't replace in its
4
+ * file. Their translations exist, but the app keeps showing the literal.
5
+ */
6
+ export declare function findNotInjected(keyGeneration: KeyGenerationResult, injection: InjectionResult): NotInjectedString[];
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.findNotInjected = findNotInjected;
4
+ /**
5
+ * Keys generated this run whose string the injector didn't replace in its
6
+ * file. Their translations exist, but the app keeps showing the literal.
7
+ */
8
+ function findNotInjected(keyGeneration, injection) {
9
+ const injected = new Set();
10
+ for (const file of injection.modifiedFiles) {
11
+ for (const i of file.injections)
12
+ injected.add(`${i.file ?? file.file}\u0000${i.key}`);
13
+ }
14
+ const skipped = new Map(injection.skippedFiles.map((f) => [f.file, f]));
15
+ const seen = new Set();
16
+ const out = [];
17
+ for (const k of keyGeneration.keys) {
18
+ if (k.isPlural)
19
+ continue; // the base key stands for its plural forms
20
+ if (k.gettext?.wrapped)
21
+ continue; // already marked in the code (gettext call / trans tag)
22
+ const file = k.extractionId.split(':')[0];
23
+ const id = `${file}\u0000${k.key}`;
24
+ if (injected.has(id) || seen.has(id))
25
+ continue;
26
+ seen.add(id);
27
+ const skip = skipped.get(file);
28
+ out.push({
29
+ file,
30
+ key: k.key,
31
+ text: k.value,
32
+ reason: skip
33
+ ? `file skipped (${skip.reason}${skip.details ? `: ${skip.details}` : ''})`
34
+ : 'no place to add the translation hook (e.g. outside a component, or a pattern the injector does not handle)',
35
+ });
36
+ }
37
+ return out;
38
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Placeholder protection for machine translation.
3
+ *
4
+ * Everything in a UI string that must reach the translated output byte-for-byte
5
+ * — interpolation variables, markup tags, URLs, never-translate terms — is
6
+ * swapped for an opaque `[#VAR_n#]` token before translation, then validated
7
+ * and swapped back. The token format is shared with the Rust translator
8
+ * (apps/translator/src/placeholders.rs), so the same text works for every
9
+ * provider.
10
+ *
11
+ * ICU messages (`{count, plural, ...}`) are not tokenized: their structure has
12
+ * to change per locale (plural categories), so they are validated by
13
+ * ./icu-message instead.
14
+ */
15
+ export interface ProtectedText {
16
+ /** Text to send to the translator, with protected spans replaced by tokens. */
17
+ text: string;
18
+ /** Original text of each token, by index: tokens[i] is what `[#VAR_i#]` stands for. */
19
+ tokens: string[];
20
+ /**
21
+ * What each token is restored to: the original text, except for a glossary
22
+ * "always translate as" term, which is restored to its fixed translation.
23
+ */
24
+ restore: string[];
25
+ }
26
+ /** A term protected as a token and restored to a fixed translation ("always translate as"). */
27
+ export interface FixedTerm {
28
+ term: string;
29
+ translation: string;
30
+ }
31
+ export interface TokenCheck {
32
+ ok: boolean;
33
+ /** Indices whose token is absent from the translation. */
34
+ missing: number[];
35
+ /** Indices whose token appears more than once. */
36
+ duplicated: number[];
37
+ /** Token indices in the translation that were never issued. */
38
+ unexpected: number[];
39
+ }
40
+ /**
41
+ * Replace every protected span (and every never-translate term) with a token.
42
+ * With `icu: true`, braces are left alone (they are ICU structure) and only
43
+ * URLs, emails, code, tags and terms are tokenized.
44
+ * `fixedTerms` ("always translate as") are tokenized too, and `restore` maps
45
+ * their tokens to the fixed translation: a plain-text translator never sees
46
+ * the term, so it can't ignore the rule.
47
+ * Literal `[#VAR_n#]` text already present in the source is protected too, so
48
+ * it can never be confused with an issued token.
49
+ */
50
+ export declare function protectText(text: string, neverTranslateTerms?: string[], options?: {
51
+ icu?: boolean;
52
+ fixedTerms?: FixedTerm[];
53
+ }): ProtectedText;
54
+ /** Every issued token must appear exactly once, and no other token may appear. */
55
+ export declare function checkTokens(translated: string, tokenCount: number): TokenCheck;
56
+ /**
57
+ * Swap tokens back. Pass protectText's `restore` (fixed glossary terms become
58
+ * their translation) or `tokens` (original text). Call only after checkTokens passed.
59
+ */
60
+ export declare function restoreText(translated: string, tokens: string[]): string;
61
+ /**
62
+ * True when nothing translatable is left once protected spans are removed —
63
+ * e.g. "{count}", "GitHub", "https://…", "%s / %s". Such strings are their
64
+ * own translation and should not be sent to a translator.
65
+ */
66
+ export declare function hasNothingToTranslate(protectedText: string): boolean;
67
+ /** Human-readable description of a failed token check, for retry prompts and reports. */
68
+ export declare function describeTokenProblem(check: TokenCheck, tokens: string[]): string;
69
+ /**
70
+ * Whether two strings contain the same protected spans (variables, tags,
71
+ * URLs, terms), ignoring order. Used to vet rewrites of an already-validated
72
+ * translation, e.g. a brand-voice polish pass.
73
+ */
74
+ export declare function sameProtectedSpans(a: string, b: string, neverTranslateTerms?: string[]): boolean;
@@ -0,0 +1,181 @@
1
+ "use strict";
2
+ /**
3
+ * Placeholder protection for machine translation.
4
+ *
5
+ * Everything in a UI string that must reach the translated output byte-for-byte
6
+ * — interpolation variables, markup tags, URLs, never-translate terms — is
7
+ * swapped for an opaque `[#VAR_n#]` token before translation, then validated
8
+ * and swapped back. The token format is shared with the Rust translator
9
+ * (apps/translator/src/placeholders.rs), so the same text works for every
10
+ * provider.
11
+ *
12
+ * ICU messages (`{count, plural, ...}`) are not tokenized: their structure has
13
+ * to change per locale (plural categories), so they are validated by
14
+ * ./icu-message instead.
15
+ */
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.protectText = protectText;
18
+ exports.checkTokens = checkTokens;
19
+ exports.restoreText = restoreText;
20
+ exports.hasNothingToTranslate = hasNothingToTranslate;
21
+ exports.describeTokenProblem = describeTokenProblem;
22
+ exports.sameProtectedSpans = sameProtectedSpans;
23
+ const tokenFor = (index) => `[#VAR_${index}#]`;
24
+ /** Matches a token, tolerating the spacing/casing drift LLMs introduce. */
25
+ const TOKEN_PATTERN = /\[\s*#\s*VAR_(\d+)\s*#\s*\]/gi;
26
+ /**
27
+ * What gets protected, in priority order (earlier alternatives win where
28
+ * matches overlap — e.g. a `{var}` inside a URL stays part of the URL).
29
+ */
30
+ const PROTECTED_PATTERNS = [
31
+ { kind: 'url', source: String.raw `\b(?:https?|ftp|mailto):[^\s<>"'\])]+[^\s<>"'\]).,;:!?]` },
32
+ { kind: 'email', source: String.raw `\b[\w.+-]+@[\w-]+(?:\.[\w-]+)+\b` },
33
+ { kind: 'code', source: String.raw `\x60[^\x60\n]+\x60` },
34
+ // i18next / Handlebars / Mustache: {{name}}, {{- name}}, {{count, number}}
35
+ { kind: 'mustache', source: String.raw `\{\{[^{}]*\}\}` },
36
+ // JS template literal leftovers: ${name}
37
+ { kind: 'template', source: String.raw `\$\{[^{}]*\}` },
38
+ // i18next nesting: $t(key) / $t(key, {...})
39
+ { kind: 'nesting', source: String.raw `\$t\([^()]*\)` },
40
+ // Python named printf: %(name)s, %(count)d, %(price).2f
41
+ { kind: 'printf', source: String.raw `%\(\w+\)[-#0 +]*\d*(?:\.\d+)?[sdifr]` },
42
+ // Escaped percent in printf-style (gettext) strings: "50%% off"
43
+ { kind: 'printf', source: '%%' },
44
+ // printf: %s %d %1$s %.2f %@ — not "%" followed by a word ("50%done")
45
+ { kind: 'printf', source: String.raw `%(?:\d+\$)?[-+0#]*\d*(?:\.\d+)?[sdifu@xXc](?![A-Za-z])` },
46
+ // ICU simple / formatted args and positional args: {name} {0} {price, number} {d, date, short}
47
+ { kind: 'argument', source: String.raw `\{\s*[\w.]+\s*(?:,\s*(?:number|date|time)\s*(?:,\s*[^{}]+)?)?\}` },
48
+ // Python str.format positional field: "Only {} left"
49
+ { kind: 'argument', source: String.raw `\{\}` },
50
+ // HTML character references in markup-bearing text: &mdash; &nbsp; &#8217;
51
+ { kind: 'entity', source: String.raw `&(?:[a-zA-Z][a-zA-Z0-9]{1,31}|#\d{1,7}|#x[0-9a-fA-F]{1,6});` },
52
+ // Markup / react-i18next Trans tags: <0>, </0>, <1/>, <b>, </strong>, <br />, <Link href="...">
53
+ { kind: 'tag', source: String.raw `<\/?\d+\s*\/?>|<\/?[A-Za-z][\w.:-]*(?:\s+[^<>]*?)?\s*\/?>` },
54
+ ];
55
+ function escapeRegex(s) {
56
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
57
+ }
58
+ /** A term as a whole term in any script (same rule as modules/glossary termPattern). */
59
+ function wholeTerm(term) {
60
+ return `(?<![\\p{L}\\p{N}_])${escapeRegex(term)}(?![\\p{L}\\p{N}_])`;
61
+ }
62
+ /** In ICU messages braces are structure, so only these kinds are tokenized. */
63
+ const ICU_SAFE_KINDS = new Set(['url', 'email', 'code', 'tag', 'entity']);
64
+ /**
65
+ * Placeholders, tags, URLs etc. come first, so a term never matches inside
66
+ * one ("{dashboard}", "https://\u2026/settings"). Terms match longest first; a
67
+ * never-translate term wins over a fixed term spelled the same (ignoring
68
+ * case). Fixed terms are the named groups `f<i>` (index into `fixed`).
69
+ */
70
+ function buildPattern(neverTranslateTerms, icu, fixedTerms) {
71
+ const sources = PROTECTED_PATTERNS.filter((p) => !icu || ICU_SAFE_KINDS.has(p.kind)).map((p) => p.source);
72
+ const never = [...new Set(neverTranslateTerms.filter((t) => t.trim().length > 0))];
73
+ const neverLower = new Set(never.map((t) => t.toLowerCase()));
74
+ const seen = new Set();
75
+ const fixed = fixedTerms.filter((f) => {
76
+ const k = f.term.toLowerCase();
77
+ if (!f.term.trim() || !f.translation || neverLower.has(k) || seen.has(k))
78
+ return false;
79
+ seen.add(k);
80
+ return true;
81
+ });
82
+ const terms = [
83
+ ...never.map((t) => ({ term: t, source: wholeTerm(t) })),
84
+ ...fixed.map((f, i) => ({ term: f.term, source: `(?<f${i}>${wholeTerm(f.term)})` })),
85
+ ].sort((a, b) => b.term.length - a.term.length);
86
+ if (terms.length > 0)
87
+ sources.push(terms.map((t) => t.source).join('|'));
88
+ return { pattern: new RegExp(sources.map((s) => `(?:${s})`).join('|'), 'giu'), fixed };
89
+ }
90
+ /**
91
+ * Replace every protected span (and every never-translate term) with a token.
92
+ * With `icu: true`, braces are left alone (they are ICU structure) and only
93
+ * URLs, emails, code, tags and terms are tokenized.
94
+ * `fixedTerms` ("always translate as") are tokenized too, and `restore` maps
95
+ * their tokens to the fixed translation: a plain-text translator never sees
96
+ * the term, so it can't ignore the rule.
97
+ * Literal `[#VAR_n#]` text already present in the source is protected too, so
98
+ * it can never be confused with an issued token.
99
+ */
100
+ function protectText(text, neverTranslateTerms = [], options = {}) {
101
+ const tokens = [];
102
+ const restore = [];
103
+ const { pattern, fixed } = buildPattern(neverTranslateTerms, options.icu ?? false, options.fixedTerms ?? []);
104
+ // Guard pre-existing token-shaped text first.
105
+ const guarded = text.replace(TOKEN_PATTERN, (m) => {
106
+ tokens.push(m);
107
+ restore.push(m);
108
+ return `\uE000${tokens.length - 1}\uE000`;
109
+ });
110
+ const protectedText = guarded
111
+ .replace(pattern, (...args) => {
112
+ const match = args[0];
113
+ const groups = args[args.length - 1];
114
+ let replacement = match;
115
+ if (fixed.length > 0 && groups && typeof groups === 'object') {
116
+ const g = groups;
117
+ const i = fixed.findIndex((_, idx) => g[`f${idx}`] !== undefined);
118
+ if (i >= 0)
119
+ replacement = fixed[i].translation;
120
+ }
121
+ tokens.push(match);
122
+ restore.push(replacement);
123
+ return tokenFor(tokens.length - 1);
124
+ })
125
+ .replace(/\uE000(\d+)\uE000/g, (_, i) => tokenFor(Number(i)));
126
+ return { text: protectedText, tokens, restore };
127
+ }
128
+ /** Every issued token must appear exactly once, and no other token may appear. */
129
+ function checkTokens(translated, tokenCount) {
130
+ const counts = new Map();
131
+ for (const m of translated.matchAll(TOKEN_PATTERN)) {
132
+ const i = Number(m[1]);
133
+ counts.set(i, (counts.get(i) ?? 0) + 1);
134
+ }
135
+ const missing = [];
136
+ const duplicated = [];
137
+ for (let i = 0; i < tokenCount; i++) {
138
+ const c = counts.get(i) ?? 0;
139
+ if (c === 0)
140
+ missing.push(i);
141
+ else if (c > 1)
142
+ duplicated.push(i);
143
+ }
144
+ const unexpected = [...counts.keys()].filter((i) => i >= tokenCount);
145
+ return { ok: missing.length === 0 && duplicated.length === 0 && unexpected.length === 0, missing, duplicated, unexpected };
146
+ }
147
+ /**
148
+ * Swap tokens back. Pass protectText's `restore` (fixed glossary terms become
149
+ * their translation) or `tokens` (original text). Call only after checkTokens passed.
150
+ */
151
+ function restoreText(translated, tokens) {
152
+ return translated.replace(TOKEN_PATTERN, (m, i) => tokens[Number(i)] ?? m);
153
+ }
154
+ /**
155
+ * True when nothing translatable is left once protected spans are removed —
156
+ * e.g. "{count}", "GitHub", "https://…", "%s / %s". Such strings are their
157
+ * own translation and should not be sent to a translator.
158
+ */
159
+ function hasNothingToTranslate(protectedText) {
160
+ return !/\p{L}/u.test(protectedText.replace(TOKEN_PATTERN, ''));
161
+ }
162
+ /** Human-readable description of a failed token check, for retry prompts and reports. */
163
+ function describeTokenProblem(check, tokens) {
164
+ const parts = [];
165
+ if (check.missing.length)
166
+ parts.push(`dropped ${check.missing.map((i) => `${tokenFor(i)} (${tokens[i]})`).join(', ')}`);
167
+ if (check.duplicated.length)
168
+ parts.push(`repeated ${check.duplicated.map(tokenFor).join(', ')}`);
169
+ if (check.unexpected.length)
170
+ parts.push(`invented ${check.unexpected.map(tokenFor).join(', ')}`);
171
+ return parts.join('; ');
172
+ }
173
+ /**
174
+ * Whether two strings contain the same protected spans (variables, tags,
175
+ * URLs, terms), ignoring order. Used to vet rewrites of an already-validated
176
+ * translation, e.g. a brand-voice polish pass.
177
+ */
178
+ function sameProtectedSpans(a, b, neverTranslateTerms = []) {
179
+ const spans = (t) => protectText(t, neverTranslateTerms).tokens.slice().sort().join('\u0001');
180
+ return spans(a) === spans(b);
181
+ }
@@ -0,0 +1,9 @@
1
+ /** Marks pseudo-localized text (`babelize translate --provider pseudo`); never part of a real translation. */
2
+ export declare const PSEUDO_OPEN = "\u27E6";
3
+ export declare const PSEUDO_CLOSE = "\u27E7";
4
+ /**
5
+ * True for pseudo-localized output: text wrapped in ⟦…⟧ (whole value, or
6
+ * each branch of a plural the pseudo provider built). Accented letters alone
7
+ * are not a signal — "Sávé" could be real text in many languages.
8
+ */
9
+ export declare function isPseudoLocalized(value: string): boolean;
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PSEUDO_CLOSE = exports.PSEUDO_OPEN = void 0;
4
+ exports.isPseudoLocalized = isPseudoLocalized;
5
+ /** Marks pseudo-localized text (`babelize translate --provider pseudo`); never part of a real translation. */
6
+ exports.PSEUDO_OPEN = '⟦';
7
+ exports.PSEUDO_CLOSE = '⟧';
8
+ /**
9
+ * True for pseudo-localized output: text wrapped in ⟦…⟧ (whole value, or
10
+ * each branch of a plural the pseudo provider built). Accented letters alone
11
+ * are not a signal — "Sávé" could be real text in many languages.
12
+ */
13
+ function isPseudoLocalized(value) {
14
+ const open = value.indexOf(exports.PSEUDO_OPEN);
15
+ return open !== -1 && value.indexOf(exports.PSEUDO_CLOSE, open + 1) !== -1;
16
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Engine v2 string utilities.
3
+ *
4
+ * Copied from v1 to preserve keygen/normalization behavior.
5
+ */
6
+ export declare function normalizeString(text: string): string;
7
+ export declare function toCamelCase(text: string): string;
8
+ export declare function inferNamespace(filePath: string): string;
9
+ export declare function generateUniqueKey(baseKey: string, existingKeys: Set<string>): string;
10
+ export declare function truncateForKey(text: string, maxWords?: number): string;
11
+ export declare function isTranslatableText(text: string): boolean;
12
+ export declare function extractPlaceholders(text: string): string[];
13
+ /**
14
+ * Protect placeholders by replacing them with AI-safe tokens before translation
15
+ */
16
+ export declare function protectPlaceholders(text: string): {
17
+ protectedText: string;
18
+ vars: string[];
19
+ };
20
+ export interface RestorationResult {
21
+ restoredText: string;
22
+ restoredCount: number;
23
+ expectedCount: number;
24
+ missingTokens: string[];
25
+ }
26
+ /**
27
+ * Restore placeholders from AI-safe tokens after translation
28
+ * Returns a diagnostic result to detect dropped variables
29
+ */
30
+ export declare function restorePlaceholders(text: string, vars: string[]): RestorationResult;
31
+ /**
32
+ * Check if text text and associated variables likely represent a pluralized string
33
+ * (e.g. "count files", "Total {length}")
34
+ */
35
+ export declare function isLikelyPlural(text: string, variables?: string[]): boolean;
36
+ export declare function hasICUSyntax(text: string): boolean;
37
+ export declare function escapeRegex(text: string): string;
38
+ export declare function hashString(text: string): string;
39
+ export declare function matchesCurrencyPattern(text: string): boolean;
40
+ export declare function matchesDatePattern(text: string): boolean;
41
+ export declare function containsPII(text: string): boolean;
42
+ export declare const SKIP_TAGS: Set<string>;
43
+ /**
44
+ * Extract content between balanced opening and closing tags, handling nested tags.
45
+ * Finds the first `<tagName>` and its matching `</tagName>`, correctly counting
46
+ * nested `<tagName ...>` elements.
47
+ */
48
+ export declare function extractBalancedTag(content: string, tagName: string, allowAttrs?: boolean): {
49
+ fullMatch: string;
50
+ body: string;
51
+ attrs: string;
52
+ } | null;