@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,34 +1,10 @@
1
- import { Plugin } from "@x18n/core";
2
- //#region src/formats/ecmascript.d.ts
3
- export declare function ecmascript(): Plugin;
4
- //#endregion
5
- //#region src/formats/ftl.d.ts
6
- export declare function ftl(): Plugin;
7
- //#endregion
8
- //#region src/formats/ini.d.ts
9
- export declare function ini(): Plugin;
10
- //#endregion
11
- //#region src/formats/json.d.ts
12
- interface JsonOptions {
13
- /** Spaces per level for new files. Existing files keep their indent. */
14
- readonly indent?: number;
15
- }
16
- export declare function json(options?: JsonOptions): Plugin;
17
- export declare function jsonc(options?: JsonOptions): Plugin;
18
- //#endregion
19
- //#region src/formats/json5.d.ts
20
- export declare function json5(): Plugin;
21
- //#endregion
22
- //#region src/formats/php.d.ts
23
- export declare function php(): Plugin;
24
- //#endregion
25
- //#region src/formats/po.d.ts
26
- export declare function po(): Plugin;
27
- //#endregion
28
- //#region src/formats/properties.d.ts
29
- export declare function properties(): Plugin;
30
- //#endregion
31
- //#region src/formats/yaml.d.ts
32
- export declare function yaml(): Plugin;
33
- //#endregion
34
- export type { JsonOptions };
1
+ import { FormatEntry } from "@x18n/core";
2
+ //#region src/formats/index.d.ts
3
+ /**
4
+ * Built-in formats by id, in claim order. A format is a folder: `<name>/entry.ts` holds its extensions and a
5
+ * `load` for `<name>/index.ts`, the format itself, which is imported when a file of that format is first read.
6
+ */
7
+ export declare const formats: {
8
+ readonly [id: string]: FormatEntry;
9
+ };
10
+ //#endregion
package/dist/formats.mjs CHANGED
@@ -1 +1,61 @@
1
- export { u as ecmascript, l as ftl, c as ini, o as json, a as json5, s as jsonc, i as php, r as po, n as properties, t as yaml } from "./formats-CFkM5co0.mjs";
1
+ //#endregion
2
+ //#region src/formats/index.ts
3
+ /**
4
+ * Built-in formats by id, in claim order. A format is a folder: `<name>/entry.ts` holds its extensions and a
5
+ * `load` for `<name>/index.ts`, the format itself, which is imported when a file of that format is first read.
6
+ */
7
+ const formats = {
8
+ json: {
9
+ extensions: ["json"],
10
+ load: () => import("./formats/json.mjs")
11
+ },
12
+ jsonc: {
13
+ extensions: ["jsonc"],
14
+ load: () => import("./formats/jsonc.mjs")
15
+ },
16
+ json5: {
17
+ extensions: ["json5"],
18
+ load: () => import("./formats/json5.mjs")
19
+ },
20
+ yaml: {
21
+ extensions: ["yml", "yaml"],
22
+ load: () => import("./formats/yaml.mjs")
23
+ },
24
+ po: {
25
+ extensions: ["po", "pot"],
26
+ load: () => import("./formats/po.mjs")
27
+ },
28
+ properties: {
29
+ extensions: ["properties"],
30
+ load: () => import("./formats/properties.mjs")
31
+ },
32
+ resx: {
33
+ extensions: ["resx"],
34
+ load: () => import("./formats/resx.mjs")
35
+ },
36
+ ftl: {
37
+ extensions: ["ftl"],
38
+ load: () => import("./formats/ftl.mjs")
39
+ },
40
+ ini: {
41
+ extensions: ["ini"],
42
+ load: () => import("./formats/ini.mjs")
43
+ },
44
+ php: {
45
+ extensions: ["php"],
46
+ load: () => import("./formats/php.mjs")
47
+ },
48
+ js: {
49
+ extensions: [
50
+ "js",
51
+ "mjs",
52
+ "cjs",
53
+ "ts",
54
+ "mts",
55
+ "cts"
56
+ ],
57
+ load: () => import("./formats/ecmascript.mjs")
58
+ }
59
+ };
60
+ //#endregion
61
+ export { formats };
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Builtins } from "@x18n/core";
2
2
  //#region src/index.d.ts
3
- /** Plugins that are always on, and the presets that `'auto'` detects. */
4
- export declare function createBuiltins(): Builtins;
3
+ /** Plugins that are always on, and the presets that `'auto'` detects. Formats, scanners and presets load on demand; `regex` and the reporters are always loaded. */
4
+ export declare function createBuiltins(): Promise<Builtins>;
5
5
  //#endregion
package/dist/index.mjs CHANGED
@@ -1,35 +1,43 @@
1
1
  import { recommended } from "./checks.mjs";
2
2
  import { i as pattern } from "./finders-CqTGh4OJ.mjs";
3
- import { a as json5, c as ini, i as php, l as ftl, n as properties, o as json, r as po, s as jsonc, t as yaml, u as ecmascript } from "./formats-CFkM5co0.mjs";
4
- import { a as js, n as vue } from "./vue-CF4pe-Yr.mjs";
5
3
  import { manifests } from "./manifests.mjs";
6
- import { n as regex } from "./regex-AYVhuSb6.mjs";
7
- import { t as presets } from "./presets-Co5SlnKr.mjs";
8
- import { reporters } from "./reporters.mjs";
9
- import { t as svelte } from "./scanners-DhnnVp7s.mjs";
4
+ import { reviews } from "./reviews.mjs";
5
+ import { formats } from "@x18n/builtins/formats";
6
+ import { presets } from "@x18n/builtins/presets";
7
+ import { reporters } from "@x18n/builtins/reporters";
8
+ import { scanners } from "@x18n/builtins/scanners";
9
+ import regex from "@x18n/builtins/scanners/regex";
10
10
  //#region src/index.ts
11
- /** Plugins that are always on, and the presets that `'auto'` detects. */
12
- function createBuiltins() {
11
+ /** Plugins that are always on, and the presets that `'auto'` detects. Formats, scanners and presets load on demand; `regex` and the reporters are always loaded. */
12
+ async function createBuiltins() {
13
+ const formatPlugins = Object.entries(formats).map(([id, entry]) => ({
14
+ name: `x18n:format-${id}`,
15
+ lazyFormats: [{
16
+ id,
17
+ ...entry
18
+ }]
19
+ }));
20
+ const scannerPlugins = Object.entries(scanners).map(([id, entry]) => ({
21
+ name: `x18n:scanner-${id}`,
22
+ lazyScanners: [{
23
+ id,
24
+ ...entry
25
+ }]
26
+ }));
27
+ const reporterPlugin = {
28
+ name: "x18n:reporters",
29
+ reporters: await Promise.all(Object.values(reporters).map(async (load) => (await load()).default))
30
+ };
13
31
  return {
14
32
  plugins: [
15
33
  manifests(),
16
- json(),
17
- jsonc(),
18
- json5(),
19
- yaml(),
20
- po(),
21
- properties(),
22
- ftl(),
23
- ini(),
24
- php(),
25
- ecmascript(),
34
+ ...formatPlugins,
26
35
  pattern(),
27
- js(),
28
- vue(),
29
- svelte(),
36
+ ...scannerPlugins,
30
37
  regex(),
31
38
  recommended(),
32
- reporters()
39
+ reviews(),
40
+ reporterPlugin
33
41
  ],
34
42
  presets
35
43
  };
@@ -1,4 +1,4 @@
1
- //#region src/scanners/js.ts
1
+ //#region src/scanners/shared/js.ts
2
2
  const textAttributeWords = /* @__PURE__ */ new Set([
3
3
  "title",
4
4
  "subtitle",
@@ -83,6 +83,8 @@ var FileScan = class {
83
83
  declared = /* @__PURE__ */ new Map();
84
84
  factories = /* @__PURE__ */ new Map();
85
85
  typed = /* @__PURE__ */ new Map();
86
+ /** Members of the file's top-level interfaces and object type aliases, for typed props (`({ t }: Props)`). */
87
+ typeMembers = /* @__PURE__ */ new Map();
86
88
  consumed = /* @__PURE__ */ new Set();
87
89
  /** Render-prop functions of factory components (`<Translation>{t => ...}</Translation>`), by start offset. */
88
90
  renderProps = /* @__PURE__ */ new Map();
@@ -145,6 +147,11 @@ var FileScan = class {
145
147
  return constant && isStringLiteral(constant) ? constant.value : void 0;
146
148
  }
147
149
  }
150
+ /** A fixed namespace, or the one an argument holds. */
151
+ namespaceArg(args, source, fallback) {
152
+ if (typeof source === "string") return source;
153
+ return source ? this.stringArg(this.argValues(args, source, fallback)[0]) : void 0;
154
+ }
148
155
  argValues(args, source, fallback) {
149
156
  const arg = args[typeof source === "number" ? source : source?.arg ?? fallback];
150
157
  const property = typeof source === "object" ? source.property : void 0;
@@ -165,13 +172,9 @@ var FileScan = class {
165
172
  }
166
173
  /** ---- Usages ---- */
167
174
  /** `expression` is the call holding `value`, kept only when the key is the call's own literal. */
168
- addKey(value, binding, namespace, expression) {
175
+ addKey(value, base, expression) {
169
176
  if ("props" in value) return;
170
177
  const node = unwrap(value);
171
- const base = {
172
- namespace: namespace ?? binding.namespace,
173
- keyPrefix: binding.keyPrefix
174
- };
175
178
  const call = expression ? { expression: this.range(expression) } : {};
176
179
  if (isStringLiteral(node)) {
177
180
  this.consumed.add(node.start);
@@ -187,7 +190,7 @@ var FileScan = class {
187
190
  return;
188
191
  }
189
192
  if (node.type === "JSXExpressionContainer") {
190
- if (node.expression.type !== "JSXEmptyExpression") this.addKey(node.expression, binding, namespace);
193
+ if (node.expression.type !== "JSXEmptyExpression") this.addKey(node.expression, base);
191
194
  return;
192
195
  }
193
196
  if (node.type === "TemplateLiteral") {
@@ -209,8 +212,8 @@ var FileScan = class {
209
212
  return;
210
213
  }
211
214
  if (node.type === "ConditionalExpression") {
212
- this.addKey(node.consequent, binding, namespace);
213
- this.addKey(node.alternate, binding, namespace);
215
+ this.addKey(node.consequent, base);
216
+ this.addKey(node.alternate, base);
214
217
  return;
215
218
  }
216
219
  if (node.type === "ArrowFunctionExpression" && node.params[0]?.type === "Identifier") return this.addSelector(node.params[0].name, node.body, base);
@@ -225,6 +228,10 @@ var FileScan = class {
225
228
  });
226
229
  return;
227
230
  }
231
+ this.addUnknown(node, base);
232
+ }
233
+ addUnknown(node, base) {
234
+ if (isStringLiteral(node)) this.consumed.add(node.start);
228
235
  this.usages.push({
229
236
  ...base,
230
237
  raw: this.text.slice(node.start, node.end),
@@ -267,10 +274,17 @@ var FileScan = class {
267
274
  handleCall(name, args, expression) {
268
275
  const bindings = this.lookupAll(name).filter((binding) => !binding.spec.factory);
269
276
  for (const binding of bindings) {
270
- const namespace = binding.spec.namespace ? this.stringArg(this.argValues(args, binding.spec.namespace, 1)[0]) : void 0;
277
+ const namespace = this.namespaceArg(args, binding.spec.namespace, 1);
278
+ const keyPrefix = binding.spec.keyPrefix ? this.stringArg(this.argValues(args, binding.spec.keyPrefix, 1)[0]) : void 0;
279
+ const base = {
280
+ namespace: namespace ?? binding.namespace,
281
+ keyPrefix: keyPrefix ?? binding.keyPrefix
282
+ };
271
283
  for (const value of this.argValues(args, binding.spec.key, 0)) {
272
- if (bindings.length > 1 && !("props" in value) && unwrap(value).type === "ObjectExpression") continue;
273
- this.addKey(value, binding, namespace, expression);
284
+ if ("props" in value) continue;
285
+ if (bindings.length > 1 && unwrap(value).type === "ObjectExpression") continue;
286
+ if (binding.spec.keyPrefix && keyPrefix === void 0) this.addUnknown(unwrap(value), base);
287
+ else this.addKey(value, base, expression);
274
288
  }
275
289
  }
276
290
  return bindings.length > 0;
@@ -321,7 +335,7 @@ var FileScan = class {
321
335
  /** The first factory spec whose namespace and prefix sources all resolve, else the first spec. */
322
336
  pickFactory(args, specs) {
323
337
  const resolved = specs.map((spec) => {
324
- const namespace = spec.namespace ? this.stringArg(this.argValues(args, spec.namespace, 0)[0]) : void 0;
338
+ const namespace = this.namespaceArg(args, spec.namespace, 0);
325
339
  const keyPrefix = spec.keyPrefix ? this.stringArg(this.argValues(args, spec.keyPrefix, 0)[0]) : void 0;
326
340
  const complete = (!spec.namespace || namespace !== void 0) && (!spec.keyPrefix || keyPrefix !== void 0);
327
341
  return {
@@ -354,17 +368,49 @@ var FileScan = class {
354
368
  if (key && local.type === "Identifier" && this.declared.has(`${key}.t`)) this.bind(`${local.name}.t`, binding);
355
369
  }
356
370
  }
371
+ /** Parameters typed as a translate function (`t: TFunction<'ns'>`), also as destructured props. */
357
372
  params(params) {
358
373
  for (const param of params) {
359
374
  const target = param.type === "TSParameterProperty" ? param.parameter : param;
360
- if (target.type !== "Identifier" || !target.typeAnnotation) continue;
361
- const annotation = target.typeAnnotation.typeAnnotation;
362
- const typeName = annotation.type === "TSTypeReference" && annotation.typeName.type === "Identifier" ? annotation.typeName.name : void 0;
363
- const spec = typeName ? this.typed.get(typeName) : void 0;
364
- if (spec) this.bind(target.name, { spec: {
375
+ const annotation = target.type === "Identifier" || target.type === "ObjectPattern" ? target.typeAnnotation?.typeAnnotation : void 0;
376
+ if (!annotation) continue;
377
+ if (target.type === "Identifier") this.bindTyped(target.name, annotation);
378
+ else if (target.type === "ObjectPattern") this.props(target, annotation);
379
+ }
380
+ }
381
+ /** `({ t }: { t: TFunction<'ns'> })`, or with an interface or type of the file. */
382
+ props(pattern, annotation) {
383
+ const members = annotation.type === "TSTypeLiteral" ? annotation.members : annotation.type === "TSTypeReference" && annotation.typeName.type === "Identifier" ? this.typeMembers.get(annotation.typeName.name) ?? [] : [];
384
+ for (const property of pattern.properties) {
385
+ if (property.type !== "Property") continue;
386
+ const local = unwrap(property.value);
387
+ const name = propertyName(property.key);
388
+ const member = members.find((m) => m.type === "TSPropertySignature" && !m.computed && propertyName(m.key) === name);
389
+ const type = member?.type === "TSPropertySignature" ? member.typeAnnotation?.typeAnnotation : void 0;
390
+ if (local.type === "Identifier" && type) this.bindTyped(local.name, type);
391
+ }
392
+ }
393
+ /** The namespace is the first type argument: `TFunction<'shared'>` or `TFunction<['shared', 'common']>`. */
394
+ bindTyped(name, annotation) {
395
+ if (annotation.type !== "TSTypeReference" || annotation.typeName.type !== "Identifier") return;
396
+ const spec = this.typed.get(annotation.typeName.name);
397
+ if (!spec) return;
398
+ const arg = annotation.typeArguments?.params[0];
399
+ const first = arg?.type === "TSTupleType" ? arg.elementTypes[0] : arg;
400
+ const namespace = first?.type === "TSLiteralType" && isStringLiteral(first.literal) ? first.literal.value : void 0;
401
+ this.bind(name, {
402
+ spec: {
365
403
  ...spec,
366
404
  types: void 0
367
- } });
405
+ },
406
+ namespace
407
+ });
408
+ }
409
+ types(program) {
410
+ for (const statement of program.body) {
411
+ const node = statement.type === "ExportNamedDeclaration" && statement.declaration ? statement.declaration : statement;
412
+ if (node.type === "TSInterfaceDeclaration") this.typeMembers.set(node.id.name, node.body.body);
413
+ else if (node.type === "TSTypeAliasDeclaration" && node.typeAnnotation.type === "TSTypeLiteral") this.typeMembers.set(node.id.name, node.typeAnnotation.members);
368
414
  }
369
415
  }
370
416
  importSpecifier(node) {
@@ -532,211 +578,10 @@ function scanSource(parse, text, options) {
532
578
  range: false
533
579
  });
534
580
  const scan = new FileScan(source, (options.offset ?? 0) + shift, options.calls, options.hardStrings !== false, new Set(options.ignoreCallees), options.textAttributes);
581
+ scan.types(result.program);
535
582
  scan.walk(result.program, void 0, "");
536
583
  scan.markers(result.program, result.comments);
537
584
  return scan.result();
538
585
  }
539
- /** The AST scanner for JS and TS, driven by call specs (`usage.calls`). */
540
- function js() {
541
- return {
542
- name: "x18n:scanner-js",
543
- scanners: [{
544
- id: "js",
545
- extensions: [
546
- "js",
547
- "jsx",
548
- "mjs",
549
- "cjs",
550
- "ts",
551
- "tsx",
552
- "mts",
553
- "cts"
554
- ],
555
- async scan(file, ctx) {
556
- return scanSource(await loadParser(), file.text, {
557
- calls: ctx.calls,
558
- filename: file.path,
559
- hardStrings: true,
560
- ignoreCallees: ctx.config.extract.ignoreCallees,
561
- textAttributes: ctx.config.extract.attributes
562
- });
563
- }
564
- }]
565
- };
566
- }
567
- //#endregion
568
- //#region src/scanners/vue.ts
569
- let compiler;
570
- function loadVueCompiler() {
571
- compiler ??= import("@vue/compiler-sfc");
572
- return compiler;
573
- }
574
- const ELEMENT = 1;
575
- const TEXT = 2;
576
- const SIMPLE_EXPRESSION = 4;
577
- const INTERPOLATION = 5;
578
- const ATTRIBUTE = 6;
579
- /** `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. */
580
- function isKeyAttribute(tag, name) {
581
- return name === "keypath" || name === "path" && (tag === "i18n" || tag === "i18n-d");
582
- }
583
- function merge(into, result) {
584
- into.usages.push(...result.usages);
585
- into.keySlots.push(...result.keySlots ?? []);
586
- into.hardStrings.push(...result.hardStrings ?? []);
587
- }
588
- var TemplateScan = class {
589
- parse;
590
- calls;
591
- textAttributes;
592
- out = {
593
- usages: [],
594
- keySlots: [],
595
- hardStrings: []
596
- };
597
- constructor(parse, calls, textAttributes) {
598
- this.parse = parse;
599
- this.calls = calls;
600
- this.textAttributes = textAttributes;
601
- }
602
- expression(content, offset) {
603
- if (!content.trim()) return;
604
- try {
605
- merge(this.out, scanSource(this.parse, content, {
606
- calls: this.calls,
607
- offset,
608
- filename: "template.ts",
609
- expression: true,
610
- hardStrings: false
611
- }));
612
- } catch {}
613
- }
614
- /** `:keypath="cond ? 'a' : 'b'"`: the expression is read as the key argument of a translate call. */
615
- keyExpression(content, offset) {
616
- const callee = "__x18nKey(";
617
- try {
618
- const result = scanSource(this.parse, `${callee}${content})`, {
619
- calls: [{ callee: "__x18nKey" }],
620
- offset: offset - 10,
621
- filename: "template.ts",
622
- hardStrings: false
623
- });
624
- merge(this.out, {
625
- ...result,
626
- usages: result.usages.map(({ expression: _, ...usage }) => usage)
627
- });
628
- } catch {}
629
- }
630
- /** `v-t="'key'"` and `v-t="{ path: 'key' }"`. */
631
- vt(dir) {
632
- const exp = dir.exp;
633
- if (!exp || !("content" in exp)) return;
634
- const offset = exp.loc.start.offset;
635
- const literal = /^\s*(['"`])(.*)\1\s*$/.exec(exp.content) ?? /path\s*:\s*(['"`])(.*?)\1/.exec(exp.content);
636
- if (!literal) return;
637
- const start = offset + exp.content.indexOf(literal[2], literal.index);
638
- const range = {
639
- start,
640
- end: start + literal[2].length
641
- };
642
- this.out.usages.push({
643
- raw: literal[2],
644
- range,
645
- kind: "call",
646
- dynamic: false
647
- });
648
- this.out.keySlots.push(range);
649
- }
650
- attribute(element, prop) {
651
- if (prop.type === ATTRIBUTE) {
652
- const value = prop.value;
653
- if (!value) return;
654
- const range = {
655
- start: value.loc.start.offset + 1,
656
- end: value.loc.end.offset - 1
657
- };
658
- if (isKeyAttribute(element.tag, prop.name)) {
659
- this.out.usages.push({
660
- raw: value.content,
661
- range,
662
- kind: "call",
663
- dynamic: false
664
- });
665
- this.out.keySlots.push(range);
666
- } else if (isTextAttribute(prop.name, this.textAttributes) && /\p{L}/u.test(value.content)) this.out.hardStrings.push({
667
- text: value.content,
668
- range: {
669
- start: prop.loc.start.offset,
670
- end: prop.loc.end.offset
671
- },
672
- kind: "vue-attribute",
673
- attribute: prop.name
674
- });
675
- return;
676
- }
677
- if (prop.name === "t") {
678
- this.vt(prop);
679
- return;
680
- }
681
- const exp = prop.exp;
682
- if (exp && "content" in exp) {
683
- if (prop.name === "bind" && prop.arg && "content" in prop.arg && isKeyAttribute(element.tag, prop.arg.content)) this.keyExpression(exp.content, exp.loc.start.offset);
684
- else this.expression(exp.content, exp.loc.start.offset);
685
- }
686
- }
687
- node(node) {
688
- if (node.type === INTERPOLATION && "content" in node.content) this.expression(node.content.content, node.content.loc.start.offset);
689
- if (node.type === TEXT) {
690
- const text = node.content.trim();
691
- if (text && /\p{L}/u.test(text)) {
692
- const start = node.loc.start.offset + node.content.indexOf(text);
693
- this.out.hardStrings.push({
694
- text,
695
- range: {
696
- start,
697
- end: start + text.length
698
- },
699
- kind: "vue-template-text"
700
- });
701
- }
702
- }
703
- if (node.type === ELEMENT) for (const prop of node.props) this.attribute(node, prop);
704
- if ("children" in node) {
705
- for (const child of node.children) if (typeof child === "object" && child.type !== SIMPLE_EXPRESSION) this.node(child);
706
- }
707
- }
708
- };
709
- /** Vue SFCs: `<script>` through the `js` scanner, template expressions, `v-t`, `<i18n-t keypath>`. */
710
- function vue() {
711
- return {
712
- name: "x18n:scanner-vue",
713
- scanners: [{
714
- id: "vue",
715
- extensions: ["vue"],
716
- async scan(file, ctx) {
717
- const [{ parse }, parseJs] = await Promise.all([loadVueCompiler(), loadParser()]);
718
- const { descriptor } = parse(file.text, {
719
- filename: file.path,
720
- sourceMap: false,
721
- ignoreEmpty: false
722
- });
723
- const template = new TemplateScan(parseJs, ctx.calls, ctx.config.extract.attributes);
724
- for (const script of [descriptor.script, descriptor.scriptSetup]) {
725
- if (!script) continue;
726
- const lang = script.lang ?? "js";
727
- merge(template.out, scanSource(parseJs, script.content, {
728
- calls: ctx.calls,
729
- ignoreCallees: ctx.config.extract.ignoreCallees,
730
- textAttributes: ctx.config.extract.attributes,
731
- offset: script.loc.start.offset,
732
- filename: `script.${lang === "tsx" || lang === "jsx" ? lang : lang === "ts" ? "ts" : "js"}`
733
- }));
734
- }
735
- if (descriptor.template?.ast) template.node(descriptor.template.ast);
736
- return template.out;
737
- }
738
- }]
739
- };
740
- }
741
586
  //#endregion
742
- export { js as a, isTextAttribute as i, vue as n, loadParser as o, calleeName as r, scanSource as s, loadVueCompiler as t };
587
+ export { loadParser as n, scanSource as r, isTextAttribute as t };
@@ -0,0 +1,109 @@
1
+ import { r as indentUnit } from "./shared-C7ceptwi.mjs";
2
+ import { t as editLiteral } from "./literal-gIdB1VGy.mjs";
3
+ import { diffText, errors, pathId } from "@x18n/core";
4
+ import { parseTree, printParseErrorCode } from "jsonc-parser";
5
+ //#region src/formats/shared/json.ts
6
+ function readTree(node, path, ranges) {
7
+ const out = {};
8
+ for (const property of node.children ?? []) {
9
+ const [keyNode, valueNode] = property.children ?? [];
10
+ if (!keyNode || !valueNode || typeof keyNode.value !== "string") continue;
11
+ const key = keyNode.value;
12
+ const childPath = [...path, key];
13
+ ranges.set(pathId(childPath), {
14
+ key: {
15
+ start: keyNode.offset + 1,
16
+ end: keyNode.offset + keyNode.length - 1
17
+ },
18
+ value: {
19
+ start: valueNode.offset,
20
+ end: valueNode.offset + valueNode.length
21
+ }
22
+ });
23
+ if (valueNode.type === "object") out[key] = readTree(valueNode, childPath, ranges);
24
+ else if (valueNode.type === "string" || valueNode.type === "number" || valueNode.type === "boolean" || valueNode.type === "null") out[key] = valueNode.value;
25
+ }
26
+ return out;
27
+ }
28
+ function objectOf(node) {
29
+ const items = [];
30
+ for (const property of node.children ?? []) {
31
+ const [keyNode, valueNode] = property.children ?? [];
32
+ if (!keyNode || !valueNode || typeof keyNode.value !== "string") continue;
33
+ items.push({
34
+ key: keyNode.value,
35
+ range: {
36
+ start: property.offset,
37
+ end: property.offset + property.length
38
+ },
39
+ value: valueNode.type === "object" ? objectOf(valueNode) : {
40
+ kind: "scalar",
41
+ range: {
42
+ start: valueNode.offset,
43
+ end: valueNode.offset + valueNode.length
44
+ },
45
+ quote: void 0
46
+ }
47
+ });
48
+ }
49
+ return {
50
+ kind: "object",
51
+ range: {
52
+ start: node.offset,
53
+ end: node.offset + node.length
54
+ },
55
+ items
56
+ };
57
+ }
58
+ /** The root object, or undefined for an empty file. */
59
+ function parseRoot(text, path, allowComments) {
60
+ const problems = [];
61
+ const root = parseTree(text, problems, {
62
+ allowTrailingComma: true,
63
+ disallowComments: !allowComments
64
+ });
65
+ if (problems.length > 0) {
66
+ const first = problems[0];
67
+ throw errors.X18N2002({
68
+ path,
69
+ reason: `${printParseErrorCode(first.error)} at offset ${first.offset}`
70
+ });
71
+ }
72
+ if (root?.type === "object") return root;
73
+ if (text.trim() === "") return void 0;
74
+ throw errors.X18N2003({ path });
75
+ }
76
+ function createJsonFormat(id, extensions, allowComments, defaultIndent) {
77
+ return {
78
+ id,
79
+ extensions,
80
+ parse(text, ctx) {
81
+ const root = parseRoot(text, ctx.path, allowComments);
82
+ const ranges = /* @__PURE__ */ new Map();
83
+ return {
84
+ tree: root ? readTree(root, [], ranges) : {},
85
+ ranges
86
+ };
87
+ },
88
+ edit(text, ops, ctx) {
89
+ const syntax = {
90
+ open: "{",
91
+ close: "}",
92
+ separator: ": ",
93
+ key: (key) => JSON.stringify(key),
94
+ leaf: (value) => JSON.stringify(value),
95
+ unit: indentUnit(text, ctx.indent ?? defaultIndent, " "),
96
+ trailingComma: false
97
+ };
98
+ let next = text.trim() === "" ? "{}\n" : text;
99
+ for (const op of ops) next = editLiteral(next, objectOf(parseRoot(next, ctx.path, allowComments)), op, syntax);
100
+ return diffText(text, next);
101
+ },
102
+ serialize(tree, ctx) {
103
+ const indent = ctx.indent ?? defaultIndent ?? 2;
104
+ return `${JSON.stringify(tree, null, indent === "tab" ? " " : indent)}\n`;
105
+ }
106
+ };
107
+ }
108
+ //#endregion
109
+ export { createJsonFormat as t };
@@ -0,0 +1,8 @@
1
+ import "@x18n/core";
2
+ //#region src/formats/shared/json.d.ts
3
+ interface JsonOptions {
4
+ /** Spaces per level for new files. Existing files keep their indent. */
5
+ readonly indent?: number;
6
+ }
7
+ //#endregion
8
+ export { JsonOptions as t };
@@ -0,0 +1,17 @@
1
+ //#region src/presets/shared/jsx-templates.ts
2
+ const jsxTemplates = [
3
+ {
4
+ template: "{t('{key}'{args})}",
5
+ kinds: ["jsx-text"]
6
+ },
7
+ {
8
+ template: "{attr}={t('{key}'{args})}",
9
+ kinds: ["jsx-attribute"]
10
+ },
11
+ {
12
+ template: "t('{key}'{args})",
13
+ kinds: ["js-string", "js-template"]
14
+ }
15
+ ];
16
+ //#endregion
17
+ export { jsxTemplates as t };