@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
+ export * from './types';
2
+ export { createDefaultTranslationProvider, toKeyResults } from './default-translation-provider';
@@ -0,0 +1,21 @@
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 __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.toKeyResults = exports.createDefaultTranslationProvider = void 0;
18
+ __exportStar(require("./types"), exports);
19
+ var default_translation_provider_1 = require("./default-translation-provider");
20
+ Object.defineProperty(exports, "createDefaultTranslationProvider", { enumerable: true, get: function () { return default_translation_provider_1.createDefaultTranslationProvider; } });
21
+ Object.defineProperty(exports, "toKeyResults", { enumerable: true, get: function () { return default_translation_provider_1.toKeyResults; } });
@@ -0,0 +1,145 @@
1
+ import type { TranslationEntry } from '../runtime/types';
2
+ import type { MemoryEntry } from '../modules/translator/memory/types';
3
+ import type { GlossaryEntryType } from '../modules/glossary/types';
4
+ /**
5
+ * Everything the engine needs from the outside world, as interfaces. The
6
+ * engine never imports a database or cache client: the cloud backend injects
7
+ * Supabase/Redis-backed implementations, the CLI injects local ones (or none).
8
+ * A missing provider means the feature is off (no TM, no glossary, no cache).
9
+ */
10
+ export type KeyFailureReason =
11
+ /** The provider's response had no entry for this key. */
12
+ 'missing_from_response'
13
+ /** The provider returned an empty string for this key. */
14
+ | 'empty_value'
15
+ /** A protected placeholder/tag/term was dropped, repeated or invented. */
16
+ | 'placeholder_lost'
17
+ /** The output is the untranslated source sentence. */
18
+ | 'untranslated'
19
+ /** An ICU message came back unparseable or with changed arguments/branches. */
20
+ | 'icu_invalid'
21
+ /** HTML/JSX tags in the output don't match the source. */
22
+ | 'markup_mismatch'
23
+ /** The provider can't handle this string (e.g. ICU or mixed scripts on NLLB). */
24
+ | 'unsupported_by_provider'
25
+ /** The request for this key's batch failed (network, service error). */
26
+ | 'provider_error';
27
+ /** Why a key has no translation in this run. Failed keys are retried next run. */
28
+ export interface KeyFailure {
29
+ reason: KeyFailureReason;
30
+ retryable: boolean;
31
+ message?: string;
32
+ }
33
+ /** A key that has no translation for a locale in a run, and why. */
34
+ export interface TranslationFailure extends KeyFailure {
35
+ locale: string;
36
+ key: string;
37
+ source: string;
38
+ }
39
+ /**
40
+ * Per-key outcome of a translation request. A provider must return exactly
41
+ * one result per requested key and never substitute the source text for a
42
+ * translation: a key it couldn't translate is a `failed` result, never a
43
+ * silent `ok`.
44
+ */
45
+ export type KeyResult = {
46
+ key: string;
47
+ status: 'ok';
48
+ value: string;
49
+ confidence?: number;
50
+ engine?: string;
51
+ warnings?: string[];
52
+ } | {
53
+ key: string;
54
+ status: 'failed';
55
+ reason: KeyFailureReason;
56
+ retryable: boolean;
57
+ message?: string;
58
+ };
59
+ export interface TranslationBatchRequest {
60
+ entries: TranslationEntry[];
61
+ /** FLORES-200 codes, e.g. eng_Latn */
62
+ sourceLang: string;
63
+ targetLang: string;
64
+ /** Brand voice to put in the prompt (LLM providers only). */
65
+ brandVoice?: string;
66
+ /** Target locale as an ISO code, e.g. "fr" or "pt-BR" (LLM providers only). */
67
+ targetLocale?: string;
68
+ /** Required terminology for this batch: source term → translation (LLM providers only). */
69
+ glossary?: Array<{
70
+ term: string;
71
+ translation: string;
72
+ }>;
73
+ /**
74
+ * Per-key notes for a repair attempt, e.g. which token the previous attempt
75
+ * dropped (LLM providers only).
76
+ */
77
+ hints?: Record<string, string>;
78
+ }
79
+ export interface TranslationBatchOptions {
80
+ /**
81
+ * Aborts the request, including any retries in progress (the caller's time
82
+ * budget ran out or its client went away). The promise then rejects.
83
+ */
84
+ signal?: AbortSignal;
85
+ }
86
+ export interface TranslationProvider {
87
+ /**
88
+ * Output is pseudo-localization (testing), not translation: it is marked
89
+ * in the lockfile and replaced by the next real run.
90
+ */
91
+ readonly pseudo?: boolean;
92
+ /**
93
+ * Translate a batch. Resolves with one KeyResult per requested entry.
94
+ * Rejects only when the request as a whole failed (network, auth, 5xx) or
95
+ * was aborted through `options.signal`.
96
+ */
97
+ translateBatch(request: TranslationBatchRequest, options?: TranslationBatchOptions): Promise<KeyResult[]>;
98
+ }
99
+ export interface TranslationMemoryContext {
100
+ ownerId: string | null;
101
+ projectName: string | null;
102
+ }
103
+ export interface TranslationMemoryStore {
104
+ /** Owner whose memory this project reads/writes, and the project's name. */
105
+ getProjectContext(projectId: string): Promise<TranslationMemoryContext>;
106
+ /** All entries for an owner and FLORES language pair. Throws on failure. */
107
+ getAllForOwner(ownerId: string, sourceLocale: string, targetLocale: string): Promise<MemoryEntry[]>;
108
+ /**
109
+ * Upsert entries. `overwrite: false` must leave existing entries untouched.
110
+ * Throws on failure.
111
+ */
112
+ saveBatch(ownerId: string, projectId: string, entries: MemoryEntry[], options?: {
113
+ overwrite?: boolean;
114
+ }): Promise<void>;
115
+ }
116
+ export interface GlossaryRule {
117
+ term: string;
118
+ type: GlossaryEntryType;
119
+ translations: Record<string, string> | null;
120
+ }
121
+ export interface ProjectRulesStore {
122
+ getGlossary(projectId: string): Promise<GlossaryRule[]>;
123
+ getBrandVoice(projectId: string): Promise<string | null>;
124
+ }
125
+ export interface TranslationCache {
126
+ get(key: string): Promise<{
127
+ hit: boolean;
128
+ data: string | null;
129
+ }>;
130
+ set(key: string, value: string, ttlSeconds: number): Promise<void>;
131
+ }
132
+ export interface EngineProviders {
133
+ translator?: TranslationProvider;
134
+ /**
135
+ * Second chance for keys the (LLM) translator failed with `provider_error`
136
+ * (rate limit, outage), tried once after the repair pass. Treated as a
137
+ * non-LLM service (e.g. NLLB): ICU and mixed-script keys are never sent to
138
+ * it, and its output isn't cached or written to translation memory. Keys it
139
+ * translated get `fallback: true` in their metadata.
140
+ */
141
+ fallbackTranslator?: TranslationProvider;
142
+ memory?: TranslationMemoryStore;
143
+ rules?: ProjectRulesStore;
144
+ cache?: TranslationCache;
145
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,49 @@
1
+ import { type LocaleGlossary, type LocaleIssue, type LocalePairReport } from './locale-qa';
2
+ import { type FindLocaleOptions, type LocaleFileSet } from './locale-files';
3
+ export interface ProjectLocaleConfig {
4
+ sourceLocale?: string;
5
+ targetLocales?: string[];
6
+ glossary?: LocaleGlossary;
7
+ }
8
+ /**
9
+ * The parts of `.babelize/config.json` the local tools use. Missing or
10
+ * malformed config is not an error: everything in it is optional.
11
+ */
12
+ export declare function readProjectLocaleConfig(projectDir: string): Promise<ProjectLocaleConfig>;
13
+ export interface LocaleSetReport {
14
+ dir: string;
15
+ layout: LocaleFileSet['layout'];
16
+ sourceLocale: string;
17
+ /** Source keys checked against. */
18
+ sourceKeys: number;
19
+ locales: LocalePairReport[];
20
+ /** Locales asked for but without a file in this set. */
21
+ missingLocales: string[];
22
+ /** Unreadable/invalid locale files. */
23
+ fileErrors: Array<{
24
+ file: string;
25
+ message: string;
26
+ }>;
27
+ }
28
+ export interface ProjectLocaleCheck {
29
+ sourceLocale: string;
30
+ sets: LocaleSetReport[];
31
+ /** Sets found without a source locale file (cannot be checked). */
32
+ skippedSets: Array<{
33
+ dir: string;
34
+ locales: string[];
35
+ }>;
36
+ }
37
+ export interface CheckProjectOptions {
38
+ sourceLocale?: string;
39
+ /** Only these target locales (default: every locale file found). */
40
+ locales?: string[];
41
+ glossary?: LocaleGlossary;
42
+ /** Restrict each locale's checks to these keys (review of a diff). */
43
+ keys?: string[];
44
+ /** Directory names skipped when looking for locale files (default: deps, build output, tests, fixtures). */
45
+ ignoreDirs?: FindLocaleOptions['ignoreDirs'];
46
+ }
47
+ /** The file a locale issue lives in, relative to the project root. */
48
+ export declare function localeIssueFile(set: Pick<LocaleSetReport, 'dir' | 'layout'>, issue: Pick<LocaleIssue, 'key' | 'locale'>): string;
49
+ export declare function checkProjectLocales(projectDir: string, options?: CheckProjectOptions): Promise<ProjectLocaleCheck>;
@@ -0,0 +1,150 @@
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.readProjectLocaleConfig = readProjectLocaleConfig;
37
+ exports.localeIssueFile = localeIssueFile;
38
+ exports.checkProjectLocales = checkProjectLocales;
39
+ /**
40
+ * QA every locale file set in a project against its source locale. Read-only,
41
+ * no network: see ./locale-qa for the checks.
42
+ */
43
+ const fs_1 = require("fs");
44
+ const path = __importStar(require("path"));
45
+ const locale_qa_1 = require("./locale-qa");
46
+ const locale_files_1 = require("./locale-files");
47
+ /**
48
+ * The parts of `.babelize/config.json` the local tools use. Missing or
49
+ * malformed config is not an error: everything in it is optional.
50
+ */
51
+ async function readProjectLocaleConfig(projectDir) {
52
+ let raw;
53
+ try {
54
+ raw = JSON.parse(await fs_1.promises.readFile(path.join(projectDir, '.babelize', 'config.json'), 'utf8'));
55
+ }
56
+ catch {
57
+ return {};
58
+ }
59
+ if (!raw || typeof raw !== 'object')
60
+ return {};
61
+ const str = (v) => (typeof v === 'string' && (0, locale_files_1.isLocaleCode)(v) ? v : undefined);
62
+ const list = (v) => (Array.isArray(v) ? v.filter((x) => typeof x === 'string' && (0, locale_files_1.isLocaleCode)(x)) : undefined);
63
+ const config = {
64
+ sourceLocale: str(raw.sourceLocale) ?? str(raw.source) ?? str(raw.sourceLanguage),
65
+ targetLocales: list(raw.targetLocales) ?? list(raw.targetLanguageIds) ?? list(raw.targetLanguages),
66
+ };
67
+ const g = raw.glossary;
68
+ if (g && typeof g === 'object') {
69
+ const neverTranslate = Array.isArray(g.neverTranslate) ? g.neverTranslate.filter((t) => typeof t === 'string') : [];
70
+ const alwaysTranslateAs = Array.isArray(g.alwaysTranslateAs)
71
+ ? g.alwaysTranslateAs.filter((e) => !!e && typeof e === 'object' && typeof e.term === 'string' &&
72
+ !!e.translations && typeof e.translations === 'object')
73
+ : [];
74
+ if (neverTranslate.length || alwaysTranslateAs.length)
75
+ config.glossary = { neverTranslate, alwaysTranslateAs };
76
+ }
77
+ return config;
78
+ }
79
+ /** The file a locale issue lives in, relative to the project root. */
80
+ function localeIssueFile(set, issue) {
81
+ if (set.layout === 'namespaced') {
82
+ const ns = issue.key.includes(':') ? issue.key.split(':')[0] : 'translation';
83
+ return `${set.dir}/${issue.locale}/${ns}.json`;
84
+ }
85
+ return `${set.dir}/${issue.locale}.json`;
86
+ }
87
+ /** Pick the source locale: explicit, `.babelize/config.json`, `en`, or the only locale every set has. */
88
+ function chooseSourceLocale(sets, explicit, configured) {
89
+ if (explicit)
90
+ return explicit;
91
+ if (configured)
92
+ return configured;
93
+ if (sets.some((s) => s.files.en))
94
+ return 'en';
95
+ const first = sets[0] && Object.keys(sets[0].files)[0];
96
+ return first ?? 'en';
97
+ }
98
+ /** Target locales from `.babelize/config.json` and the local lockfile (`.babelize/local/babelize-lock.json`). */
99
+ async function expectedTargetLocales(projectDir, config) {
100
+ const found = new Set(config.targetLocales ?? []);
101
+ try {
102
+ const lock = JSON.parse(await fs_1.promises.readFile(path.join(projectDir, '.babelize', 'local', 'babelize-lock.json'), 'utf8'));
103
+ for (const entry of Object.values(lock.keys ?? {}))
104
+ for (const l of Object.keys(entry.languages ?? {}))
105
+ found.add(l);
106
+ }
107
+ catch {
108
+ // No lockfile: nothing translated locally yet.
109
+ }
110
+ return [...found].filter((l) => (0, locale_files_1.isLocaleCode)(l));
111
+ }
112
+ async function checkProjectLocales(projectDir, options = {}) {
113
+ const config = await readProjectLocaleConfig(projectDir);
114
+ // Locales the project is supposed to have: a configured or previously
115
+ // translated locale without a file is reported, not silently skipped.
116
+ const expected = options.locales?.length ? [] : await expectedTargetLocales(projectDir, config);
117
+ const sets = await (0, locale_files_1.findLocaleFileSets)(projectDir, { ignoreDirs: options.ignoreDirs });
118
+ const sourceLocale = chooseSourceLocale(sets, options.sourceLocale, config.sourceLocale);
119
+ const glossary = options.glossary ?? config.glossary;
120
+ const reports = [];
121
+ const skippedSets = [];
122
+ for (const set of sets) {
123
+ if (!set.files[sourceLocale]) {
124
+ skippedSets.push({ dir: set.dir, locales: Object.keys(set.files).sort() });
125
+ continue;
126
+ }
127
+ const source = await (0, locale_files_1.loadLocale)(projectDir, set, sourceLocale);
128
+ const available = Object.keys(set.files).filter((l) => l !== sourceLocale).sort();
129
+ const wanted = options.locales?.length
130
+ ? options.locales.filter((l) => l !== sourceLocale)
131
+ : [...new Set([...available, ...expected])].filter((l) => l !== sourceLocale).sort();
132
+ const fileErrors = [...source.errors];
133
+ const locales = [];
134
+ for (const locale of wanted.filter((l) => set.files[l])) {
135
+ const target = await (0, locale_files_1.loadLocale)(projectDir, set, locale);
136
+ fileErrors.push(...target.errors);
137
+ locales.push((0, locale_qa_1.checkLocalePair)(source.messages, target.messages, locale, { sourceLocale, glossary, keys: options.keys }));
138
+ }
139
+ reports.push({
140
+ dir: set.dir,
141
+ layout: set.layout,
142
+ sourceLocale,
143
+ sourceKeys: Object.keys(source.messages).length,
144
+ locales,
145
+ missingLocales: wanted.filter((l) => !set.files[l]),
146
+ fileErrors,
147
+ });
148
+ }
149
+ return { sourceLocale, sets: reports, skippedSets };
150
+ }
@@ -0,0 +1,44 @@
1
+ export interface LocaleFileSet {
2
+ /** Directory holding the set, relative to the project root (posix). */
3
+ dir: string;
4
+ layout: 'flat' | 'namespaced';
5
+ /** locale → files (relative to the project root, posix). */
6
+ files: Record<string, string[]>;
7
+ }
8
+ export interface LoadedLocale {
9
+ locale: string;
10
+ files: string[];
11
+ messages: Record<string, string>;
12
+ /** Files that could not be read or parsed, with the reason. */
13
+ errors: Array<{
14
+ file: string;
15
+ message: string;
16
+ }>;
17
+ }
18
+ /**
19
+ * Directories never searched for locale files: dependencies, build output,
20
+ * and test/fixture trees (whose locale files are test data, not the app's).
21
+ */
22
+ export declare const DEFAULT_LOCALE_IGNORE_DIRS: readonly string[];
23
+ export interface FindLocaleOptions {
24
+ /** Directory names to skip at any depth (default DEFAULT_LOCALE_IGNORE_DIRS). */
25
+ ignoreDirs?: readonly string[];
26
+ }
27
+ export declare function isLocaleCode(name: string): boolean;
28
+ /** True when a relative path runs through an ignored directory. */
29
+ export declare function isIgnoredLocalePath(rel: string, ignoreDirs?: readonly string[]): boolean;
30
+ /**
31
+ * Group relative `.json` paths into locale file sets. Paths that aren't
32
+ * `<dir>/<locale>.json` or `<dir>/<locale>/<ns>.json` under a locale folder
33
+ * are ignored.
34
+ */
35
+ export declare function groupLocaleFiles(paths: string[], options?: FindLocaleOptions): LocaleFileSet[];
36
+ export declare function findLocaleFileSets(projectDir: string, options?: FindLocaleOptions): Promise<LocaleFileSet[]>;
37
+ /** Reads a project file by relative path; null when it doesn't exist (in that version). */
38
+ export type ProjectFileReader = (rel: string) => Promise<string | null>;
39
+ /**
40
+ * Read and flatten one locale of a set. Namespaced files are keyed `ns:key`.
41
+ * `read` reads another version of the files (e.g. from git); by default the
42
+ * working tree is read.
43
+ */
44
+ export declare function loadLocale(projectDir: string, set: LocaleFileSet, locale: string, read?: ProjectFileReader): Promise<LoadedLocale>;
@@ -0,0 +1,159 @@
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.DEFAULT_LOCALE_IGNORE_DIRS = void 0;
40
+ exports.isLocaleCode = isLocaleCode;
41
+ exports.isIgnoredLocalePath = isIgnoredLocalePath;
42
+ exports.groupLocaleFiles = groupLocaleFiles;
43
+ exports.findLocaleFileSets = findLocaleFileSets;
44
+ exports.loadLocale = loadLocale;
45
+ /**
46
+ * Find a project's JSON locale files without running the pipeline (read-only).
47
+ *
48
+ * Recognised layouts, under any directory named locales/messages/lang(s)/
49
+ * i18n/translations (the engine itself writes `<publicDir>/locales/<locale>.json`):
50
+ *
51
+ * locales/en.json, locales/fr.json flat: one file per locale
52
+ * locales/en/common.json, locales/fr/common.json namespaced (i18next): keys become `common:key`
53
+ */
54
+ const path = __importStar(require("path"));
55
+ const fs_1 = require("fs");
56
+ const fast_glob_1 = __importDefault(require("fast-glob"));
57
+ const locale_qa_1 = require("./locale-qa");
58
+ const LOCALE_DIR_NAMES = ['locales', 'locale', 'messages', 'lang', 'langs', 'i18n', 'translations'];
59
+ /**
60
+ * Directories never searched for locale files: dependencies, build output,
61
+ * and test/fixture trees (whose locale files are test data, not the app's).
62
+ */
63
+ exports.DEFAULT_LOCALE_IGNORE_DIRS = [
64
+ 'node_modules', '.git', '.next', 'dist', 'build', 'coverage', '.babelize', 'out',
65
+ '__fixtures__', 'fixtures', '__tests__', '__mocks__', 'test', 'tests', 'testing', 'e2e',
66
+ ];
67
+ /** en, fr, pt-BR, zh_Hant, sr-Latn-RS — a locale code, not a namespace like "common". */
68
+ const LOCALE_CODE = /^[a-z]{2,3}(?:[-_](?:[A-Z][a-z]{3}|[A-Za-z]{2}|\d{3}))*$/;
69
+ function isLocaleCode(name) {
70
+ return LOCALE_CODE.test(name);
71
+ }
72
+ function toPosix(p) {
73
+ return p.split(path.sep).join('/');
74
+ }
75
+ /** True when a relative path runs through an ignored directory. */
76
+ function isIgnoredLocalePath(rel, ignoreDirs = exports.DEFAULT_LOCALE_IGNORE_DIRS) {
77
+ const dirs = rel.split('/').slice(0, -1);
78
+ return dirs.some((d) => ignoreDirs.includes(d));
79
+ }
80
+ /**
81
+ * Group relative `.json` paths into locale file sets. Paths that aren't
82
+ * `<dir>/<locale>.json` or `<dir>/<locale>/<ns>.json` under a locale folder
83
+ * are ignored.
84
+ */
85
+ function groupLocaleFiles(paths, options = {}) {
86
+ var _a;
87
+ const ignoreDirs = options.ignoreDirs ?? exports.DEFAULT_LOCALE_IGNORE_DIRS;
88
+ const sets = new Map();
89
+ for (const rel of [...new Set(paths.map(toPosix))].sort()) {
90
+ if (!rel.endsWith('.json') || isIgnoredLocalePath(rel, ignoreDirs))
91
+ continue;
92
+ const parts = rel.split('/');
93
+ const file = parts[parts.length - 1];
94
+ const parent = parts[parts.length - 2];
95
+ let dir;
96
+ let locale;
97
+ let layout;
98
+ if (parent !== undefined && LOCALE_DIR_NAMES.includes(parent) && isLocaleCode(file.slice(0, -5))) {
99
+ dir = parts.slice(0, -1).join('/');
100
+ locale = file.slice(0, -5);
101
+ layout = 'flat';
102
+ }
103
+ else if (parts.length >= 3 && LOCALE_DIR_NAMES.includes(parts[parts.length - 3]) && isLocaleCode(parent)) {
104
+ dir = parts.slice(0, -2).join('/');
105
+ locale = parent;
106
+ layout = 'namespaced';
107
+ }
108
+ else {
109
+ continue;
110
+ }
111
+ const id = `${dir}\u0000${layout}`;
112
+ const set = sets.get(id) ?? { dir, layout, files: {} };
113
+ ((_a = set.files)[locale] ?? (_a[locale] = [])).push(rel);
114
+ sets.set(id, set);
115
+ }
116
+ return [...sets.values()];
117
+ }
118
+ async function findLocaleFileSets(projectDir, options = {}) {
119
+ const names = `{${LOCALE_DIR_NAMES.join(',')}}`;
120
+ const ignoreDirs = options.ignoreDirs ?? exports.DEFAULT_LOCALE_IGNORE_DIRS;
121
+ const found = await (0, fast_glob_1.default)([`**/${names}/*.json`, `**/${names}/*/*.json`], {
122
+ cwd: projectDir,
123
+ ignore: ignoreDirs.map((d) => `**/${d}/**`),
124
+ onlyFiles: true,
125
+ deep: 8,
126
+ followSymbolicLinks: false,
127
+ });
128
+ return groupLocaleFiles(found, options);
129
+ }
130
+ /**
131
+ * Read and flatten one locale of a set. Namespaced files are keyed `ns:key`.
132
+ * `read` reads another version of the files (e.g. from git); by default the
133
+ * working tree is read.
134
+ */
135
+ async function loadLocale(projectDir, set, locale, read = (rel) => fs_1.promises.readFile(path.join(projectDir, rel), 'utf8').catch((err) => {
136
+ if (err.code === 'ENOENT')
137
+ return null;
138
+ throw err;
139
+ })) {
140
+ const files = set.files[locale] ?? [];
141
+ const messages = {};
142
+ const errors = [];
143
+ for (const rel of files) {
144
+ try {
145
+ const content = await read(rel);
146
+ if (content === null)
147
+ continue;
148
+ const data = JSON.parse(content);
149
+ const flat = (0, locale_qa_1.flattenLocaleMessages)(data);
150
+ const ns = set.layout === 'namespaced' ? path.basename(rel, '.json') : null;
151
+ for (const [k, v] of Object.entries(flat))
152
+ messages[ns ? `${ns}:${k}` : k] = v;
153
+ }
154
+ catch (err) {
155
+ errors.push({ file: toPosix(rel), message: err instanceof SyntaxError ? `invalid JSON: ${err.message}` : err.message });
156
+ }
157
+ }
158
+ return { locale, files, messages, errors };
159
+ }
@@ -0,0 +1,61 @@
1
+ export type LocaleIssueCode = 'missing_key' | 'extra_key' | 'empty_value' | 'untranslated' | 'placeholder_mismatch' | 'icu_invalid' | 'icu_mismatch' | 'plural_category_invalid' | 'plural_category_missing' | 'glossary_violation'
2
+ /** The source text changed but the translation didn't (review of a diff). */
3
+ | 'stale_translation'
4
+ /** A translation present at the base of a diff was deleted while the source still has it. */
5
+ | 'translation_removed'
6
+ /** Pseudo-localized test output (`--provider pseudo`, ⟦…⟧), not a translation. */
7
+ | 'pseudo_translation';
8
+ export type LocaleIssueSeverity = 'error' | 'warning' | 'info';
9
+ export interface LocaleIssue {
10
+ locale: string;
11
+ key: string;
12
+ code: LocaleIssueCode;
13
+ severity: LocaleIssueSeverity;
14
+ /** What is wrong, in plain words. */
15
+ message: string;
16
+ }
17
+ export interface LocaleGlossary {
18
+ /** Terms that must appear unchanged in every translation (brand names, …). */
19
+ neverTranslate?: string[];
20
+ /** Terms with a fixed translation per locale. */
21
+ alwaysTranslateAs?: Array<{
22
+ term: string;
23
+ translations: Record<string, string>;
24
+ }>;
25
+ }
26
+ export interface LocalePairOptions {
27
+ /** Source locale code; values identical to the source aren't flagged for a locale of the same language. */
28
+ sourceLocale?: string;
29
+ glossary?: LocaleGlossary;
30
+ /**
31
+ * Only check these keys (e.g. the keys changed in a diff). Missing/extra
32
+ * detection is limited to them too.
33
+ */
34
+ keys?: string[];
35
+ }
36
+ export interface LocalePairReport {
37
+ locale: string;
38
+ /** Keys in the source locale (plural groups count once). */
39
+ total: number;
40
+ /** Source keys (or plural groups) with a non-empty value in the target. */
41
+ translated: number;
42
+ /** Issue count by code. */
43
+ counts: Partial<Record<LocaleIssueCode, number>>;
44
+ errors: number;
45
+ warnings: number;
46
+ issues: LocaleIssue[];
47
+ }
48
+ /**
49
+ * Flatten nested locale JSON into dotted keys with string values. The
50
+ * engine's `_meta` block is dropped; non-string leaves (numbers, arrays) are
51
+ * not messages and are skipped.
52
+ */
53
+ export declare function flattenLocaleMessages(data: unknown, prefix?: string, out?: Record<string, string>): Record<string, string>;
54
+ /**
55
+ * Check one target locale against the source locale. Both maps are flat
56
+ * (see {@link flattenLocaleMessages}). Issues come in key order, errors and
57
+ * warnings mixed; callers cap and sort as they need.
58
+ */
59
+ export declare function checkLocalePair(source: Record<string, string>, target: Record<string, string>, locale: string, options?: LocalePairOptions): LocalePairReport;
60
+ /** Recompute `counts`/`errors`/`warnings` from `issues` (after adding issues to a report). */
61
+ export declare function tallyLocaleIssues(report: LocalePairReport): LocalePairReport;