@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 +21 -0
- package/README.md +37 -0
- package/dist/index.d.mts +345 -0
- package/dist/index.mjs +2433 -0
- package/package.json +60 -0
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
|
+
[](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
|
package/dist/index.d.mts
ADDED
|
@@ -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 };
|