@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.
Files changed (170) hide show
  1. package/dist/angular-templates-DqIOGfzT.mjs +8 -0
  2. package/dist/checks.mjs +3 -2
  3. package/dist/extractors.d.mts +2 -2
  4. package/dist/extractors.mjs +18 -6
  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 +211 -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 +30 -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 +200 -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 +113 -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 +28 -22
  33. package/dist/{vue-5GFR5hft.mjs → js-CV-ECNJG.mjs} +133 -273
  34. package/dist/json-CyjI8bjp.d.mts +8 -0
  35. package/dist/json-Db-8qSbE.mjs +73 -0
  36. package/dist/jsx-templates-B1J0no3i.mjs +17 -0
  37. package/dist/locale-root-C_rch3IQ.mjs +15 -0
  38. package/dist/manifests.d.mts +8 -0
  39. package/dist/manifests.mjs +66 -2
  40. package/dist/placeholders.d.mts +11 -0
  41. package/dist/placeholders.mjs +28 -1
  42. package/dist/plugin-Cb1b0axD.mjs +83 -0
  43. package/dist/plural-forms-B7_QX4nG.mjs +12 -0
  44. package/dist/presets/cargo-i18n.d.mts +6 -0
  45. package/dist/presets/cargo-i18n.mjs +28 -0
  46. package/dist/presets/chrome-extension.d.mts +6 -0
  47. package/dist/presets/chrome-extension.mjs +52 -0
  48. package/dist/presets/dotnet-resx.d.mts +6 -0
  49. package/dist/presets/dotnet-resx.mjs +64 -0
  50. package/dist/presets/easy-localization.d.mts +6 -0
  51. package/dist/presets/easy-localization.mjs +36 -0
  52. package/dist/presets/ember-intl.d.mts +5 -0
  53. package/dist/presets/ember-intl.mjs +31 -0
  54. package/dist/presets/fluent-react.d.mts +5 -0
  55. package/dist/presets/fluent-react.mjs +46 -0
  56. package/dist/presets/fluent-vue.d.mts +5 -0
  57. package/dist/presets/fluent-vue.mjs +51 -0
  58. package/dist/presets/flutter-translate.d.mts +6 -0
  59. package/dist/presets/flutter-translate.mjs +30 -0
  60. package/dist/presets/flutter.d.mts +6 -0
  61. package/dist/presets/flutter.mjs +21 -0
  62. package/dist/presets/general.d.mts +6 -0
  63. package/dist/presets/general.mjs +17 -0
  64. package/dist/presets/globalize.d.mts +5 -0
  65. package/dist/presets/globalize.mjs +25 -0
  66. package/dist/presets/gravity-ui-i18n.d.mts +6 -0
  67. package/dist/presets/gravity-ui-i18n.mjs +41 -0
  68. package/dist/presets/i18n-js.d.mts +5 -0
  69. package/dist/presets/i18n-js.mjs +19 -0
  70. package/dist/presets/i18n-tag.d.mts +5 -0
  71. package/dist/presets/i18n-tag.mjs +21 -0
  72. package/dist/presets/i18next-shopify.d.mts +6 -0
  73. package/dist/presets/i18next-shopify.mjs +23 -0
  74. package/dist/presets/i18next.d.mts +5 -0
  75. package/dist/presets/i18next.mjs +10 -0
  76. package/dist/presets/i18nice.d.mts +6 -0
  77. package/dist/presets/i18nice.mjs +29 -0
  78. package/dist/presets/jekyll.d.mts +5 -0
  79. package/dist/presets/jekyll.mjs +23 -0
  80. package/dist/presets/laravel-vue-i18n.d.mts +6 -0
  81. package/dist/presets/laravel-vue-i18n.mjs +54 -0
  82. package/dist/presets/laravel.d.mts +6 -0
  83. package/dist/presets/laravel.mjs +23 -0
  84. package/dist/presets/lingui.d.mts +5 -0
  85. package/dist/presets/lingui.mjs +61 -0
  86. package/dist/presets/next-international.d.mts +5 -0
  87. package/dist/presets/next-international.mjs +27 -0
  88. package/dist/presets/next-intl.d.mts +5 -0
  89. package/dist/presets/next-intl.mjs +55 -0
  90. package/dist/presets/next-translate.d.mts +5 -0
  91. package/dist/presets/next-translate.mjs +53 -0
  92. package/dist/presets/ngx-translate.d.mts +5 -0
  93. package/dist/presets/ngx-translate.mjs +41 -0
  94. package/dist/presets/nuxt-i18n-micro.d.mts +5 -0
  95. package/dist/presets/nuxt-i18n-micro.mjs +81 -0
  96. package/dist/presets/nuxt-i18n.d.mts +5 -0
  97. package/dist/presets/nuxt-i18n.mjs +23 -0
  98. package/dist/presets/paraglide.d.mts +5 -0
  99. package/dist/presets/paraglide.mjs +38 -0
  100. package/dist/presets/php-gettext.d.mts +6 -0
  101. package/dist/presets/php-gettext.mjs +27 -0
  102. package/dist/presets/php-joomla.d.mts +6 -0
  103. package/dist/presets/php-joomla.mjs +22 -0
  104. package/dist/presets/polyglot.d.mts +5 -0
  105. package/dist/presets/polyglot.mjs +23 -0
  106. package/dist/presets/python-gettext.d.mts +6 -0
  107. package/dist/presets/python-gettext.mjs +44 -0
  108. package/dist/presets/react-i18next.d.mts +5 -0
  109. package/dist/presets/react-i18next.mjs +67 -0
  110. package/dist/presets/react-intl.d.mts +5 -0
  111. package/dist/presets/react-intl.mjs +47 -0
  112. package/dist/presets/ruby-rails.d.mts +7 -0
  113. package/dist/presets/ruby-rails.mjs +44 -0
  114. package/dist/presets/solid-primitives-i18n.d.mts +5 -0
  115. package/dist/presets/solid-primitives-i18n.mjs +29 -0
  116. package/dist/presets/svelte-i18n.d.mts +5 -0
  117. package/dist/presets/svelte-i18n.mjs +33 -0
  118. package/dist/presets/svelte-i18next.d.mts +5 -0
  119. package/dist/presets/svelte-i18next.mjs +27 -0
  120. package/dist/presets/transloco.d.mts +5 -0
  121. package/dist/presets/transloco.mjs +44 -0
  122. package/dist/presets/typesafe-i18n.d.mts +6 -0
  123. package/dist/presets/typesafe-i18n.mjs +61 -0
  124. package/dist/presets/ui5.d.mts +5 -0
  125. package/dist/presets/ui5.mjs +35 -0
  126. package/dist/presets/vscode-extension.d.mts +5 -0
  127. package/dist/presets/vscode-extension.mjs +53 -0
  128. package/dist/presets/vue-i18n.d.mts +5 -0
  129. package/dist/presets/vue-i18n.mjs +59 -0
  130. package/dist/presets.d.mts +7 -54
  131. package/dist/presets.mjs +346 -1
  132. package/dist/reporters/github.d.mts +6 -0
  133. package/dist/reporters/github.mjs +27 -0
  134. package/dist/reporters/json.d.mts +6 -0
  135. package/dist/reporters/json.mjs +24 -0
  136. package/dist/reporters/markdown.d.mts +6 -0
  137. package/dist/reporters/markdown.mjs +37 -0
  138. package/dist/reporters/pretty.d.mts +6 -0
  139. package/dist/reporters/pretty.mjs +64 -0
  140. package/dist/reporters/sarif.d.mts +6 -0
  141. package/dist/reporters/sarif.mjs +48 -0
  142. package/dist/reporters.d.mts +5 -12
  143. package/dist/reporters.mjs +8 -218
  144. package/dist/scanners/astro.d.mts +6 -0
  145. package/dist/scanners/astro.mjs +256 -0
  146. package/dist/scanners/js.d.mts +6 -0
  147. package/dist/scanners/js.mjs +33 -0
  148. package/dist/scanners/regex-scopes.d.mts +14 -0
  149. package/dist/scanners/regex-scopes.mjs +41 -0
  150. package/dist/scanners/regex.d.mts +9 -0
  151. package/dist/{regex-B9TBGgXC.mjs → scanners/regex.mjs} +2 -33
  152. package/dist/scanners/svelte.d.mts +6 -0
  153. package/dist/{scanners-B27IEH4_.mjs → scanners/svelte.mjs} +8 -23
  154. package/dist/scanners/vue.d.mts +6 -0
  155. package/dist/scanners/vue.mjs +172 -0
  156. package/dist/scanners.d.mts +9 -47
  157. package/dist/scanners.mjs +36 -4
  158. package/dist/sfc-blocks-DaGKDZsb.mjs +213 -0
  159. package/dist/shared-BWaXUmwA.mjs +70 -0
  160. package/dist/shared-D-tbsYmc.mjs +30 -0
  161. package/dist/shared-D4NJBKEP.mjs +17 -0
  162. package/dist/shared-Tq06G0gp.mjs +6 -0
  163. package/dist/shared-meYeVckh.mjs +11 -0
  164. package/dist/vue-compiler-0sfLIiiL.mjs +8 -0
  165. package/package.json +67 -3
  166. package/dist/formats-CFkM5co0.mjs +0 -1370
  167. package/dist/presets-DJrGl3HR.mjs +0 -1184
  168. package/dist/sfc-blocks-BIPqKcvd.mjs +0 -161
  169. package/dist/yaml-plugin.d.mts +0 -8
  170. package/dist/yaml-plugin.mjs +0 -68
@@ -1,5 +1,5 @@
1
- import { a as loadParser, o as scanSource } from "./vue-5GFR5hft.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";
@@ -14,33 +14,18 @@ const expressionTypes = /* @__PURE__ */ new Set([
14
14
  "AwaitBlock",
15
15
  "KeyBlock"
16
16
  ]);
17
- const ignoredAttributes = /* @__PURE__ */ new Set([
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
- const name = typeof attribute?.name === "string" ? attribute.name : void 0;
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
- ...name ? { attribute: name } : {}
28
+ attribute: attribute.name
44
29
  });
45
30
  return;
46
31
  }
@@ -101,7 +86,7 @@ function svelte() {
101
86
  return;
102
87
  }
103
88
  if (expressionTypes.has(node.type) && "expression" in node && isSvelteNode(node.expression)) scan(file.text.slice(node.expression.start, node.expression.end), node.expression.start, true);
104
- if (node.type === "Text" && "data" in node && typeof node.data === "string") textHardString(node, node.data, parentType === "Attribute" ? attribute : void 0, hardStrings);
89
+ if (node.type === "Text" && "data" in node && typeof node.data === "string") textHardString(node, node.data, parentType === "Attribute" ? attribute : void 0, ctx.config.extract.attributes, hardStrings);
105
90
  const owner = node.type === "Attribute" ? node : attribute;
106
91
  for (const [key, child] of Object.entries(node)) if (key !== "expression" && typeof child === "object") visit(child, node.type, owner);
107
92
  };
@@ -116,4 +101,4 @@ function svelte() {
116
101
  };
117
102
  }
118
103
  //#endregion
119
- export { svelte as 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,49 +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
- }
21
- export declare function scanSource(parse: ParseSync, text: string, options: ScanSourceOptions): ScanResult;
22
- /** The AST scanner for JS and TS, driven by call specs (`usage.calls`). */
23
- export declare function js(): Plugin;
24
- //#endregion
25
- //#region src/scanners/regex.d.ts
26
- /** Turns a pattern with a `{key}` placeholder into a regex whose first group is the key. */
27
- export declare function compileUsagePattern(pattern: string): RegExp;
28
- export declare function scanWithRegex(text: string, regexes: readonly RegExp[]): UsageCandidate[];
29
- /** The `regex` scanner over `usage.regex` specs, plus extra specs given here (YAML plugins). */
30
- export declare function regex(specs?: readonly RegexScannerSpec[], id?: string): Plugin;
1
+ import { ScannerEntry } from "@x18n/core";
2
+ //#region src/scanners/index.d.ts
31
3
  /**
32
- * Regions after each match of `pattern` (group 1 = namespace, or key prefix with `as: 'keyPrefix'`),
33
- * up to the next match: i18n-ally `scopeRangeRegex`. With `wholeFile`, the first match scopes the whole file.
4
+ * Built-in scanners by id, in scan order. A scanner is a folder: `<name>/entry.ts` holds its extensions and a
5
+ * `load` for `<name>/index.ts`, imported when a file it scans is first scanned. `regex` is not here: its
6
+ * extensions come from `usage.regex`, so it is always loaded.
34
7
  */
35
- export declare function regexScopes(pattern: string, id?: string, options?: {
36
- readonly as?: "namespace" | "keyPrefix";
37
- readonly extensions?: readonly string[];
38
- readonly wholeFile?: boolean;
39
- }): Plugin;
40
- //#endregion
41
- //#region src/scanners/svelte.d.ts
42
- /** Svelte components: `<script>` and template expressions through the `js` scanner. Node only at first. */
43
- export declare function svelte(): Plugin;
44
- //#endregion
45
- //#region src/scanners/vue.d.ts
46
- /** Vue SFCs: `<script>` through the `js` scanner, template expressions, `v-t`, `<i18n-t keypath>`. */
47
- export declare function vue(): Plugin;
48
- //#endregion
49
- export type { ScanSourceOptions };
8
+ export declare const scanners: {
9
+ readonly [id: string]: ScannerEntry;
10
+ };
11
+ //#endregion
package/dist/scanners.mjs CHANGED
@@ -1,4 +1,36 @@
1
- import { a as loadParser, i as js, n as vue, o as scanSource, r as calleeName } from "./vue-5GFR5hft.mjs";
2
- import { i as scanWithRegex, n as regex, r as regexScopes, t as compileUsagePattern } from "./regex-B9TBGgXC.mjs";
3
- import { t as svelte } from "./scanners-B27IEH4_.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,70 @@
1
+ //#region src/formats/shared/index.ts
2
+ /** Indent of the first indented line, so edits keep the file's style. */
3
+ function detectIndent(text, fallback) {
4
+ if (fallback === "tab") return {
5
+ insertSpaces: false,
6
+ tabSize: 1
7
+ };
8
+ if (typeof fallback === "number") return {
9
+ insertSpaces: true,
10
+ tabSize: fallback
11
+ };
12
+ const match = /^([ \t]+)\S/m.exec(text);
13
+ if (match?.[1]?.startsWith(" ")) return {
14
+ insertSpaces: false,
15
+ tabSize: 1
16
+ };
17
+ return {
18
+ insertSpaces: true,
19
+ tabSize: match?.[1]?.length ?? 2
20
+ };
21
+ }
22
+ function detectEol(text) {
23
+ return text.includes("\r\n") ? "\r\n" : "\n";
24
+ }
25
+ function toLeaf(value) {
26
+ if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") return value;
27
+ if (value instanceof Date) return value.toISOString();
28
+ }
29
+ function leafText(value) {
30
+ return value === null ? "" : String(value);
31
+ }
32
+ function splice(text, range, insert) {
33
+ return text.slice(0, range.start) + insert + text.slice(range.end);
34
+ }
35
+ /** Appends `chunk` on a line of its own, in the file's line endings. */
36
+ function appendLines(text, chunk) {
37
+ const eol = detectEol(text);
38
+ return (text === "" || text.endsWith("\n") ? text : text + eol) + chunk.replace(/\n/g, eol);
39
+ }
40
+ function splitLines(text) {
41
+ const out = [];
42
+ let start = 0;
43
+ while (start < text.length) {
44
+ const lf = text.indexOf("\n", start);
45
+ const next = lf === -1 ? text.length : lf + 1;
46
+ const end = lf === -1 ? text.length : lf > start && text[lf - 1] === "\r" ? lf - 1 : lf;
47
+ out.push({
48
+ start,
49
+ end,
50
+ next
51
+ });
52
+ start = next;
53
+ }
54
+ return out;
55
+ }
56
+ /** An entry's range widened to swallow the blank lines that separate it, so deleting it leaves one separator. */
57
+ function withBlankLines(text, range) {
58
+ const after = /^(?:[ \t]*\r?\n)*(?:[ \t]*$)?/.exec(text.slice(range.end))[0].length;
59
+ if (range.end + after < text.length) return {
60
+ start: range.start,
61
+ end: range.end + after
62
+ };
63
+ const before = /(?<=\n)(?:[ \t]*\r?\n)+$/.exec(text.slice(0, range.start))?.[0].length ?? 0;
64
+ return {
65
+ start: range.start - before,
66
+ end: text.length
67
+ };
68
+ }
69
+ //#endregion
70
+ export { splice as a, withBlankLines as c, leafText as i, detectEol as n, splitLines as o, detectIndent as r, toLeaf as s, appendLines as t };
@@ -0,0 +1,30 @@
1
+ //#region src/reporters/shared/index.ts
2
+ const codes = {
3
+ red: 31,
4
+ yellow: 33,
5
+ cyan: 36,
6
+ gray: 90,
7
+ blue: 34,
8
+ bold: 1
9
+ };
10
+ function paint(ctx, color, text) {
11
+ return ctx.color ? `\u001B[${codes[color]}m${text}\u001B[0m` : text;
12
+ }
13
+ function plural(count, word) {
14
+ return `${count} ${word}${count === 1 ? "" : "s"}`;
15
+ }
16
+ function totals(inputs) {
17
+ return inputs.reduce((sum, input) => ({
18
+ errors: sum.errors + input.summary.errors,
19
+ warnings: sum.warnings + input.summary.warnings,
20
+ infos: sum.infos + input.summary.infos,
21
+ files: sum.files + input.summary.files
22
+ }), {
23
+ errors: 0,
24
+ warnings: 0,
25
+ infos: 0,
26
+ files: 0
27
+ });
28
+ }
29
+ //#endregion
30
+ export { totals as i, paint as n, plural as r, codes as t };
@@ -0,0 +1,17 @@
1
+ //#region src/presets/shared/index.ts
2
+ function dependsOn(names, prefixes = []) {
3
+ return (ctx) => ctx.hasAnyDependency(names) || [...ctx.dependencies].some((dep) => prefixes.some((prefix) => dep.startsWith(prefix)));
4
+ }
5
+ /** Extensions of JS and TS sources. */
6
+ const scriptExtensions = [
7
+ "js",
8
+ "jsx",
9
+ "mjs",
10
+ "cjs",
11
+ "ts",
12
+ "tsx",
13
+ "mts",
14
+ "cts"
15
+ ];
16
+ //#endregion
17
+ export { scriptExtensions as n, dependsOn as t };
@@ -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 };