@x18n/builtins 0.0.13 → 0.0.15
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.d.mts +1 -0
- package/dist/checks.mjs +26 -1
- package/dist/extractors.mjs +7 -3
- 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 +232 -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 +143 -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 +198 -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 +140 -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 +30 -22
- package/dist/{vue-CF4pe-Yr.mjs → js-CV-ECNJG.mjs} +67 -222
- package/dist/json-CGNSGS97.mjs +109 -0
- package/dist/json-CyjI8bjp.d.mts +8 -0
- package/dist/jsx-templates-B1J0no3i.mjs +17 -0
- package/dist/literal-gIdB1VGy.mjs +125 -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-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-i18n-js.d.mts +5 -0
- package/dist/presets/ruby-i18n-js.mjs +19 -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/reviews.d.mts +6 -0
- package/dist/reviews.mjs +35 -0
- 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-AYVhuSb6.mjs → scanners/regex.mjs} +2 -40
- package/dist/scanners/svelte.d.mts +6 -0
- package/dist/{scanners-DhnnVp7s.mjs → scanners/svelte.mjs} +3 -3
- package/dist/scanners/vue.d.mts +6 -0
- package/dist/scanners/vue.mjs +172 -0
- package/dist/scanners.d.mts +9 -50
- package/dist/scanners.mjs +36 -4
- package/dist/sfc-blocks-DaGKDZsb.mjs +213 -0
- package/dist/shared-C7ceptwi.mjs +55 -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 +68 -3
- package/dist/formats-CFkM5co0.mjs +0 -1370
- package/dist/presets-Co5SlnKr.mjs +0 -1187
- package/dist/sfc-blocks-BqQ8m9rX.mjs +0 -161
- package/dist/yaml-plugin.d.mts +0 -8
- package/dist/yaml-plugin.mjs +0 -68
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//#region src/scanners/regex.ts
|
|
1
|
+
//#region src/scanners/regex/index.ts
|
|
2
2
|
/** The key part of i18n-ally `usageMatchRegex` patterns. */
|
|
3
3
|
const keyPattern = "[\\w\\d\\. \\-\\[\\]\\/:]+?";
|
|
4
4
|
/** Turns a pattern with a `{key}` placeholder into a regex whose first group is the key. */
|
|
@@ -62,43 +62,5 @@ function regex(specs = [], id = "regex") {
|
|
|
62
62
|
scanners: [scanner]
|
|
63
63
|
};
|
|
64
64
|
}
|
|
65
|
-
/**
|
|
66
|
-
* Regions after each match of `pattern` (group 1 = namespace, or key prefix with `as: 'keyPrefix'`; or the
|
|
67
|
-
* named groups `namespace` and `keyPrefix`), up to the next match: i18n-ally `scopeRangeRegex`.
|
|
68
|
-
* With `wholeFile`, the first match scopes the whole file. Matches that capture nothing are skipped.
|
|
69
|
-
*/
|
|
70
|
-
function regexScopes(pattern, id = "scopes", options = {}) {
|
|
71
|
-
const re = new RegExp(pattern, "g");
|
|
72
|
-
return {
|
|
73
|
-
name: `x18n:scanner-${id}`,
|
|
74
|
-
scanners: [{
|
|
75
|
-
id,
|
|
76
|
-
extensions: options.extensions ?? ["*"],
|
|
77
|
-
scan(file) {
|
|
78
|
-
const found = [...file.text.matchAll(re)].map((match) => ({
|
|
79
|
-
match,
|
|
80
|
-
values: match.groups ? {
|
|
81
|
-
namespace: match.groups.namespace,
|
|
82
|
-
keyPrefix: match.groups.keyPrefix
|
|
83
|
-
} : options.as === "keyPrefix" ? { keyPrefix: match[1] } : { namespace: match[1] }
|
|
84
|
-
})).filter(({ values }) => values.namespace !== void 0 || values.keyPrefix !== void 0);
|
|
85
|
-
if (options.wholeFile) found.splice(1);
|
|
86
|
-
return {
|
|
87
|
-
usages: [],
|
|
88
|
-
scopes: found.map(({ match, values }, i) => ({
|
|
89
|
-
range: options.wholeFile ? {
|
|
90
|
-
start: 0,
|
|
91
|
-
end: file.text.length
|
|
92
|
-
} : {
|
|
93
|
-
start: match.index + match[0].length,
|
|
94
|
-
end: found[i + 1]?.match.index ?? file.text.length
|
|
95
|
-
},
|
|
96
|
-
...values
|
|
97
|
-
}))
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
}]
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
65
|
//#endregion
|
|
104
|
-
export {
|
|
66
|
+
export { compileUsagePattern, regex as default, regex, scanWithRegex };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Plugin } from "@x18n/core";
|
|
2
|
+
//#region src/scanners/svelte/index.d.ts
|
|
3
|
+
/** Svelte components: `<script>` and template expressions through the `js` scanner. Node only at first. */
|
|
4
|
+
export declare function svelte(): Plugin;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { svelte as default };
|
|
@@ -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";
|
|
@@ -101,4 +101,4 @@ function svelte() {
|
|
|
101
101
|
};
|
|
102
102
|
}
|
|
103
103
|
//#endregion
|
|
104
|
-
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,52 +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
|
-
/** Attributes whose values are text, on top of the built-in ones (`extract.attributes`). */
|
|
21
|
-
readonly textAttributes?: readonly string[] | undefined;
|
|
22
|
-
}
|
|
23
|
-
export declare function scanSource(parse: ParseSync, text: string, options: ScanSourceOptions): ScanResult;
|
|
24
|
-
/** The AST scanner for JS and TS, driven by call specs (`usage.calls`). */
|
|
25
|
-
export declare function js(): Plugin;
|
|
26
|
-
//#endregion
|
|
27
|
-
//#region src/scanners/regex.d.ts
|
|
28
|
-
/** Turns a pattern with a `{key}` placeholder into a regex whose first group is the key. */
|
|
29
|
-
export declare function compileUsagePattern(pattern: string): RegExp;
|
|
30
|
-
export declare function scanWithRegex(text: string, regexes: readonly RegExp[]): UsageCandidate[];
|
|
31
|
-
/** The `regex` scanner over `usage.regex` specs, plus extra specs given here (YAML plugins). */
|
|
32
|
-
export declare function regex(specs?: readonly RegexScannerSpec[], id?: string): Plugin;
|
|
1
|
+
import { ScannerEntry } from "@x18n/core";
|
|
2
|
+
//#region src/scanners/index.d.ts
|
|
33
3
|
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
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.
|
|
37
7
|
*/
|
|
38
|
-
export declare
|
|
39
|
-
readonly
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}): Plugin;
|
|
43
|
-
//#endregion
|
|
44
|
-
//#region src/scanners/svelte.d.ts
|
|
45
|
-
/** Svelte components: `<script>` and template expressions through the `js` scanner. Node only at first. */
|
|
46
|
-
export declare function svelte(): Plugin;
|
|
47
|
-
//#endregion
|
|
48
|
-
//#region src/scanners/vue.d.ts
|
|
49
|
-
/** Vue SFCs: `<script>` through the `js` scanner, template expressions, `v-t`, `<i18n-t keypath>`. */
|
|
50
|
-
export declare function vue(): Plugin;
|
|
51
|
-
//#endregion
|
|
52
|
-
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,55 @@
|
|
|
1
|
+
//#region src/formats/shared/index.ts
|
|
2
|
+
/** One indent level: the configured one, else the file's first indented line, else `fallback`. */
|
|
3
|
+
function indentUnit(text, indent, fallback) {
|
|
4
|
+
if (indent !== void 0) return indent === "tab" ? " " : " ".repeat(indent);
|
|
5
|
+
return /^([ \t]+)\S/m.exec(text)?.[1] ?? fallback;
|
|
6
|
+
}
|
|
7
|
+
function detectEol(text) {
|
|
8
|
+
return text.includes("\r\n") ? "\r\n" : "\n";
|
|
9
|
+
}
|
|
10
|
+
function toLeaf(value) {
|
|
11
|
+
if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") return value;
|
|
12
|
+
if (value instanceof Date) return value.toISOString();
|
|
13
|
+
}
|
|
14
|
+
function leafText(value) {
|
|
15
|
+
return value === null ? "" : String(value);
|
|
16
|
+
}
|
|
17
|
+
function splice(text, range, insert) {
|
|
18
|
+
return text.slice(0, range.start) + insert + text.slice(range.end);
|
|
19
|
+
}
|
|
20
|
+
/** Appends `chunk` on a line of its own, in the file's line endings. */
|
|
21
|
+
function appendLines(text, chunk) {
|
|
22
|
+
const eol = detectEol(text);
|
|
23
|
+
return (text === "" || text.endsWith("\n") ? text : text + eol) + chunk.replace(/\n/g, eol);
|
|
24
|
+
}
|
|
25
|
+
function splitLines(text) {
|
|
26
|
+
const out = [];
|
|
27
|
+
let start = 0;
|
|
28
|
+
while (start < text.length) {
|
|
29
|
+
const lf = text.indexOf("\n", start);
|
|
30
|
+
const next = lf === -1 ? text.length : lf + 1;
|
|
31
|
+
const end = lf === -1 ? text.length : lf > start && text[lf - 1] === "\r" ? lf - 1 : lf;
|
|
32
|
+
out.push({
|
|
33
|
+
start,
|
|
34
|
+
end,
|
|
35
|
+
next
|
|
36
|
+
});
|
|
37
|
+
start = next;
|
|
38
|
+
}
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
/** An entry's range widened to swallow the blank lines that separate it, so deleting it leaves one separator. */
|
|
42
|
+
function withBlankLines(text, range) {
|
|
43
|
+
const after = /^(?:[ \t]*\r?\n)*(?:[ \t]*$)?/.exec(text.slice(range.end))[0].length;
|
|
44
|
+
if (range.end + after < text.length) return {
|
|
45
|
+
start: range.start,
|
|
46
|
+
end: range.end + after
|
|
47
|
+
};
|
|
48
|
+
const before = /(?<=\n)(?:[ \t]*\r?\n)+$/.exec(text.slice(0, range.start))?.[0].length ?? 0;
|
|
49
|
+
return {
|
|
50
|
+
start: range.start - before,
|
|
51
|
+
end: text.length
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
//#endregion
|
|
55
|
+
export { splice as a, withBlankLines as c, leafText as i, detectEol as n, splitLines as o, indentUnit 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 };
|