@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,393 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.__test = exports.MAX_BRANCH_UNITS = void 0;
4
+ exports.targetCategories = targetCategories;
5
+ exports.planIcuBranches = planIcuBranches;
6
+ exports.escapeIcuText = escapeIcuText;
7
+ /**
8
+ * ICU plural/select messages for plain-text machine translation (NLLB).
9
+ *
10
+ * NLLB translates sentences, not ICU syntax. So an ICU message is taken apart
11
+ * into the plain sentences it can render, each sentence is translated on its
12
+ * own, and the translations are put back together as an ICU message for the
13
+ * target language:
14
+ *
15
+ * - Text around a selector is moved into its branches, so every translated
16
+ * unit is a whole sentence ("You have {count, plural, one {# file} other
17
+ * {# files}}." → one {You have # file.} other {You have # files.}).
18
+ * - A plural gets the TARGET language's CLDR categories (Polish one/few/many/
19
+ * other, Japanese only other). Each category is translated from the source
20
+ * branch of the same name, or the source `other`; exact `=n` branches stay.
21
+ * - The count (`#`, `{count}`, or a gettext `%(count)d`) is replaced by a real
22
+ * number of that category (Polish few → 2, many → 5, Arabic many → 11), so
23
+ * the translator inflects the words around it. The same number must come
24
+ * back exactly where it can be turned back into the count; if it doesn't,
25
+ * that branch — and so the message — fails.
26
+ * - `select` branches keep their keys; nested selectors are expanded the same
27
+ * way. Anything that can't be expressed fails with a reason; a message is
28
+ * never put together from a partial set of branches.
29
+ *
30
+ * Infrastructure-free: the caller sends the units to its translator and hands
31
+ * the per-unit results back to `compose`.
32
+ */
33
+ const icu_message_1 = require("../../utils/icu-message");
34
+ const placeholder_protection_1 = require("../../utils/placeholder-protection");
35
+ const translation_sanitizer_1 = require("../../utils/translation-sanitizer");
36
+ /** Leaves (translated sentences) one message may expand to. */
37
+ exports.MAX_BRANCH_UNITS = 64;
38
+ const CLDR_ORDER = ['zero', 'one', 'two', 'few', 'many', 'other'];
39
+ /**
40
+ * Numbers tried for a plural category, most natural first. NLLB tends to
41
+ * spell out small numbers ("five files", "fünf Akten"), which loses the
42
+ * count, so two- and three-digit numbers come first; 1 and 0 only where
43
+ * nothing else fits (a branch for exactly one number may be spelled out).
44
+ */
45
+ const CANDIDATES = (() => {
46
+ const preferred = [25, 22, 23, 24, 21, 26, 27, 28, 29, 103, 104, 101, 102, 111, 11, 12, 13, 14, 15, 2, 3, 4, 5, 6, 7, 8, 9, 10, 100, 1, 0];
47
+ const rest = [];
48
+ for (let n = 16; n <= 200; n++)
49
+ if (!preferred.includes(n))
50
+ rest.push(n);
51
+ // Russian/Polish `other` (and similar) is only reached by fractions.
52
+ return [...preferred, ...rest, 1.5, 2.5, 0.5, 3.5];
53
+ })();
54
+ const ARG_SENTINEL = /\uE001(\d+)\uE001/g;
55
+ const POUND_SENTINEL = '\uE002';
56
+ const TOKEN_PATTERN = /\[\s*#\s*VAR_\d+\s*#\s*\]/gi;
57
+ class PlanError extends Error {
58
+ }
59
+ /** Plural categories of a locale, CLDR order. */
60
+ function targetCategories(locale, type) {
61
+ try {
62
+ const found = new Set(new Intl.PluralRules(locale, { type }).resolvedOptions().pluralCategories);
63
+ return CLDR_ORDER.filter((c) => found.has(c));
64
+ }
65
+ catch {
66
+ return ['one', 'other'];
67
+ }
68
+ }
69
+ function rulesFor(locale, type) {
70
+ return new Intl.PluralRules(locale, { type: type === 'selectordinal' ? 'ordinal' : 'cardinal' });
71
+ }
72
+ /** Candidate numbers for a category (or the exact value of an `=n` branch). */
73
+ function numbersFor(locale, type, key) {
74
+ if (/^=\d+$/.test(key))
75
+ return [Number(key.slice(1))];
76
+ const rules = rulesFor(locale, type);
77
+ return CANDIDATES.filter((n) => rules.select(n) === key);
78
+ }
79
+ /** Keys a plural gets in the target language: the source's exact `=n` keys, then each target category that has a usable number. */
80
+ function targetKeys(sel, locale) {
81
+ const exact = Object.keys(sel.options).filter((k) => /^=\d+$/.test(k));
82
+ const cats = targetCategories(locale, sel.type === 'selectordinal' ? 'ordinal' : 'cardinal')
83
+ // A category reached only by huge numbers (French "many" = millions) is left
84
+ // to ICU's `other` fallback: such a number doesn't survive translation intact.
85
+ .filter((c) => c === 'other' || numbersFor(locale, sel.type, c).length > 0);
86
+ return [...exact, ...cats];
87
+ }
88
+ function sourceBranch(sel, key) {
89
+ const branch = sel.options[key] ?? sel.options.other;
90
+ if (!branch)
91
+ throw new PlanError(`{${sel.name}, ${sel.type}} has no "other" branch`);
92
+ return branch;
93
+ }
94
+ const isSelector = (n) => n.type === 'plural' || n.type === 'select' || n.type === 'selectordinal';
95
+ /**
96
+ * Plan the branch-by-branch translation of `source` into `targetLocale`, or
97
+ * explain why it can't be done.
98
+ */
99
+ function planIcuBranches(source, targetLocale, options = {}) {
100
+ try {
101
+ if (/,\s*(?:plural|selectordinal)\s*,\s*offset\s*:/.test(source)) {
102
+ throw new PlanError('plural offsets are not supported');
103
+ }
104
+ let nodes = (0, icu_message_1.parseIcu)(source);
105
+ for (const name of [...(options.pluralizeArgs ?? [])].reverse()) {
106
+ nodes = [{ type: 'plural', name, options: { other: nodes } }];
107
+ }
108
+ const units = [];
109
+ const tree = build(nodes, [], { units, targetLocale, options });
110
+ return { units, compose: (results, checkUnit) => compose(tree, results, checkUnit) };
111
+ }
112
+ catch (err) {
113
+ if (err instanceof PlanError || err instanceof icu_message_1.IcuParseError)
114
+ return { error: err.message };
115
+ throw err;
116
+ }
117
+ }
118
+ function build(nodes, path, state) {
119
+ const at = nodes.findIndex(isSelector);
120
+ if (at === -1) {
121
+ if (state.units.length >= exports.MAX_BRANCH_UNITS) {
122
+ throw new PlanError(`the message has more than ${exports.MAX_BRANCH_UNITS} branch combinations`);
123
+ }
124
+ const unit = makeUnit(nodes, path, state);
125
+ state.units.push(unit);
126
+ return { kind: 'leaf', unit };
127
+ }
128
+ const sel = nodes[at];
129
+ const prefix = nodes.slice(0, at);
130
+ const suffix = nodes.slice(at + 1);
131
+ // Moving text into the branches of `sel` must not move a `#` of an outer
132
+ // plural under another selector: it would then mean something else.
133
+ if ([...prefix, ...suffix].some((n) => n.type === 'pound')) {
134
+ throw new PlanError(`"#" outside the nested {${sel.name}, ${sel.type}} can't be translated branch by branch`);
135
+ }
136
+ if (path.some((p) => p.name === sel.name)) {
137
+ throw new PlanError(`{${sel.name}} is selected on twice`);
138
+ }
139
+ const keys = sel.type === 'select' ? Object.keys(sel.options) : targetKeys(sel, state.targetLocale);
140
+ if (sel.type === 'select' && !keys.includes('other')) {
141
+ throw new PlanError(`{${sel.name}, select} has no "other" branch`);
142
+ }
143
+ const options = keys.map((key) => [
144
+ key,
145
+ build([...prefix, ...sourceBranch(sel, key), ...suffix], [...path, { type: sel.type, name: sel.name, key }], state),
146
+ ]);
147
+ return { kind: 'selector', type: sel.type, name: sel.name, options };
148
+ }
149
+ function argSource(n) {
150
+ return n.format ? `{${n.name}, ${n.format.replace(/^(\w+),\s*/, '$1, ')}}` : `{${n.name}}`;
151
+ }
152
+ /** Digits already in the text (so a representative number is not confused with them). */
153
+ function digitRuns(text) {
154
+ return new Set([...text.replace(TOKEN_PATTERN, ' ').matchAll(/\p{Nd}+(?:[.,٫]\p{Nd}+)?/gu)].map((m) => normalizeNumber(m[0])));
155
+ }
156
+ function makeUnit(nodes, path, state) {
157
+ const id = String(state.units.length);
158
+ const parent = path[path.length - 1];
159
+ const inPlural = !!parent && parent.type !== 'select';
160
+ const label = path.length ? path.map((p) => `${p.name}=${p.key}`).join(', ') : 'message';
161
+ const plurals = path.filter((p) => p.type !== 'select');
162
+ const innermostPlural = plurals[plurals.length - 1];
163
+ // 1. Text with sentinels: arguments, `#`, and count literals become placeholders.
164
+ const args = [];
165
+ const literals = [];
166
+ const occurrences = [];
167
+ let text = '';
168
+ const literal = state.options.valuePlaceholder;
169
+ for (const n of nodes) {
170
+ if (n.type === 'text') {
171
+ if (literal && innermostPlural && n.value.includes(literal)) {
172
+ const parts = n.value.split(literal);
173
+ parts.forEach((part, i) => {
174
+ text += part;
175
+ if (i < parts.length - 1) {
176
+ literals.push(literal);
177
+ const restore = `\uE003${literals.length - 1}\uE003`;
178
+ occurrences.push({ owner: innermostPlural, kind: 'literal', restore, label: literal });
179
+ text += `\uE004${occurrences.length - 1}\uE004`;
180
+ }
181
+ });
182
+ }
183
+ else {
184
+ text += n.value;
185
+ }
186
+ }
187
+ else if (n.type === 'pound') {
188
+ // Only ever directly under its plural (see build()).
189
+ occurrences.push({ owner: parent, kind: 'pound', restore: POUND_SENTINEL, label: '#' });
190
+ text += `\uE004${occurrences.length - 1}\uE004`;
191
+ }
192
+ else if (n.type === 'argument') {
193
+ const owner = !n.format ? plurals.find((p) => p.name === n.name) : undefined;
194
+ args.push(argSource(n));
195
+ const restore = `\uE001${args.length - 1}\uE001`;
196
+ if (owner) {
197
+ occurrences.push({ owner, kind: `arg:${n.name}`, restore, label: `{${n.name}}` });
198
+ text += `\uE004${occurrences.length - 1}\uE004`;
199
+ }
200
+ else {
201
+ text += restore;
202
+ }
203
+ }
204
+ }
205
+ // 2. A number of the branch's category for each count occurrence; one number per (plural, kind).
206
+ const taken = digitRuns(text.replace(/[\uE001\uE003\uE004]\d+[\uE001\uE003\uE004]/g, ' '));
207
+ const slotsByKind = new Map();
208
+ const used = new Set();
209
+ for (const occ of occurrences) {
210
+ const kindKey = `${occ.owner.name}\u0000${occ.kind}`;
211
+ let slot = slotsByKind.get(kindKey);
212
+ if (!slot) {
213
+ const numbers = numbersFor(state.targetLocale, occ.owner.type, occ.owner.key).map(String);
214
+ const pick = numbers.find((n) => !taken.has(n) && !used.has(n));
215
+ if (pick === undefined) {
216
+ throw new PlanError(`no number to stand for ${occ.label} in the "${occ.owner.name}=${occ.owner.key}" branch`);
217
+ }
218
+ used.add(pick);
219
+ // A plural added over a plain count must show it in every branch (compareIcuMessages), so no spelling out there.
220
+ const mayBeSpelledOut = numbers.length === 1 && !(state.options.pluralizeArgs ?? []).includes(occ.owner.name);
221
+ slot = { text: pick, count: 0, restore: occ.restore, label: occ.label, mayBeSpelledOut };
222
+ slotsByKind.set(kindKey, slot);
223
+ }
224
+ slot.count++;
225
+ }
226
+ // Every occurrence of a literal restores the same text, so one slot (and its first sentinel) serves them all.
227
+ const numbered = text.replace(/\uE004(\d+)\uE004/g, (_, i) => slotsByKind.get(`${occurrences[Number(i)].owner.name}\u0000${occurrences[Number(i)].kind}`).text);
228
+ // 3. Protect tags, URLs and terms; arguments left as sentinels become tokens too.
229
+ const never = state.options.neverTranslateTerms ?? [];
230
+ const fixedTerms = state.options.fixedTerms ?? [];
231
+ const prot = (0, placeholder_protection_1.protectText)(numbered, never, { fixedTerms });
232
+ const tokens = [...prot.tokens];
233
+ const restore = [...prot.restore];
234
+ const tokenized = prot.text.replace(ARG_SENTINEL, (m) => {
235
+ tokens.push(m);
236
+ restore.push(m);
237
+ return `[#VAR_${tokens.length - 1}#]`;
238
+ });
239
+ // The translator service masks tokens as numbers (7007): a count glued to a
240
+ // token ("<b>5</b>") would merge into one digit run and break both. Keep
241
+ // them apart while translating; the space is taken out again afterwards.
242
+ const sendText = tokenized
243
+ .replace(/(\[#VAR_\d+#\])(?=\p{Nd})/gu, '$1 ')
244
+ .replace(/(?<=\p{Nd})(\[#VAR_\d+#\])/gu, ' $1');
245
+ return {
246
+ id,
247
+ label,
248
+ sendText,
249
+ spaced: sendText !== tokenized,
250
+ tokens,
251
+ restore,
252
+ slots: [...slotsByKind.values()],
253
+ passthrough: (0, placeholder_protection_1.hasNothingToTranslate)(sendText),
254
+ args,
255
+ literals,
256
+ inPlural,
257
+ sentinelText: withFixedTerms(text.replace(/\uE004(\d+)\uE004/g, (_, i) => occurrences[Number(i)].restore), never, fixedTerms),
258
+ };
259
+ }
260
+ const ZEROS = [0x30, 0x660, 0x6f0, 0x7c0, 0x966, 0x9e6, 0xa66, 0xae6, 0xb66, 0xbe6, 0xc66, 0xce6, 0xd66, 0xde6, 0xe50, 0xed0, 0xf20, 0x1040, 0x1090, 0x17e0, 0x1810, 0x1946, 0x19d0, 0xff10];
261
+ /** "١٢" → "12", "1,5" → "1.5". */
262
+ function normalizeNumber(s) {
263
+ let out = '';
264
+ for (const ch of s) {
265
+ const cp = ch.codePointAt(0);
266
+ const zero = ZEROS.find((z) => cp >= z && cp <= z + 9);
267
+ out += zero !== undefined ? String(cp - zero) : '.';
268
+ }
269
+ return out;
270
+ }
271
+ /** Replace each slot's number in the translation by its sentinel; null when a number is missing or repeated. */
272
+ function restoreNumbers(translated, slots) {
273
+ if (slots.length === 0)
274
+ return { text: translated };
275
+ const found = new Map();
276
+ // Numbers only count outside [#VAR_n#] tokens.
277
+ const pieces = translated.split(/(\[\s*#\s*VAR_\d+\s*#\s*\])/i);
278
+ const out = pieces.map((piece, i) => {
279
+ if (i % 2 === 1)
280
+ return piece;
281
+ return piece.replace(/(?<![\p{Nd}.,٫])\p{Nd}+(?:[.,٫]\p{Nd}+)?(?![\p{Nd}])/gu, (m) => {
282
+ const value = normalizeNumber(m);
283
+ const slot = slots.find((s) => s.text === value);
284
+ if (!slot)
285
+ return m;
286
+ found.set(slot, (found.get(slot) ?? 0) + 1);
287
+ return slot.restore;
288
+ });
289
+ }).join('');
290
+ for (const slot of slots) {
291
+ const n = found.get(slot) ?? 0;
292
+ if (n === 0 && slot.mayBeSpelledOut)
293
+ continue;
294
+ if (n !== slot.count) {
295
+ return {
296
+ problem: n === 0
297
+ ? `the number ${slot.text} standing for ${slot.label} was dropped`
298
+ : `the number ${slot.text} standing for ${slot.label} appears ${n} times (expected ${slot.count})`,
299
+ };
300
+ }
301
+ }
302
+ return { text: out };
303
+ }
304
+ /** ICU-escape literal text: apostrophes before syntax, and `{`, `}` (and `#` in a plural) quoted. */
305
+ function escapeIcuText(text, inPlural) {
306
+ const special = inPlural ? /[{}#]/ : /[{}]/;
307
+ let out = '';
308
+ for (let i = 0; i < text.length; i++) {
309
+ const ch = text[i];
310
+ if (ch === "'") {
311
+ const next = text[i + 1];
312
+ out += next === undefined || /[{}#']/.test(next) ? "''" : "'";
313
+ }
314
+ else if (special.test(ch)) {
315
+ let j = i;
316
+ while (j < text.length && special.test(text[j]))
317
+ j++;
318
+ out += `'${text.slice(i, j)}'`;
319
+ i = j - 1;
320
+ }
321
+ else {
322
+ out += ch;
323
+ }
324
+ }
325
+ return out;
326
+ }
327
+ /** Text with sentinels → ICU source for a unit. */
328
+ /** A passthrough branch is used as is, except that fixed glossary terms become their translation. */
329
+ function withFixedTerms(text, never, fixedTerms) {
330
+ if (fixedTerms.length === 0)
331
+ return text;
332
+ const p = (0, placeholder_protection_1.protectText)(text, never, { fixedTerms });
333
+ return (0, placeholder_protection_1.restoreText)(p.text, p.restore);
334
+ }
335
+ function serializeUnit(unit, sentinelText) {
336
+ const parts = sentinelText.split(/(\uE001\d+\uE001|\uE002|\uE003\d+\uE003)/);
337
+ return parts.map((p, i) => {
338
+ if (i % 2 === 0)
339
+ return escapeIcuText(p, unit.inPlural);
340
+ if (p === POUND_SENTINEL)
341
+ return '#';
342
+ const arg = /^\uE001(\d+)\uE001$/.exec(p);
343
+ if (arg)
344
+ return unit.args[Number(arg[1])];
345
+ const lit = /^\uE003(\d+)\uE003$/.exec(p);
346
+ return escapeIcuText(unit.literals[Number(lit[1])], unit.inPlural);
347
+ }).join('');
348
+ }
349
+ function translateUnit(unit, result, checkUnit) {
350
+ const fail = (reason, retryable, message) => ({ ok: false, reason, retryable, message: `${unit.label}: ${message}` });
351
+ if (unit.passthrough)
352
+ return { ok: true, value: serializeUnit(unit, unit.sentinelText) };
353
+ if (!result)
354
+ return fail('missing_from_response', true, 'the translator returned no translation for this branch');
355
+ if (result.status === 'failed')
356
+ return fail(result.reason, result.retryable, result.message ?? `translator reported ${result.reason}`);
357
+ const raw = (0, translation_sanitizer_1.sanitizeTranslation)(result.value, { nllb: true, source: unit.sendText });
358
+ if (!raw.trim())
359
+ return fail('empty_value', true, 'the translator returned an empty string');
360
+ const tokenCheck = (0, placeholder_protection_1.checkTokens)(raw, unit.tokens.length);
361
+ if (!tokenCheck.ok)
362
+ return fail('placeholder_lost', true, `Placeholder problem: ${(0, placeholder_protection_1.describeTokenProblem)(tokenCheck, unit.tokens)}`);
363
+ const numbers = restoreNumbers(raw, unit.slots);
364
+ if ('problem' in numbers)
365
+ return fail('placeholder_lost', true, `Placeholder problem: ${numbers.problem}`);
366
+ const extra = checkUnit?.(unit, raw);
367
+ if (extra)
368
+ return { ...extra, message: `${unit.label}: ${extra.message}` };
369
+ // Tokens back to their text; argument tokens become sentinels again.
370
+ let restored = numbers.text;
371
+ if (unit.spaced) {
372
+ // Glue counts and tokens back together where the source had them glued.
373
+ restored = restored
374
+ .replace(/(\[\s*#\s*VAR_\d+\s*#\s*\])[ \u00A0]+(?=\uE002|\uE001\d+\uE001|\uE003\d+\uE003)/gi, '$1')
375
+ .replace(/(\uE002|\uE001\d+\uE001|\uE003\d+\uE003)[ \u00A0]+(?=\[\s*#\s*VAR_)/gi, '$1');
376
+ }
377
+ const sentinelText = restored.replace(/\[\s*#\s*VAR_(\d+)\s*#\s*\]/gi, (_, i) => unit.restore[Number(i)]);
378
+ return { ok: true, value: serializeUnit(unit, sentinelText) };
379
+ }
380
+ function compose(tree, results, checkUnit) {
381
+ if (tree.kind === 'leaf')
382
+ return translateUnit(tree.unit, results.get(tree.unit.id), checkUnit);
383
+ const parts = [];
384
+ for (const [key, child] of tree.options) {
385
+ const r = compose(child, results, checkUnit);
386
+ if (!r.ok)
387
+ return r;
388
+ parts.push(`${key} {${r.value}}`);
389
+ }
390
+ return { ok: true, value: `{${tree.name}, ${tree.type}, ${parts.join(' ')}}` };
391
+ }
392
+ /** Exposed for tests. */
393
+ exports.__test = { normalizeNumber, restoreNumbers, numbersFor };
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Babelize Error Types
3
+ *
4
+ * Typed error class for structured error handling across the engine.
5
+ */
6
+ export type BabelizeErrorCode = 'UNSUPPORTED_LANGUAGE' | 'TRANSLATION_FAILED' | 'INVALID_INPUT';
7
+ export declare class BabelizeError extends Error {
8
+ readonly code: BabelizeErrorCode;
9
+ constructor(code: BabelizeErrorCode, message: string);
10
+ }
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ /**
3
+ * Babelize Error Types
4
+ *
5
+ * Typed error class for structured error handling across the engine.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.BabelizeError = void 0;
9
+ class BabelizeError extends Error {
10
+ constructor(code, message) {
11
+ super(message);
12
+ this.name = 'BabelizeError';
13
+ this.code = code;
14
+ // Maintains proper prototype chain for instanceof checks
15
+ Object.setPrototypeOf(this, BabelizeError.prototype);
16
+ }
17
+ }
18
+ exports.BabelizeError = BabelizeError;
@@ -0,0 +1 @@
1
+ export declare const FLORES_MAP: Record<string, string>;
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FLORES_MAP = void 0;
4
+ exports.FLORES_MAP = {
5
+ af: "afr_Latn",
6
+ sq: "als_Latn",
7
+ am: "amh_Ethi",
8
+ ar: "arb_Arab",
9
+ hy: "hye_Armn",
10
+ az: "azj_Latn",
11
+ eu: "eus_Latn",
12
+ be: "bel_Cyrl",
13
+ bn: "ben_Beng",
14
+ bs: "bos_Latn",
15
+ bg: "bul_Cyrl",
16
+ my: "mya_Mymr",
17
+ ca: "cat_Latn",
18
+ ceb: "ceb_Latn",
19
+ hr: "hrv_Latn",
20
+ cs: "ces_Latn",
21
+ da: "dan_Latn",
22
+ nl: "nld_Latn",
23
+ en: "eng_Latn",
24
+ eo: "epo_Latn",
25
+ et: "est_Latn",
26
+ tl: "tgl_Latn",
27
+ fi: "fin_Latn",
28
+ fr: "fra_Latn",
29
+ gl: "glg_Latn",
30
+ ka: "kat_Geor",
31
+ de: "deu_Latn",
32
+ el: "ell_Grek",
33
+ gu: "guj_Gujr",
34
+ ht: "hat_Latn",
35
+ ha: "hau_Latn",
36
+ haw: "haw_Latn",
37
+ he: "heb_Hebr",
38
+ hi: "hin_Deva",
39
+ hmn: "hmn_Latn",
40
+ hu: "hun_Latn",
41
+ is: "isl_Latn",
42
+ ig: "ibo_Latn",
43
+ id: "ind_Latn",
44
+ ga: "gle_Latn",
45
+ it: "ita_Latn",
46
+ ja: "jpn_Jpan",
47
+ jw: "jav_Latn",
48
+ kn: "kan_Knda",
49
+ kk: "kaz_Cyrl",
50
+ km: "khm_Khmr",
51
+ ko: "kor_Hang",
52
+ ku: "kmr_Latn",
53
+ ky: "kir_Cyrl",
54
+ lo: "lao_Laoo",
55
+ la: "lat_Latn",
56
+ lv: "lvs_Latn",
57
+ lt: "lit_Latn",
58
+ lb: "ltz_Latn",
59
+ mk: "mkd_Cyrl",
60
+ mg: "plt_Latn",
61
+ ms: "zsm_Latn",
62
+ ml: "mal_Mlym",
63
+ mt: "mlt_Latn",
64
+ mi: "mri_Latn",
65
+ mr: "mar_Deva",
66
+ mn: "khk_Cyrl",
67
+ ne: "npi_Deva",
68
+ no: "nob_Latn",
69
+ nb: "nob_Latn",
70
+ ny: "nya_Latn",
71
+ ps: "pbt_Arab",
72
+ fa: "pes_Arab",
73
+ pl: "pol_Latn",
74
+ pt: "por_Latn",
75
+ pa: "pan_Guru",
76
+ ro: "ron_Latn",
77
+ ru: "rus_Cyrl",
78
+ sm: "smo_Latn",
79
+ gd: "gla_Latn",
80
+ sr: "srp_Cyrl",
81
+ st: "sot_Latn",
82
+ sn: "sna_Latn",
83
+ sd: "snd_Arab",
84
+ si: "sin_Sinh",
85
+ sk: "slk_Latn",
86
+ sl: "slv_Latn",
87
+ so: "som_Latn",
88
+ es: "spa_Latn",
89
+ su: "sun_Latn",
90
+ sw: "swh_Latn",
91
+ sv: "swe_Latn",
92
+ tg: "tgk_Cyrl",
93
+ ta: "tam_Taml",
94
+ te: "tel_Telu",
95
+ th: "tha_Thai",
96
+ tr: "tur_Latn",
97
+ uk: "ukr_Cyrl",
98
+ ur: "urd_Arab",
99
+ uz: "uzn_Latn",
100
+ vi: "vie_Latn",
101
+ cy: "cym_Latn",
102
+ xh: "xho_Latn",
103
+ yi: "ydd_Hebr",
104
+ yo: "yor_Latn",
105
+ zu: "zul_Latn",
106
+ zh: "zho_Hans",
107
+ };
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Language Codes Module — barrel export
3
+ *
4
+ * Single import point for all language code utilities:
5
+ * import { toFlores, isSupported, RTL_LANGUAGES } from '../modules/translator/language-codes';
6
+ */
7
+ export { BabelizeError } from './errors';
8
+ export type { BabelizeErrorCode } from './errors';
9
+ export { FLORES_MAP } from './flores-map';
10
+ export { toFlores, fromFlores, isSupported, isNllbSupported, getSupportedLanguages, storageLangCode, localeFromStorageCode } from './language-mapper';
11
+ export { RTL_LANGUAGES, isRTL } from './rtl-languages';
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ /**
3
+ * Language Codes Module — barrel export
4
+ *
5
+ * Single import point for all language code utilities:
6
+ * import { toFlores, isSupported, RTL_LANGUAGES } from '../modules/translator/language-codes';
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.isRTL = exports.RTL_LANGUAGES = exports.localeFromStorageCode = exports.storageLangCode = exports.getSupportedLanguages = exports.isNllbSupported = exports.isSupported = exports.fromFlores = exports.toFlores = exports.FLORES_MAP = exports.BabelizeError = void 0;
10
+ var errors_1 = require("./errors");
11
+ Object.defineProperty(exports, "BabelizeError", { enumerable: true, get: function () { return errors_1.BabelizeError; } });
12
+ var flores_map_1 = require("./flores-map");
13
+ Object.defineProperty(exports, "FLORES_MAP", { enumerable: true, get: function () { return flores_map_1.FLORES_MAP; } });
14
+ var language_mapper_1 = require("./language-mapper");
15
+ Object.defineProperty(exports, "toFlores", { enumerable: true, get: function () { return language_mapper_1.toFlores; } });
16
+ Object.defineProperty(exports, "fromFlores", { enumerable: true, get: function () { return language_mapper_1.fromFlores; } });
17
+ Object.defineProperty(exports, "isSupported", { enumerable: true, get: function () { return language_mapper_1.isSupported; } });
18
+ Object.defineProperty(exports, "isNllbSupported", { enumerable: true, get: function () { return language_mapper_1.isNllbSupported; } });
19
+ Object.defineProperty(exports, "getSupportedLanguages", { enumerable: true, get: function () { return language_mapper_1.getSupportedLanguages; } });
20
+ Object.defineProperty(exports, "storageLangCode", { enumerable: true, get: function () { return language_mapper_1.storageLangCode; } });
21
+ Object.defineProperty(exports, "localeFromStorageCode", { enumerable: true, get: function () { return language_mapper_1.localeFromStorageCode; } });
22
+ var rtl_languages_1 = require("./rtl-languages");
23
+ Object.defineProperty(exports, "RTL_LANGUAGES", { enumerable: true, get: function () { return rtl_languages_1.RTL_LANGUAGES; } });
24
+ Object.defineProperty(exports, "isRTL", { enumerable: true, get: function () { return rtl_languages_1.isRTL; } });
@@ -0,0 +1,25 @@
1
+ /**
2
+ * FLORES-200 code for an ISO locale.
3
+ * - Region variants map to their language (pt-BR → por_Latn); Chinese regions
4
+ * pick the script (zh-TW → zho_Hant, zh-CN → zho_Hans).
5
+ * - Script subtags are honoured: sr-Cyrl → srp_Cyrl, pa-Arab → pnb_Arab. A
6
+ * script FLORES-200 lacks gets a FLORES-style code (sr-Latn → srp_Latn) that
7
+ * only LLM providers can use — see isNllbSupported().
8
+ */
9
+ export declare function toFlores(isoCode: string): string;
10
+ export declare function fromFlores(floresCode: string): string;
11
+ /** True when any translator can handle the locale (LLM providers use the ISO tag). */
12
+ export declare function isSupported(isoCode: string): boolean;
13
+ /** True when the NLLB translator service can translate into the locale. */
14
+ export declare function isNllbSupported(isoCode: string): boolean;
15
+ /**
16
+ * Key for storing translations of `isoCode` (translation cache, translation
17
+ * memory): the FLORES code including the script, plus the region when there
18
+ * is one, so variants never share translations — pt-BR → por_Latn-br,
19
+ * sr-Latn → srp_Latn, sr-Cyrl-RS → srp_Cyrl-rs. A plain language code keeps
20
+ * its FLORES code (pt → por_Latn), as before.
21
+ */
22
+ export declare function storageLangCode(isoCode: string): string;
23
+ /** BCP-47 tag for a storage code (por_Latn-br → pt-BR, srp_Latn → sr-Latn). */
24
+ export declare function localeFromStorageCode(code: string): string;
25
+ export declare function getSupportedLanguages(): string[];