@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
@@ -6,5 +6,13 @@ export declare const composerJson: ManifestReader;
6
6
  export declare const pubspecYaml: ManifestReader;
7
7
  /** Every `gem 'name'` line, not just `rails-i18n` as in i18n-ally. */
8
8
  export declare const gemfile: ManifestReader;
9
+ /** Reads the PEP 621, PEP 735 and Poetry dependency tables; dependency names need no full TOML parser. */
10
+ export declare const pyprojectToml: ManifestReader;
11
+ export declare const requirementsTxt: ManifestReader;
12
+ /**
13
+ * Dependency names from `[dependencies]`, `[dev-dependencies]`, `[build-dependencies]`, `[workspace.dependencies]`
14
+ * and target tables, and `[dependencies.name]` headers. A renamed dependency reports its alias, not `package`. TOML puts each key on its own line, so a line scan is enough.
15
+ */
16
+ export declare const cargoToml: ManifestReader;
9
17
  export declare function manifests(): Plugin;
10
18
  //#endregion
@@ -34,6 +34,67 @@ const gemfile = {
34
34
  file: "Gemfile",
35
35
  read: (text) => [...text.matchAll(/^\s*gem\s+["']([^"']+)["']/gm)].map((match) => match[1])
36
36
  };
37
+ /** The PEP 503 normalized name of a PEP 508 requirement: `Flask_Babel[async]>=4` is `flask-babel`. */
38
+ function requirementName(requirement) {
39
+ const name = /^\s*([a-z\d][\w.-]*)/i.exec(requirement)?.[1];
40
+ return name ? [name.toLowerCase().replace(/[-_.]+/g, "-")] : [];
41
+ }
42
+ /** TOML strings outside comments, up to the first `]` outside a string (`"i18nice[YAML]"` has none) when `toClose`. */
43
+ function tomlStrings(text, toClose) {
44
+ const out = [];
45
+ for (const match of text.matchAll(/"([^"\n]*)"|'([^'\n]*)'|#.*|(\])/g)) {
46
+ if (match[3] && toClose) break;
47
+ const value = match[1] ?? match[2];
48
+ if (value !== void 0) out.push(value);
49
+ }
50
+ return out;
51
+ }
52
+ /** Reads the PEP 621, PEP 735 and Poetry dependency tables; dependency names need no full TOML parser. */
53
+ const pyprojectToml = {
54
+ id: "pyproject.toml",
55
+ file: "pyproject.toml",
56
+ read: (text) => text.replace(/"""[\s\S]*?"""|'''[\s\S]*?'''/g, "\"\"").split(/^[ \t]*\[/m).flatMap((table) => {
57
+ const header = /^\[?([^\]\n]+)\]/.exec(table)?.[1]?.trim();
58
+ const body = table.slice(table.indexOf("\n") + 1);
59
+ if (header === "project") {
60
+ const start = /^\s*dependencies\s*=\s*\[/m.exec(body);
61
+ return start ? tomlStrings(body.slice(start.index + start[0].length), true).flatMap(requirementName) : [];
62
+ }
63
+ if (header === "project.optional-dependencies" || header === "dependency-groups") return tomlStrings(body, false).flatMap(requirementName);
64
+ if (header && /^tool\.poetry\.(?:group\.[\w-]+\.)?(?:dev-)?dependencies$/.test(header)) return [...body.matchAll(/^\s*(?:"([^"\n]+)"|'([^'\n]+)'|([\w-]+))\s*[.=]/gm)].flatMap((match) => requirementName(match[1] ?? match[2] ?? match[3])).filter((name) => name !== "python");
65
+ return [];
66
+ })
67
+ };
68
+ const requirementsTxt = {
69
+ id: "requirements.txt",
70
+ file: "requirements.txt",
71
+ read: (text) => text.split("\n").flatMap((line) => line.trim().startsWith("-") ? [] : requirementName(line))
72
+ };
73
+ const cargoSection = /^\[\s*((?:workspace\.|target\.[^\]]+\.)?(?:dev-|build-)?dependencies)(?:\.([\w-]+))?\s*\]/;
74
+ const cargoKey = /^\s*"?([\w-]+)"?\s*[.=]/;
75
+ /**
76
+ * Dependency names from `[dependencies]`, `[dev-dependencies]`, `[build-dependencies]`, `[workspace.dependencies]`
77
+ * and target tables, and `[dependencies.name]` headers. A renamed dependency reports its alias, not `package`. TOML puts each key on its own line, so a line scan is enough.
78
+ */
79
+ const cargoToml = {
80
+ id: "Cargo.toml",
81
+ file: "Cargo.toml",
82
+ read(text) {
83
+ const names = [];
84
+ let inDependencies = false;
85
+ for (const line of text.split("\n")) {
86
+ if (line.trimStart().startsWith("[")) {
87
+ const section = cargoSection.exec(line.trim());
88
+ inDependencies = section !== null && section[2] === void 0;
89
+ if (section?.[2]) names.push(section[2]);
90
+ continue;
91
+ }
92
+ const name = inDependencies ? cargoKey.exec(line)?.[1] : void 0;
93
+ if (name) names.push(name);
94
+ }
95
+ return names;
96
+ }
97
+ };
37
98
  function manifests() {
38
99
  return {
39
100
  name: "x18n:manifests",
@@ -41,9 +102,12 @@ function manifests() {
41
102
  packageJson,
42
103
  composerJson,
43
104
  pubspecYaml,
44
- gemfile
105
+ gemfile,
106
+ pyprojectToml,
107
+ requirementsTxt,
108
+ cargoToml
45
109
  ]
46
110
  };
47
111
  }
48
112
  //#endregion
49
- export { composerJson, gemfile, manifests, packageJson, pubspecYaml };
113
+ export { cargoToml, composerJson, gemfile, manifests, packageJson, pubspecYaml, pyprojectToml, requirementsTxt };
@@ -2,12 +2,21 @@ import { PlaceholderSyntax, Plugin } from "@x18n/core";
2
2
  //#region src/placeholders/index.d.ts
3
3
  /** `{name}`: vue-i18n named interpolation and similar. */
4
4
  export declare const braces: PlaceholderSyntax;
5
+ /** `{}`: easy_localization positional arguments. */
6
+ export declare const positionalBraces: PlaceholderSyntax;
7
+ /** `{name}` with an optional type, formatters or switch-case: `{name:string}`, `{date|weekday}`, `{gender|{male: his, *: their}}`. `{{a|b}}` plurals are not placeholders. */
8
+ export declare const typedBraces: PlaceholderSyntax;
5
9
  /** `{{name}}`: i18next interpolation, including `{{name, format}}` and `{{- raw}}`. */
6
10
  export declare const doubleBraces: PlaceholderSyntax;
7
11
  /** `%{name}`: Rails, Polyglot. */
8
12
  export declare const percentBraces: PlaceholderSyntax;
9
13
  /** `%s`, `%d`, `%1$s`: printf. */
10
14
  export declare const printf: PlaceholderSyntax;
15
+ /**
16
+ * `%(name)s`, `%s`, `%d`: Python percent formatting, as in gettext catalogs. No space flag, so `50% of` is text.
17
+ * URL escapes are text too: those with an uppercase hex letter (`%2F`), and any `%XX` after `://` in the same word (`%20a`).
18
+ */
19
+ export declare const pythonPercent: PlaceholderSyntax;
11
20
  /** `@:key` and `@.modifier:key`: vue-i18n linked messages. */
12
21
  export declare const linked: PlaceholderSyntax;
13
22
  /** `{ $name }`: Fluent variables. */
@@ -16,6 +25,8 @@ export declare const fluent: PlaceholderSyntax;
16
25
  export declare const colon: PlaceholderSyntax;
17
26
  /** `$name$`: Chrome extension messages. */
18
27
  export declare const dollars: PlaceholderSyntax;
28
+ /** `{0}`, `{0:C}`, `{0,-10}`, `{name}`: .NET composite format items. `{{` and `}}` are literal braces. */
29
+ export declare const dotnet: PlaceholderSyntax;
19
30
  /** `$t(key)` nesting in i18next. */
20
31
  export declare const nesting: PlaceholderSyntax;
21
32
  /** ICU MessageFormat arguments: `{name}`, `{count, plural, ...}`. Nested sub-messages are scanned too. */
@@ -20,12 +20,21 @@ function byRegex(id, regex, example) {
20
20
  }
21
21
  /** `{name}`: vue-i18n named interpolation and similar. */
22
22
  const braces = byRegex("braces", /(?<![{$])\{\s*([\w.-]+)\s*\}(?!\})/g, "{name}");
23
+ /** `{}`: easy_localization positional arguments. */
24
+ const positionalBraces = byRegex("positional-braces", /\{\}/g, "{}");
25
+ /** `{name}` with an optional type, formatters or switch-case: `{name:string}`, `{date|weekday}`, `{gender|{male: his, *: their}}`. `{{a|b}}` plurals are not placeholders. */
26
+ const typedBraces = byRegex("typed-braces", /(?<![{$])\{\s*(\w+)\??\s*(?::[^{}|]*)?(?:\|(?:\{[^{}]*\}|[^{}]*))?\}(?!\})/g, "{name}");
23
27
  /** `{{name}}`: i18next interpolation, including `{{name, format}}` and `{{- raw}}`. */
24
28
  const doubleBraces = byRegex("double-braces", /\{\{-?\s*([\w.]+)\s*(?:,[^}]*)?\}\}/g, "{{name}}");
25
29
  /** `%{name}`: Rails, Polyglot. */
26
30
  const percentBraces = byRegex("percent-braces", /%\{(\w+)\}/g, "%{name}");
27
31
  /** `%s`, `%d`, `%1$s`: printf. */
28
32
  const printf = byRegex("printf", /%(?:(\d+)\$)?[-+ 0#]*(?:[1-9]\d*)?(?:\.\d+)?[sdifoxXeEgGc@]/g, "%s");
33
+ /**
34
+ * `%(name)s`, `%s`, `%d`: Python percent formatting, as in gettext catalogs. No space flag, so `50% of` is text.
35
+ * URL escapes are text too: those with an uppercase hex letter (`%2F`), and any `%XX` after `://` in the same word (`%20a`).
36
+ */
37
+ const pythonPercent = byRegex("python-percent", /(?<!%)%(?![\dA-F][A-F])(?!(?<=:\/\/\S*%)[\dA-Fa-f]{2})(?:\((\w+)\))?[-#0+]*(?:[1-9]\d*|\*)?(?:\.\d+)?[c-giouxXEFGrsa]/g, "%(name)s");
29
38
  /** `@:key` and `@.modifier:key`: vue-i18n linked messages. */
30
39
  const linked = {
31
40
  ...byRegex("linked", /@(?:\.\w+)?:\(?([\w.-]+)\)?/g, "@:key"),
@@ -37,6 +46,24 @@ const fluent = byRegex("fluent", /\{\s*\$([\w-]+)(?:\s[^}]*)?\}/g, "{ $name }");
37
46
  const colon = byRegex("colon", /(?<![\w:]):([a-z_]\w*)/gi, ":name");
38
47
  /** `$name$`: Chrome extension messages. */
39
48
  const dollars = byRegex("dollars", /\$(\w+)\$/g, "$name$");
49
+ /** `{0}`, `{0:C}`, `{0,-10}`, `{name}`: .NET composite format items. `{{` and `}}` are literal braces. */
50
+ const dotnet = {
51
+ id: "dotnet",
52
+ example: "{0}",
53
+ find(value) {
54
+ const out = [];
55
+ for (const match of value.matchAll(/\{\{|\}\}|\{\s*(\w+)\s*(?:,\s*-?\d+\s*)?(?::[^{}]*)?\}/g)) if (match[1] !== void 0) out.push({
56
+ syntax: "dotnet",
57
+ text: match[0],
58
+ name: match[1],
59
+ range: {
60
+ start: match.index,
61
+ end: match.index + match[0].length
62
+ }
63
+ });
64
+ return out;
65
+ }
66
+ };
40
67
  /** `$t(key)` nesting in i18next. */
41
68
  const nesting = {
42
69
  ...byRegex("nesting", /\$t\(([\w.:-]+)(?:,[^)]*)?\)/g, "$t(key)"),
@@ -128,4 +155,4 @@ function placeholderFromShape(shape) {
128
155
  return byRegex(`shape:${shape}`, new RegExp(`${escape(before ?? "")}\\s*([\\w.-]+)\\s*${escape(after ?? "")}`, "g"), shape);
129
156
  }
130
157
  //#endregion
131
- export { braces, colon, dollars, doubleBraces, fluent, icu, linked, nesting, percentBraces, placeholderFromShape, placeholders, printf };
158
+ export { braces, colon, dollars, dotnet, doubleBraces, fluent, icu, linked, nesting, percentBraces, placeholderFromShape, placeholders, positionalBraces, printf, pythonPercent, typedBraces };
@@ -0,0 +1,83 @@
1
+ import { doubleBraces, nesting } from "./placeholders.mjs";
2
+ import { createLibraryPlugin } from "@x18n/core";
3
+ //#region src/presets/i18next/plugin.ts
4
+ /** `defaultNS` and `fallbackNS` from the i18next init code (`i18n.ts`, `next-i18next.config.js`), as keys without a namespace use them. */
5
+ async function findNamespaces(host) {
6
+ const files = await host.glob(["**/*i18n*.{js,mjs,cjs,ts,mts,cts}", "**/i18n/index.{js,ts}"], {
7
+ ignore: ["**/node_modules/**", "**/dist/**"],
8
+ gitignore: true
9
+ });
10
+ const found = {
11
+ default: void 0,
12
+ fallback: void 0
13
+ };
14
+ for (const file of files.slice(0, 20)) {
15
+ const text = await host.readFile(file) ?? "";
16
+ found.default ??= /\bdefaultNS\s*:\s*['"`]([^'"`]+)['"`]/.exec(text)?.[1];
17
+ const fallback = /\bfallbackNS\s*:\s*(\[[^\]]*\]|['"`][^'"`]+['"`])/.exec(text)?.[1];
18
+ found.fallback ??= fallback ? [...fallback.matchAll(/['"`]([^'"`]+)['"`]/g)].map((m) => m[1]) : void 0;
19
+ }
20
+ return found;
21
+ }
22
+ const i18nextPlugin = createLibraryPlugin({
23
+ name: "x18n:i18next",
24
+ namespace: {
25
+ separator: ":",
26
+ default: "translation",
27
+ fromPath: "slash"
28
+ },
29
+ extra: async (draft, ctx) => {
30
+ const user = ctx.user.namespace;
31
+ if (user?.default && user.fallback) return;
32
+ const found = await findNamespaces(ctx.host);
33
+ draft.namespace = {
34
+ ...draft.namespace,
35
+ default: user?.default ?? found.default ?? draft.namespace.default,
36
+ fallback: user?.fallback ?? found.fallback ?? draft.namespace.fallback
37
+ };
38
+ },
39
+ calls: [
40
+ {
41
+ callee: [
42
+ "t",
43
+ "i18next.t",
44
+ "i18n.t"
45
+ ],
46
+ namespace: {
47
+ arg: 1,
48
+ property: "ns"
49
+ }
50
+ },
51
+ {
52
+ callee: [
53
+ "getFixedT",
54
+ "i18next.getFixedT",
55
+ "i18n.getFixedT"
56
+ ],
57
+ factory: true,
58
+ returns: ["t"],
59
+ namespace: { arg: 1 },
60
+ keyPrefix: { arg: 2 }
61
+ },
62
+ {
63
+ callee: "tr",
64
+ types: ["TFunction"]
65
+ }
66
+ ],
67
+ placeholders: [doubleBraces, nesting],
68
+ derivedKeys: [
69
+ "{key}_ordinal_{ordinal}",
70
+ "{key}_{plural}",
71
+ "{key}_*"
72
+ ],
73
+ translatePrompt: (draft, ctx) => {
74
+ draft.notes.push(`i18next plural keys end in _${ctx.localeInfo.pluralCategories.join(", _")} for ${ctx.targetLocale}; write one key per category.`);
75
+ },
76
+ templates: [{
77
+ template: "i18next.t('{key}'{args})",
78
+ kinds: ["js-string", "js-template"],
79
+ imports: ["import i18next from 'i18next'"]
80
+ }]
81
+ });
82
+ //#endregion
83
+ export { i18nextPlugin as t };
@@ -0,0 +1,12 @@
1
+ //#region src/presets/shared/plural-forms.ts
2
+ /** Not `{plural}`: easy_localization ignores CLDR plural rules by default, and both libraries fall back to `other`. */
3
+ const pluralForms = [
4
+ "zero",
5
+ "one",
6
+ "two",
7
+ "few",
8
+ "many",
9
+ "other"
10
+ ];
11
+ //#endregion
12
+ export { pluralForms as t };
@@ -0,0 +1,6 @@
1
+ import { Preset } from "@x18n/core";
2
+ //#region src/presets/cargo-i18n/index.d.ts
3
+ /** cargo-i18n with the Fluent system: `i18n.toml`, `{assets_dir}/{locale}/{crate}.ftl` and the `fl!` macro of `i18n-embed-fl`. */
4
+ export declare function cargoI18n(): Preset;
5
+ //#endregion
6
+ export { cargoI18n as default };
@@ -0,0 +1,28 @@
1
+ import { fluent } from "../placeholders.mjs";
2
+ import { createLibraryPlugin } from "@x18n/core";
3
+ //#region src/presets/cargo-i18n/index.ts
4
+ /** cargo-i18n with the Fluent system: `i18n.toml`, `{assets_dir}/{locale}/{crate}.ftl` and the `fl!` macro of `i18n-embed-fl`. */
5
+ function cargoI18n() {
6
+ return {
7
+ name: "cargo-i18n",
8
+ plugins: [createLibraryPlugin({
9
+ name: "x18n:cargo-i18n",
10
+ regex: [{
11
+ extensions: ["rs"],
12
+ patterns: ["\\bfl!\\(\\s*(?:[^,\"]+,\\s*)?\"({key})\""]
13
+ }],
14
+ derivedKeys: ["{key}.*"],
15
+ extra: async (draft, ctx) => {
16
+ const toml = await ctx.host.readFile("i18n.toml") ?? "";
17
+ const fallback = /^\s*fallback_language\s*=\s*["']([^"']+)["']/m.exec(toml)?.[1];
18
+ if (fallback) draft.sourceLocale = fallback;
19
+ const assetsDir = /^\s*assets_dir\s*=\s*["']([^"']+)["']/m.exec(toml)?.[1]?.replace(/^\.\/|\/$/g, "") ?? "i18n";
20
+ draft.resources = [...draft.resources, { pattern: `${assetsDir}/{locale}/*.ftl` }];
21
+ },
22
+ templates: [{ template: "fl!(LANGUAGE_LOADER, \"{key}\")" }],
23
+ placeholders: [fluent]
24
+ })]
25
+ };
26
+ }
27
+ //#endregion
28
+ export { cargoI18n, cargoI18n as default };
@@ -0,0 +1,6 @@
1
+ import { Preset } from "@x18n/core";
2
+ //#region src/presets/chrome-extension/index.d.ts
3
+ /** Chrome and WebExtension `_locales/{locale}/messages.json`: `{ "name": { "message": "..." } }` reads as `name`. */
4
+ export declare function chromeExtension(): Preset;
5
+ //#endregion
6
+ export { chromeExtension as default };
@@ -0,0 +1,52 @@
1
+ import { dollars } from "../placeholders.mjs";
2
+ import { createLibraryPlugin, isMessageLeaf } from "@x18n/core";
3
+ //#region src/presets/chrome-extension/index.ts
4
+ /** Chrome and WebExtension `_locales/{locale}/messages.json`: `{ "name": { "message": "..." } }` reads as `name`. */
5
+ function chromeExtension() {
6
+ return {
7
+ name: "chrome-extension",
8
+ plugins: [createLibraryPlugin({
9
+ name: "x18n:chrome-extension",
10
+ calls: [{ callee: [
11
+ "chrome.i18n.getMessage",
12
+ "browser.i18n.getMessage",
13
+ "i18n.getMessage"
14
+ ] }],
15
+ regex: [{
16
+ extensions: [
17
+ "json",
18
+ "html",
19
+ "css"
20
+ ],
21
+ patterns: ["__MSG_({key})__"]
22
+ }],
23
+ resources: [{ pattern: "_locales/{locale}/messages.json" }, { pattern: "{src,public,extension}/_locales/{locale}/messages.json" }],
24
+ transformResource: {
25
+ applies: (ctx) => /(?:^|\/)_locales\/[^/]+\/messages\.json$/.test(ctx.path),
26
+ read(tree) {
27
+ const out = {};
28
+ for (const [name, entry] of Object.entries(tree)) {
29
+ const message = isMessageLeaf(entry) ? void 0 : entry.message;
30
+ if (message !== void 0 && isMessageLeaf(message)) out[name] = message;
31
+ }
32
+ return out;
33
+ },
34
+ toStored: (path) => [path.join("."), "message"]
35
+ },
36
+ templates: [{
37
+ template: "chrome.i18n.getMessage('{key}')",
38
+ kinds: ["js-string", "js-template"]
39
+ }],
40
+ placeholders: [dollars]
41
+ }), {
42
+ name: "x18n:chrome-extension-source-locale",
43
+ async config(draft, ctx) {
44
+ const manifest = await ctx.host.readFile("manifest.json") ?? await ctx.host.readFile("src/manifest.json") ?? await ctx.host.readFile("public/manifest.json");
45
+ const locale = manifest ? /"default_locale"\s*:\s*"([^"]+)"/.exec(manifest)?.[1] : void 0;
46
+ if (locale) draft.sourceLocale = locale;
47
+ }
48
+ }]
49
+ };
50
+ }
51
+ //#endregion
52
+ export { chromeExtension, chromeExtension as default };
@@ -0,0 +1,6 @@
1
+ import { Preset } from "@x18n/core";
2
+ //#region src/presets/dotnet-resx/index.d.ts
3
+ /** .NET `IStringLocalizer` and strongly typed resources over `.resx` files: `Strings.resx` holds the source locale, `Strings.de.resx` German. */
4
+ export declare function dotnetResx(): Preset;
5
+ //#endregion
6
+ export { dotnetResx as default };
@@ -0,0 +1,64 @@
1
+ import { dotnet } from "../placeholders.mjs";
2
+ import { createLibraryPlugin } from "@x18n/core";
3
+ //#region src/presets/dotnet-resx/index.ts
4
+ /** .NET satellite assemblies: `X.<culture>.resx` translates `X.resx`, so `Views.Shared.Nav.resx` alone is a source file. */
5
+ const resxFinder = {
6
+ id: "dotnet-resx",
7
+ async find(ctx) {
8
+ const paths = new Set(await ctx.host.glob(["**/*.resx"], {
9
+ ignore: [
10
+ "**/node_modules/**",
11
+ "**/bin/**",
12
+ "**/obj/**"
13
+ ],
14
+ gitignore: false
15
+ }));
16
+ return [...paths].map((path) => {
17
+ const satellite = /^(.+)\.([^./]+)\.resx$/.exec(path);
18
+ return {
19
+ path,
20
+ locale: satellite && paths.has(`${satellite[1]}.resx`) ? satellite[2] : ctx.config.sourceLocale,
21
+ namespace: void 0
22
+ };
23
+ });
24
+ },
25
+ resolveNew({ locale, model }, ctx) {
26
+ if (model?.finder !== "dotnet-resx") return void 0;
27
+ const suffix = model.path.endsWith(`.${model.locale}.resx`) ? `.${model.locale}.resx` : ".resx";
28
+ const base = model.path.slice(0, -suffix.length);
29
+ return locale === ctx.config.sourceLocale ? `${base}.resx` : `${base}.${locale}.resx`;
30
+ }
31
+ };
32
+ /** .NET `IStringLocalizer` and strongly typed resources over `.resx` files: `Strings.resx` holds the source locale, `Strings.de.resx` German. */
33
+ function dotnetResx() {
34
+ return {
35
+ name: "dotnet-resx",
36
+ plugins: [createLibraryPlugin({
37
+ name: "x18n:dotnet-resx",
38
+ regex: [{
39
+ extensions: [
40
+ "cs",
41
+ "cshtml",
42
+ "razor"
43
+ ],
44
+ patterns: [
45
+ "\\b(?:\\w*[Ll]ocali[sz]er|L|Loc)\\[\\s*\"([^\"]+)\"",
46
+ "\\b\\w*[Ll]ocali[sz]er\\.Get(?:String|Html)\\(\\s*\"([^\"]+)\"",
47
+ "(?<![\\w.])(?:Properties\\.)?Resources\\.(?!ResourceManager\\b|Culture\\b)([A-Za-z_]\\w*)\\b(?![.(<])"
48
+ ]
49
+ }],
50
+ finders: [resxFinder],
51
+ keyStyle: "flat",
52
+ templates: [{
53
+ template: "@Localizer[\"{key}\"]",
54
+ files: ["**/*.cshtml", "**/*.razor"]
55
+ }, {
56
+ template: "_localizer[\"{key}\"]",
57
+ files: ["**/*.cs"]
58
+ }],
59
+ placeholders: [dotnet]
60
+ })]
61
+ };
62
+ }
63
+ //#endregion
64
+ export { dotnetResx as default, dotnetResx };
@@ -0,0 +1,6 @@
1
+ import { Preset } from "@x18n/core";
2
+ //#region src/presets/easy-localization/index.d.ts
3
+ /** `easy_localization`: `'key'.tr()`, `tr('key')`, `context.tr('key')`, plural and gender forms as nested keys. */
4
+ export declare function easyLocalization(): Preset;
5
+ //#endregion
6
+ export { easyLocalization as default };
@@ -0,0 +1,36 @@
1
+ import { braces, linked, positionalBraces } from "../placeholders.mjs";
2
+ import { t as pluralForms } from "../plural-forms-B7_QX4nG.mjs";
3
+ import { createLibraryPlugin } from "@x18n/core";
4
+ //#region src/presets/easy-localization/index.ts
5
+ /** `easy_localization`: `'key'.tr()`, `tr('key')`, `context.tr('key')`, plural and gender forms as nested keys. */
6
+ function easyLocalization() {
7
+ return {
8
+ name: "easy-localization",
9
+ plugins: [createLibraryPlugin({
10
+ name: "x18n:easy-localization",
11
+ regex: [{
12
+ extensions: ["dart"],
13
+ patterns: [
14
+ "['\"]({key})['\"]\\s*\\.(?:tr|plural)\\(",
15
+ "(?<![\\w$])Text\\(\\s*['\"]({key})['\"]\\s*\\)\\s*\\.(?:tr|plural)\\(",
16
+ "(?<![\\w$.])(?:context\\.)?(?:tr|plural)\\(\\s*['\"]({key})['\"]"
17
+ ]
18
+ }],
19
+ derivedKeys: [
20
+ ...pluralForms,
21
+ "male",
22
+ "female"
23
+ ].map((form) => `{key}.${form}`),
24
+ resources: [{ pattern: "assets/translations/{locale}.{ext}" }],
25
+ keyStyle: "nested",
26
+ templates: [{ template: "'{key}'.tr()" }],
27
+ placeholders: [
28
+ positionalBraces,
29
+ braces,
30
+ linked
31
+ ]
32
+ })]
33
+ };
34
+ }
35
+ //#endregion
36
+ export { easyLocalization as default, easyLocalization };
@@ -0,0 +1,5 @@
1
+ import { Preset } from "@x18n/core";
2
+ //#region src/presets/ember-intl/index.d.ts
3
+ export declare function emberIntl(): Preset;
4
+ //#endregion
5
+ export { emberIntl as default };
@@ -0,0 +1,31 @@
1
+ import { icu } from "../placeholders.mjs";
2
+ import { createLibraryPlugin } from "@x18n/core";
3
+ //#region src/presets/ember-intl/index.ts
4
+ function emberIntl() {
5
+ return {
6
+ name: "ember-intl",
7
+ plugins: [createLibraryPlugin({
8
+ name: "x18n:ember-intl",
9
+ calls: [{ callee: ["intl.t"] }],
10
+ regex: [{
11
+ extensions: [
12
+ "hbs",
13
+ "gjs",
14
+ "gts"
15
+ ],
16
+ patterns: ["\\{\\{\\s*t\\s+['\"`]({key})['\"`]", "\\(\\s*t\\s+['\"`]({key})['\"`]"]
17
+ }],
18
+ resources: [{ pattern: "translations/{locale}.{ext}" }, { pattern: "translations/{namespaces}/{locale}.{ext}" }],
19
+ templates: [{
20
+ template: "{{t '{key}'}}",
21
+ files: ["**/*.hbs"]
22
+ }, {
23
+ template: "this.intl.t('{key}'{args})",
24
+ kinds: ["js-string", "js-template"]
25
+ }],
26
+ placeholders: [icu]
27
+ })]
28
+ };
29
+ }
30
+ //#endregion
31
+ export { emberIntl as default, emberIntl };
@@ -0,0 +1,5 @@
1
+ import { Preset } from "@x18n/core";
2
+ //#region src/presets/fluent-react/index.d.ts
3
+ export declare function fluentReact(): Preset;
4
+ //#endregion
5
+ export { fluentReact as default };
@@ -0,0 +1,46 @@
1
+ import { fluent } from "../placeholders.mjs";
2
+ import { createLibraryPlugin } from "@x18n/core";
3
+ //#region src/presets/fluent-react/index.ts
4
+ function fluentReact() {
5
+ return {
6
+ name: "fluent-react",
7
+ plugins: [createLibraryPlugin({
8
+ name: "x18n:fluent-react",
9
+ calls: [{
10
+ callee: "Localized",
11
+ key: {
12
+ arg: 0,
13
+ property: "id"
14
+ }
15
+ }, { callee: [
16
+ "l10n.getString",
17
+ "props.getString",
18
+ "this.props.getString"
19
+ ] }],
20
+ derivedKeys: ["{key}.*"],
21
+ resources: [
22
+ { pattern: "public/locales/{locale}/*.ftl" },
23
+ { pattern: "locales/{locale}.ftl" },
24
+ { pattern: "src/locales/{locale}.ftl" }
25
+ ],
26
+ templates: [
27
+ {
28
+ template: "<Localized id=\"{key}\" />",
29
+ kinds: ["jsx-text"],
30
+ imports: ["import { Localized } from '@fluent/react'"]
31
+ },
32
+ {
33
+ template: "{attr}={l10n.getString('{key}'{args})}",
34
+ kinds: ["jsx-attribute"]
35
+ },
36
+ {
37
+ template: "l10n.getString('{key}'{args})",
38
+ kinds: ["js-string", "js-template"]
39
+ }
40
+ ],
41
+ placeholders: [fluent]
42
+ })]
43
+ };
44
+ }
45
+ //#endregion
46
+ export { fluentReact as default, fluentReact };
@@ -0,0 +1,5 @@
1
+ import { Preset } from "@x18n/core";
2
+ //#region src/presets/fluent-vue/index.d.ts
3
+ export declare function fluentVue(): Preset;
4
+ //#endregion
5
+ export { fluentVue as default };
@@ -0,0 +1,51 @@
1
+ import { n as sfcFluentBlocks } from "../sfc-blocks-DaGKDZsb.mjs";
2
+ import { fluent } from "../placeholders.mjs";
3
+ import { createLibraryPlugin } from "@x18n/core";
4
+ //#region src/presets/fluent-vue/index.ts
5
+ function fluentVue() {
6
+ return {
7
+ name: "fluent-vue",
8
+ plugins: [createLibraryPlugin({
9
+ name: "x18n:fluent-vue",
10
+ calls: [{ callee: [
11
+ "$t",
12
+ "$ta",
13
+ "fluent.format",
14
+ "fluent.formatAttrs",
15
+ "this.$t",
16
+ "this.$ta"
17
+ ] }, {
18
+ callee: "useFluent",
19
+ factory: true,
20
+ returns: [
21
+ "$t",
22
+ "$ta",
23
+ "format",
24
+ "formatAttrs"
25
+ ]
26
+ }],
27
+ regex: [{
28
+ extensions: ["vue"],
29
+ patterns: ["v-t:({key})[\\s\\n=>]", "<i18n[\\s\\n]+path=['\"`]({key})['\"`]"]
30
+ }],
31
+ resources: [{ pattern: "locales/{locale}.ftl" }, { pattern: "src/locales/{locale}.ftl" }],
32
+ templates: [
33
+ {
34
+ template: "{{ $t('{key}'{args}) }}",
35
+ kinds: ["vue-template-text"]
36
+ },
37
+ {
38
+ template: ":{attr}=\"$t('{key}'{args})\"",
39
+ kinds: ["vue-attribute"]
40
+ },
41
+ {
42
+ template: "$t('{key}'{args})",
43
+ kinds: ["js-string", "js-template"]
44
+ }
45
+ ],
46
+ placeholders: [fluent]
47
+ }), sfcFluentBlocks()]
48
+ };
49
+ }
50
+ //#endregion
51
+ export { fluentVue as default, fluentVue };
@@ -0,0 +1,6 @@
1
+ import { Preset } from "@x18n/core";
2
+ //#region src/presets/flutter-translate/index.d.ts
3
+ /** `flutter_translate`: `translate('key')`, `translatePlural('key', n)` with plural forms as nested keys. */
4
+ export declare function flutterTranslate(): Preset;
5
+ //#endregion
6
+ export { flutterTranslate as default };