@formatjs/cli-lib 8.3.2 → 8.4.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.
- package/package.json +7 -3
- package/src/compile.d.ts +4 -0
- package/src/compile.js +8 -3
- package/src/extract.d.ts +4 -0
- package/src/extract.js +13 -3
- package/src/svelte_extractor.d.ts +2 -0
- package/src/svelte_extractor.js +117 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@formatjs/cli-lib",
|
|
3
3
|
"description": "Lib for CLI for formatjs.",
|
|
4
|
-
"version": "8.
|
|
4
|
+
"version": "8.4.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Linjie Ding <linjie@airtable.com>",
|
|
7
7
|
"type": "module",
|
|
@@ -23,13 +23,14 @@
|
|
|
23
23
|
"loud-rejection": "^2",
|
|
24
24
|
"typescript": "^5.6.0",
|
|
25
25
|
"@formatjs/icu-messageformat-parser": "3.5.2",
|
|
26
|
-
"@formatjs/
|
|
27
|
-
"@formatjs/
|
|
26
|
+
"@formatjs/ts-transformer": "4.4.1",
|
|
27
|
+
"@formatjs/icu-skeleton-parser": "2.1.2"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
30
|
"@glimmer/syntax": "^0.84.3 || ^0.95.0",
|
|
31
31
|
"@vue/compiler-core": "^3.5.0",
|
|
32
32
|
"content-tag": "^4.1.0",
|
|
33
|
+
"svelte": "^5.0.0",
|
|
33
34
|
"vue": "^3.5.0"
|
|
34
35
|
},
|
|
35
36
|
"bugs": "https://github.com/formatjs/formatjs/issues",
|
|
@@ -73,6 +74,9 @@
|
|
|
73
74
|
},
|
|
74
75
|
"content-tag": {
|
|
75
76
|
"optional": true
|
|
77
|
+
},
|
|
78
|
+
"svelte": {
|
|
79
|
+
"optional": true
|
|
76
80
|
}
|
|
77
81
|
},
|
|
78
82
|
"repository": "formatjs/formatjs.git"
|
package/src/compile.d.ts
CHANGED
package/src/compile.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { parse } from "@formatjs/icu-messageformat-parser";
|
|
2
|
-
import { outputFile
|
|
2
|
+
import { outputFile } from "fs-extra/esm";
|
|
3
|
+
import { readFile } from "fs/promises";
|
|
3
4
|
import * as stringifyNs from "json-stable-stringify";
|
|
4
5
|
import { debug, warn, writeStdout } from "./console_utils.js";
|
|
5
6
|
import { resolveBuiltinFormatter } from "./formatters/index.js";
|
|
@@ -16,12 +17,16 @@ const stringify = stringifyNs.default || stringifyNs;
|
|
|
16
17
|
*/
|
|
17
18
|
export async function compile(inputFiles, opts = {}) {
|
|
18
19
|
debug("Compiling files:", inputFiles);
|
|
19
|
-
const { ast, format, pseudoLocale, skipErrors, ignoreTag } = opts;
|
|
20
|
+
const { ast, format, pseudoLocale, skipErrors, ignoreTag, signal } = opts;
|
|
21
|
+
signal?.throwIfAborted();
|
|
20
22
|
const formatter = await resolveBuiltinFormatter(format);
|
|
21
23
|
const messages = {};
|
|
22
24
|
const messageAsts = {};
|
|
23
25
|
const idsWithFileName = {};
|
|
24
|
-
const compiledFiles = await Promise.all(inputFiles.map((f) =>
|
|
26
|
+
const compiledFiles = await Promise.all(inputFiles.map((f) => readFile(f, {
|
|
27
|
+
encoding: "utf8",
|
|
28
|
+
signal
|
|
29
|
+
}).then((content) => JSON.parse(content)).then(formatter.compile)));
|
|
25
30
|
debug("Compiled files:", compiledFiles);
|
|
26
31
|
for (let i = 0; i < inputFiles.length; i++) {
|
|
27
32
|
const inputFile = inputFiles[i];
|
package/src/extract.d.ts
CHANGED
|
@@ -60,6 +60,10 @@ export type ExtractOpts = Opts & {
|
|
|
60
60
|
* Whether to hoist selectors & flatten sentences
|
|
61
61
|
*/
|
|
62
62
|
flatten?: boolean;
|
|
63
|
+
/**
|
|
64
|
+
* An AbortSignal to cancel the extraction
|
|
65
|
+
*/
|
|
66
|
+
signal?: AbortSignal;
|
|
63
67
|
} & Pick<Opts, "onMsgExtracted" | "onMetaExtracted">;
|
|
64
68
|
/**
|
|
65
69
|
* Extract strings from source files
|
package/src/extract.js
CHANGED
|
@@ -60,6 +60,10 @@ async function processFile(source, fn, { idInterpolationPattern, ...opts }) {
|
|
|
60
60
|
debug("Processing %s using vue extractor", fn);
|
|
61
61
|
const { parseFile } = await import("./vue_extractor.js");
|
|
62
62
|
parseFile(source, fn, scriptParseFn);
|
|
63
|
+
} else if (fn.endsWith(".svelte")) {
|
|
64
|
+
debug("Processing %s using svelte extractor", fn);
|
|
65
|
+
const { parseFile } = await import("./svelte_extractor.js");
|
|
66
|
+
parseFile(source, fn, scriptParseFn);
|
|
63
67
|
} else if (fn.endsWith(".hbs")) {
|
|
64
68
|
debug("Processing %s using hbs extractor", fn);
|
|
65
69
|
const { parseFile } = await import("./hbs_extractor.js");
|
|
@@ -90,7 +94,7 @@ async function processFile(source, fn, { idInterpolationPattern, ...opts }) {
|
|
|
90
94
|
* matters for some `format`
|
|
91
95
|
*/
|
|
92
96
|
export async function extract(files, extractOpts) {
|
|
93
|
-
const { throws, readFromStdin, ...opts } = extractOpts;
|
|
97
|
+
const { throws, readFromStdin, signal, ...opts } = extractOpts;
|
|
94
98
|
// When throws is not explicitly true, we want to collect partial results
|
|
95
99
|
const shouldThrow = throws === true;
|
|
96
100
|
// Pass throws option to transformer for per-message error handling
|
|
@@ -114,7 +118,10 @@ export async function extract(files, extractOpts) {
|
|
|
114
118
|
if (!shouldThrow) {
|
|
115
119
|
const settledResults = await Promise.allSettled(files.map(async (fn) => {
|
|
116
120
|
debug("Extracting file:", fn);
|
|
117
|
-
const source = await readFile(fn,
|
|
121
|
+
const source = await readFile(fn, {
|
|
122
|
+
encoding: "utf8",
|
|
123
|
+
signal
|
|
124
|
+
});
|
|
118
125
|
return processFile(source, fn, optsWithThrows);
|
|
119
126
|
}));
|
|
120
127
|
rawResults = settledResults.map((result) => {
|
|
@@ -128,7 +135,10 @@ export async function extract(files, extractOpts) {
|
|
|
128
135
|
} else {
|
|
129
136
|
rawResults = await Promise.all(files.map(async (fn) => {
|
|
130
137
|
debug("Extracting file:", fn);
|
|
131
|
-
const source = await readFile(fn,
|
|
138
|
+
const source = await readFile(fn, {
|
|
139
|
+
encoding: "utf8",
|
|
140
|
+
signal
|
|
141
|
+
});
|
|
132
142
|
return processFile(source, fn, optsWithThrows);
|
|
133
143
|
}));
|
|
134
144
|
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { parse } from "svelte/compiler";
|
|
2
|
+
function walkFragment(fragment, source, parseScriptFn) {
|
|
3
|
+
if (!fragment || !fragment.nodes) {
|
|
4
|
+
return;
|
|
5
|
+
}
|
|
6
|
+
for (const node of fragment.nodes) {
|
|
7
|
+
walkNode(node, source, parseScriptFn);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
function extractExpression(expression, source, parseScriptFn) {
|
|
11
|
+
if (!expression || expression.start == null || expression.end == null) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const content = source.slice(expression.start, expression.end);
|
|
15
|
+
try {
|
|
16
|
+
parseScriptFn(`(${content})`);
|
|
17
|
+
} catch (e) {
|
|
18
|
+
console.warn(`Failed to parse "${content}". Ignore this if content has no extractable message`, e);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function walkNode(node, source, parseScriptFn) {
|
|
22
|
+
switch (node.type) {
|
|
23
|
+
case "ExpressionTag":
|
|
24
|
+
extractExpression(node.expression, source, parseScriptFn);
|
|
25
|
+
break;
|
|
26
|
+
case "ConstTag":
|
|
27
|
+
// {@const x = expr} — the declaration contains the expression
|
|
28
|
+
if (node.declaration) {
|
|
29
|
+
const content = source.slice(node.declaration.start, node.declaration.end);
|
|
30
|
+
try {
|
|
31
|
+
parseScriptFn(content);
|
|
32
|
+
} catch (e) {
|
|
33
|
+
console.warn(`Failed to parse const declaration. Ignore this if content has no extractable message`, e);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
break;
|
|
37
|
+
case "IfBlock":
|
|
38
|
+
extractExpression(node.test, source, parseScriptFn);
|
|
39
|
+
walkFragment(node.consequent, source, parseScriptFn);
|
|
40
|
+
walkFragment(node.alternate, source, parseScriptFn);
|
|
41
|
+
break;
|
|
42
|
+
case "EachBlock":
|
|
43
|
+
extractExpression(node.expression, source, parseScriptFn);
|
|
44
|
+
if (node.key) {
|
|
45
|
+
extractExpression(node.key, source, parseScriptFn);
|
|
46
|
+
}
|
|
47
|
+
walkFragment(node.body, source, parseScriptFn);
|
|
48
|
+
walkFragment(node.fallback, source, parseScriptFn);
|
|
49
|
+
break;
|
|
50
|
+
case "AwaitBlock":
|
|
51
|
+
extractExpression(node.expression, source, parseScriptFn);
|
|
52
|
+
walkFragment(node.pending, source, parseScriptFn);
|
|
53
|
+
walkFragment(node.then, source, parseScriptFn);
|
|
54
|
+
walkFragment(node.catch, source, parseScriptFn);
|
|
55
|
+
break;
|
|
56
|
+
case "KeyBlock":
|
|
57
|
+
extractExpression(node.expression, source, parseScriptFn);
|
|
58
|
+
walkFragment(node.fragment, source, parseScriptFn);
|
|
59
|
+
break;
|
|
60
|
+
case "SnippetBlock":
|
|
61
|
+
walkFragment(node.body, source, parseScriptFn);
|
|
62
|
+
break;
|
|
63
|
+
case "RegularElement":
|
|
64
|
+
case "Component":
|
|
65
|
+
case "SvelteComponent":
|
|
66
|
+
case "SvelteElement":
|
|
67
|
+
case "SvelteHead":
|
|
68
|
+
case "SvelteBody":
|
|
69
|
+
case "SvelteWindow":
|
|
70
|
+
case "SvelteDocument":
|
|
71
|
+
case "SlotElement":
|
|
72
|
+
case "SvelteSelf":
|
|
73
|
+
case "SvelteFragment":
|
|
74
|
+
// Process attributes
|
|
75
|
+
if (node.attributes) {
|
|
76
|
+
for (const attr of node.attributes) {
|
|
77
|
+
if (attr.type === "Attribute") {
|
|
78
|
+
if (Array.isArray(attr.value)) {
|
|
79
|
+
for (const v of attr.value) {
|
|
80
|
+
if (v.type === "ExpressionTag") {
|
|
81
|
+
extractExpression(v.expression, source, parseScriptFn);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
} else if (typeof attr.value === "object" && attr.value !== null && attr.value.type === "ExpressionTag") {
|
|
85
|
+
extractExpression(attr.value.expression, source, parseScriptFn);
|
|
86
|
+
}
|
|
87
|
+
} else if (attr.type === "SpreadAttribute") {
|
|
88
|
+
extractExpression(attr.expression, source, parseScriptFn);
|
|
89
|
+
} else if (attr.expression) {
|
|
90
|
+
// Directive nodes (OnDirective, BindDirective, etc.)
|
|
91
|
+
extractExpression(attr.expression, source, parseScriptFn);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// Process children
|
|
96
|
+
walkFragment(node.fragment, source, parseScriptFn);
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
export function parseFile(source, filename, parseScriptFn) {
|
|
101
|
+
const ast = parse(source, {
|
|
102
|
+
modern: true,
|
|
103
|
+
filename
|
|
104
|
+
});
|
|
105
|
+
// Walk the template fragment
|
|
106
|
+
walkFragment(ast.fragment, source, parseScriptFn);
|
|
107
|
+
// Process <script> block
|
|
108
|
+
if (ast.instance) {
|
|
109
|
+
const program = ast.instance.content;
|
|
110
|
+
parseScriptFn(source.slice(program.start, program.end));
|
|
111
|
+
}
|
|
112
|
+
// Process <script context="module"> / <script module> block
|
|
113
|
+
if (ast.module) {
|
|
114
|
+
const program = ast.module.content;
|
|
115
|
+
parseScriptFn(source.slice(program.start, program.end));
|
|
116
|
+
}
|
|
117
|
+
}
|