@x18n/builtins 0.0.12 → 0.0.14
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/dist/angular-templates-DqIOGfzT.mjs +8 -0
- package/dist/checks.mjs +3 -2
- package/dist/extractors.d.mts +2 -2
- package/dist/extractors.mjs +18 -6
- package/dist/finders.d.mts +4 -1
- package/dist/finders.mjs +1 -1
- package/dist/formats/ecmascript.d.mts +5 -0
- package/dist/formats/ecmascript.mjs +211 -0
- package/dist/formats/ftl.d.mts +5 -0
- package/dist/formats/ftl.mjs +234 -0
- package/dist/formats/ini.d.mts +5 -0
- package/dist/formats/ini.mjs +158 -0
- package/dist/formats/json.d.mts +6 -0
- package/dist/formats/json.mjs +10 -0
- package/dist/formats/json5.d.mts +5 -0
- package/dist/formats/json5.mjs +30 -0
- package/dist/formats/jsonc.d.mts +6 -0
- package/dist/formats/jsonc.mjs +10 -0
- package/dist/formats/php.d.mts +5 -0
- package/dist/formats/php.mjs +200 -0
- package/dist/formats/po.d.mts +5 -0
- package/dist/formats/po.mjs +177 -0
- package/dist/formats/properties.d.mts +5 -0
- package/dist/formats/properties.mjs +123 -0
- package/dist/formats/resx.d.mts +6 -0
- package/dist/formats/resx.mjs +202 -0
- package/dist/formats/yaml.d.mts +5 -0
- package/dist/formats/yaml.mjs +113 -0
- package/dist/formats.d.mts +10 -34
- package/dist/formats.mjs +61 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +28 -22
- package/dist/{vue-5GFR5hft.mjs → js-CV-ECNJG.mjs} +133 -273
- package/dist/json-CyjI8bjp.d.mts +8 -0
- package/dist/json-Db-8qSbE.mjs +73 -0
- package/dist/jsx-templates-B1J0no3i.mjs +17 -0
- package/dist/locale-root-C_rch3IQ.mjs +15 -0
- package/dist/manifests.d.mts +8 -0
- package/dist/manifests.mjs +66 -2
- package/dist/placeholders.d.mts +11 -0
- package/dist/placeholders.mjs +28 -1
- package/dist/plugin-Cb1b0axD.mjs +83 -0
- package/dist/plural-forms-B7_QX4nG.mjs +12 -0
- package/dist/presets/cargo-i18n.d.mts +6 -0
- package/dist/presets/cargo-i18n.mjs +28 -0
- package/dist/presets/chrome-extension.d.mts +6 -0
- package/dist/presets/chrome-extension.mjs +52 -0
- package/dist/presets/dotnet-resx.d.mts +6 -0
- package/dist/presets/dotnet-resx.mjs +64 -0
- package/dist/presets/easy-localization.d.mts +6 -0
- package/dist/presets/easy-localization.mjs +36 -0
- package/dist/presets/ember-intl.d.mts +5 -0
- package/dist/presets/ember-intl.mjs +31 -0
- package/dist/presets/fluent-react.d.mts +5 -0
- package/dist/presets/fluent-react.mjs +46 -0
- package/dist/presets/fluent-vue.d.mts +5 -0
- package/dist/presets/fluent-vue.mjs +51 -0
- package/dist/presets/flutter-translate.d.mts +6 -0
- package/dist/presets/flutter-translate.mjs +30 -0
- package/dist/presets/flutter.d.mts +6 -0
- package/dist/presets/flutter.mjs +21 -0
- package/dist/presets/general.d.mts +6 -0
- package/dist/presets/general.mjs +17 -0
- package/dist/presets/globalize.d.mts +5 -0
- package/dist/presets/globalize.mjs +25 -0
- package/dist/presets/gravity-ui-i18n.d.mts +6 -0
- package/dist/presets/gravity-ui-i18n.mjs +41 -0
- package/dist/presets/i18n-js.d.mts +5 -0
- package/dist/presets/i18n-js.mjs +19 -0
- package/dist/presets/i18n-tag.d.mts +5 -0
- package/dist/presets/i18n-tag.mjs +21 -0
- package/dist/presets/i18next-shopify.d.mts +6 -0
- package/dist/presets/i18next-shopify.mjs +23 -0
- package/dist/presets/i18next.d.mts +5 -0
- package/dist/presets/i18next.mjs +10 -0
- package/dist/presets/i18nice.d.mts +6 -0
- package/dist/presets/i18nice.mjs +29 -0
- package/dist/presets/jekyll.d.mts +5 -0
- package/dist/presets/jekyll.mjs +23 -0
- package/dist/presets/laravel-vue-i18n.d.mts +6 -0
- package/dist/presets/laravel-vue-i18n.mjs +54 -0
- package/dist/presets/laravel.d.mts +6 -0
- package/dist/presets/laravel.mjs +23 -0
- package/dist/presets/lingui.d.mts +5 -0
- package/dist/presets/lingui.mjs +61 -0
- package/dist/presets/next-international.d.mts +5 -0
- package/dist/presets/next-international.mjs +27 -0
- package/dist/presets/next-intl.d.mts +5 -0
- package/dist/presets/next-intl.mjs +55 -0
- package/dist/presets/next-translate.d.mts +5 -0
- package/dist/presets/next-translate.mjs +53 -0
- package/dist/presets/ngx-translate.d.mts +5 -0
- package/dist/presets/ngx-translate.mjs +41 -0
- package/dist/presets/nuxt-i18n-micro.d.mts +5 -0
- package/dist/presets/nuxt-i18n-micro.mjs +81 -0
- package/dist/presets/nuxt-i18n.d.mts +5 -0
- package/dist/presets/nuxt-i18n.mjs +23 -0
- package/dist/presets/paraglide.d.mts +5 -0
- package/dist/presets/paraglide.mjs +38 -0
- package/dist/presets/php-gettext.d.mts +6 -0
- package/dist/presets/php-gettext.mjs +27 -0
- package/dist/presets/php-joomla.d.mts +6 -0
- package/dist/presets/php-joomla.mjs +22 -0
- package/dist/presets/polyglot.d.mts +5 -0
- package/dist/presets/polyglot.mjs +23 -0
- package/dist/presets/python-gettext.d.mts +6 -0
- package/dist/presets/python-gettext.mjs +44 -0
- package/dist/presets/react-i18next.d.mts +5 -0
- package/dist/presets/react-i18next.mjs +67 -0
- package/dist/presets/react-intl.d.mts +5 -0
- package/dist/presets/react-intl.mjs +47 -0
- package/dist/presets/ruby-rails.d.mts +7 -0
- package/dist/presets/ruby-rails.mjs +44 -0
- package/dist/presets/solid-primitives-i18n.d.mts +5 -0
- package/dist/presets/solid-primitives-i18n.mjs +29 -0
- package/dist/presets/svelte-i18n.d.mts +5 -0
- package/dist/presets/svelte-i18n.mjs +33 -0
- package/dist/presets/svelte-i18next.d.mts +5 -0
- package/dist/presets/svelte-i18next.mjs +27 -0
- package/dist/presets/transloco.d.mts +5 -0
- package/dist/presets/transloco.mjs +44 -0
- package/dist/presets/typesafe-i18n.d.mts +6 -0
- package/dist/presets/typesafe-i18n.mjs +61 -0
- package/dist/presets/ui5.d.mts +5 -0
- package/dist/presets/ui5.mjs +35 -0
- package/dist/presets/vscode-extension.d.mts +5 -0
- package/dist/presets/vscode-extension.mjs +53 -0
- package/dist/presets/vue-i18n.d.mts +5 -0
- package/dist/presets/vue-i18n.mjs +59 -0
- package/dist/presets.d.mts +7 -54
- package/dist/presets.mjs +346 -1
- package/dist/reporters/github.d.mts +6 -0
- package/dist/reporters/github.mjs +27 -0
- package/dist/reporters/json.d.mts +6 -0
- package/dist/reporters/json.mjs +24 -0
- package/dist/reporters/markdown.d.mts +6 -0
- package/dist/reporters/markdown.mjs +37 -0
- package/dist/reporters/pretty.d.mts +6 -0
- package/dist/reporters/pretty.mjs +64 -0
- package/dist/reporters/sarif.d.mts +6 -0
- package/dist/reporters/sarif.mjs +48 -0
- package/dist/reporters.d.mts +5 -12
- package/dist/reporters.mjs +8 -218
- package/dist/scanners/astro.d.mts +6 -0
- package/dist/scanners/astro.mjs +256 -0
- package/dist/scanners/js.d.mts +6 -0
- package/dist/scanners/js.mjs +33 -0
- package/dist/scanners/regex-scopes.d.mts +14 -0
- package/dist/scanners/regex-scopes.mjs +41 -0
- package/dist/scanners/regex.d.mts +9 -0
- package/dist/{regex-B9TBGgXC.mjs → scanners/regex.mjs} +2 -33
- package/dist/scanners/svelte.d.mts +6 -0
- package/dist/{scanners-B27IEH4_.mjs → scanners/svelte.mjs} +8 -23
- package/dist/scanners/vue.d.mts +6 -0
- package/dist/scanners/vue.mjs +172 -0
- package/dist/scanners.d.mts +9 -47
- package/dist/scanners.mjs +36 -4
- package/dist/sfc-blocks-DaGKDZsb.mjs +213 -0
- package/dist/shared-BWaXUmwA.mjs +70 -0
- package/dist/shared-D-tbsYmc.mjs +30 -0
- package/dist/shared-D4NJBKEP.mjs +17 -0
- package/dist/shared-Tq06G0gp.mjs +6 -0
- package/dist/shared-meYeVckh.mjs +11 -0
- package/dist/vue-compiler-0sfLIiiL.mjs +8 -0
- package/package.json +67 -3
- package/dist/formats-CFkM5co0.mjs +0 -1370
- package/dist/presets-DJrGl3HR.mjs +0 -1184
- package/dist/sfc-blocks-BIPqKcvd.mjs +0 -161
- package/dist/yaml-plugin.d.mts +0 -8
- package/dist/yaml-plugin.mjs +0 -68
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
//#region src/scanners/svelte.ts
|
|
1
|
+
import { n as loadParser, r as scanSource, t as isTextAttribute } from "../js-CV-ECNJG.mjs";
|
|
2
|
+
//#region src/scanners/svelte/index.ts
|
|
3
3
|
let compiler;
|
|
4
4
|
function isSvelteNode(value) {
|
|
5
5
|
return typeof value === "object" && value !== null && "type" in value && typeof value.type === "string" && "start" in value && typeof value.start === "number" && "end" in value && typeof value.end === "number";
|
|
@@ -14,33 +14,18 @@ const expressionTypes = /* @__PURE__ */ new Set([
|
|
|
14
14
|
"AwaitBlock",
|
|
15
15
|
"KeyBlock"
|
|
16
16
|
]);
|
|
17
|
-
|
|
18
|
-
"class",
|
|
19
|
-
"style",
|
|
20
|
-
"id",
|
|
21
|
-
"name",
|
|
22
|
-
"type",
|
|
23
|
-
"href",
|
|
24
|
-
"src",
|
|
25
|
-
"for",
|
|
26
|
-
"role",
|
|
27
|
-
"lang",
|
|
28
|
-
"rel",
|
|
29
|
-
"target"
|
|
30
|
-
]);
|
|
31
|
-
function textHardString(node, data, attribute, out) {
|
|
17
|
+
function textHardString(node, data, attribute, textAttributes, out) {
|
|
32
18
|
const text = data.trim();
|
|
33
|
-
|
|
34
|
-
if (!text || !/\p{L}/u.test(text) || name !== void 0 && ignoredAttributes.has(name)) return;
|
|
19
|
+
if (!text || !/\p{L}/u.test(text)) return;
|
|
35
20
|
if (attribute) {
|
|
36
|
-
out.push({
|
|
21
|
+
if (typeof attribute.name === "string" && isTextAttribute(attribute.name, textAttributes)) out.push({
|
|
37
22
|
text,
|
|
38
23
|
range: {
|
|
39
24
|
start: attribute.start,
|
|
40
25
|
end: attribute.end
|
|
41
26
|
},
|
|
42
27
|
kind: "svelte-attribute",
|
|
43
|
-
|
|
28
|
+
attribute: attribute.name
|
|
44
29
|
});
|
|
45
30
|
return;
|
|
46
31
|
}
|
|
@@ -101,7 +86,7 @@ function svelte() {
|
|
|
101
86
|
return;
|
|
102
87
|
}
|
|
103
88
|
if (expressionTypes.has(node.type) && "expression" in node && isSvelteNode(node.expression)) scan(file.text.slice(node.expression.start, node.expression.end), node.expression.start, true);
|
|
104
|
-
if (node.type === "Text" && "data" in node && typeof node.data === "string") textHardString(node, node.data, parentType === "Attribute" ? attribute : void 0, hardStrings);
|
|
89
|
+
if (node.type === "Text" && "data" in node && typeof node.data === "string") textHardString(node, node.data, parentType === "Attribute" ? attribute : void 0, ctx.config.extract.attributes, hardStrings);
|
|
105
90
|
const owner = node.type === "Attribute" ? node : attribute;
|
|
106
91
|
for (const [key, child] of Object.entries(node)) if (key !== "expression" && typeof child === "object") visit(child, node.type, owner);
|
|
107
92
|
};
|
|
@@ -116,4 +101,4 @@ function svelte() {
|
|
|
116
101
|
};
|
|
117
102
|
}
|
|
118
103
|
//#endregion
|
|
119
|
-
export { svelte as
|
|
104
|
+
export { svelte as default, svelte };
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { t as loadVueCompiler } from "../vue-compiler-0sfLIiiL.mjs";
|
|
2
|
+
import { n as loadParser, r as scanSource, t as isTextAttribute } from "../js-CV-ECNJG.mjs";
|
|
3
|
+
//#region src/scanners/vue/index.ts
|
|
4
|
+
const ELEMENT = 1;
|
|
5
|
+
const TEXT = 2;
|
|
6
|
+
const SIMPLE_EXPRESSION = 4;
|
|
7
|
+
const INTERPOLATION = 5;
|
|
8
|
+
const ATTRIBUTE = 6;
|
|
9
|
+
/** `keypath` is the vue-i18n prop for a key, on `<i18n-t>` and on the app's own wrappers of it; `path` on the legacy components. */
|
|
10
|
+
function isKeyAttribute(tag, name) {
|
|
11
|
+
return name === "keypath" || name === "path" && (tag === "i18n" || tag === "i18n-d");
|
|
12
|
+
}
|
|
13
|
+
function merge(into, result) {
|
|
14
|
+
into.usages.push(...result.usages);
|
|
15
|
+
into.keySlots.push(...result.keySlots ?? []);
|
|
16
|
+
into.hardStrings.push(...result.hardStrings ?? []);
|
|
17
|
+
}
|
|
18
|
+
var TemplateScan = class {
|
|
19
|
+
parse;
|
|
20
|
+
calls;
|
|
21
|
+
textAttributes;
|
|
22
|
+
out = {
|
|
23
|
+
usages: [],
|
|
24
|
+
keySlots: [],
|
|
25
|
+
hardStrings: []
|
|
26
|
+
};
|
|
27
|
+
constructor(parse, calls, textAttributes) {
|
|
28
|
+
this.parse = parse;
|
|
29
|
+
this.calls = calls;
|
|
30
|
+
this.textAttributes = textAttributes;
|
|
31
|
+
}
|
|
32
|
+
expression(content, offset) {
|
|
33
|
+
if (!content.trim()) return;
|
|
34
|
+
try {
|
|
35
|
+
merge(this.out, scanSource(this.parse, content, {
|
|
36
|
+
calls: this.calls,
|
|
37
|
+
offset,
|
|
38
|
+
filename: "template.ts",
|
|
39
|
+
expression: true,
|
|
40
|
+
hardStrings: false
|
|
41
|
+
}));
|
|
42
|
+
} catch {}
|
|
43
|
+
}
|
|
44
|
+
/** `:keypath="cond ? 'a' : 'b'"`: the expression is read as the key argument of a translate call. */
|
|
45
|
+
keyExpression(content, offset) {
|
|
46
|
+
const callee = "__x18nKey(";
|
|
47
|
+
try {
|
|
48
|
+
const result = scanSource(this.parse, `${callee}${content})`, {
|
|
49
|
+
calls: [{ callee: "__x18nKey" }],
|
|
50
|
+
offset: offset - 10,
|
|
51
|
+
filename: "template.ts",
|
|
52
|
+
hardStrings: false
|
|
53
|
+
});
|
|
54
|
+
merge(this.out, {
|
|
55
|
+
...result,
|
|
56
|
+
usages: result.usages.map(({ expression: _, ...usage }) => usage)
|
|
57
|
+
});
|
|
58
|
+
} catch {}
|
|
59
|
+
}
|
|
60
|
+
/** `v-t="'key'"` and `v-t="{ path: 'key' }"`. */
|
|
61
|
+
vt(dir) {
|
|
62
|
+
const exp = dir.exp;
|
|
63
|
+
if (!exp || !("content" in exp)) return;
|
|
64
|
+
const offset = exp.loc.start.offset;
|
|
65
|
+
const literal = /^\s*(['"`])(.*)\1\s*$/.exec(exp.content) ?? /path\s*:\s*(['"`])(.*?)\1/.exec(exp.content);
|
|
66
|
+
if (!literal) return;
|
|
67
|
+
const start = offset + exp.content.indexOf(literal[2], literal.index);
|
|
68
|
+
const range = {
|
|
69
|
+
start,
|
|
70
|
+
end: start + literal[2].length
|
|
71
|
+
};
|
|
72
|
+
this.out.usages.push({
|
|
73
|
+
raw: literal[2],
|
|
74
|
+
range,
|
|
75
|
+
kind: "call",
|
|
76
|
+
dynamic: false
|
|
77
|
+
});
|
|
78
|
+
this.out.keySlots.push(range);
|
|
79
|
+
}
|
|
80
|
+
attribute(element, prop) {
|
|
81
|
+
if (prop.type === ATTRIBUTE) {
|
|
82
|
+
const value = prop.value;
|
|
83
|
+
if (!value) return;
|
|
84
|
+
const range = {
|
|
85
|
+
start: value.loc.start.offset + 1,
|
|
86
|
+
end: value.loc.end.offset - 1
|
|
87
|
+
};
|
|
88
|
+
if (isKeyAttribute(element.tag, prop.name)) {
|
|
89
|
+
this.out.usages.push({
|
|
90
|
+
raw: value.content,
|
|
91
|
+
range,
|
|
92
|
+
kind: "call",
|
|
93
|
+
dynamic: false
|
|
94
|
+
});
|
|
95
|
+
this.out.keySlots.push(range);
|
|
96
|
+
} else if (isTextAttribute(prop.name, this.textAttributes) && /\p{L}/u.test(value.content)) this.out.hardStrings.push({
|
|
97
|
+
text: value.content,
|
|
98
|
+
range: {
|
|
99
|
+
start: prop.loc.start.offset,
|
|
100
|
+
end: prop.loc.end.offset
|
|
101
|
+
},
|
|
102
|
+
kind: "vue-attribute",
|
|
103
|
+
attribute: prop.name
|
|
104
|
+
});
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (prop.name === "t") {
|
|
108
|
+
this.vt(prop);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const exp = prop.exp;
|
|
112
|
+
if (exp && "content" in exp) {
|
|
113
|
+
if (prop.name === "bind" && prop.arg && "content" in prop.arg && isKeyAttribute(element.tag, prop.arg.content)) this.keyExpression(exp.content, exp.loc.start.offset);
|
|
114
|
+
else this.expression(exp.content, exp.loc.start.offset);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
node(node) {
|
|
118
|
+
if (node.type === INTERPOLATION && "content" in node.content) this.expression(node.content.content, node.content.loc.start.offset);
|
|
119
|
+
if (node.type === TEXT) {
|
|
120
|
+
const text = node.content.trim();
|
|
121
|
+
if (text && /\p{L}/u.test(text)) {
|
|
122
|
+
const start = node.loc.start.offset + node.content.indexOf(text);
|
|
123
|
+
this.out.hardStrings.push({
|
|
124
|
+
text,
|
|
125
|
+
range: {
|
|
126
|
+
start,
|
|
127
|
+
end: start + text.length
|
|
128
|
+
},
|
|
129
|
+
kind: "vue-template-text"
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (node.type === ELEMENT) for (const prop of node.props) this.attribute(node, prop);
|
|
134
|
+
if ("children" in node) {
|
|
135
|
+
for (const child of node.children) if (typeof child === "object" && child.type !== SIMPLE_EXPRESSION) this.node(child);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
/** Vue SFCs: `<script>` through the `js` scanner, template expressions, `v-t`, `<i18n-t keypath>`. */
|
|
140
|
+
function vue() {
|
|
141
|
+
return {
|
|
142
|
+
name: "x18n:scanner-vue",
|
|
143
|
+
scanners: [{
|
|
144
|
+
id: "vue",
|
|
145
|
+
extensions: ["vue"],
|
|
146
|
+
async scan(file, ctx) {
|
|
147
|
+
const [{ parse }, parseJs] = await Promise.all([loadVueCompiler(), loadParser()]);
|
|
148
|
+
const { descriptor } = parse(file.text, {
|
|
149
|
+
filename: file.path,
|
|
150
|
+
sourceMap: false,
|
|
151
|
+
ignoreEmpty: false
|
|
152
|
+
});
|
|
153
|
+
const template = new TemplateScan(parseJs, ctx.calls, ctx.config.extract.attributes);
|
|
154
|
+
for (const script of [descriptor.script, descriptor.scriptSetup]) {
|
|
155
|
+
if (!script) continue;
|
|
156
|
+
const lang = script.lang ?? "js";
|
|
157
|
+
merge(template.out, scanSource(parseJs, script.content, {
|
|
158
|
+
calls: ctx.calls,
|
|
159
|
+
ignoreCallees: ctx.config.extract.ignoreCallees,
|
|
160
|
+
textAttributes: ctx.config.extract.attributes,
|
|
161
|
+
offset: script.loc.start.offset,
|
|
162
|
+
filename: `script.${lang === "tsx" || lang === "jsx" ? lang : lang === "ts" ? "ts" : "js"}`
|
|
163
|
+
}));
|
|
164
|
+
}
|
|
165
|
+
if (descriptor.template?.ast) template.node(descriptor.template.ast);
|
|
166
|
+
return template.out;
|
|
167
|
+
}
|
|
168
|
+
}]
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
//#endregion
|
|
172
|
+
export { vue as default, vue };
|
package/dist/scanners.d.mts
CHANGED
|
@@ -1,49 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
//#region src/scanners/js.d.ts
|
|
4
|
-
/** `a.b.c` for identifier and member chains, `this.` dropped; undefined for anything else. */
|
|
5
|
-
export declare function calleeName(node: Node): string | undefined;
|
|
6
|
-
type ParseSync = typeof import("oxc-parser").parseSync;
|
|
7
|
-
/** Loads oxc lazily: native in Node, WASM through the package's browser export. */
|
|
8
|
-
export declare function loadParser(): Promise<ParseSync>;
|
|
9
|
-
interface ScanSourceOptions {
|
|
10
|
-
readonly calls: readonly CallSpec[];
|
|
11
|
-
/** Offset of `text` in the file, for embedded code such as Vue `<script>`. */
|
|
12
|
-
readonly offset?: number;
|
|
13
|
-
readonly hardStrings?: boolean;
|
|
14
|
-
/** File name for the parser; picks JS, TS or JSX. */
|
|
15
|
-
readonly filename: string;
|
|
16
|
-
/** Parse `text` as one expression (template interpolations). */
|
|
17
|
-
readonly expression?: boolean;
|
|
18
|
-
/** Callees whose string arguments are never hard strings (`extract.ignoreCallees`). */
|
|
19
|
-
readonly ignoreCallees?: readonly string[] | undefined;
|
|
20
|
-
}
|
|
21
|
-
export declare function scanSource(parse: ParseSync, text: string, options: ScanSourceOptions): ScanResult;
|
|
22
|
-
/** The AST scanner for JS and TS, driven by call specs (`usage.calls`). */
|
|
23
|
-
export declare function js(): Plugin;
|
|
24
|
-
//#endregion
|
|
25
|
-
//#region src/scanners/regex.d.ts
|
|
26
|
-
/** Turns a pattern with a `{key}` placeholder into a regex whose first group is the key. */
|
|
27
|
-
export declare function compileUsagePattern(pattern: string): RegExp;
|
|
28
|
-
export declare function scanWithRegex(text: string, regexes: readonly RegExp[]): UsageCandidate[];
|
|
29
|
-
/** The `regex` scanner over `usage.regex` specs, plus extra specs given here (YAML plugins). */
|
|
30
|
-
export declare function regex(specs?: readonly RegexScannerSpec[], id?: string): Plugin;
|
|
1
|
+
import { ScannerEntry } from "@x18n/core";
|
|
2
|
+
//#region src/scanners/index.d.ts
|
|
31
3
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
4
|
+
* Built-in scanners by id, in scan order. A scanner is a folder: `<name>/entry.ts` holds its extensions and a
|
|
5
|
+
* `load` for `<name>/index.ts`, imported when a file it scans is first scanned. `regex` is not here: its
|
|
6
|
+
* extensions come from `usage.regex`, so it is always loaded.
|
|
34
7
|
*/
|
|
35
|
-
export declare
|
|
36
|
-
readonly
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}): Plugin;
|
|
40
|
-
//#endregion
|
|
41
|
-
//#region src/scanners/svelte.d.ts
|
|
42
|
-
/** Svelte components: `<script>` and template expressions through the `js` scanner. Node only at first. */
|
|
43
|
-
export declare function svelte(): Plugin;
|
|
44
|
-
//#endregion
|
|
45
|
-
//#region src/scanners/vue.d.ts
|
|
46
|
-
/** Vue SFCs: `<script>` through the `js` scanner, template expressions, `v-t`, `<i18n-t keypath>`. */
|
|
47
|
-
export declare function vue(): Plugin;
|
|
48
|
-
//#endregion
|
|
49
|
-
export type { ScanSourceOptions };
|
|
8
|
+
export declare const scanners: {
|
|
9
|
+
readonly [id: string]: ScannerEntry;
|
|
10
|
+
};
|
|
11
|
+
//#endregion
|
package/dist/scanners.mjs
CHANGED
|
@@ -1,4 +1,36 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
//#endregion
|
|
2
|
+
//#region src/scanners/index.ts
|
|
3
|
+
/**
|
|
4
|
+
* Built-in scanners by id, in scan order. A scanner is a folder: `<name>/entry.ts` holds its extensions and a
|
|
5
|
+
* `load` for `<name>/index.ts`, imported when a file it scans is first scanned. `regex` is not here: its
|
|
6
|
+
* extensions come from `usage.regex`, so it is always loaded.
|
|
7
|
+
*/
|
|
8
|
+
const scanners = {
|
|
9
|
+
js: {
|
|
10
|
+
extensions: [
|
|
11
|
+
"js",
|
|
12
|
+
"jsx",
|
|
13
|
+
"mjs",
|
|
14
|
+
"cjs",
|
|
15
|
+
"ts",
|
|
16
|
+
"tsx",
|
|
17
|
+
"mts",
|
|
18
|
+
"cts"
|
|
19
|
+
],
|
|
20
|
+
load: () => import("./scanners/js.mjs")
|
|
21
|
+
},
|
|
22
|
+
vue: {
|
|
23
|
+
extensions: ["vue"],
|
|
24
|
+
load: () => import("./scanners/vue.mjs")
|
|
25
|
+
},
|
|
26
|
+
svelte: {
|
|
27
|
+
extensions: ["svelte"],
|
|
28
|
+
load: () => import("./scanners/svelte.mjs")
|
|
29
|
+
},
|
|
30
|
+
astro: {
|
|
31
|
+
extensions: ["astro"],
|
|
32
|
+
load: () => import("./scanners/astro.mjs")
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
//#endregion
|
|
36
|
+
export { scanners };
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import json from "./formats/json.mjs";
|
|
2
|
+
import json5 from "./formats/json5.mjs";
|
|
3
|
+
import yaml from "./formats/yaml.mjs";
|
|
4
|
+
import { t as loadVueCompiler } from "./vue-compiler-0sfLIiiL.mjs";
|
|
5
|
+
import { isMessageLeaf, pathId } from "@x18n/core";
|
|
6
|
+
//#region src/finders/sfc-blocks.ts
|
|
7
|
+
const formatOfLang = {
|
|
8
|
+
json: "json",
|
|
9
|
+
json5: "json5",
|
|
10
|
+
yaml: "yaml",
|
|
11
|
+
yml: "yaml"
|
|
12
|
+
};
|
|
13
|
+
/** A format for blocks that hold every locale (`{ "en": {...}, "de": {...} }`): each resource sees its locale's subtree. */
|
|
14
|
+
function localeRoot(base) {
|
|
15
|
+
return {
|
|
16
|
+
id: `sfc-${base.id}`,
|
|
17
|
+
extensions: [],
|
|
18
|
+
parse(text, ctx) {
|
|
19
|
+
const parsed = base.parse(text, ctx);
|
|
20
|
+
const subtree = parsed.tree[ctx.locale];
|
|
21
|
+
const tree = subtree && !isMessageLeaf(subtree) ? subtree : {};
|
|
22
|
+
const ranges = /* @__PURE__ */ new Map();
|
|
23
|
+
const prefix = `${pathId([ctx.locale])}\u0000`;
|
|
24
|
+
for (const [id, range] of parsed.ranges ?? []) if (id.startsWith(prefix)) ranges.set(id.slice(prefix.length), range);
|
|
25
|
+
return {
|
|
26
|
+
tree,
|
|
27
|
+
ranges
|
|
28
|
+
};
|
|
29
|
+
},
|
|
30
|
+
edit: base.edit ? (text, ops, ctx) => base.edit(text, ops.map((op) => ({
|
|
31
|
+
...op,
|
|
32
|
+
path: [ctx.locale, ...op.path]
|
|
33
|
+
})), ctx) : void 0
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/** Resolves `src` against the SFC's folder (builtins have no `node:path`); undefined above the project root. */
|
|
37
|
+
function resolveSrc(from, src) {
|
|
38
|
+
const parts = src.startsWith("/") ? [] : from.split("/").slice(0, -1);
|
|
39
|
+
for (const part of src.split("/")) if (part === "..") {
|
|
40
|
+
if (parts.pop() === void 0) return void 0;
|
|
41
|
+
} else if (part && part !== ".") parts.push(part);
|
|
42
|
+
return parts.join("/");
|
|
43
|
+
}
|
|
44
|
+
/** The file and lang of a `src` block, if one of the block formats can read it; a `.js` or `.ts` file is not parsed as JSON. */
|
|
45
|
+
function srcFile(from, src, lang) {
|
|
46
|
+
const path = resolveSrc(from, src);
|
|
47
|
+
if (path === void 0) return void 0;
|
|
48
|
+
const fileLang = lang ?? path.slice(path.lastIndexOf(".") + 1);
|
|
49
|
+
return Object.hasOwn(formatOfLang, fileLang) ? {
|
|
50
|
+
path,
|
|
51
|
+
lang: fileLang
|
|
52
|
+
} : void 0;
|
|
53
|
+
}
|
|
54
|
+
/** One resource per locale of a block: its `locale` attribute, or every top-level key. */
|
|
55
|
+
function blockResources(bases, i18n, sourceLocale) {
|
|
56
|
+
const { path, locale, block } = i18n;
|
|
57
|
+
const base = bases.find((f) => f.id === (formatOfLang[block.lang ?? "json"] ?? "json"));
|
|
58
|
+
if (locale) return [{
|
|
59
|
+
path,
|
|
60
|
+
locale,
|
|
61
|
+
namespace: void 0,
|
|
62
|
+
format: base.id,
|
|
63
|
+
block: i18n.src ? void 0 : block
|
|
64
|
+
}];
|
|
65
|
+
try {
|
|
66
|
+
return Object.keys(base.parse(i18n.content, {
|
|
67
|
+
path,
|
|
68
|
+
locale: "",
|
|
69
|
+
namespace: void 0,
|
|
70
|
+
indent: void 0
|
|
71
|
+
}).tree).map((locale, i) => ({
|
|
72
|
+
path,
|
|
73
|
+
locale,
|
|
74
|
+
namespace: void 0,
|
|
75
|
+
format: `sfc-${base.id}`,
|
|
76
|
+
block: {
|
|
77
|
+
...block,
|
|
78
|
+
index: block.index * 1e3 + i
|
|
79
|
+
}
|
|
80
|
+
}));
|
|
81
|
+
} catch {
|
|
82
|
+
return [{
|
|
83
|
+
path,
|
|
84
|
+
locale: sourceLocale,
|
|
85
|
+
namespace: void 0,
|
|
86
|
+
format: `sfc-${base.id}`,
|
|
87
|
+
block
|
|
88
|
+
}];
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Vue `<i18n>` blocks: one resource per block and locale (`locale="en"`, or every top-level locale key).
|
|
93
|
+
* A block with `src` is a resource in the referenced file, so edits go there.
|
|
94
|
+
*/
|
|
95
|
+
function sfcBlocks() {
|
|
96
|
+
const bases = [
|
|
97
|
+
json(),
|
|
98
|
+
json5(),
|
|
99
|
+
yaml()
|
|
100
|
+
].flatMap((plugin) => plugin.formats ?? []);
|
|
101
|
+
return {
|
|
102
|
+
name: "x18n:finder-sfc-blocks",
|
|
103
|
+
formats: bases.map(localeRoot),
|
|
104
|
+
localeFinders: [{
|
|
105
|
+
id: "sfc-blocks",
|
|
106
|
+
async find(ctx) {
|
|
107
|
+
const files = await ctx.host.glob(["**/*.vue"], {
|
|
108
|
+
ignore: ctx.config.usage.exclude,
|
|
109
|
+
gitignore: true
|
|
110
|
+
});
|
|
111
|
+
const { parse } = await loadVueCompiler();
|
|
112
|
+
const out = [];
|
|
113
|
+
const sources = /* @__PURE__ */ new Set();
|
|
114
|
+
for (const path of files) {
|
|
115
|
+
const text = await ctx.host.readFile(path);
|
|
116
|
+
if (!text?.includes("<i18n")) continue;
|
|
117
|
+
const { descriptor } = parse(text, {
|
|
118
|
+
filename: path,
|
|
119
|
+
sourceMap: false
|
|
120
|
+
});
|
|
121
|
+
for (const [index, { type, attrs, content, loc }] of descriptor.customBlocks.entries()) {
|
|
122
|
+
if (type !== "i18n") continue;
|
|
123
|
+
const lang = typeof attrs.lang === "string" ? attrs.lang : void 0;
|
|
124
|
+
const locale = typeof attrs.locale === "string" ? attrs.locale : void 0;
|
|
125
|
+
if (typeof attrs.src !== "string") {
|
|
126
|
+
out.push(...blockResources(bases, {
|
|
127
|
+
path,
|
|
128
|
+
content,
|
|
129
|
+
locale,
|
|
130
|
+
block: {
|
|
131
|
+
index,
|
|
132
|
+
range: {
|
|
133
|
+
start: loc.start.offset,
|
|
134
|
+
end: loc.end.offset
|
|
135
|
+
},
|
|
136
|
+
lang: lang ?? "json"
|
|
137
|
+
},
|
|
138
|
+
src: false
|
|
139
|
+
}, ctx.config.sourceLocale));
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
const src = srcFile(path, attrs.src, lang);
|
|
143
|
+
const srcText = !src || sources.has(src.path) ? void 0 : await ctx.host.readFile(src.path);
|
|
144
|
+
if (!src || srcText === void 0) continue;
|
|
145
|
+
sources.add(src.path);
|
|
146
|
+
const block = {
|
|
147
|
+
index,
|
|
148
|
+
range: {
|
|
149
|
+
start: 0,
|
|
150
|
+
end: srcText.length
|
|
151
|
+
},
|
|
152
|
+
lang: src.lang
|
|
153
|
+
};
|
|
154
|
+
out.push(...blockResources(bases, {
|
|
155
|
+
path: src.path,
|
|
156
|
+
content: srcText,
|
|
157
|
+
locale,
|
|
158
|
+
block,
|
|
159
|
+
src: true
|
|
160
|
+
}, ctx.config.sourceLocale));
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return out;
|
|
164
|
+
}
|
|
165
|
+
}]
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
/** fluent-vue `<fluent locale="en">` blocks in Vue SFCs, read with the `ftl` format. */
|
|
169
|
+
function sfcFluentBlocks() {
|
|
170
|
+
return {
|
|
171
|
+
name: "x18n:finder-sfc-fluent-blocks",
|
|
172
|
+
localeFinders: [{
|
|
173
|
+
id: "sfc-fluent-blocks",
|
|
174
|
+
async find(ctx) {
|
|
175
|
+
const files = await ctx.host.glob(["**/*.vue"], {
|
|
176
|
+
ignore: ctx.config.usage.exclude,
|
|
177
|
+
gitignore: true
|
|
178
|
+
});
|
|
179
|
+
const { parse } = await loadVueCompiler();
|
|
180
|
+
const out = [];
|
|
181
|
+
for (const path of files) {
|
|
182
|
+
const text = await ctx.host.readFile(path);
|
|
183
|
+
if (!text?.includes("<fluent")) continue;
|
|
184
|
+
parse(text, {
|
|
185
|
+
filename: path,
|
|
186
|
+
sourceMap: false
|
|
187
|
+
}).descriptor.customBlocks.forEach((block, index) => {
|
|
188
|
+
if (block.type === "fluent" && typeof block.attrs.locale === "string") {
|
|
189
|
+
const range = {
|
|
190
|
+
start: block.loc.start.offset,
|
|
191
|
+
end: block.loc.end.offset
|
|
192
|
+
};
|
|
193
|
+
out.push({
|
|
194
|
+
path,
|
|
195
|
+
locale: block.attrs.locale,
|
|
196
|
+
namespace: void 0,
|
|
197
|
+
format: "ftl",
|
|
198
|
+
block: {
|
|
199
|
+
index,
|
|
200
|
+
range,
|
|
201
|
+
lang: "ftl"
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
return out;
|
|
208
|
+
}
|
|
209
|
+
}]
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
//#endregion
|
|
213
|
+
export { sfcFluentBlocks as n, sfcBlocks as t };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
//#region src/formats/shared/index.ts
|
|
2
|
+
/** Indent of the first indented line, so edits keep the file's style. */
|
|
3
|
+
function detectIndent(text, fallback) {
|
|
4
|
+
if (fallback === "tab") return {
|
|
5
|
+
insertSpaces: false,
|
|
6
|
+
tabSize: 1
|
|
7
|
+
};
|
|
8
|
+
if (typeof fallback === "number") return {
|
|
9
|
+
insertSpaces: true,
|
|
10
|
+
tabSize: fallback
|
|
11
|
+
};
|
|
12
|
+
const match = /^([ \t]+)\S/m.exec(text);
|
|
13
|
+
if (match?.[1]?.startsWith(" ")) return {
|
|
14
|
+
insertSpaces: false,
|
|
15
|
+
tabSize: 1
|
|
16
|
+
};
|
|
17
|
+
return {
|
|
18
|
+
insertSpaces: true,
|
|
19
|
+
tabSize: match?.[1]?.length ?? 2
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function detectEol(text) {
|
|
23
|
+
return text.includes("\r\n") ? "\r\n" : "\n";
|
|
24
|
+
}
|
|
25
|
+
function toLeaf(value) {
|
|
26
|
+
if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") return value;
|
|
27
|
+
if (value instanceof Date) return value.toISOString();
|
|
28
|
+
}
|
|
29
|
+
function leafText(value) {
|
|
30
|
+
return value === null ? "" : String(value);
|
|
31
|
+
}
|
|
32
|
+
function splice(text, range, insert) {
|
|
33
|
+
return text.slice(0, range.start) + insert + text.slice(range.end);
|
|
34
|
+
}
|
|
35
|
+
/** Appends `chunk` on a line of its own, in the file's line endings. */
|
|
36
|
+
function appendLines(text, chunk) {
|
|
37
|
+
const eol = detectEol(text);
|
|
38
|
+
return (text === "" || text.endsWith("\n") ? text : text + eol) + chunk.replace(/\n/g, eol);
|
|
39
|
+
}
|
|
40
|
+
function splitLines(text) {
|
|
41
|
+
const out = [];
|
|
42
|
+
let start = 0;
|
|
43
|
+
while (start < text.length) {
|
|
44
|
+
const lf = text.indexOf("\n", start);
|
|
45
|
+
const next = lf === -1 ? text.length : lf + 1;
|
|
46
|
+
const end = lf === -1 ? text.length : lf > start && text[lf - 1] === "\r" ? lf - 1 : lf;
|
|
47
|
+
out.push({
|
|
48
|
+
start,
|
|
49
|
+
end,
|
|
50
|
+
next
|
|
51
|
+
});
|
|
52
|
+
start = next;
|
|
53
|
+
}
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
/** An entry's range widened to swallow the blank lines that separate it, so deleting it leaves one separator. */
|
|
57
|
+
function withBlankLines(text, range) {
|
|
58
|
+
const after = /^(?:[ \t]*\r?\n)*(?:[ \t]*$)?/.exec(text.slice(range.end))[0].length;
|
|
59
|
+
if (range.end + after < text.length) return {
|
|
60
|
+
start: range.start,
|
|
61
|
+
end: range.end + after
|
|
62
|
+
};
|
|
63
|
+
const before = /(?<=\n)(?:[ \t]*\r?\n)+$/.exec(text.slice(0, range.start))?.[0].length ?? 0;
|
|
64
|
+
return {
|
|
65
|
+
start: range.start - before,
|
|
66
|
+
end: text.length
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
//#endregion
|
|
70
|
+
export { splice as a, withBlankLines as c, leafText as i, detectEol as n, splitLines as o, detectIndent as r, toLeaf as s, appendLines as t };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//#region src/reporters/shared/index.ts
|
|
2
|
+
const codes = {
|
|
3
|
+
red: 31,
|
|
4
|
+
yellow: 33,
|
|
5
|
+
cyan: 36,
|
|
6
|
+
gray: 90,
|
|
7
|
+
blue: 34,
|
|
8
|
+
bold: 1
|
|
9
|
+
};
|
|
10
|
+
function paint(ctx, color, text) {
|
|
11
|
+
return ctx.color ? `\u001B[${codes[color]}m${text}\u001B[0m` : text;
|
|
12
|
+
}
|
|
13
|
+
function plural(count, word) {
|
|
14
|
+
return `${count} ${word}${count === 1 ? "" : "s"}`;
|
|
15
|
+
}
|
|
16
|
+
function totals(inputs) {
|
|
17
|
+
return inputs.reduce((sum, input) => ({
|
|
18
|
+
errors: sum.errors + input.summary.errors,
|
|
19
|
+
warnings: sum.warnings + input.summary.warnings,
|
|
20
|
+
infos: sum.infos + input.summary.infos,
|
|
21
|
+
files: sum.files + input.summary.files
|
|
22
|
+
}), {
|
|
23
|
+
errors: 0,
|
|
24
|
+
warnings: 0,
|
|
25
|
+
infos: 0,
|
|
26
|
+
files: 0
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
//#endregion
|
|
30
|
+
export { totals as i, paint as n, plural as r, codes as t };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
//#region src/presets/shared/index.ts
|
|
2
|
+
function dependsOn(names, prefixes = []) {
|
|
3
|
+
return (ctx) => ctx.hasAnyDependency(names) || [...ctx.dependencies].some((dep) => prefixes.some((prefix) => dep.startsWith(prefix)));
|
|
4
|
+
}
|
|
5
|
+
/** Extensions of JS and TS sources. */
|
|
6
|
+
const scriptExtensions = [
|
|
7
|
+
"js",
|
|
8
|
+
"jsx",
|
|
9
|
+
"mjs",
|
|
10
|
+
"cjs",
|
|
11
|
+
"ts",
|
|
12
|
+
"tsx",
|
|
13
|
+
"mts",
|
|
14
|
+
"cts"
|
|
15
|
+
];
|
|
16
|
+
//#endregion
|
|
17
|
+
export { scriptExtensions as n, dependsOn as t };
|