@x18n/builtins 0.0.12 → 0.0.13
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/checks.mjs +3 -2
- package/dist/extractors.d.mts +2 -2
- package/dist/extractors.mjs +11 -3
- package/dist/finders.mjs +1 -1
- package/dist/index.mjs +4 -4
- package/dist/{presets-DJrGl3HR.mjs → presets-Co5SlnKr.mjs} +17 -14
- package/dist/presets.mjs +1 -1
- package/dist/{regex-B9TBGgXC.mjs → regex-AYVhuSb6.mjs} +14 -7
- package/dist/{scanners-B27IEH4_.mjs → scanners-DhnnVp7s.mjs} +6 -21
- package/dist/scanners.d.mts +5 -2
- package/dist/scanners.mjs +3 -3
- package/dist/{sfc-blocks-BIPqKcvd.mjs → sfc-blocks-BqQ8m9rX.mjs} +1 -1
- package/dist/{vue-5GFR5hft.mjs → vue-CF4pe-Yr.mjs} +84 -69
- package/dist/yaml-plugin.mjs +1 -1
- package/package.json +2 -2
package/dist/checks.mjs
CHANGED
|
@@ -32,7 +32,7 @@ const missingKey = {
|
|
|
32
32
|
const missingTranslation = {
|
|
33
33
|
id: "missing-translation",
|
|
34
34
|
defaultSeverity: "warning",
|
|
35
|
-
description: "A source-locale key that target locales lack. One diagnostic per key, at the source entry.",
|
|
35
|
+
description: "A source-locale key that target locales lack, or a plural form their language needs. One diagnostic per key, at the source entry.",
|
|
36
36
|
*run(ctx) {
|
|
37
37
|
const missing = /* @__PURE__ */ new Map();
|
|
38
38
|
for (const coverage of ctx.coverage()) {
|
|
@@ -44,7 +44,8 @@ const missingTranslation = {
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
for (const [key, locales] of missing) {
|
|
47
|
-
const
|
|
47
|
+
const other = ctx.formOf(key)?.keyOf("other");
|
|
48
|
+
const source = ctx.message(key, ctx.sourceLocale) ?? (other === void 0 ? void 0 : ctx.message(other, ctx.sourceLocale));
|
|
48
49
|
yield {
|
|
49
50
|
key,
|
|
50
51
|
locale: ctx.sourceLocale,
|
package/dist/extractors.d.mts
CHANGED
|
@@ -3,12 +3,12 @@ import { ExtractItem, ExtractOptions, ExtractTemplate, HardString, KeyString, Pr
|
|
|
3
3
|
export type Score = "must-include" | "should-include" | "none" | "should-exclude" | "must-exclude";
|
|
4
4
|
export interface ExtractionRule {
|
|
5
5
|
readonly id: string;
|
|
6
|
-
readonly test: (text: string, kind: string) => Score;
|
|
6
|
+
readonly test: (text: string, kind: string, attribute: string | undefined) => Score;
|
|
7
7
|
}
|
|
8
8
|
/** Built-in rules, applied in order. A `must-*` wins at once; otherwise the last `should-*` decides. */
|
|
9
9
|
export declare const rules: readonly ExtractionRule[];
|
|
10
10
|
/** Folds rule scores the i18n-ally way (`shouldExtract`). */
|
|
11
|
-
export declare function shouldExtract(text: string, kind: string, list?: readonly ExtractionRule[]): boolean;
|
|
11
|
+
export declare function shouldExtract(text: string, kind: string, list?: readonly ExtractionRule[], attribute?: string): boolean;
|
|
12
12
|
export type KeyCase = "camel" | "snake" | "kebab" | "dot";
|
|
13
13
|
/** "Forgot password?" becomes `forgotPassword` (camel), `forgot_password`, `forgot-password` or `forgot.password`. */
|
|
14
14
|
export declare function slugify(text: string, keyCase?: KeyCase, maxLength?: number): string;
|
package/dist/extractors.mjs
CHANGED
|
@@ -43,6 +43,14 @@ const rules = [
|
|
|
43
43
|
return tokens.every((t) => /^!?[a-z][\w:/[\].#%!-]*$/.test(t)) && tokens.filter((t) => /[-:!/[\d]/.test(t)).length * 2 > tokens.length ? "must-exclude" : "none";
|
|
44
44
|
}
|
|
45
45
|
},
|
|
46
|
+
{
|
|
47
|
+
id: "attributify",
|
|
48
|
+
test: (text, kind, attribute) => kind.endsWith("attribute") && (attribute === "text" || attribute?.includes(":") === true) && text.trim().split(/\s+/).every((t) => /^!?-?[a-z\d$[(][\w:/[\]().,#%!$+~-]*$/.test(t)) ? "must-exclude" : "none"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: "css-value",
|
|
52
|
+
test: (text) => /^-?[a-z][\w-]*\(.*\)$/s.test(text.trim()) || /\bvar\(--/.test(text) ? "must-exclude" : "none"
|
|
53
|
+
},
|
|
46
54
|
{
|
|
47
55
|
id: "non-ascii",
|
|
48
56
|
test: (text) => /\P{ASCII}/u.test(text) && /\p{L}/u.test(text) ? "must-include" : "none"
|
|
@@ -80,10 +88,10 @@ function isExtractableFile(path) {
|
|
|
80
88
|
return !nonUiFiles.some((pattern) => pattern.test(path));
|
|
81
89
|
}
|
|
82
90
|
/** Folds rule scores the i18n-ally way (`shouldExtract`). */
|
|
83
|
-
function shouldExtract(text, kind, list = rules) {
|
|
91
|
+
function shouldExtract(text, kind, list = rules, attribute) {
|
|
84
92
|
let result = "none";
|
|
85
93
|
for (const rule of list) {
|
|
86
|
-
const score = rule.test(text, kind);
|
|
94
|
+
const score = rule.test(text, kind, attribute);
|
|
87
95
|
if (score === "must-include") return true;
|
|
88
96
|
if (score === "must-exclude") return false;
|
|
89
97
|
if (score !== "none") result = score;
|
|
@@ -216,7 +224,7 @@ function commentIgnores(text) {
|
|
|
216
224
|
function* extractableHardStrings(options, hardStrings, textOf) {
|
|
217
225
|
const ignores = /* @__PURE__ */ new Map();
|
|
218
226
|
for (const hard of hardStrings) {
|
|
219
|
-
if (!isExtractableFile(hard.path) || !shouldExtract(hard.text, hard.kind) || ignoredByConfig(options, hard)) continue;
|
|
227
|
+
if (!isExtractableFile(hard.path) || !shouldExtract(hard.text, hard.kind, rules, hard.attribute) || ignoredByConfig(options, hard)) continue;
|
|
220
228
|
let ignored = ignores.get(hard.path);
|
|
221
229
|
if (!ignored) {
|
|
222
230
|
const text = textOf(hard.path);
|
package/dist/finders.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { a as patternFinder, i as pattern, n as guessResources, r as namespaceFromPath, t as compilePattern } from "./finders-CqTGh4OJ.mjs";
|
|
2
|
-
import { n as sfcFluentBlocks, t as sfcBlocks } from "./sfc-blocks-
|
|
2
|
+
import { n as sfcFluentBlocks, t as sfcBlocks } from "./sfc-blocks-BqQ8m9rX.mjs";
|
|
3
3
|
export { compilePattern, guessResources, namespaceFromPath, pattern, patternFinder, sfcBlocks, sfcFluentBlocks };
|
package/dist/index.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { recommended } from "./checks.mjs";
|
|
2
2
|
import { i as pattern } from "./finders-CqTGh4OJ.mjs";
|
|
3
3
|
import { a as json5, c as ini, i as php, l as ftl, n as properties, o as json, r as po, s as jsonc, t as yaml, u as ecmascript } from "./formats-CFkM5co0.mjs";
|
|
4
|
-
import {
|
|
4
|
+
import { a as js, n as vue } from "./vue-CF4pe-Yr.mjs";
|
|
5
5
|
import { manifests } from "./manifests.mjs";
|
|
6
|
-
import { n as regex } from "./regex-
|
|
7
|
-
import { t as presets } from "./presets-
|
|
6
|
+
import { n as regex } from "./regex-AYVhuSb6.mjs";
|
|
7
|
+
import { t as presets } from "./presets-Co5SlnKr.mjs";
|
|
8
8
|
import { reporters } from "./reporters.mjs";
|
|
9
|
-
import { t as svelte } from "./scanners-
|
|
9
|
+
import { t as svelte } from "./scanners-DhnnVp7s.mjs";
|
|
10
10
|
//#region src/index.ts
|
|
11
11
|
/** Plugins that are always on, and the presets that `'auto'` detects. */
|
|
12
12
|
function createBuiltins() {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { defaultTemplates } from "./extractors.mjs";
|
|
2
|
-
import { n as sfcFluentBlocks, t as sfcBlocks } from "./sfc-blocks-
|
|
2
|
+
import { n as sfcFluentBlocks, t as sfcBlocks } from "./sfc-blocks-BqQ8m9rX.mjs";
|
|
3
3
|
import { braces, colon, dollars, doubleBraces, fluent, icu, linked, nesting, percentBraces, placeholders, printf } from "./placeholders.mjs";
|
|
4
|
-
import { r as regexScopes } from "./regex-
|
|
4
|
+
import { r as regexScopes } from "./regex-AYVhuSb6.mjs";
|
|
5
5
|
import { isMessageLeaf } from "@x18n/core";
|
|
6
6
|
//#region src/presets/shared.ts
|
|
7
7
|
/** One plugin holding a library's call specs, regexes, namespace rules, defaults and extraction templates. */
|
|
@@ -295,7 +295,11 @@ const i18nextPlugin = library({
|
|
|
295
295
|
types: ["TFunction"]
|
|
296
296
|
}
|
|
297
297
|
],
|
|
298
|
-
derivedKeys: [
|
|
298
|
+
derivedKeys: [
|
|
299
|
+
"{key}_ordinal_{ordinal}",
|
|
300
|
+
"{key}_{plural}",
|
|
301
|
+
"{key}_*"
|
|
302
|
+
],
|
|
299
303
|
translatePrompt: (draft, ctx) => {
|
|
300
304
|
draft.notes.push(`i18next plural keys end in _${ctx.localeInfo.pluralCategories.join(", _")} for ${ctx.targetLocale}; write one key per category.`);
|
|
301
305
|
},
|
|
@@ -350,6 +354,10 @@ function reactI18next() {
|
|
|
350
354
|
namespace: {
|
|
351
355
|
arg: 0,
|
|
352
356
|
property: "ns"
|
|
357
|
+
},
|
|
358
|
+
keyPrefix: {
|
|
359
|
+
arg: 0,
|
|
360
|
+
property: "keyPrefix"
|
|
353
361
|
}
|
|
354
362
|
}
|
|
355
363
|
],
|
|
@@ -363,7 +371,7 @@ function reactI18next() {
|
|
|
363
371
|
imports: ["import i18next from 'i18next'"]
|
|
364
372
|
}]
|
|
365
373
|
}),
|
|
366
|
-
regexScopes("\\bwithTranslation\\(\\s
|
|
374
|
+
regexScopes("\\bwithTranslation\\(\\s*(?:\\[?\\s*['\"`](?<namespace>[^'\"`]+)['\"`])?(?:[^)]*?\\bkeyPrefix\\s*:\\s*['\"`](?<keyPrefix>[^'\"`]+)['\"`])?", "react-i18next-hoc", {
|
|
367
375
|
wholeFile: true,
|
|
368
376
|
extensions: scriptExtensions
|
|
369
377
|
})
|
|
@@ -764,15 +772,10 @@ function nextTranslate() {
|
|
|
764
772
|
}
|
|
765
773
|
/** Shopify's i18next format: plural and ordinal forms as nested keys (`{key}.one`). */
|
|
766
774
|
function i18nextShopify() {
|
|
767
|
-
const
|
|
768
|
-
"
|
|
769
|
-
"
|
|
770
|
-
"
|
|
771
|
-
"two",
|
|
772
|
-
"few",
|
|
773
|
-
"many",
|
|
774
|
-
"other",
|
|
775
|
-
...Array.from({ length: 10 }, (_, i) => String(i))
|
|
775
|
+
const derivedKeys = [
|
|
776
|
+
"{key}.ordinal.{ordinal}",
|
|
777
|
+
"{key}.{plural}",
|
|
778
|
+
...["plural", ...Array.from({ length: 10 }, (_, i) => String(i))].map((form) => `{key}.${form}`)
|
|
776
779
|
];
|
|
777
780
|
return {
|
|
778
781
|
name: "i18next-shopify",
|
|
@@ -781,7 +784,7 @@ function i18nextShopify() {
|
|
|
781
784
|
...reactI18next().plugins,
|
|
782
785
|
library({
|
|
783
786
|
name: "i18next-shopify",
|
|
784
|
-
derivedKeys
|
|
787
|
+
derivedKeys,
|
|
785
788
|
keyStyle: "nested"
|
|
786
789
|
}),
|
|
787
790
|
placeholders(braces)
|
package/dist/presets.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { n as chromeExtension, r as emberIntl, i as fluentVue, S as flutter, C as general, a as globalize, o as i18nJs, s as i18nTag, w as i18next, c as i18nextShopify, g as jekyll, _ as laravel, l as lingui, u as nextInternational, T as nextIntl, d as nextTranslate, M as ngxTranslate, E as nuxtI18n, f as paraglide, v as phpGettext, y as phpJoomla, p as polyglot, t as presets, b as railsLazyKey, D as reactI18next, O as reactIntl, x as rubyRails, m as solidPrimitivesI18n, k as svelteI18n, N as transloco, h as ui5, A as vscodeExtension, j as vueI18n } from "./presets-
|
|
1
|
+
export { n as chromeExtension, r as emberIntl, i as fluentVue, S as flutter, C as general, a as globalize, o as i18nJs, s as i18nTag, w as i18next, c as i18nextShopify, g as jekyll, _ as laravel, l as lingui, u as nextInternational, T as nextIntl, d as nextTranslate, M as ngxTranslate, E as nuxtI18n, f as paraglide, v as phpGettext, y as phpJoomla, p as polyglot, t as presets, b as railsLazyKey, D as reactI18next, O as reactIntl, x as rubyRails, m as solidPrimitivesI18n, k as svelteI18n, N as transloco, h as ui5, A as vscodeExtension, j as vueI18n } from "./presets-Co5SlnKr.mjs";
|
|
@@ -63,8 +63,9 @@ function regex(specs = [], id = "regex") {
|
|
|
63
63
|
};
|
|
64
64
|
}
|
|
65
65
|
/**
|
|
66
|
-
* Regions after each match of `pattern` (group 1 = namespace, or key prefix with `as: 'keyPrefix'
|
|
67
|
-
* up to the next match: i18n-ally `scopeRangeRegex`.
|
|
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.
|
|
68
69
|
*/
|
|
69
70
|
function regexScopes(pattern, id = "scopes", options = {}) {
|
|
70
71
|
const re = new RegExp(pattern, "g");
|
|
@@ -74,19 +75,25 @@ function regexScopes(pattern, id = "scopes", options = {}) {
|
|
|
74
75
|
id,
|
|
75
76
|
extensions: options.extensions ?? ["*"],
|
|
76
77
|
scan(file) {
|
|
77
|
-
const
|
|
78
|
-
|
|
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);
|
|
79
86
|
return {
|
|
80
87
|
usages: [],
|
|
81
|
-
scopes:
|
|
88
|
+
scopes: found.map(({ match, values }, i) => ({
|
|
82
89
|
range: options.wholeFile ? {
|
|
83
90
|
start: 0,
|
|
84
91
|
end: file.text.length
|
|
85
92
|
} : {
|
|
86
93
|
start: match.index + match[0].length,
|
|
87
|
-
end:
|
|
94
|
+
end: found[i + 1]?.match.index ?? file.text.length
|
|
88
95
|
},
|
|
89
|
-
...
|
|
96
|
+
...values
|
|
90
97
|
}))
|
|
91
98
|
};
|
|
92
99
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { i as isTextAttribute, o as loadParser, s as scanSource } from "./vue-CF4pe-Yr.mjs";
|
|
2
2
|
//#region src/scanners/svelte.ts
|
|
3
3
|
let compiler;
|
|
4
4
|
function isSvelteNode(value) {
|
|
@@ -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
|
};
|
package/dist/scanners.d.mts
CHANGED
|
@@ -17,6 +17,8 @@ interface ScanSourceOptions {
|
|
|
17
17
|
readonly expression?: boolean;
|
|
18
18
|
/** Callees whose string arguments are never hard strings (`extract.ignoreCallees`). */
|
|
19
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;
|
|
20
22
|
}
|
|
21
23
|
export declare function scanSource(parse: ParseSync, text: string, options: ScanSourceOptions): ScanResult;
|
|
22
24
|
/** The AST scanner for JS and TS, driven by call specs (`usage.calls`). */
|
|
@@ -29,8 +31,9 @@ export declare function scanWithRegex(text: string, regexes: readonly RegExp[]):
|
|
|
29
31
|
/** The `regex` scanner over `usage.regex` specs, plus extra specs given here (YAML plugins). */
|
|
30
32
|
export declare function regex(specs?: readonly RegexScannerSpec[], id?: string): Plugin;
|
|
31
33
|
/**
|
|
32
|
-
* Regions after each match of `pattern` (group 1 = namespace, or key prefix with `as: 'keyPrefix'
|
|
33
|
-
* up to the next match: i18n-ally `scopeRangeRegex`.
|
|
34
|
+
* Regions after each match of `pattern` (group 1 = namespace, or key prefix with `as: 'keyPrefix'`; or the
|
|
35
|
+
* named groups `namespace` and `keyPrefix`), up to the next match: i18n-ally `scopeRangeRegex`.
|
|
36
|
+
* With `wholeFile`, the first match scopes the whole file. Matches that capture nothing are skipped.
|
|
34
37
|
*/
|
|
35
38
|
export declare function regexScopes(pattern: string, id?: string, options?: {
|
|
36
39
|
readonly as?: "namespace" | "keyPrefix";
|
package/dist/scanners.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
import { i as scanWithRegex, n as regex, r as regexScopes, t as compileUsagePattern } from "./regex-
|
|
3
|
-
import { t as svelte } from "./scanners-
|
|
1
|
+
import { a as js, n as vue, o as loadParser, r as calleeName, s as scanSource } from "./vue-CF4pe-Yr.mjs";
|
|
2
|
+
import { i as scanWithRegex, n as regex, r as regexScopes, t as compileUsagePattern } from "./regex-AYVhuSb6.mjs";
|
|
3
|
+
import { t as svelte } from "./scanners-DhnnVp7s.mjs";
|
|
4
4
|
export { calleeName, compileUsagePattern, js, loadParser, regex, regexScopes, scanSource, scanWithRegex, svelte, vue };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as json5, o as json, t as yaml } from "./formats-CFkM5co0.mjs";
|
|
2
|
-
import { t as loadVueCompiler } from "./vue-
|
|
2
|
+
import { t as loadVueCompiler } from "./vue-CF4pe-Yr.mjs";
|
|
3
3
|
import { isMessageLeaf, pathId } from "@x18n/core";
|
|
4
4
|
//#region src/finders/sfc-blocks.ts
|
|
5
5
|
const formatOfLang = {
|
|
@@ -1,30 +1,30 @@
|
|
|
1
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"
|
|
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 = [];
|
|
@@ -83,11 +84,14 @@ var FileScan = class {
|
|
|
83
84
|
factories = /* @__PURE__ */ new Map();
|
|
84
85
|
typed = /* @__PURE__ */ new Map();
|
|
85
86
|
consumed = /* @__PURE__ */ new Set();
|
|
86
|
-
|
|
87
|
+
/** Render-prop functions of factory components (`<Translation>{t => ...}</Translation>`), by start offset. */
|
|
88
|
+
renderProps = /* @__PURE__ */ new Map();
|
|
89
|
+
constructor(text, offset, calls, collectHardStrings, ignoredCallees = /* @__PURE__ */ new Set(), textAttributes = []) {
|
|
87
90
|
this.text = text;
|
|
88
91
|
this.offset = offset;
|
|
89
92
|
this.collectHardStrings = collectHardStrings;
|
|
90
93
|
this.ignoredCallees = ignoredCallees;
|
|
94
|
+
this.textAttributes = textAttributes;
|
|
91
95
|
for (const spec of calls) for (const name of [spec.callee].flat()) if (spec.factory) {
|
|
92
96
|
const list = this.factories.get(name) ?? [];
|
|
93
97
|
this.factories.set(name, list);
|
|
@@ -160,18 +164,21 @@ var FileScan = class {
|
|
|
160
164
|
return object.properties.flatMap((p) => p.type === "Property" ? [p.value] : []);
|
|
161
165
|
}
|
|
162
166
|
/** ---- Usages ---- */
|
|
163
|
-
|
|
167
|
+
/** `expression` is the call holding `value`, kept only when the key is the call's own literal. */
|
|
168
|
+
addKey(value, binding, namespace, expression) {
|
|
164
169
|
if ("props" in value) return;
|
|
165
170
|
const node = unwrap(value);
|
|
166
171
|
const base = {
|
|
167
172
|
namespace: namespace ?? binding.namespace,
|
|
168
173
|
keyPrefix: binding.keyPrefix
|
|
169
174
|
};
|
|
175
|
+
const call = expression ? { expression: this.range(expression) } : {};
|
|
170
176
|
if (isStringLiteral(node)) {
|
|
171
177
|
this.consumed.add(node.start);
|
|
172
178
|
this.keySlots.push(this.range(innerRange(node)));
|
|
173
179
|
this.usages.push({
|
|
174
180
|
...base,
|
|
181
|
+
...call,
|
|
175
182
|
raw: node.value,
|
|
176
183
|
range: this.range(innerRange(node)),
|
|
177
184
|
kind: "call",
|
|
@@ -193,6 +200,7 @@ var FileScan = class {
|
|
|
193
200
|
});
|
|
194
201
|
this.usages.push({
|
|
195
202
|
...base,
|
|
203
|
+
...call,
|
|
196
204
|
raw,
|
|
197
205
|
range,
|
|
198
206
|
kind: "call",
|
|
@@ -256,30 +264,42 @@ var FileScan = class {
|
|
|
256
264
|
const object = this.constants.get(node.object.name);
|
|
257
265
|
return object && object.type === "ObjectExpression" ? objectProperty(object, node.property.name) : void 0;
|
|
258
266
|
}
|
|
259
|
-
handleCall(name, args) {
|
|
267
|
+
handleCall(name, args, expression) {
|
|
260
268
|
const bindings = this.lookupAll(name).filter((binding) => !binding.spec.factory);
|
|
261
269
|
for (const binding of bindings) {
|
|
262
270
|
const namespace = binding.spec.namespace ? this.stringArg(this.argValues(args, binding.spec.namespace, 1)[0]) : void 0;
|
|
263
271
|
for (const value of this.argValues(args, binding.spec.key, 0)) {
|
|
264
272
|
if (bindings.length > 1 && !("props" in value) && unwrap(value).type === "ObjectExpression") continue;
|
|
265
|
-
this.addKey(value, binding, namespace);
|
|
273
|
+
this.addKey(value, binding, namespace, expression);
|
|
266
274
|
}
|
|
267
275
|
}
|
|
268
276
|
return bindings.length > 0;
|
|
269
277
|
}
|
|
270
278
|
call(node) {
|
|
271
279
|
const name = calleeName(node.callee);
|
|
272
|
-
if (name !== void 0) this.handleCall(name, node.arguments
|
|
280
|
+
if (name !== void 0) this.handleCall(name, node.arguments, {
|
|
281
|
+
start: node.start,
|
|
282
|
+
end: node.end
|
|
283
|
+
});
|
|
273
284
|
}
|
|
274
|
-
|
|
275
|
-
const name = jsxName(node.name);
|
|
276
|
-
if (!name || !this.lookup(name)) return;
|
|
285
|
+
jsxProps(node) {
|
|
277
286
|
const props = /* @__PURE__ */ new Map();
|
|
278
287
|
for (const attribute of node.attributes) if (attribute.type === "JSXAttribute" && attribute.value && attribute.name.type === "JSXIdentifier") {
|
|
279
288
|
props.set(attribute.name.name, attribute.value);
|
|
280
289
|
if (isStringLiteral(attribute.value)) this.consumed.add(attribute.value.start);
|
|
281
290
|
}
|
|
282
|
-
|
|
291
|
+
return { props };
|
|
292
|
+
}
|
|
293
|
+
jsx(node) {
|
|
294
|
+
const name = jsxName(node.name);
|
|
295
|
+
if (name && this.lookup(name)) this.handleCall(name, [this.jsxProps(node)]);
|
|
296
|
+
}
|
|
297
|
+
jsxElement(node) {
|
|
298
|
+
const name = jsxName(node.openingElement.name);
|
|
299
|
+
const specs = name ? this.factories.get(name) : void 0;
|
|
300
|
+
if (!specs) return;
|
|
301
|
+
const binding = this.pickFactory([this.jsxProps(node.openingElement)], specs);
|
|
302
|
+
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
303
|
}
|
|
284
304
|
/** ---- Bindings ---- */
|
|
285
305
|
declarator(id, init) {
|
|
@@ -289,7 +309,7 @@ var FileScan = class {
|
|
|
289
309
|
if (value.type === "CallExpression") {
|
|
290
310
|
const factory = calleeName(value.callee);
|
|
291
311
|
const specs = factory ? this.factories.get(factory) : void 0;
|
|
292
|
-
if (specs) this.bindFactory(id, value, specs);
|
|
312
|
+
if (specs) this.bindFactory(id, value.arguments, specs);
|
|
293
313
|
return;
|
|
294
314
|
}
|
|
295
315
|
if (id.type === "Identifier" && (value.type === "Identifier" || value.type === "MemberExpression")) {
|
|
@@ -299,10 +319,10 @@ var FileScan = class {
|
|
|
299
319
|
}
|
|
300
320
|
}
|
|
301
321
|
/** The first factory spec whose namespace and prefix sources all resolve, else the first spec. */
|
|
302
|
-
pickFactory(
|
|
322
|
+
pickFactory(args, specs) {
|
|
303
323
|
const resolved = specs.map((spec) => {
|
|
304
|
-
const namespace = spec.namespace ? this.stringArg(this.argValues(
|
|
305
|
-
const keyPrefix = spec.keyPrefix ? this.stringArg(this.argValues(
|
|
324
|
+
const namespace = spec.namespace ? this.stringArg(this.argValues(args, spec.namespace, 0)[0]) : void 0;
|
|
325
|
+
const keyPrefix = spec.keyPrefix ? this.stringArg(this.argValues(args, spec.keyPrefix, 0)[0]) : void 0;
|
|
306
326
|
const complete = (!spec.namespace || namespace !== void 0) && (!spec.keyPrefix || keyPrefix !== void 0);
|
|
307
327
|
return {
|
|
308
328
|
binding: {
|
|
@@ -318,8 +338,8 @@ var FileScan = class {
|
|
|
318
338
|
});
|
|
319
339
|
return (resolved.find((r) => r.complete) ?? resolved[0]).binding;
|
|
320
340
|
}
|
|
321
|
-
bindFactory(id,
|
|
322
|
-
const binding = this.pickFactory(
|
|
341
|
+
bindFactory(id, args, specs) {
|
|
342
|
+
const binding = this.pickFactory(args, specs);
|
|
323
343
|
const returns = (binding.spec.returns ?? ["t"]).filter((name) => !name.includes("."));
|
|
324
344
|
if (id.type === "Identifier") {
|
|
325
345
|
this.bind(id.name, binding);
|
|
@@ -383,6 +403,9 @@ var FileScan = class {
|
|
|
383
403
|
case "CallExpression":
|
|
384
404
|
this.call(node);
|
|
385
405
|
break;
|
|
406
|
+
case "JSXElement":
|
|
407
|
+
this.jsxElement(node);
|
|
408
|
+
break;
|
|
386
409
|
case "JSXOpeningElement":
|
|
387
410
|
this.jsx(node);
|
|
388
411
|
break;
|
|
@@ -391,9 +414,12 @@ var FileScan = class {
|
|
|
391
414
|
break;
|
|
392
415
|
case "FunctionDeclaration":
|
|
393
416
|
case "FunctionExpression":
|
|
394
|
-
case "ArrowFunctionExpression":
|
|
417
|
+
case "ArrowFunctionExpression": {
|
|
395
418
|
this.params(node.params);
|
|
419
|
+
const binding = this.renderProps.get(node.start);
|
|
420
|
+
if (binding && node.params[0]?.type === "Identifier") this.bind(node.params[0].name, binding);
|
|
396
421
|
break;
|
|
422
|
+
}
|
|
397
423
|
case "ImportSpecifier":
|
|
398
424
|
this.importSpecifier(node);
|
|
399
425
|
break;
|
|
@@ -413,7 +439,7 @@ var FileScan = class {
|
|
|
413
439
|
if (this.consumed.has(node.start) || !parent) return;
|
|
414
440
|
if (parent.type === "JSXAttribute") {
|
|
415
441
|
const name = parent.name.type === "JSXIdentifier" ? parent.name.name : "";
|
|
416
|
-
if (isStringLiteral(node) &&
|
|
442
|
+
if (isStringLiteral(node) && isTextAttribute(name, this.textAttributes)) this.hard(node.value, {
|
|
417
443
|
start: parent.start,
|
|
418
444
|
end: parent.end
|
|
419
445
|
}, "jsx-attribute", name);
|
|
@@ -505,7 +531,7 @@ function scanSource(parse, text, options) {
|
|
|
505
531
|
sourceType: "module",
|
|
506
532
|
range: false
|
|
507
533
|
});
|
|
508
|
-
const scan = new FileScan(source, (options.offset ?? 0) + shift, options.calls, options.hardStrings !== false, new Set(options.ignoreCallees));
|
|
534
|
+
const scan = new FileScan(source, (options.offset ?? 0) + shift, options.calls, options.hardStrings !== false, new Set(options.ignoreCallees), options.textAttributes);
|
|
509
535
|
scan.walk(result.program, void 0, "");
|
|
510
536
|
scan.markers(result.program, result.comments);
|
|
511
537
|
return scan.result();
|
|
@@ -531,7 +557,8 @@ function js() {
|
|
|
531
557
|
calls: ctx.calls,
|
|
532
558
|
filename: file.path,
|
|
533
559
|
hardStrings: true,
|
|
534
|
-
ignoreCallees: ctx.config.extract.ignoreCallees
|
|
560
|
+
ignoreCallees: ctx.config.extract.ignoreCallees,
|
|
561
|
+
textAttributes: ctx.config.extract.attributes
|
|
535
562
|
});
|
|
536
563
|
}
|
|
537
564
|
}]
|
|
@@ -553,25 +580,6 @@ const ATTRIBUTE = 6;
|
|
|
553
580
|
function isKeyAttribute(tag, name) {
|
|
554
581
|
return name === "keypath" || name === "path" && (tag === "i18n" || tag === "i18n-d");
|
|
555
582
|
}
|
|
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
583
|
function merge(into, result) {
|
|
576
584
|
into.usages.push(...result.usages);
|
|
577
585
|
into.keySlots.push(...result.keySlots ?? []);
|
|
@@ -580,14 +588,16 @@ function merge(into, result) {
|
|
|
580
588
|
var TemplateScan = class {
|
|
581
589
|
parse;
|
|
582
590
|
calls;
|
|
591
|
+
textAttributes;
|
|
583
592
|
out = {
|
|
584
593
|
usages: [],
|
|
585
594
|
keySlots: [],
|
|
586
595
|
hardStrings: []
|
|
587
596
|
};
|
|
588
|
-
constructor(parse, calls) {
|
|
597
|
+
constructor(parse, calls, textAttributes) {
|
|
589
598
|
this.parse = parse;
|
|
590
599
|
this.calls = calls;
|
|
600
|
+
this.textAttributes = textAttributes;
|
|
591
601
|
}
|
|
592
602
|
expression(content, offset) {
|
|
593
603
|
if (!content.trim()) return;
|
|
@@ -605,12 +615,16 @@ var TemplateScan = class {
|
|
|
605
615
|
keyExpression(content, offset) {
|
|
606
616
|
const callee = "__x18nKey(";
|
|
607
617
|
try {
|
|
608
|
-
|
|
618
|
+
const result = scanSource(this.parse, `${callee}${content})`, {
|
|
609
619
|
calls: [{ callee: "__x18nKey" }],
|
|
610
620
|
offset: offset - 10,
|
|
611
621
|
filename: "template.ts",
|
|
612
622
|
hardStrings: false
|
|
613
|
-
})
|
|
623
|
+
});
|
|
624
|
+
merge(this.out, {
|
|
625
|
+
...result,
|
|
626
|
+
usages: result.usages.map(({ expression: _, ...usage }) => usage)
|
|
627
|
+
});
|
|
614
628
|
} catch {}
|
|
615
629
|
}
|
|
616
630
|
/** `v-t="'key'"` and `v-t="{ path: 'key' }"`. */
|
|
@@ -649,7 +663,7 @@ var TemplateScan = class {
|
|
|
649
663
|
dynamic: false
|
|
650
664
|
});
|
|
651
665
|
this.out.keySlots.push(range);
|
|
652
|
-
} else if (
|
|
666
|
+
} else if (isTextAttribute(prop.name, this.textAttributes) && /\p{L}/u.test(value.content)) this.out.hardStrings.push({
|
|
653
667
|
text: value.content,
|
|
654
668
|
range: {
|
|
655
669
|
start: prop.loc.start.offset,
|
|
@@ -706,13 +720,14 @@ function vue() {
|
|
|
706
720
|
sourceMap: false,
|
|
707
721
|
ignoreEmpty: false
|
|
708
722
|
});
|
|
709
|
-
const template = new TemplateScan(parseJs, ctx.calls);
|
|
723
|
+
const template = new TemplateScan(parseJs, ctx.calls, ctx.config.extract.attributes);
|
|
710
724
|
for (const script of [descriptor.script, descriptor.scriptSetup]) {
|
|
711
725
|
if (!script) continue;
|
|
712
726
|
const lang = script.lang ?? "js";
|
|
713
727
|
merge(template.out, scanSource(parseJs, script.content, {
|
|
714
728
|
calls: ctx.calls,
|
|
715
729
|
ignoreCallees: ctx.config.extract.ignoreCallees,
|
|
730
|
+
textAttributes: ctx.config.extract.attributes,
|
|
716
731
|
offset: script.loc.start.offset,
|
|
717
732
|
filename: `script.${lang === "tsx" || lang === "jsx" ? lang : lang === "ts" ? "ts" : "js"}`
|
|
718
733
|
}));
|
|
@@ -724,4 +739,4 @@ function vue() {
|
|
|
724
739
|
};
|
|
725
740
|
}
|
|
726
741
|
//#endregion
|
|
727
|
-
export {
|
|
742
|
+
export { js as a, isTextAttribute as i, vue as n, loadParser as o, calleeName as r, scanSource as s, loadVueCompiler as t };
|
package/dist/yaml-plugin.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { placeholderFromShape } from "./placeholders.mjs";
|
|
2
|
-
import { r as regexScopes } from "./regex-
|
|
2
|
+
import { r as regexScopes } from "./regex-AYVhuSb6.mjs";
|
|
3
3
|
//#region src/yaml-plugin/index.ts
|
|
4
4
|
/**
|
|
5
5
|
* Compiles a validated YAML plugin into plugin objects. Each section maps to the
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@x18n/builtins",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.13",
|
|
5
5
|
"description": "Built-in x18n plugins and presets: formats, locale finders, scanners, checks and reporters",
|
|
6
6
|
"author": "Anthony Fu <anthonyfu117@hotmail.com>",
|
|
7
7
|
"license": "MIT",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
],
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@vue/compiler-sfc": "^3.5.44",
|
|
36
|
-
"@x18n/core": "0.0.
|
|
36
|
+
"@x18n/core": "0.0.13",
|
|
37
37
|
"json5": "^2.2.3",
|
|
38
38
|
"jsonc-parser": "^3.3.1",
|
|
39
39
|
"oxc-parser": "^0.155.0",
|