@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.
Files changed (173) hide show
  1. package/dist/angular-templates-DqIOGfzT.mjs +8 -0
  2. package/dist/checks.d.mts +1 -0
  3. package/dist/checks.mjs +26 -1
  4. package/dist/extractors.mjs +7 -3
  5. package/dist/finders.d.mts +4 -1
  6. package/dist/finders.mjs +1 -1
  7. package/dist/formats/ecmascript.d.mts +5 -0
  8. package/dist/formats/ecmascript.mjs +232 -0
  9. package/dist/formats/ftl.d.mts +5 -0
  10. package/dist/formats/ftl.mjs +234 -0
  11. package/dist/formats/ini.d.mts +5 -0
  12. package/dist/formats/ini.mjs +158 -0
  13. package/dist/formats/json.d.mts +6 -0
  14. package/dist/formats/json.mjs +10 -0
  15. package/dist/formats/json5.d.mts +5 -0
  16. package/dist/formats/json5.mjs +143 -0
  17. package/dist/formats/jsonc.d.mts +6 -0
  18. package/dist/formats/jsonc.mjs +10 -0
  19. package/dist/formats/php.d.mts +5 -0
  20. package/dist/formats/php.mjs +198 -0
  21. package/dist/formats/po.d.mts +5 -0
  22. package/dist/formats/po.mjs +177 -0
  23. package/dist/formats/properties.d.mts +5 -0
  24. package/dist/formats/properties.mjs +123 -0
  25. package/dist/formats/resx.d.mts +6 -0
  26. package/dist/formats/resx.mjs +202 -0
  27. package/dist/formats/yaml.d.mts +5 -0
  28. package/dist/formats/yaml.mjs +140 -0
  29. package/dist/formats.d.mts +10 -34
  30. package/dist/formats.mjs +61 -1
  31. package/dist/index.d.mts +2 -2
  32. package/dist/index.mjs +30 -22
  33. package/dist/{vue-CF4pe-Yr.mjs → js-CV-ECNJG.mjs} +67 -222
  34. package/dist/json-CGNSGS97.mjs +109 -0
  35. package/dist/json-CyjI8bjp.d.mts +8 -0
  36. package/dist/jsx-templates-B1J0no3i.mjs +17 -0
  37. package/dist/literal-gIdB1VGy.mjs +125 -0
  38. package/dist/locale-root-C_rch3IQ.mjs +15 -0
  39. package/dist/manifests.d.mts +8 -0
  40. package/dist/manifests.mjs +66 -2
  41. package/dist/placeholders.d.mts +11 -0
  42. package/dist/placeholders.mjs +28 -1
  43. package/dist/plugin-Cb1b0axD.mjs +83 -0
  44. package/dist/plural-forms-B7_QX4nG.mjs +12 -0
  45. package/dist/presets/cargo-i18n.d.mts +6 -0
  46. package/dist/presets/cargo-i18n.mjs +28 -0
  47. package/dist/presets/chrome-extension.d.mts +6 -0
  48. package/dist/presets/chrome-extension.mjs +52 -0
  49. package/dist/presets/dotnet-resx.d.mts +6 -0
  50. package/dist/presets/dotnet-resx.mjs +64 -0
  51. package/dist/presets/easy-localization.d.mts +6 -0
  52. package/dist/presets/easy-localization.mjs +36 -0
  53. package/dist/presets/ember-intl.d.mts +5 -0
  54. package/dist/presets/ember-intl.mjs +31 -0
  55. package/dist/presets/fluent-react.d.mts +5 -0
  56. package/dist/presets/fluent-react.mjs +46 -0
  57. package/dist/presets/fluent-vue.d.mts +5 -0
  58. package/dist/presets/fluent-vue.mjs +51 -0
  59. package/dist/presets/flutter-translate.d.mts +6 -0
  60. package/dist/presets/flutter-translate.mjs +30 -0
  61. package/dist/presets/flutter.d.mts +6 -0
  62. package/dist/presets/flutter.mjs +21 -0
  63. package/dist/presets/general.d.mts +6 -0
  64. package/dist/presets/general.mjs +17 -0
  65. package/dist/presets/globalize.d.mts +5 -0
  66. package/dist/presets/globalize.mjs +25 -0
  67. package/dist/presets/gravity-ui-i18n.d.mts +6 -0
  68. package/dist/presets/gravity-ui-i18n.mjs +41 -0
  69. package/dist/presets/i18n-tag.d.mts +5 -0
  70. package/dist/presets/i18n-tag.mjs +21 -0
  71. package/dist/presets/i18next-shopify.d.mts +6 -0
  72. package/dist/presets/i18next-shopify.mjs +23 -0
  73. package/dist/presets/i18next.d.mts +5 -0
  74. package/dist/presets/i18next.mjs +10 -0
  75. package/dist/presets/i18nice.d.mts +6 -0
  76. package/dist/presets/i18nice.mjs +29 -0
  77. package/dist/presets/jekyll.d.mts +5 -0
  78. package/dist/presets/jekyll.mjs +23 -0
  79. package/dist/presets/laravel-vue-i18n.d.mts +6 -0
  80. package/dist/presets/laravel-vue-i18n.mjs +54 -0
  81. package/dist/presets/laravel.d.mts +6 -0
  82. package/dist/presets/laravel.mjs +23 -0
  83. package/dist/presets/lingui.d.mts +5 -0
  84. package/dist/presets/lingui.mjs +61 -0
  85. package/dist/presets/next-international.d.mts +5 -0
  86. package/dist/presets/next-international.mjs +27 -0
  87. package/dist/presets/next-intl.d.mts +5 -0
  88. package/dist/presets/next-intl.mjs +55 -0
  89. package/dist/presets/next-translate.d.mts +5 -0
  90. package/dist/presets/next-translate.mjs +53 -0
  91. package/dist/presets/ngx-translate.d.mts +5 -0
  92. package/dist/presets/ngx-translate.mjs +41 -0
  93. package/dist/presets/nuxt-i18n-micro.d.mts +5 -0
  94. package/dist/presets/nuxt-i18n-micro.mjs +81 -0
  95. package/dist/presets/nuxt-i18n.d.mts +5 -0
  96. package/dist/presets/nuxt-i18n.mjs +23 -0
  97. package/dist/presets/paraglide.d.mts +5 -0
  98. package/dist/presets/paraglide.mjs +38 -0
  99. package/dist/presets/php-gettext.d.mts +6 -0
  100. package/dist/presets/php-gettext.mjs +27 -0
  101. package/dist/presets/php-joomla.d.mts +6 -0
  102. package/dist/presets/php-joomla.mjs +22 -0
  103. package/dist/presets/polyglot.d.mts +5 -0
  104. package/dist/presets/polyglot.mjs +23 -0
  105. package/dist/presets/python-gettext.d.mts +6 -0
  106. package/dist/presets/python-gettext.mjs +44 -0
  107. package/dist/presets/react-i18next.d.mts +5 -0
  108. package/dist/presets/react-i18next.mjs +67 -0
  109. package/dist/presets/react-intl.d.mts +5 -0
  110. package/dist/presets/react-intl.mjs +47 -0
  111. package/dist/presets/ruby-i18n-js.d.mts +5 -0
  112. package/dist/presets/ruby-i18n-js.mjs +19 -0
  113. package/dist/presets/ruby-rails.d.mts +7 -0
  114. package/dist/presets/ruby-rails.mjs +44 -0
  115. package/dist/presets/solid-primitives-i18n.d.mts +5 -0
  116. package/dist/presets/solid-primitives-i18n.mjs +29 -0
  117. package/dist/presets/svelte-i18n.d.mts +5 -0
  118. package/dist/presets/svelte-i18n.mjs +33 -0
  119. package/dist/presets/svelte-i18next.d.mts +5 -0
  120. package/dist/presets/svelte-i18next.mjs +27 -0
  121. package/dist/presets/transloco.d.mts +5 -0
  122. package/dist/presets/transloco.mjs +44 -0
  123. package/dist/presets/typesafe-i18n.d.mts +6 -0
  124. package/dist/presets/typesafe-i18n.mjs +61 -0
  125. package/dist/presets/ui5.d.mts +5 -0
  126. package/dist/presets/ui5.mjs +35 -0
  127. package/dist/presets/vscode-extension.d.mts +5 -0
  128. package/dist/presets/vscode-extension.mjs +53 -0
  129. package/dist/presets/vue-i18n.d.mts +5 -0
  130. package/dist/presets/vue-i18n.mjs +59 -0
  131. package/dist/presets.d.mts +7 -54
  132. package/dist/presets.mjs +346 -1
  133. package/dist/reporters/github.d.mts +6 -0
  134. package/dist/reporters/github.mjs +27 -0
  135. package/dist/reporters/json.d.mts +6 -0
  136. package/dist/reporters/json.mjs +24 -0
  137. package/dist/reporters/markdown.d.mts +6 -0
  138. package/dist/reporters/markdown.mjs +37 -0
  139. package/dist/reporters/pretty.d.mts +6 -0
  140. package/dist/reporters/pretty.mjs +64 -0
  141. package/dist/reporters/sarif.d.mts +6 -0
  142. package/dist/reporters/sarif.mjs +48 -0
  143. package/dist/reporters.d.mts +5 -12
  144. package/dist/reporters.mjs +8 -218
  145. package/dist/reviews.d.mts +6 -0
  146. package/dist/reviews.mjs +35 -0
  147. package/dist/scanners/astro.d.mts +6 -0
  148. package/dist/scanners/astro.mjs +256 -0
  149. package/dist/scanners/js.d.mts +6 -0
  150. package/dist/scanners/js.mjs +33 -0
  151. package/dist/scanners/regex-scopes.d.mts +14 -0
  152. package/dist/scanners/regex-scopes.mjs +41 -0
  153. package/dist/scanners/regex.d.mts +9 -0
  154. package/dist/{regex-AYVhuSb6.mjs → scanners/regex.mjs} +2 -40
  155. package/dist/scanners/svelte.d.mts +6 -0
  156. package/dist/{scanners-DhnnVp7s.mjs → scanners/svelte.mjs} +3 -3
  157. package/dist/scanners/vue.d.mts +6 -0
  158. package/dist/scanners/vue.mjs +172 -0
  159. package/dist/scanners.d.mts +9 -50
  160. package/dist/scanners.mjs +36 -4
  161. package/dist/sfc-blocks-DaGKDZsb.mjs +213 -0
  162. package/dist/shared-C7ceptwi.mjs +55 -0
  163. package/dist/shared-D-tbsYmc.mjs +30 -0
  164. package/dist/shared-D4NJBKEP.mjs +17 -0
  165. package/dist/shared-Tq06G0gp.mjs +6 -0
  166. package/dist/shared-meYeVckh.mjs +11 -0
  167. package/dist/vue-compiler-0sfLIiiL.mjs +8 -0
  168. package/package.json +68 -3
  169. package/dist/formats-CFkM5co0.mjs +0 -1370
  170. package/dist/presets-Co5SlnKr.mjs +0 -1187
  171. package/dist/sfc-blocks-BqQ8m9rX.mjs +0 -161
  172. package/dist/yaml-plugin.d.mts +0 -8
  173. 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 { scanWithRegex as i, regex as n, regexScopes as r, compileUsagePattern as t };
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 { i as isTextAttribute, o as loadParser, s as scanSource } from "./vue-CF4pe-Yr.mjs";
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 t };
104
+ export { svelte as default, svelte };
@@ -0,0 +1,6 @@
1
+ import { Plugin } from "@x18n/core";
2
+ //#region src/scanners/vue/index.d.ts
3
+ /** Vue SFCs: `<script>` through the `js` scanner, template expressions, `v-t`, `<i18n-t keypath>`. */
4
+ export declare function vue(): Plugin;
5
+ //#endregion
6
+ export { vue as default };
@@ -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 };
@@ -1,52 +1,11 @@
1
- import { CallSpec, Plugin, RegexScannerSpec, ScanResult, UsageCandidate } from "@x18n/core";
2
- import { Node } from "oxc-parser";
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
- * 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.
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 function regexScopes(pattern: string, id?: string, options?: {
39
- readonly as?: "namespace" | "keyPrefix";
40
- readonly extensions?: readonly string[];
41
- readonly wholeFile?: boolean;
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
- 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
- export { calleeName, compileUsagePattern, js, loadParser, regex, regexScopes, scanSource, scanWithRegex, svelte, vue };
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 };
@@ -0,0 +1,6 @@
1
+ //#region src/presets/nuxt-i18n/shared.ts
2
+ async function readNuxtConfig(host) {
3
+ return await host.readFile("nuxt.config.ts") ?? await host.readFile("nuxt.config.js");
4
+ }
5
+ //#endregion
6
+ export { readNuxtConfig as t };