@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,30 +1,30 @@
|
|
|
1
|
-
//#region src/scanners/js.ts
|
|
2
|
-
const
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"to",
|
|
19
|
-
"target",
|
|
20
|
-
"rel",
|
|
21
|
-
"lang",
|
|
22
|
-
"xmlns",
|
|
23
|
-
"viewBox",
|
|
24
|
-
"d",
|
|
25
|
-
"fill",
|
|
26
|
-
"stroke"
|
|
1
|
+
//#region src/scanners/shared/js.ts
|
|
2
|
+
const textAttributeWords = /* @__PURE__ */ new Set([
|
|
3
|
+
"title",
|
|
4
|
+
"subtitle",
|
|
5
|
+
"label",
|
|
6
|
+
"text",
|
|
7
|
+
"placeholder",
|
|
8
|
+
"alt",
|
|
9
|
+
"content",
|
|
10
|
+
"description",
|
|
11
|
+
"caption",
|
|
12
|
+
"message",
|
|
13
|
+
"tooltip",
|
|
14
|
+
"hint",
|
|
15
|
+
"summary",
|
|
16
|
+
"heading",
|
|
17
|
+
"headline"
|
|
27
18
|
]);
|
|
19
|
+
/**
|
|
20
|
+
* Markup attributes whose values users read: those whose last word is a text word (`title`, `aria-label`,
|
|
21
|
+
* `confirmText`, `empty-message`), plus `extract.attributes`. Every other attribute holds code: classes,
|
|
22
|
+
* URLs, ids, SVG geometry, attributify utilities.
|
|
23
|
+
*/
|
|
24
|
+
function isTextAttribute(name, extra = []) {
|
|
25
|
+
const last = name.replace(/([a-z\d])([A-Z])/g, "$1-$2").toLowerCase().split(/[-_:.]/).at(-1);
|
|
26
|
+
return textAttributeWords.has(last) || extra.includes(name);
|
|
27
|
+
}
|
|
28
28
|
function isNode(value) {
|
|
29
29
|
return typeof value === "object" && value !== null && "type" in value && typeof value.type === "string" && "start" in value;
|
|
30
30
|
}
|
|
@@ -73,6 +73,7 @@ var FileScan = class {
|
|
|
73
73
|
offset;
|
|
74
74
|
collectHardStrings;
|
|
75
75
|
ignoredCallees;
|
|
76
|
+
textAttributes;
|
|
76
77
|
usages = [];
|
|
77
78
|
keySlots = [];
|
|
78
79
|
hardStrings = [];
|
|
@@ -82,12 +83,17 @@ var FileScan = class {
|
|
|
82
83
|
declared = /* @__PURE__ */ new Map();
|
|
83
84
|
factories = /* @__PURE__ */ new Map();
|
|
84
85
|
typed = /* @__PURE__ */ new Map();
|
|
86
|
+
/** Members of the file's top-level interfaces and object type aliases, for typed props (`({ t }: Props)`). */
|
|
87
|
+
typeMembers = /* @__PURE__ */ new Map();
|
|
85
88
|
consumed = /* @__PURE__ */ new Set();
|
|
86
|
-
|
|
89
|
+
/** Render-prop functions of factory components (`<Translation>{t => ...}</Translation>`), by start offset. */
|
|
90
|
+
renderProps = /* @__PURE__ */ new Map();
|
|
91
|
+
constructor(text, offset, calls, collectHardStrings, ignoredCallees = /* @__PURE__ */ new Set(), textAttributes = []) {
|
|
87
92
|
this.text = text;
|
|
88
93
|
this.offset = offset;
|
|
89
94
|
this.collectHardStrings = collectHardStrings;
|
|
90
95
|
this.ignoredCallees = ignoredCallees;
|
|
96
|
+
this.textAttributes = textAttributes;
|
|
91
97
|
for (const spec of calls) for (const name of [spec.callee].flat()) if (spec.factory) {
|
|
92
98
|
const list = this.factories.get(name) ?? [];
|
|
93
99
|
this.factories.set(name, list);
|
|
@@ -141,6 +147,11 @@ var FileScan = class {
|
|
|
141
147
|
return constant && isStringLiteral(constant) ? constant.value : void 0;
|
|
142
148
|
}
|
|
143
149
|
}
|
|
150
|
+
/** A fixed namespace, or the one an argument holds. */
|
|
151
|
+
namespaceArg(args, source, fallback) {
|
|
152
|
+
if (typeof source === "string") return source;
|
|
153
|
+
return source ? this.stringArg(this.argValues(args, source, fallback)[0]) : void 0;
|
|
154
|
+
}
|
|
144
155
|
argValues(args, source, fallback) {
|
|
145
156
|
const arg = args[typeof source === "number" ? source : source?.arg ?? fallback];
|
|
146
157
|
const property = typeof source === "object" ? source.property : void 0;
|
|
@@ -160,18 +171,17 @@ var FileScan = class {
|
|
|
160
171
|
return object.properties.flatMap((p) => p.type === "Property" ? [p.value] : []);
|
|
161
172
|
}
|
|
162
173
|
/** ---- Usages ---- */
|
|
163
|
-
|
|
174
|
+
/** `expression` is the call holding `value`, kept only when the key is the call's own literal. */
|
|
175
|
+
addKey(value, base, expression) {
|
|
164
176
|
if ("props" in value) return;
|
|
165
177
|
const node = unwrap(value);
|
|
166
|
-
const
|
|
167
|
-
namespace: namespace ?? binding.namespace,
|
|
168
|
-
keyPrefix: binding.keyPrefix
|
|
169
|
-
};
|
|
178
|
+
const call = expression ? { expression: this.range(expression) } : {};
|
|
170
179
|
if (isStringLiteral(node)) {
|
|
171
180
|
this.consumed.add(node.start);
|
|
172
181
|
this.keySlots.push(this.range(innerRange(node)));
|
|
173
182
|
this.usages.push({
|
|
174
183
|
...base,
|
|
184
|
+
...call,
|
|
175
185
|
raw: node.value,
|
|
176
186
|
range: this.range(innerRange(node)),
|
|
177
187
|
kind: "call",
|
|
@@ -180,7 +190,7 @@ var FileScan = class {
|
|
|
180
190
|
return;
|
|
181
191
|
}
|
|
182
192
|
if (node.type === "JSXExpressionContainer") {
|
|
183
|
-
if (node.expression.type !== "JSXEmptyExpression") this.addKey(node.expression,
|
|
193
|
+
if (node.expression.type !== "JSXEmptyExpression") this.addKey(node.expression, base);
|
|
184
194
|
return;
|
|
185
195
|
}
|
|
186
196
|
if (node.type === "TemplateLiteral") {
|
|
@@ -193,6 +203,7 @@ var FileScan = class {
|
|
|
193
203
|
});
|
|
194
204
|
this.usages.push({
|
|
195
205
|
...base,
|
|
206
|
+
...call,
|
|
196
207
|
raw,
|
|
197
208
|
range,
|
|
198
209
|
kind: "call",
|
|
@@ -201,8 +212,8 @@ var FileScan = class {
|
|
|
201
212
|
return;
|
|
202
213
|
}
|
|
203
214
|
if (node.type === "ConditionalExpression") {
|
|
204
|
-
this.addKey(node.consequent,
|
|
205
|
-
this.addKey(node.alternate,
|
|
215
|
+
this.addKey(node.consequent, base);
|
|
216
|
+
this.addKey(node.alternate, base);
|
|
206
217
|
return;
|
|
207
218
|
}
|
|
208
219
|
if (node.type === "ArrowFunctionExpression" && node.params[0]?.type === "Identifier") return this.addSelector(node.params[0].name, node.body, base);
|
|
@@ -217,6 +228,10 @@ var FileScan = class {
|
|
|
217
228
|
});
|
|
218
229
|
return;
|
|
219
230
|
}
|
|
231
|
+
this.addUnknown(node, base);
|
|
232
|
+
}
|
|
233
|
+
addUnknown(node, base) {
|
|
234
|
+
if (isStringLiteral(node)) this.consumed.add(node.start);
|
|
220
235
|
this.usages.push({
|
|
221
236
|
...base,
|
|
222
237
|
raw: this.text.slice(node.start, node.end),
|
|
@@ -256,30 +271,49 @@ var FileScan = class {
|
|
|
256
271
|
const object = this.constants.get(node.object.name);
|
|
257
272
|
return object && object.type === "ObjectExpression" ? objectProperty(object, node.property.name) : void 0;
|
|
258
273
|
}
|
|
259
|
-
handleCall(name, args) {
|
|
274
|
+
handleCall(name, args, expression) {
|
|
260
275
|
const bindings = this.lookupAll(name).filter((binding) => !binding.spec.factory);
|
|
261
276
|
for (const binding of bindings) {
|
|
262
|
-
const namespace =
|
|
277
|
+
const namespace = this.namespaceArg(args, binding.spec.namespace, 1);
|
|
278
|
+
const keyPrefix = binding.spec.keyPrefix ? this.stringArg(this.argValues(args, binding.spec.keyPrefix, 1)[0]) : void 0;
|
|
279
|
+
const base = {
|
|
280
|
+
namespace: namespace ?? binding.namespace,
|
|
281
|
+
keyPrefix: keyPrefix ?? binding.keyPrefix
|
|
282
|
+
};
|
|
263
283
|
for (const value of this.argValues(args, binding.spec.key, 0)) {
|
|
264
|
-
if (
|
|
265
|
-
|
|
284
|
+
if ("props" in value) continue;
|
|
285
|
+
if (bindings.length > 1 && unwrap(value).type === "ObjectExpression") continue;
|
|
286
|
+
if (binding.spec.keyPrefix && keyPrefix === void 0) this.addUnknown(unwrap(value), base);
|
|
287
|
+
else this.addKey(value, base, expression);
|
|
266
288
|
}
|
|
267
289
|
}
|
|
268
290
|
return bindings.length > 0;
|
|
269
291
|
}
|
|
270
292
|
call(node) {
|
|
271
293
|
const name = calleeName(node.callee);
|
|
272
|
-
if (name !== void 0) this.handleCall(name, node.arguments
|
|
294
|
+
if (name !== void 0) this.handleCall(name, node.arguments, {
|
|
295
|
+
start: node.start,
|
|
296
|
+
end: node.end
|
|
297
|
+
});
|
|
273
298
|
}
|
|
274
|
-
|
|
275
|
-
const name = jsxName(node.name);
|
|
276
|
-
if (!name || !this.lookup(name)) return;
|
|
299
|
+
jsxProps(node) {
|
|
277
300
|
const props = /* @__PURE__ */ new Map();
|
|
278
301
|
for (const attribute of node.attributes) if (attribute.type === "JSXAttribute" && attribute.value && attribute.name.type === "JSXIdentifier") {
|
|
279
302
|
props.set(attribute.name.name, attribute.value);
|
|
280
303
|
if (isStringLiteral(attribute.value)) this.consumed.add(attribute.value.start);
|
|
281
304
|
}
|
|
282
|
-
|
|
305
|
+
return { props };
|
|
306
|
+
}
|
|
307
|
+
jsx(node) {
|
|
308
|
+
const name = jsxName(node.name);
|
|
309
|
+
if (name && this.lookup(name)) this.handleCall(name, [this.jsxProps(node)]);
|
|
310
|
+
}
|
|
311
|
+
jsxElement(node) {
|
|
312
|
+
const name = jsxName(node.openingElement.name);
|
|
313
|
+
const specs = name ? this.factories.get(name) : void 0;
|
|
314
|
+
if (!specs) return;
|
|
315
|
+
const binding = this.pickFactory([this.jsxProps(node.openingElement)], specs);
|
|
316
|
+
for (const child of node.children) if (child.type === "JSXExpressionContainer" && (child.expression.type === "ArrowFunctionExpression" || child.expression.type === "FunctionExpression")) this.renderProps.set(child.expression.start, binding);
|
|
283
317
|
}
|
|
284
318
|
/** ---- Bindings ---- */
|
|
285
319
|
declarator(id, init) {
|
|
@@ -289,7 +323,7 @@ var FileScan = class {
|
|
|
289
323
|
if (value.type === "CallExpression") {
|
|
290
324
|
const factory = calleeName(value.callee);
|
|
291
325
|
const specs = factory ? this.factories.get(factory) : void 0;
|
|
292
|
-
if (specs) this.bindFactory(id, value, specs);
|
|
326
|
+
if (specs) this.bindFactory(id, value.arguments, specs);
|
|
293
327
|
return;
|
|
294
328
|
}
|
|
295
329
|
if (id.type === "Identifier" && (value.type === "Identifier" || value.type === "MemberExpression")) {
|
|
@@ -299,10 +333,10 @@ var FileScan = class {
|
|
|
299
333
|
}
|
|
300
334
|
}
|
|
301
335
|
/** The first factory spec whose namespace and prefix sources all resolve, else the first spec. */
|
|
302
|
-
pickFactory(
|
|
336
|
+
pickFactory(args, specs) {
|
|
303
337
|
const resolved = specs.map((spec) => {
|
|
304
|
-
const namespace =
|
|
305
|
-
const keyPrefix = spec.keyPrefix ? this.stringArg(this.argValues(
|
|
338
|
+
const namespace = this.namespaceArg(args, spec.namespace, 0);
|
|
339
|
+
const keyPrefix = spec.keyPrefix ? this.stringArg(this.argValues(args, spec.keyPrefix, 0)[0]) : void 0;
|
|
306
340
|
const complete = (!spec.namespace || namespace !== void 0) && (!spec.keyPrefix || keyPrefix !== void 0);
|
|
307
341
|
return {
|
|
308
342
|
binding: {
|
|
@@ -318,8 +352,8 @@ var FileScan = class {
|
|
|
318
352
|
});
|
|
319
353
|
return (resolved.find((r) => r.complete) ?? resolved[0]).binding;
|
|
320
354
|
}
|
|
321
|
-
bindFactory(id,
|
|
322
|
-
const binding = this.pickFactory(
|
|
355
|
+
bindFactory(id, args, specs) {
|
|
356
|
+
const binding = this.pickFactory(args, specs);
|
|
323
357
|
const returns = (binding.spec.returns ?? ["t"]).filter((name) => !name.includes("."));
|
|
324
358
|
if (id.type === "Identifier") {
|
|
325
359
|
this.bind(id.name, binding);
|
|
@@ -334,17 +368,49 @@ var FileScan = class {
|
|
|
334
368
|
if (key && local.type === "Identifier" && this.declared.has(`${key}.t`)) this.bind(`${local.name}.t`, binding);
|
|
335
369
|
}
|
|
336
370
|
}
|
|
371
|
+
/** Parameters typed as a translate function (`t: TFunction<'ns'>`), also as destructured props. */
|
|
337
372
|
params(params) {
|
|
338
373
|
for (const param of params) {
|
|
339
374
|
const target = param.type === "TSParameterProperty" ? param.parameter : param;
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
375
|
+
const annotation = target.type === "Identifier" || target.type === "ObjectPattern" ? target.typeAnnotation?.typeAnnotation : void 0;
|
|
376
|
+
if (!annotation) continue;
|
|
377
|
+
if (target.type === "Identifier") this.bindTyped(target.name, annotation);
|
|
378
|
+
else if (target.type === "ObjectPattern") this.props(target, annotation);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
/** `({ t }: { t: TFunction<'ns'> })`, or with an interface or type of the file. */
|
|
382
|
+
props(pattern, annotation) {
|
|
383
|
+
const members = annotation.type === "TSTypeLiteral" ? annotation.members : annotation.type === "TSTypeReference" && annotation.typeName.type === "Identifier" ? this.typeMembers.get(annotation.typeName.name) ?? [] : [];
|
|
384
|
+
for (const property of pattern.properties) {
|
|
385
|
+
if (property.type !== "Property") continue;
|
|
386
|
+
const local = unwrap(property.value);
|
|
387
|
+
const name = propertyName(property.key);
|
|
388
|
+
const member = members.find((m) => m.type === "TSPropertySignature" && !m.computed && propertyName(m.key) === name);
|
|
389
|
+
const type = member?.type === "TSPropertySignature" ? member.typeAnnotation?.typeAnnotation : void 0;
|
|
390
|
+
if (local.type === "Identifier" && type) this.bindTyped(local.name, type);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
/** The namespace is the first type argument: `TFunction<'shared'>` or `TFunction<['shared', 'common']>`. */
|
|
394
|
+
bindTyped(name, annotation) {
|
|
395
|
+
if (annotation.type !== "TSTypeReference" || annotation.typeName.type !== "Identifier") return;
|
|
396
|
+
const spec = this.typed.get(annotation.typeName.name);
|
|
397
|
+
if (!spec) return;
|
|
398
|
+
const arg = annotation.typeArguments?.params[0];
|
|
399
|
+
const first = arg?.type === "TSTupleType" ? arg.elementTypes[0] : arg;
|
|
400
|
+
const namespace = first?.type === "TSLiteralType" && isStringLiteral(first.literal) ? first.literal.value : void 0;
|
|
401
|
+
this.bind(name, {
|
|
402
|
+
spec: {
|
|
345
403
|
...spec,
|
|
346
404
|
types: void 0
|
|
347
|
-
}
|
|
405
|
+
},
|
|
406
|
+
namespace
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
types(program) {
|
|
410
|
+
for (const statement of program.body) {
|
|
411
|
+
const node = statement.type === "ExportNamedDeclaration" && statement.declaration ? statement.declaration : statement;
|
|
412
|
+
if (node.type === "TSInterfaceDeclaration") this.typeMembers.set(node.id.name, node.body.body);
|
|
413
|
+
else if (node.type === "TSTypeAliasDeclaration" && node.typeAnnotation.type === "TSTypeLiteral") this.typeMembers.set(node.id.name, node.typeAnnotation.members);
|
|
348
414
|
}
|
|
349
415
|
}
|
|
350
416
|
importSpecifier(node) {
|
|
@@ -383,6 +449,9 @@ var FileScan = class {
|
|
|
383
449
|
case "CallExpression":
|
|
384
450
|
this.call(node);
|
|
385
451
|
break;
|
|
452
|
+
case "JSXElement":
|
|
453
|
+
this.jsxElement(node);
|
|
454
|
+
break;
|
|
386
455
|
case "JSXOpeningElement":
|
|
387
456
|
this.jsx(node);
|
|
388
457
|
break;
|
|
@@ -391,9 +460,12 @@ var FileScan = class {
|
|
|
391
460
|
break;
|
|
392
461
|
case "FunctionDeclaration":
|
|
393
462
|
case "FunctionExpression":
|
|
394
|
-
case "ArrowFunctionExpression":
|
|
463
|
+
case "ArrowFunctionExpression": {
|
|
395
464
|
this.params(node.params);
|
|
465
|
+
const binding = this.renderProps.get(node.start);
|
|
466
|
+
if (binding && node.params[0]?.type === "Identifier") this.bind(node.params[0].name, binding);
|
|
396
467
|
break;
|
|
468
|
+
}
|
|
397
469
|
case "ImportSpecifier":
|
|
398
470
|
this.importSpecifier(node);
|
|
399
471
|
break;
|
|
@@ -413,7 +485,7 @@ var FileScan = class {
|
|
|
413
485
|
if (this.consumed.has(node.start) || !parent) return;
|
|
414
486
|
if (parent.type === "JSXAttribute") {
|
|
415
487
|
const name = parent.name.type === "JSXIdentifier" ? parent.name.name : "";
|
|
416
|
-
if (isStringLiteral(node) &&
|
|
488
|
+
if (isStringLiteral(node) && isTextAttribute(name, this.textAttributes)) this.hard(node.value, {
|
|
417
489
|
start: parent.start,
|
|
418
490
|
end: parent.end
|
|
419
491
|
}, "jsx-attribute", name);
|
|
@@ -505,223 +577,11 @@ function scanSource(parse, text, options) {
|
|
|
505
577
|
sourceType: "module",
|
|
506
578
|
range: false
|
|
507
579
|
});
|
|
508
|
-
const scan = new FileScan(source, (options.offset ?? 0) + shift, options.calls, options.hardStrings !== false, new Set(options.ignoreCallees));
|
|
580
|
+
const scan = new FileScan(source, (options.offset ?? 0) + shift, options.calls, options.hardStrings !== false, new Set(options.ignoreCallees), options.textAttributes);
|
|
581
|
+
scan.types(result.program);
|
|
509
582
|
scan.walk(result.program, void 0, "");
|
|
510
583
|
scan.markers(result.program, result.comments);
|
|
511
584
|
return scan.result();
|
|
512
585
|
}
|
|
513
|
-
/** The AST scanner for JS and TS, driven by call specs (`usage.calls`). */
|
|
514
|
-
function js() {
|
|
515
|
-
return {
|
|
516
|
-
name: "x18n:scanner-js",
|
|
517
|
-
scanners: [{
|
|
518
|
-
id: "js",
|
|
519
|
-
extensions: [
|
|
520
|
-
"js",
|
|
521
|
-
"jsx",
|
|
522
|
-
"mjs",
|
|
523
|
-
"cjs",
|
|
524
|
-
"ts",
|
|
525
|
-
"tsx",
|
|
526
|
-
"mts",
|
|
527
|
-
"cts"
|
|
528
|
-
],
|
|
529
|
-
async scan(file, ctx) {
|
|
530
|
-
return scanSource(await loadParser(), file.text, {
|
|
531
|
-
calls: ctx.calls,
|
|
532
|
-
filename: file.path,
|
|
533
|
-
hardStrings: true,
|
|
534
|
-
ignoreCallees: ctx.config.extract.ignoreCallees
|
|
535
|
-
});
|
|
536
|
-
}
|
|
537
|
-
}]
|
|
538
|
-
};
|
|
539
|
-
}
|
|
540
|
-
//#endregion
|
|
541
|
-
//#region src/scanners/vue.ts
|
|
542
|
-
let compiler;
|
|
543
|
-
function loadVueCompiler() {
|
|
544
|
-
compiler ??= import("@vue/compiler-sfc");
|
|
545
|
-
return compiler;
|
|
546
|
-
}
|
|
547
|
-
const ELEMENT = 1;
|
|
548
|
-
const TEXT = 2;
|
|
549
|
-
const SIMPLE_EXPRESSION = 4;
|
|
550
|
-
const INTERPOLATION = 5;
|
|
551
|
-
const ATTRIBUTE = 6;
|
|
552
|
-
/** `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. */
|
|
553
|
-
function isKeyAttribute(tag, name) {
|
|
554
|
-
return name === "keypath" || name === "path" && (tag === "i18n" || tag === "i18n-d");
|
|
555
|
-
}
|
|
556
|
-
const ignoredAttributes = /* @__PURE__ */ new Set([
|
|
557
|
-
"class",
|
|
558
|
-
"style",
|
|
559
|
-
"key",
|
|
560
|
-
"ref",
|
|
561
|
-
"id",
|
|
562
|
-
"name",
|
|
563
|
-
"type",
|
|
564
|
-
"href",
|
|
565
|
-
"src",
|
|
566
|
-
"to",
|
|
567
|
-
"for",
|
|
568
|
-
"role",
|
|
569
|
-
"lang",
|
|
570
|
-
"target",
|
|
571
|
-
"rel",
|
|
572
|
-
"is",
|
|
573
|
-
"slot"
|
|
574
|
-
]);
|
|
575
|
-
function merge(into, result) {
|
|
576
|
-
into.usages.push(...result.usages);
|
|
577
|
-
into.keySlots.push(...result.keySlots ?? []);
|
|
578
|
-
into.hardStrings.push(...result.hardStrings ?? []);
|
|
579
|
-
}
|
|
580
|
-
var TemplateScan = class {
|
|
581
|
-
parse;
|
|
582
|
-
calls;
|
|
583
|
-
out = {
|
|
584
|
-
usages: [],
|
|
585
|
-
keySlots: [],
|
|
586
|
-
hardStrings: []
|
|
587
|
-
};
|
|
588
|
-
constructor(parse, calls) {
|
|
589
|
-
this.parse = parse;
|
|
590
|
-
this.calls = calls;
|
|
591
|
-
}
|
|
592
|
-
expression(content, offset) {
|
|
593
|
-
if (!content.trim()) return;
|
|
594
|
-
try {
|
|
595
|
-
merge(this.out, scanSource(this.parse, content, {
|
|
596
|
-
calls: this.calls,
|
|
597
|
-
offset,
|
|
598
|
-
filename: "template.ts",
|
|
599
|
-
expression: true,
|
|
600
|
-
hardStrings: false
|
|
601
|
-
}));
|
|
602
|
-
} catch {}
|
|
603
|
-
}
|
|
604
|
-
/** `:keypath="cond ? 'a' : 'b'"`: the expression is read as the key argument of a translate call. */
|
|
605
|
-
keyExpression(content, offset) {
|
|
606
|
-
const callee = "__x18nKey(";
|
|
607
|
-
try {
|
|
608
|
-
merge(this.out, scanSource(this.parse, `${callee}${content})`, {
|
|
609
|
-
calls: [{ callee: "__x18nKey" }],
|
|
610
|
-
offset: offset - 10,
|
|
611
|
-
filename: "template.ts",
|
|
612
|
-
hardStrings: false
|
|
613
|
-
}));
|
|
614
|
-
} catch {}
|
|
615
|
-
}
|
|
616
|
-
/** `v-t="'key'"` and `v-t="{ path: 'key' }"`. */
|
|
617
|
-
vt(dir) {
|
|
618
|
-
const exp = dir.exp;
|
|
619
|
-
if (!exp || !("content" in exp)) return;
|
|
620
|
-
const offset = exp.loc.start.offset;
|
|
621
|
-
const literal = /^\s*(['"`])(.*)\1\s*$/.exec(exp.content) ?? /path\s*:\s*(['"`])(.*?)\1/.exec(exp.content);
|
|
622
|
-
if (!literal) return;
|
|
623
|
-
const start = offset + exp.content.indexOf(literal[2], literal.index);
|
|
624
|
-
const range = {
|
|
625
|
-
start,
|
|
626
|
-
end: start + literal[2].length
|
|
627
|
-
};
|
|
628
|
-
this.out.usages.push({
|
|
629
|
-
raw: literal[2],
|
|
630
|
-
range,
|
|
631
|
-
kind: "call",
|
|
632
|
-
dynamic: false
|
|
633
|
-
});
|
|
634
|
-
this.out.keySlots.push(range);
|
|
635
|
-
}
|
|
636
|
-
attribute(element, prop) {
|
|
637
|
-
if (prop.type === ATTRIBUTE) {
|
|
638
|
-
const value = prop.value;
|
|
639
|
-
if (!value) return;
|
|
640
|
-
const range = {
|
|
641
|
-
start: value.loc.start.offset + 1,
|
|
642
|
-
end: value.loc.end.offset - 1
|
|
643
|
-
};
|
|
644
|
-
if (isKeyAttribute(element.tag, prop.name)) {
|
|
645
|
-
this.out.usages.push({
|
|
646
|
-
raw: value.content,
|
|
647
|
-
range,
|
|
648
|
-
kind: "call",
|
|
649
|
-
dynamic: false
|
|
650
|
-
});
|
|
651
|
-
this.out.keySlots.push(range);
|
|
652
|
-
} else if (!ignoredAttributes.has(prop.name) && !prop.name.startsWith("data-") && /\p{L}/u.test(value.content)) this.out.hardStrings.push({
|
|
653
|
-
text: value.content,
|
|
654
|
-
range: {
|
|
655
|
-
start: prop.loc.start.offset,
|
|
656
|
-
end: prop.loc.end.offset
|
|
657
|
-
},
|
|
658
|
-
kind: "vue-attribute",
|
|
659
|
-
attribute: prop.name
|
|
660
|
-
});
|
|
661
|
-
return;
|
|
662
|
-
}
|
|
663
|
-
if (prop.name === "t") {
|
|
664
|
-
this.vt(prop);
|
|
665
|
-
return;
|
|
666
|
-
}
|
|
667
|
-
const exp = prop.exp;
|
|
668
|
-
if (exp && "content" in exp) {
|
|
669
|
-
if (prop.name === "bind" && prop.arg && "content" in prop.arg && isKeyAttribute(element.tag, prop.arg.content)) this.keyExpression(exp.content, exp.loc.start.offset);
|
|
670
|
-
else this.expression(exp.content, exp.loc.start.offset);
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
node(node) {
|
|
674
|
-
if (node.type === INTERPOLATION && "content" in node.content) this.expression(node.content.content, node.content.loc.start.offset);
|
|
675
|
-
if (node.type === TEXT) {
|
|
676
|
-
const text = node.content.trim();
|
|
677
|
-
if (text && /\p{L}/u.test(text)) {
|
|
678
|
-
const start = node.loc.start.offset + node.content.indexOf(text);
|
|
679
|
-
this.out.hardStrings.push({
|
|
680
|
-
text,
|
|
681
|
-
range: {
|
|
682
|
-
start,
|
|
683
|
-
end: start + text.length
|
|
684
|
-
},
|
|
685
|
-
kind: "vue-template-text"
|
|
686
|
-
});
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
if (node.type === ELEMENT) for (const prop of node.props) this.attribute(node, prop);
|
|
690
|
-
if ("children" in node) {
|
|
691
|
-
for (const child of node.children) if (typeof child === "object" && child.type !== SIMPLE_EXPRESSION) this.node(child);
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
};
|
|
695
|
-
/** Vue SFCs: `<script>` through the `js` scanner, template expressions, `v-t`, `<i18n-t keypath>`. */
|
|
696
|
-
function vue() {
|
|
697
|
-
return {
|
|
698
|
-
name: "x18n:scanner-vue",
|
|
699
|
-
scanners: [{
|
|
700
|
-
id: "vue",
|
|
701
|
-
extensions: ["vue"],
|
|
702
|
-
async scan(file, ctx) {
|
|
703
|
-
const [{ parse }, parseJs] = await Promise.all([loadVueCompiler(), loadParser()]);
|
|
704
|
-
const { descriptor } = parse(file.text, {
|
|
705
|
-
filename: file.path,
|
|
706
|
-
sourceMap: false,
|
|
707
|
-
ignoreEmpty: false
|
|
708
|
-
});
|
|
709
|
-
const template = new TemplateScan(parseJs, ctx.calls);
|
|
710
|
-
for (const script of [descriptor.script, descriptor.scriptSetup]) {
|
|
711
|
-
if (!script) continue;
|
|
712
|
-
const lang = script.lang ?? "js";
|
|
713
|
-
merge(template.out, scanSource(parseJs, script.content, {
|
|
714
|
-
calls: ctx.calls,
|
|
715
|
-
ignoreCallees: ctx.config.extract.ignoreCallees,
|
|
716
|
-
offset: script.loc.start.offset,
|
|
717
|
-
filename: `script.${lang === "tsx" || lang === "jsx" ? lang : lang === "ts" ? "ts" : "js"}`
|
|
718
|
-
}));
|
|
719
|
-
}
|
|
720
|
-
if (descriptor.template?.ast) template.node(descriptor.template.ast);
|
|
721
|
-
return template.out;
|
|
722
|
-
}
|
|
723
|
-
}]
|
|
724
|
-
};
|
|
725
|
-
}
|
|
726
586
|
//#endregion
|
|
727
|
-
export { loadParser as
|
|
587
|
+
export { loadParser as n, scanSource as r, isTextAttribute as t };
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { n as detectEol, r as detectIndent } from "./shared-BWaXUmwA.mjs";
|
|
2
|
+
import { diffText, errors, pathId } from "@x18n/core";
|
|
3
|
+
import { applyEdits, modify, parseTree, printParseErrorCode } from "jsonc-parser";
|
|
4
|
+
//#region src/formats/shared/json.ts
|
|
5
|
+
function readTree(node, path, ranges) {
|
|
6
|
+
const out = {};
|
|
7
|
+
for (const property of node.children ?? []) {
|
|
8
|
+
const [keyNode, valueNode] = property.children ?? [];
|
|
9
|
+
if (!keyNode || !valueNode || typeof keyNode.value !== "string") continue;
|
|
10
|
+
const key = keyNode.value;
|
|
11
|
+
const childPath = [...path, key];
|
|
12
|
+
ranges.set(pathId(childPath), {
|
|
13
|
+
key: {
|
|
14
|
+
start: keyNode.offset + 1,
|
|
15
|
+
end: keyNode.offset + keyNode.length - 1
|
|
16
|
+
},
|
|
17
|
+
value: {
|
|
18
|
+
start: valueNode.offset,
|
|
19
|
+
end: valueNode.offset + valueNode.length
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
if (valueNode.type === "object") out[key] = readTree(valueNode, childPath, ranges);
|
|
23
|
+
else if (valueNode.type === "string" || valueNode.type === "number" || valueNode.type === "boolean" || valueNode.type === "null") out[key] = valueNode.value;
|
|
24
|
+
}
|
|
25
|
+
return out;
|
|
26
|
+
}
|
|
27
|
+
function createJsonFormat(id, extensions, allowComments, defaultIndent) {
|
|
28
|
+
return {
|
|
29
|
+
id,
|
|
30
|
+
extensions,
|
|
31
|
+
parse(text, ctx) {
|
|
32
|
+
const problems = [];
|
|
33
|
+
const root = parseTree(text, problems, {
|
|
34
|
+
allowTrailingComma: true,
|
|
35
|
+
disallowComments: !allowComments
|
|
36
|
+
});
|
|
37
|
+
if (problems.length > 0) {
|
|
38
|
+
const first = problems[0];
|
|
39
|
+
throw errors.X18N2002({
|
|
40
|
+
path: ctx.path,
|
|
41
|
+
reason: `${printParseErrorCode(first.error)} at offset ${first.offset}`
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
if (!root || root.type !== "object") {
|
|
45
|
+
if (text.trim() === "") return {
|
|
46
|
+
tree: {},
|
|
47
|
+
ranges: /* @__PURE__ */ new Map()
|
|
48
|
+
};
|
|
49
|
+
throw errors.X18N2003({ path: ctx.path });
|
|
50
|
+
}
|
|
51
|
+
const ranges = /* @__PURE__ */ new Map();
|
|
52
|
+
return {
|
|
53
|
+
tree: readTree(root, [], ranges),
|
|
54
|
+
ranges
|
|
55
|
+
};
|
|
56
|
+
},
|
|
57
|
+
edit(text, ops, ctx) {
|
|
58
|
+
const formattingOptions = {
|
|
59
|
+
...detectIndent(text, ctx.indent ?? defaultIndent),
|
|
60
|
+
eol: detectEol(text)
|
|
61
|
+
};
|
|
62
|
+
let next = text.trim() === "" ? "{}\n" : text;
|
|
63
|
+
for (const op of ops) next = applyEdits(next, modify(next, [...op.path], op.type === "set" ? op.value : void 0, { formattingOptions }));
|
|
64
|
+
return diffText(text, next);
|
|
65
|
+
},
|
|
66
|
+
serialize(tree, ctx) {
|
|
67
|
+
const indent = ctx.indent ?? defaultIndent ?? 2;
|
|
68
|
+
return `${JSON.stringify(tree, null, indent === "tab" ? " " : indent)}\n`;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
//#endregion
|
|
73
|
+
export { createJsonFormat as t };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
//#region src/presets/shared/jsx-templates.ts
|
|
2
|
+
const jsxTemplates = [
|
|
3
|
+
{
|
|
4
|
+
template: "{t('{key}'{args})}",
|
|
5
|
+
kinds: ["jsx-text"]
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
template: "{attr}={t('{key}'{args})}",
|
|
9
|
+
kinds: ["jsx-attribute"]
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
template: "t('{key}'{args})",
|
|
13
|
+
kinds: ["js-string", "js-template"]
|
|
14
|
+
}
|
|
15
|
+
];
|
|
16
|
+
//#endregion
|
|
17
|
+
export { jsxTemplates as t };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { isMessageLeaf } from "@x18n/core";
|
|
2
|
+
//#region src/presets/shared/locale-root.ts
|
|
3
|
+
/** Files that start with their locale, `en: { users: ... }`, as in Rails and i18nice. */
|
|
4
|
+
function localeRoot(applies) {
|
|
5
|
+
return {
|
|
6
|
+
applies,
|
|
7
|
+
read(tree, ctx) {
|
|
8
|
+
const root = tree[ctx.locale];
|
|
9
|
+
return root !== void 0 && !isMessageLeaf(root) ? root : {};
|
|
10
|
+
},
|
|
11
|
+
toStored: (path, ctx) => [ctx.locale, ...path]
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
export { localeRoot as t };
|