@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,221 +1,11 @@
1
- import { createLineIndex, envelope, toWireDiagnostic } from "@x18n/core";
2
1
  //#region src/reporters/index.ts
3
- const codes = {
4
- red: 31,
5
- yellow: 33,
6
- cyan: 36,
7
- gray: 90,
8
- blue: 34,
9
- bold: 1
2
+ /** Reporters by id. Each is also its own module, `@x18n/builtins/reporters/<id>`. */
3
+ const reporters = {
4
+ pretty: () => import("./reporters/pretty.mjs"),
5
+ json: () => import("./reporters/json.mjs"),
6
+ markdown: () => import("./reporters/markdown.mjs"),
7
+ github: () => import("./reporters/github.mjs"),
8
+ sarif: () => import("./reporters/sarif.mjs")
10
9
  };
11
- function paint(ctx, color, text) {
12
- return ctx.color ? `\u001B[${codes[color]}m${text}\u001B[0m` : text;
13
- }
14
- function plural(count, word) {
15
- return `${count} ${word}${count === 1 ? "" : "s"}`;
16
- }
17
- function totals(inputs) {
18
- return inputs.reduce((sum, input) => ({
19
- errors: sum.errors + input.summary.errors,
20
- warnings: sum.warnings + input.summary.warnings,
21
- infos: sum.infos + input.summary.infos,
22
- files: sum.files + input.summary.files
23
- }), {
24
- errors: 0,
25
- warnings: 0,
26
- infos: 0,
27
- files: 0
28
- });
29
- }
30
- const severityColor = {
31
- error: "red",
32
- warning: "yellow",
33
- info: "blue",
34
- hint: "gray"
35
- };
36
- /** The severity as a highlighted label, like ` ERROR `. */
37
- function badge(ctx, severity) {
38
- const label = ` ${severity.toUpperCase()} `;
39
- return ctx.color ? `\u001B[1;7;${codes[severityColor[severity]]}m${label}\u001B[0m` : label;
40
- }
41
- /** The marked line with one line of context on each side, and `~` under the range. */
42
- function codeFrame(text, start, end, severity, ctx) {
43
- const index = createLineIndex(text);
44
- const from = index.position(start);
45
- const to = index.position(end);
46
- const first = Math.max(0, from.line - 1);
47
- const last = Math.min(index.lineCount - 1, from.line + 1);
48
- const pad = String(last + 1).length;
49
- const gutter = (n) => `${paint(ctx, "gray", n.padStart(pad))} ${paint(ctx, "gray", "│")} `;
50
- const width = Math.max(1, (to.line === from.line ? to.character : index.line(from.line).length) - from.character);
51
- const lines = [];
52
- for (let n = first; n <= last; n++) {
53
- const line = index.line(n);
54
- lines.push(`${gutter(String(n + 1))}${n === from.line ? line : paint(ctx, "gray", line)}`);
55
- if (n === from.line) lines.push(`${gutter("")}${" ".repeat(from.character)}${paint(ctx, severityColor[severity], "~".repeat(width))}`);
56
- }
57
- return lines.join("\n");
58
- }
59
- /** `path:line:col`, then the severity, message and check, a code frame, and a summary. */
60
- const prettyReporter = {
61
- id: "pretty",
62
- report(inputs, ctx) {
63
- const blocks = [];
64
- for (const input of inputs) for (const d of input.diagnostics) {
65
- const head = `${badge(ctx, d.severity)} ${d.message} ${paint(ctx, "gray", d.code)}`;
66
- if (!d.location) {
67
- blocks.push(head);
68
- continue;
69
- }
70
- const text = input.text(d.location.path);
71
- const position = createLineIndex(text ?? "").position(d.location.range.start);
72
- const where = `${paint(ctx, "cyan", ctx.displayPath(d.location.path, input.project))}${paint(ctx, "gray", `:${position.line + 1}:${position.character + 1}`)}`;
73
- const frame = text !== void 0 && d.location.range.end > d.location.range.start ? `\n${codeFrame(text, d.location.range.start, d.location.range.end, d.severity, ctx)}` : "";
74
- blocks.push(`${where}\n${head}${frame}`);
75
- }
76
- const sum = totals(inputs);
77
- if (sum.errors + sum.warnings === 0) blocks.push(paint(ctx, "gray", "Found no errors."));
78
- else {
79
- const parts = [
80
- sum.errors ? paint(ctx, "red", `✖ ${plural(sum.errors, "error")}`) : "",
81
- sum.warnings ? paint(ctx, "yellow", `⚠ ${plural(sum.warnings, "warning")}`) : "",
82
- paint(ctx, "gray", plural(sum.files, "file"))
83
- ].filter(Boolean);
84
- blocks.push(parts.join(" "));
85
- }
86
- return `${blocks.join("\n\n")}\n`;
87
- }
88
- };
89
- /** The versioned JSON contract of `x18n check --json`. */
90
- const jsonReporter = {
91
- id: "json",
92
- report(inputs) {
93
- const diagnostics = inputs.flatMap((input) => input.diagnostics.map((d) => toWireDiagnostic(input.project, d, input.text)));
94
- const data = { projects: inputs.map((input) => ({
95
- name: input.project,
96
- summary: input.summary,
97
- coverage: input.coverage.map((c) => ({
98
- locale: c.locale,
99
- total: c.total,
100
- translated: c.translated,
101
- missing: c.missing.length,
102
- empty: c.empty.length,
103
- outdated: c.outdated.length
104
- }))
105
- })) };
106
- return `${JSON.stringify(envelope("check", data, diagnostics), null, 2)}\n`;
107
- }
108
- };
109
- function escapeCell(text) {
110
- return text.replace(/\|/g, "\\|").replace(/\n/g, " ");
111
- }
112
- /** Summary and tables for PR comments and agent context. */
113
- const markdownReporter = {
114
- id: "markdown",
115
- report(inputs, ctx) {
116
- const sum = totals(inputs);
117
- const out = [
118
- "## x18n check",
119
- "",
120
- `**${plural(sum.errors, "error")}**, ${plural(sum.warnings, "warning")} in ${plural(sum.files, "file")}.`,
121
- ""
122
- ];
123
- for (const input of inputs) {
124
- if (inputs.length > 1) out.push(`### ${input.project}`, "");
125
- out.push("| Locale | Translated | Missing | Empty |", "| --- | --- | --- | --- |");
126
- for (const c of input.coverage) out.push(`| ${c.locale} | ${c.translated}/${c.total} | ${c.missing.length} | ${c.empty.length} |`);
127
- out.push("");
128
- if (input.diagnostics.length === 0) continue;
129
- out.push("| Severity | Check | Location | Message |", "| --- | --- | --- | --- |");
130
- for (const d of input.diagnostics.slice(0, 200)) {
131
- const line = d.location ? createLineIndex(input.text(d.location.path) ?? "").position(d.location.range.start).line + 1 : void 0;
132
- const where = d.location ? `\`${ctx.displayPath(d.location.path, input.project)}:${line}\`` : "";
133
- out.push(`| ${d.severity} | \`${d.code}\` | ${where} | ${escapeCell(d.message)} |`);
134
- }
135
- if (input.diagnostics.length > 200) out.push("", `_${input.diagnostics.length - 200} more not shown._`);
136
- out.push("");
137
- }
138
- return `${out.join("\n")}\n`;
139
- }
140
- };
141
- function escapeProperty(text) {
142
- return text.replace(/%/g, "%25").replace(/\r/g, "%0D").replace(/\n/g, "%0A").replace(/:/g, "%3A").replace(/,/g, "%2C");
143
- }
144
- function escapeData(text) {
145
- return text.replace(/%/g, "%25").replace(/\r/g, "%0D").replace(/\n/g, "%0A");
146
- }
147
- /** GitHub Actions workflow commands, which become annotations on the PR. */
148
- const githubReporter = {
149
- id: "github",
150
- report(inputs, ctx) {
151
- const out = [];
152
- for (const input of inputs) for (const d of input.diagnostics) {
153
- const level = d.severity === "error" ? "error" : d.severity === "warning" ? "warning" : "notice";
154
- const props = [`title=${escapeProperty(`x18n ${d.code}`)}`];
155
- if (d.location) {
156
- const wire = toWireDiagnostic(input.project, d, input.text).range;
157
- props.unshift(`file=${escapeProperty(ctx.displayPath(d.location.path, input.project))}`, `line=${wire.line}`, `col=${wire.column}`, `endLine=${wire.endLine}`, `endColumn=${wire.endColumn}`);
158
- }
159
- out.push(`::${level} ${props.join(",")}::${escapeData(d.message)}`);
160
- }
161
- return out.length ? `${out.join("\n")}\n` : "";
162
- }
163
- };
164
- /** SARIF 2.1.0 for code scanning. */
165
- const sarifReporter = {
166
- id: "sarif",
167
- report(inputs, ctx) {
168
- const all = inputs.flatMap((input) => input.diagnostics.map((d) => ({
169
- input,
170
- d
171
- })));
172
- const rules = [...new Set(all.map(({ d }) => d.code))].sort();
173
- const results = all.map(({ input, d }) => {
174
- const wire = toWireDiagnostic(input.project, d, input.text);
175
- return {
176
- ruleId: d.code,
177
- level: d.severity === "error" ? "error" : d.severity === "warning" ? "warning" : "note",
178
- message: { text: d.message },
179
- locations: d.location && wire.range ? [{ physicalLocation: {
180
- artifactLocation: { uri: ctx.displayPath(d.location.path, input.project) },
181
- region: {
182
- startLine: wire.range.line,
183
- startColumn: wire.range.column,
184
- endLine: wire.range.endLine,
185
- endColumn: wire.range.endColumn
186
- }
187
- } }] : []
188
- };
189
- });
190
- const log = {
191
- $schema: "https://json.schemastore.org/sarif-2.1.0.json",
192
- version: "2.1.0",
193
- runs: [{
194
- tool: { driver: {
195
- name: "x18n",
196
- informationUri: "https://x18n.antfu.dev",
197
- rules: rules.map((id) => ({
198
- id,
199
- helpUri: `https://x18n.antfu.dev/checks/${id}`
200
- }))
201
- } },
202
- results
203
- }]
204
- };
205
- return `${JSON.stringify(log, null, 2)}\n`;
206
- }
207
- };
208
- function reporters() {
209
- return {
210
- name: "x18n:reporters",
211
- reporters: [
212
- prettyReporter,
213
- jsonReporter,
214
- markdownReporter,
215
- githubReporter,
216
- sarifReporter
217
- ]
218
- };
219
- }
220
10
  //#endregion
221
- export { githubReporter, jsonReporter, markdownReporter, prettyReporter, reporters, sarifReporter };
11
+ export { reporters };
@@ -0,0 +1,6 @@
1
+ import { Plugin, ReviewStore } from "@x18n/core";
2
+ //#region src/reviews/index.d.ts
3
+ /** Reviews in a YAML file (`.x18n/reviews.yml`), tracked in git so they show up in pull requests. */
4
+ export declare const yamlReviewStore: ReviewStore;
5
+ export declare function reviews(): Plugin;
6
+ //#endregion
@@ -0,0 +1,35 @@
1
+ import { ReviewFileSchema, validate } from "@x18n/core";
2
+ import { parse, stringify } from "yaml";
3
+ //#region src/reviews/index.ts
4
+ const header = "# yaml-language-server: $schema=https://x18n.antfu.dev/schema/reviews.json\n";
5
+ /** Reviews in a YAML file (`.x18n/reviews.yml`), tracked in git so they show up in pull requests. */
6
+ const yamlReviewStore = {
7
+ id: "yaml",
8
+ parse(text) {
9
+ const result = validate(ReviewFileSchema, parse(text) ?? { x18n: 1 });
10
+ if (!result.success) throw new Error(result.issues.map((issue) => `${issue.path || "(root)"}: ${issue.message}`).join("; "));
11
+ return {
12
+ threads: result.output.threads ?? {},
13
+ sources: result.output.sources ?? {}
14
+ };
15
+ },
16
+ serialize(data) {
17
+ const file = {
18
+ x18n: 1,
19
+ ...Object.keys(data.threads).length > 0 && { threads: data.threads },
20
+ ...Object.keys(data.sources).length > 0 && { sources: data.sources }
21
+ };
22
+ return header + stringify(file, {
23
+ lineWidth: 0,
24
+ aliasDuplicateObjects: false
25
+ });
26
+ }
27
+ };
28
+ function reviews() {
29
+ return {
30
+ name: "x18n:reviews",
31
+ reviewStores: [yamlReviewStore]
32
+ };
33
+ }
34
+ //#endregion
35
+ export { reviews, yamlReviewStore };
@@ -0,0 +1,6 @@
1
+ import { Plugin } from "@x18n/core";
2
+ //#region src/scanners/astro/index.d.ts
3
+ /** Astro components: the `---` frontmatter, `<script>` and template expressions through the `js` scanner. */
4
+ export declare function astro(): Plugin;
5
+ //#endregion
6
+ export { astro as default };
@@ -0,0 +1,256 @@
1
+ import { n as loadParser, r as scanSource, t as isTextAttribute } from "../js-CV-ECNJG.mjs";
2
+ //#region src/scanners/astro/index.ts
3
+ const voidElements = /* @__PURE__ */ new Set([
4
+ "area",
5
+ "base",
6
+ "br",
7
+ "col",
8
+ "embed",
9
+ "hr",
10
+ "img",
11
+ "input",
12
+ "link",
13
+ "meta",
14
+ "source",
15
+ "track",
16
+ "wbr"
17
+ ]);
18
+ /**
19
+ * Splits an Astro template into the parts the `js` scanner reads: top-level `{...}` expressions and
20
+ * `<script>` contents, plus text and text attributes as hard strings. Markup inside an expression
21
+ * (`{items.map(i => <li>{t(i)}</li>)}`) stays in the expression, which oxc parses as JSX.
22
+ */
23
+ var AstroTemplate = class {
24
+ text;
25
+ textAttributes;
26
+ expressions = [];
27
+ scripts = [];
28
+ hardStrings = [];
29
+ constructor(text, textAttributes) {
30
+ this.text = text;
31
+ this.textAttributes = textAttributes;
32
+ }
33
+ /** Index after the first `token` from `i`, or the end of the text. */
34
+ after(i, token) {
35
+ const at = this.text.indexOf(token, i);
36
+ return at < 0 ? this.text.length : at + token.length;
37
+ }
38
+ /** Index after the run of characters matching `pattern` (sticky) at `i`. */
39
+ skip(pattern, i) {
40
+ pattern.lastIndex = i;
41
+ return pattern.test(this.text) ? pattern.lastIndex : i;
42
+ }
43
+ /** Markup from `i`. At the top level it records what it finds; nested in an expression it returns after its closing tag. */
44
+ markup(i, nested) {
45
+ const { text } = this;
46
+ let textStart = i;
47
+ while (i < text.length) {
48
+ const c = text[i];
49
+ const next = text[i + 1] ?? "";
50
+ if (c !== "{" && c !== "<") {
51
+ i++;
52
+ continue;
53
+ }
54
+ let end;
55
+ if (c === "{") end = this.expression(i, nested);
56
+ else if (text.startsWith("<!--", i)) end = this.after(i, "-->");
57
+ else if (next === "/" || next === "!" || next === "?") end = this.after(i, ">");
58
+ else if (/[a-z>]/i.test(next)) end = this.element(i, nested);
59
+ else {
60
+ i++;
61
+ continue;
62
+ }
63
+ if (!nested) this.textNode(textStart, i);
64
+ if (nested && c === "<" && next === "/") return end;
65
+ i = textStart = end;
66
+ }
67
+ if (!nested) this.textNode(textStart, i);
68
+ return i;
69
+ }
70
+ element(i, nested) {
71
+ const { text } = this;
72
+ const nameEnd = this.skip(/[^\s/>{]*/y, i + 1);
73
+ const name = text.slice(i + 1, nameEnd).toLowerCase();
74
+ let j = nameEnd;
75
+ while (j < text.length) {
76
+ j = this.skip(/\s*/y, j);
77
+ if (text[j] === ">" || text.startsWith("/>", j)) break;
78
+ if (text[j] === "{") {
79
+ j = this.expression(j, nested);
80
+ continue;
81
+ }
82
+ const attributeStart = j;
83
+ j = Math.max(this.skip(/[^\s=>/{]+/y, j), j + 1);
84
+ if (text[j] !== "=") continue;
85
+ const attribute = text.slice(attributeStart, j);
86
+ const quote = text[++j];
87
+ if (quote === "{") j = this.expression(j, nested);
88
+ else if (quote === "`") {
89
+ const end = this.template(j) + 1;
90
+ if (!nested) this.expressions.push({
91
+ start: j,
92
+ end
93
+ });
94
+ j = end;
95
+ } else if (quote === "\"" || quote === "'") {
96
+ const end = this.after(j + 1, quote);
97
+ const value = text.slice(j + 1, end - 1);
98
+ if (!nested && isTextAttribute(attribute, this.textAttributes) && /\p{L}/u.test(value)) this.hardStrings.push({
99
+ text: value,
100
+ range: {
101
+ start: attributeStart,
102
+ end
103
+ },
104
+ kind: "astro-attribute",
105
+ attribute
106
+ });
107
+ j = end;
108
+ } else j = this.skip(/[^\s>]*/y, j);
109
+ }
110
+ const selfClosing = text.startsWith("/>", j);
111
+ const end = Math.min(j + (selfClosing ? 2 : 1), text.length);
112
+ if (name === "script" || name === "style") {
113
+ const close = new RegExp(`</${name}`, "gi");
114
+ close.lastIndex = end;
115
+ const closeStart = close.exec(text)?.index ?? text.length;
116
+ if (name === "script" && !nested) this.scripts.push({
117
+ start: end,
118
+ end: closeStart
119
+ });
120
+ return this.after(closeStart, ">");
121
+ }
122
+ if (!nested || selfClosing || voidElements.has(name)) return end;
123
+ return this.markup(end, true);
124
+ }
125
+ /** `{...}` at `i`; returns the index after its `}`. */
126
+ expression(i, nested) {
127
+ const close = this.js(i + 1);
128
+ if (!nested) this.expressions.push({
129
+ start: i + 1,
130
+ end: close
131
+ });
132
+ return close + 1;
133
+ }
134
+ /** Index of the `}` that closes JS code starting at `i`, past strings, comments and JSX. */
135
+ js(i) {
136
+ const { text } = this;
137
+ let depth = 0;
138
+ let previous = "(";
139
+ for (; i < text.length; i++) {
140
+ const c = text[i];
141
+ if (c === "\"" || c === "'") i = this.literal(i, c);
142
+ else if (c === "`") i = this.template(i);
143
+ else if (text.startsWith("//", i)) i = this.after(i, "\n") - 1;
144
+ else if (text.startsWith("/*", i)) i = this.after(i, "*/") - 1;
145
+ else if (c === "/" && this.startsOperand(i, previous)) i = this.literal(i, "/");
146
+ else if (c === "{") depth++;
147
+ else if (c === "}") {
148
+ if (depth-- === 0) return i;
149
+ } else if (c === "<" && /[a-z>]/i.test(text[i + 1] ?? "") && this.startsOperand(i, previous)) {
150
+ i = this.element(i, true) - 1;
151
+ previous = ">";
152
+ continue;
153
+ }
154
+ if (!/\s/.test(c)) previous = c;
155
+ }
156
+ return text.length;
157
+ }
158
+ /** Whether `i` starts a value rather than continuing one: `<` opens JSX and `/` a regex there, after a value they are operators. */
159
+ startsOperand(i, previous) {
160
+ return !/[\w$)\]]/.test(previous) || /\breturn\s*$/.test(this.text.slice(Math.max(0, i - 10), i));
161
+ }
162
+ /** Index of the `quote` that closes the string or regex literal at `i`, past escapes and regex classes. */
163
+ literal(i, quote) {
164
+ const { text } = this;
165
+ let inClass = false;
166
+ for (i++; i < text.length; i++) {
167
+ const c = text[i];
168
+ if (c === "\\") i++;
169
+ else if (quote === "/" && (c === "[" || c === "]")) inClass = c === "[";
170
+ else if (c === quote && !inClass) return i;
171
+ }
172
+ return text.length;
173
+ }
174
+ /** Index of the backtick that closes the template literal at `i`. */
175
+ template(i) {
176
+ const { text } = this;
177
+ for (i++; i < text.length; i++) if (text[i] === "\\") i++;
178
+ else if (text[i] === "`") return i;
179
+ else if (text.startsWith("${", i)) i = this.js(i + 2);
180
+ return text.length;
181
+ }
182
+ textNode(start, end) {
183
+ const data = this.text.slice(start, end);
184
+ const text = data.trim();
185
+ if (!/\p{L}/u.test(text)) return;
186
+ const at = start + data.indexOf(text);
187
+ this.hardStrings.push({
188
+ text,
189
+ range: {
190
+ start: at,
191
+ end: at + text.length
192
+ },
193
+ kind: "astro-text"
194
+ });
195
+ }
196
+ };
197
+ /** Astro components: the `---` frontmatter, `<script>` and template expressions through the `js` scanner. */
198
+ function astro() {
199
+ return {
200
+ name: "x18n:scanner-astro",
201
+ scanners: [{
202
+ id: "astro",
203
+ extensions: ["astro"],
204
+ async scan(file, ctx) {
205
+ const parse = await loadParser();
206
+ const { text } = file;
207
+ const usages = [];
208
+ const keySlots = [];
209
+ const hardStrings = [];
210
+ const add = (range, filename, expression, reportHardStrings) => {
211
+ let result;
212
+ try {
213
+ result = scanSource(parse, text.slice(range.start, range.end), {
214
+ calls: ctx.calls,
215
+ ignoreCallees: ctx.config.extract.ignoreCallees,
216
+ textAttributes: ctx.config.extract.attributes,
217
+ offset: range.start,
218
+ filename,
219
+ expression,
220
+ hardStrings: reportHardStrings
221
+ });
222
+ } catch {
223
+ return;
224
+ }
225
+ usages.push(...result.usages);
226
+ keySlots.push(...result.keySlots ?? []);
227
+ hardStrings.push(...result.hardStrings ?? []);
228
+ };
229
+ let templateStart = 0;
230
+ const open = /^\s*---/.exec(text);
231
+ if (open) {
232
+ const close = /^---/gm;
233
+ close.lastIndex = open[0].length;
234
+ const end = close.exec(text)?.index ?? text.length;
235
+ add({
236
+ start: open[0].length,
237
+ end
238
+ }, "frontmatter.ts", false, true);
239
+ templateStart = end + 3;
240
+ }
241
+ const template = new AstroTemplate(text, ctx.config.extract.attributes);
242
+ template.markup(templateStart, false);
243
+ for (const range of template.scripts) add(range, "script.ts", false, false);
244
+ for (const range of template.expressions) add(range, "template.tsx", true, false);
245
+ hardStrings.push(...template.hardStrings);
246
+ return {
247
+ usages,
248
+ keySlots,
249
+ hardStrings
250
+ };
251
+ }
252
+ }]
253
+ };
254
+ }
255
+ //#endregion
256
+ export { astro, astro as default };
@@ -0,0 +1,6 @@
1
+ import { Plugin } from "@x18n/core";
2
+ //#region src/scanners/js/index.d.ts
3
+ /** The AST scanner for JS and TS, driven by call specs (`usage.calls`). */
4
+ export declare function js(): Plugin;
5
+ //#endregion
6
+ export { js as default };
@@ -0,0 +1,33 @@
1
+ import { n as loadParser, r as scanSource } from "../js-CV-ECNJG.mjs";
2
+ //#region src/scanners/js/index.ts
3
+ /** The AST scanner for JS and TS, driven by call specs (`usage.calls`). */
4
+ function js() {
5
+ return {
6
+ name: "x18n:scanner-js",
7
+ scanners: [{
8
+ id: "js",
9
+ extensions: [
10
+ "js",
11
+ "jsx",
12
+ "mjs",
13
+ "cjs",
14
+ "ts",
15
+ "tsx",
16
+ "mts",
17
+ "cts"
18
+ ],
19
+ async scan(file, ctx) {
20
+ const parse = await loadParser();
21
+ return scanSource(parse, file.text, {
22
+ calls: ctx.calls,
23
+ filename: file.path,
24
+ hardStrings: true,
25
+ ignoreCallees: ctx.config.extract.ignoreCallees,
26
+ textAttributes: ctx.config.extract.attributes
27
+ });
28
+ }
29
+ }]
30
+ };
31
+ }
32
+ //#endregion
33
+ export { js as default, js };
@@ -0,0 +1,14 @@
1
+ import { Plugin } from "@x18n/core";
2
+ //#region src/scanners/regex-scopes/index.d.ts
3
+ /**
4
+ * Regions after each match of `pattern` (group 1 = namespace, or key prefix with `as: 'keyPrefix'`; or the
5
+ * named groups `namespace` and `keyPrefix`), up to the next match: i18n-ally `scopeRangeRegex`.
6
+ * With `wholeFile`, the first match scopes the whole file. Matches that capture nothing are skipped.
7
+ */
8
+ export declare function regexScopes(pattern: string, id?: string, options?: {
9
+ readonly as?: "namespace" | "keyPrefix";
10
+ readonly extensions?: readonly string[];
11
+ readonly wholeFile?: boolean;
12
+ }): Plugin;
13
+ //#endregion
14
+ export { regexScopes as default };
@@ -0,0 +1,41 @@
1
+ //#region src/scanners/regex-scopes/index.ts
2
+ /**
3
+ * Regions after each match of `pattern` (group 1 = namespace, or key prefix with `as: 'keyPrefix'`; or the
4
+ * named groups `namespace` and `keyPrefix`), up to the next match: i18n-ally `scopeRangeRegex`.
5
+ * With `wholeFile`, the first match scopes the whole file. Matches that capture nothing are skipped.
6
+ */
7
+ function regexScopes(pattern, id = "scopes", options = {}) {
8
+ const re = new RegExp(pattern, "g");
9
+ return {
10
+ name: `x18n:scanner-${id}`,
11
+ scanners: [{
12
+ id,
13
+ extensions: options.extensions ?? ["*"],
14
+ scan(file) {
15
+ const found = [...file.text.matchAll(re)].map((match) => ({
16
+ match,
17
+ values: match.groups ? {
18
+ namespace: match.groups.namespace,
19
+ keyPrefix: match.groups.keyPrefix
20
+ } : options.as === "keyPrefix" ? { keyPrefix: match[1] } : { namespace: match[1] }
21
+ })).filter(({ values }) => values.namespace !== void 0 || values.keyPrefix !== void 0);
22
+ if (options.wholeFile) found.splice(1);
23
+ return {
24
+ usages: [],
25
+ scopes: found.map(({ match, values }, i) => ({
26
+ range: options.wholeFile ? {
27
+ start: 0,
28
+ end: file.text.length
29
+ } : {
30
+ start: match.index + match[0].length,
31
+ end: found[i + 1]?.match.index ?? file.text.length
32
+ },
33
+ ...values
34
+ }))
35
+ };
36
+ }
37
+ }]
38
+ };
39
+ }
40
+ //#endregion
41
+ export { regexScopes as default, regexScopes };
@@ -0,0 +1,9 @@
1
+ import { Plugin, RegexScannerSpec, UsageCandidate } from "@x18n/core";
2
+ //#region src/scanners/regex/index.d.ts
3
+ /** Turns a pattern with a `{key}` placeholder into a regex whose first group is the key. */
4
+ export declare function compileUsagePattern(pattern: string): RegExp;
5
+ export declare function scanWithRegex(text: string, regexes: readonly RegExp[]): UsageCandidate[];
6
+ /** The `regex` scanner over `usage.regex` specs, plus extra specs given here (YAML plugins). */
7
+ export declare function regex(specs?: readonly RegexScannerSpec[], id?: string): Plugin;
8
+ //#endregion
9
+ export { regex as default };