@zintljs/extractor 0.1.0-alpha.6

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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026-present Khalid F. Shuhail
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,37 @@
1
+ # @zintljs/extractor
2
+
3
+ > AST-based message extractor for [Zintl](https://github.com/zintljs/zintl).
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@zintljs/extractor.svg?color=863bff&label=)](https://npmjs.com/package/@zintljs/extractor)
6
+
7
+ This is an **internal package**. You almost certainly want [`zintl`](https://npmjs.com/package/zintl) instead — it bundles this extractor behind a ready-to-use Vite plugin.
8
+
9
+ Install it directly only if you are building custom tooling on top of Zintl's extraction layer.
10
+
11
+ ## What it does
12
+
13
+ `@zintljs/extractor` is a **pure metadata provider**. It scans source syntax with high-performance [oxc](https://oxc.rs) AST parsers and reports what it finds. It never modifies your source files.
14
+
15
+ It is deliberately **framework-blind** — it carries no implicit knowledge of React, Vue, or Svelte. All framework behavior is supplied by the caller through configuration.
16
+
17
+ ## Intelligent stitching
18
+
19
+ The extractor does not emit raw string literals. It stitches template literals, JSX fragments, and HTML strings into logical **Stitched Units**:
20
+
21
+ - **HTML fragmentation** — large `innerHTML` strings are split along tag boundaries. Translatable text between tags becomes its own key; the tags themselves are preserved as structure.
22
+ - **Variable normalization** — unnamed expressions are normalized to stable placeholders (`{input}`, `{inputN}`), so identical UI fragments share a translation key regardless of their position in a template.
23
+ - **Comment directives** — `@zintl-ignore`, `@zintl-note`, and `@zintl-pass` are recognized and attached to the units they annotate.
24
+
25
+ ## Installation
26
+
27
+ ```bash
28
+ npm install @zintljs/extractor
29
+ ```
30
+
31
+ ## Requirements
32
+
33
+ - Node.js `^22.18.0 || >=24.11.0`
34
+
35
+ ## License
36
+
37
+ [MIT](./LICENSE) © Khalid F. Shuhail
@@ -0,0 +1,345 @@
1
+ import { Expression, Node } from "@oxc-project/types";
2
+ //#region src/logger.d.ts
3
+ type LogLevel = "silent" | "error" | "warn" | "info";
4
+ interface LoggerOptions {
5
+ level?: LogLevel;
6
+ prefix?: string;
7
+ debug?: boolean | string;
8
+ }
9
+ declare class ZintlLogger {
10
+ private level;
11
+ private prefix;
12
+ private debugEnabled;
13
+ private debugScope;
14
+ private lastDebugTime;
15
+ constructor(options?: LoggerOptions);
16
+ private setupDebug;
17
+ private formatMessage;
18
+ private formatDebugMessage;
19
+ error(message: string, ...args: any[]): void;
20
+ warn(message: string, ...args: any[]): void;
21
+ info(message: string, ...args: any[]): void;
22
+ debug(message: string, ...args: any[]): void;
23
+ /**
24
+ * Create a sub-logger with a combined prefix.
25
+ */
26
+ withPrefix(subPrefix: string): ZintlLogger;
27
+ setLevel(level: LogLevel): void;
28
+ }
29
+ /** Global default logger */
30
+ declare const logger: ZintlLogger;
31
+ //#endregion
32
+ //#region src/types.d.ts
33
+ interface ExtractedMessage {
34
+ id: string;
35
+ text: string;
36
+ contexts: string[];
37
+ boundaryId: string;
38
+ location: {
39
+ line: number;
40
+ column: number;
41
+ };
42
+ note?: string;
43
+ variables: string[];
44
+ sinkTypes: string[];
45
+ passVars?: Record<string, string>;
46
+ }
47
+ /** A resolved dependency on another boundary. */
48
+ interface BoundaryDep {
49
+ /** Boundary id (relative path without extension). */
50
+ id: string;
51
+ /** True if reachable only via a dynamic import() expression. */
52
+ dynamic: boolean;
53
+ /** Specific exported identifiers being used from this dependency. */
54
+ bindings?: string[];
55
+ }
56
+ /** Location of a trust anchor (zintl() call). */
57
+ interface AnchorSite {
58
+ start: number;
59
+ end: number;
60
+ scope: "module" | "function";
61
+ boundaryId: string;
62
+ originalArgs: string;
63
+ argType: "literal" | "expression";
64
+ isTopLevel: boolean;
65
+ originalName: string;
66
+ /** Optional range of the governing statement (e.g. ExpressionStatement including semicolon). */
67
+ statementRange?: {
68
+ start: number;
69
+ end: number;
70
+ };
71
+ /** Optional code snippet discovered by tracing the anchor argument for hoisting into bootstrap. */
72
+ detectionCode?: string;
73
+ }
74
+ interface HtmlProjectionPayload {
75
+ title?: string;
76
+ description?: string;
77
+ dir?: string;
78
+ /** Found module scripts via <script type="module" src="..."> */
79
+ scripts: string[];
80
+ }
81
+ interface ExtractionResult {
82
+ messages: ExtractedMessage[];
83
+ code: string;
84
+ /** List of string replacements (original offsets) to apply to original code. */
85
+ transforms: Transform[];
86
+ needsLoader: boolean;
87
+ /** If true, this module contains at least one trust anchor (zintl). */
88
+ hasZintlMacro: boolean;
89
+ /** If true, this module contains a side-effect import "zintl" ($M). */
90
+ hasZintlMarker: boolean;
91
+ /** Locations of zintl calls for compiler-driven catalog injection. */
92
+ anchorSites: AnchorSite[];
93
+ mode: "entry" | "boundary";
94
+ runtimeImports: string[];
95
+ dependencies: BoundaryDep[];
96
+ usedKeys: Set<string>;
97
+ /** Map of boundaryId -> sha1 hash of its messages (text+context+note) */
98
+ boundaryHashes: Record<string, string>;
99
+ /** Location and actual source of existing import from "zintl" used for merging. */
100
+ zintlImportGroup?: {
101
+ start: number;
102
+ end: number;
103
+ source: string;
104
+ };
105
+ /** Map of exported identifier -> internal boundary ID. */
106
+ exportedBoundaries: Record<string, string>;
107
+ /** Map of parent boundary -> set of child boundaries it depends on (local calls) */
108
+ internalDeps: Record<string, string[]>;
109
+ /** Full-fidelity UI sink observations captured at the visitor level. */
110
+ rawSinks: RawSink[];
111
+ /** Explicit t() calls captured at the visitor level. */
112
+ rawManualTranslations: RawManualT[];
113
+ /** Component function insertion positions (e.g. function body block starts). */
114
+ componentFunctions?: number[];
115
+ /** HTML-specific projection data (for .html files). */
116
+ htmlProjection?: HtmlProjectionPayload;
117
+ }
118
+ /**
119
+ * A raw UI string captured at the exact point of discovery in a visitor.
120
+ *
121
+ * Unlike the adapter approach (which reconstructs data from transforms),
122
+ * this captures full-fidelity observation data including:
123
+ * - Exact source locations (start/end offsets)
124
+ * - Variable expressions as source code strings
125
+ * - @zintl-pass context variables
126
+ * - Fragment and quote conversion metadata
127
+ */
128
+ interface RawSink {
129
+ /** Stitched text content (after fragmentation + variable normalization). */
130
+ text: string;
131
+ /** UI context: "innerHTML", "title", "h1", "aria-label", "label", etc. */
132
+ sinkType: string;
133
+ /** Start offset in source code for the replaceable range. */
134
+ start: number;
135
+ /** End offset in source code for the replaceable range. */
136
+ end: number;
137
+ /** Source line number. */
138
+ line: number;
139
+ /** Source column number. */
140
+ column: number;
141
+ /** Owning boundary ID. */
142
+ boundaryId: string;
143
+ /** Interpolated variable bindings with source expressions. */
144
+ variables: RawVariable[];
145
+ /** Translator note from @zintl-note. */
146
+ note?: string;
147
+ /** Context variables from @zintl-pass (target-language asymmetry). */
148
+ passVars?: Record<string, string>;
149
+ /** True if this is a fragment within a larger template/string (inline replacement). */
150
+ isFragment: boolean;
151
+ /** True if the replacement requires JSX curly braces wrapping. */
152
+ requiresJsxBraces?: boolean;
153
+ /** If fragment: start offset of the fragment sub-range. */
154
+ fragmentStart?: number;
155
+ /** If fragment: end offset of the fragment sub-range. */
156
+ fragmentEnd?: number;
157
+ /** If fragment: start offset of the enclosing host node. */
158
+ hostStart?: number;
159
+ /** If fragment: end offset of the enclosing host node. */
160
+ hostEnd?: number;
161
+ /** True if the host string literal must be converted to a template literal. */
162
+ requiresQuoteConversion?: boolean;
163
+ /** Phrase tag attribute map (alias -> original open tag). */
164
+ tagMap?: TagMapEntry[];
165
+ }
166
+ interface TagMapEntry {
167
+ alias: string;
168
+ originalOpen: string;
169
+ tagName: string;
170
+ }
171
+ /**
172
+ * A variable binding captured with its source expression.
173
+ */
174
+ interface RawVariable {
175
+ /** Variable name after normalization (e.g. "input", "name"). */
176
+ name: string;
177
+ /** Variable name before normalization (e.g. "var0", "name"). */
178
+ originalName: string;
179
+ /** Source code of the expression (e.g. "user.name", "items.length"). */
180
+ expression: string;
181
+ /** Start offset of the expression in source. */
182
+ start: number;
183
+ /** End offset of the expression in source. */
184
+ end: number;
185
+ }
186
+ /**
187
+ * An explicit t("key") call captured at the visitor level.
188
+ */
189
+ interface RawManualT {
190
+ /** The string key passed to t("key"). */
191
+ key: string;
192
+ /** Start offset of the t(...) call. */
193
+ start: number;
194
+ /** End offset of the t(...) call. */
195
+ end: number;
196
+ /** Source line number. */
197
+ line: number;
198
+ /** Source column number. */
199
+ column: number;
200
+ /** Owning boundary ID. */
201
+ boundaryId: string;
202
+ /** Optional source code for the parameters object. */
203
+ paramsSource?: string;
204
+ }
205
+ interface LiteralSource {
206
+ node: Node;
207
+ text: string;
208
+ context: string;
209
+ location: {
210
+ line: number;
211
+ column: number;
212
+ };
213
+ note?: string;
214
+ variables?: string[];
215
+ transformStart?: number;
216
+ transformEnd?: number;
217
+ inlineReplacement?: boolean;
218
+ normalizedVariables?: Record<string, string>;
219
+ passVars?: Record<string, string>;
220
+ tagMap?: TagMapEntry[];
221
+ }
222
+ interface BoundaryInfo {
223
+ id: string;
224
+ active: boolean;
225
+ }
226
+ interface Transform {
227
+ start: number;
228
+ end: number;
229
+ replacement: string;
230
+ msgId?: string;
231
+ originalText?: string;
232
+ boundaryId: string;
233
+ argNode?: Expression;
234
+ }
235
+ /**
236
+ * A structural sink descriptor.
237
+ *
238
+ * Deliberately framework-blind: there are no `"react"` / `"vue"` / `"svelte"` /
239
+ * `"nextjs"` members. Framework presets live in `@zintljs/compiler/facets` and
240
+ * expand to these low-level forms before they ever reach the extractor.
241
+ */
242
+ type TargetDescriptor = `jsx:*:${string}` | `jsx:${string}:${string}` | `dom:prop:${string}` | `dom:attr:${string}` | `obj:field:${string}` | `html:attr:${string}` | TargetPlugin;
243
+ interface TargetPlugin {
244
+ name: string;
245
+ resolveOptions?: (base: ExtractionOptions) => Partial<ExtractionOptions>;
246
+ createVisitor?: (ctx: any) => any;
247
+ fastPathHint?: string | string[];
248
+ }
249
+ interface SfcBlockRule {
250
+ id: string;
251
+ pattern: RegExp;
252
+ action: "javascript" | "html" | "ignore";
253
+ resolveVirtualExtension?: (attributes: string) => string;
254
+ isActiveContent?: boolean;
255
+ }
256
+ interface SfcRule {
257
+ extensions: string[];
258
+ blocks: SfcBlockRule[];
259
+ }
260
+ interface SuppressionRule {
261
+ targets?: string[];
262
+ match: {
263
+ types: string[];
264
+ names: string[];
265
+ isTopLevel?: boolean;
266
+ };
267
+ bypassIf?: "hasAnchor";
268
+ }
269
+ interface MustacheRule {
270
+ extensions: string[];
271
+ pattern: RegExp;
272
+ }
273
+ interface CompiledExtractionState {
274
+ jsxAttributes: Set<string>;
275
+ jsxElementAttributes: Map<string, Set<string>>;
276
+ domProperties: Set<string>;
277
+ objectFields: Set<string>;
278
+ htmlAttributes: Set<string>;
279
+ plugins: TargetPlugin[];
280
+ fastPathHints: string[];
281
+ uniqueHints: string[];
282
+ fastPathRegex: RegExp;
283
+ hasDomSinks: boolean;
284
+ hasJsxSinks: boolean;
285
+ sfcRules: SfcRule[];
286
+ suppressionRules: SuppressionRule[];
287
+ mustacheRegex?: RegExp | null;
288
+ mustacheRules?: MustacheRule[];
289
+ }
290
+ interface ExtractionOptions {
291
+ runtimePackage?: string;
292
+ uiAttributes?: Set<string>;
293
+ uiObjectFields?: Set<string>;
294
+ uiSinkProperties?: string[];
295
+ targets?: TargetDescriptor[];
296
+ logger?: ZintlLogger;
297
+ isZeroConfig?: boolean;
298
+ sfcRules?: SfcRule[];
299
+ suppressionRules?: SuppressionRule[];
300
+ activeRange?: {
301
+ start: number;
302
+ end: number;
303
+ };
304
+ isSfcTemplate?: boolean;
305
+ compiledState?: CompiledExtractionState;
306
+ }
307
+ //#endregion
308
+ //#region src/parser.d.ts
309
+ declare function extract(code: string, filePath: string, fileBoundaryId: string, options?: ExtractionOptions): ExtractionResult;
310
+ //#endregion
311
+ //#region src/hashing.d.ts
312
+ declare function generateMessageId(messageText: string, _context?: string, _note?: string): string;
313
+ //#endregion
314
+ //#region src/constants.d.ts
315
+ /**
316
+ * The extractor's only constants.
317
+ *
318
+ * The former `DEFAULT_UI_ATTRIBUTES` / `DEFAULT_UI_OBJECT_FIELDS` /
319
+ * `DEFAULT_UI_SINK_PROPERTIES` sets and `TEMPLATE_ATTR_REGEX` lived here and
320
+ * encoded opinions about which DOM and JSX attributes are translatable. That is
321
+ * facet knowledge — it now lives in `@zintljs/compiler/facets` and reaches the
322
+ * extractor as descriptors. All four were already unreferenced (one survived
323
+ * only inside a commented-out line) and have been removed.
324
+ */
325
+ declare const ZINTL_MACRO = "zintl";
326
+ declare const RUNTIME_PACKAGE = "zintl";
327
+ /**
328
+ * Module specifiers that carry the Zintl runtime surface.
329
+ *
330
+ * This list was previously inlined at four call sites (`parser.ts`, two in
331
+ * `visitors/program.ts`, one in `visitors/bindings.ts`) and the four had drifted:
332
+ * the `bindings.ts` copy omitted the bare `"zintl"` literal, so a project with a
333
+ * custom `runtimePackage` would have had its bare `"zintl"` imports recognised
334
+ * by three of the four checks and missed by the fourth.
335
+ */
336
+ declare const RUNTIME_SPECIFIERS: readonly string[];
337
+ /** Whether an import specifier resolves to the Zintl runtime. */
338
+ declare function isRuntimeSpecifier(source: string, runtimePackage?: string): boolean;
339
+ declare const HTML_TAG_SPLIT_REGEX: RegExp;
340
+ //#endregion
341
+ //#region src/targets.d.ts
342
+ type ResolvedTargets = CompiledExtractionState;
343
+ declare function resolveTargets(targets: TargetDescriptor[]): ResolvedTargets;
344
+ //#endregion
345
+ export { AnchorSite, BoundaryDep, BoundaryInfo, CompiledExtractionState, ExtractedMessage, ExtractionOptions, ExtractionResult, HTML_TAG_SPLIT_REGEX, HtmlProjectionPayload, LiteralSource, LogLevel, LoggerOptions, MustacheRule, RUNTIME_PACKAGE, RUNTIME_SPECIFIERS, RawManualT, RawSink, RawVariable, ResolvedTargets, SfcBlockRule, SfcRule, SuppressionRule, TagMapEntry, TargetDescriptor, TargetPlugin, Transform, ZINTL_MACRO, ZintlLogger, extract, generateMessageId, isRuntimeSpecifier, logger, resolveTargets };