@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
@@ -0,0 +1,125 @@
1
+ import { a as splice, n as detectEol } from "./shared-C7ceptwi.mjs";
2
+ import { isMessageLeaf } from "@x18n/core";
3
+ //#region src/formats/shared/literal.ts
4
+ /** Prints a value at `depth`. Objects span lines, one entry per line. */
5
+ function literalText(value, syntax, depth = 0) {
6
+ if (isMessageLeaf(value)) return syntax.leaf(value, void 0);
7
+ const entries = Object.entries(value);
8
+ if (entries.length === 0) return `${syntax.open}${syntax.close}`;
9
+ const pad = syntax.unit.repeat(depth + 1);
10
+ const lines = entries.map(([key, child]) => `${pad}${syntax.key(key)}${syntax.separator}${literalText(child, syntax, depth + 1)}`);
11
+ return `${syntax.open}\n${lines.join(",\n")}${syntax.trailingComma ? "," : ""}\n${syntax.unit.repeat(depth)}${syntax.close}`;
12
+ }
13
+ /** Applies one op to the text of `root` and leaves everything else as it is: comments, quotes, order and layout. */
14
+ function editLiteral(text, root, op, syntax) {
15
+ const last = root.items.at(-1);
16
+ const style = last ? {
17
+ ...syntax,
18
+ trailingComma: commaAfter(text, last.range.end) !== void 0
19
+ } : syntax;
20
+ let node = root;
21
+ let depth = 0;
22
+ while (depth < op.path.length && node.kind === "object") {
23
+ const object = node;
24
+ const item = object.items.findLast((i) => i.key === op.path[depth]);
25
+ if (!item) break;
26
+ if (op.type === "delete" && depth === op.path.length - 1) return removeItem(text, object, item);
27
+ node = item.value;
28
+ depth++;
29
+ }
30
+ if (op.type === "delete") return text;
31
+ const rest = op.path.slice(depth);
32
+ if (node.kind === "object" && rest.length > 0) return insertItem(text, node, rest, op.value, style);
33
+ const value = nest(rest, op.value);
34
+ return splice(text, node.range, isMessageLeaf(value) ? style.leaf(value, node.kind === "scalar" ? node.quote : void 0) : reindent(literalText(value, style), indentAt(text, node.range.start), detectEol(text)));
35
+ }
36
+ function nest(path, value) {
37
+ return path.reduceRight((child, segment) => ({ [segment]: child }), value);
38
+ }
39
+ function reindent(text, indent, eol) {
40
+ return text.replace(/\n/g, `${eol}${indent}`);
41
+ }
42
+ function lineStart(text, pos) {
43
+ return text.lastIndexOf("\n", pos - 1) + 1;
44
+ }
45
+ function indentAt(text, pos) {
46
+ return /^[ \t]*/.exec(text.slice(lineStart(text, pos)))[0];
47
+ }
48
+ /** The `,` after a value, past blanks and comments. */
49
+ function commaAfter(text, pos) {
50
+ const at = pos + /^(?:\s|\/\/[^\n]*|#[^\n]*|\/\*[\s\S]*?\*\/)*/.exec(text.slice(pos))[0].length;
51
+ return text[at] === "," ? at : void 0;
52
+ }
53
+ /** The end of the line at `pos`, before the line break, when only blanks and a comment follow. */
54
+ function lineEnd(text, pos) {
55
+ const end = pos + /^[ \t]*(?:(?:\/\/|#).*|\/\*.*?\*\/[ \t]*)?/.exec(text.slice(pos))[0].length;
56
+ return end === text.length || text.startsWith("\n", end) || text.startsWith("\r\n", end) ? end : void 0;
57
+ }
58
+ function insertItem(text, object, path, value, syntax) {
59
+ const eol = detectEol(text);
60
+ const entry = (indent) => `${syntax.key(path[0])}${syntax.separator}${reindent(literalText(nest(path.slice(1), value), syntax), indent, eol)}`;
61
+ const first = object.items[0];
62
+ const last = object.items.at(-1);
63
+ if (!first || !last) {
64
+ const base = indentAt(text, object.range.start);
65
+ const indent = base + syntax.unit;
66
+ const close = object.range.end - 1;
67
+ const blank = /\s*$/.exec(text.slice(object.range.start, close))[0].length;
68
+ return splice(text, {
69
+ start: close - blank,
70
+ end: close
71
+ }, `${eol}${indent}${entry(indent)}${syntax.trailingComma ? "," : ""}${eol}${base}`);
72
+ }
73
+ const comma = commaAfter(text, last.range.end);
74
+ const end = comma === void 0 ? last.range.end : comma + 1;
75
+ if (!text.slice(object.range.start, first.range.start).includes("\n")) {
76
+ const indent = indentAt(text, object.range.start);
77
+ return splice(text, {
78
+ start: end,
79
+ end
80
+ }, comma === void 0 ? `, ${entry(indent)}` : ` ${entry(indent)},`);
81
+ }
82
+ const indent = indentAt(text, first.range.start);
83
+ const at = lineEnd(text, end) ?? end;
84
+ const next = splice(text, {
85
+ start: at,
86
+ end: at
87
+ }, `${eol}${indent}${entry(indent)}${comma === void 0 ? "" : ","}`);
88
+ return comma === void 0 ? splice(next, {
89
+ start: end,
90
+ end
91
+ }, ",") : next;
92
+ }
93
+ function removeItem(text, object, item) {
94
+ const comma = commaAfter(text, item.range.end);
95
+ const end = comma === void 0 ? item.range.end : comma + 1;
96
+ const previous = object.items[object.items.indexOf(item) - 1];
97
+ const before = comma === void 0 && previous ? commaAfter(text, previous.range.end) : void 0;
98
+ const close = lineEnd(text, end);
99
+ const opening = text.slice(object.range.start, item.range.start);
100
+ if (object.items.length === 1 && !/\/\/|\/\*|#/.test(opening) && text.slice(close ?? end, object.range.end - 1).trim() === "") return splice(text, {
101
+ start: object.range.start + opening.trimEnd().length,
102
+ end: object.range.end - 1
103
+ }, "");
104
+ const start = lineStart(text, item.range.start);
105
+ if (close !== void 0 && /^[ \t]*$/.test(text.slice(start, item.range.start))) {
106
+ const next = splice(text, {
107
+ start,
108
+ end: close + (/^\r?\n/.exec(text.slice(close))?.[0].length ?? 0)
109
+ }, "");
110
+ return before === void 0 ? next : splice(next, {
111
+ start: before,
112
+ end: before + 1
113
+ }, "");
114
+ }
115
+ if (before !== void 0) return splice(text, {
116
+ start: before,
117
+ end
118
+ }, "");
119
+ return splice(text, {
120
+ start: item.range.start,
121
+ end: end + /^[ \t]*/.exec(text.slice(end))[0].length
122
+ }, "");
123
+ }
124
+ //#endregion
125
+ export { literalText as n, editLiteral as t };
@@ -0,0 +1,15 @@
1
+ import { isMessageLeaf } from "@x18n/core";
2
+ //#region src/presets/shared/locale-root.ts
3
+ /** Files that start with their locale, `en: { users: ... }`, as in Rails and i18nice. */
4
+ function localeRoot(applies) {
5
+ return {
6
+ applies,
7
+ read(tree, ctx) {
8
+ const root = tree[ctx.locale];
9
+ return root !== void 0 && !isMessageLeaf(root) ? root : {};
10
+ },
11
+ toStored: (path, ctx) => [ctx.locale, ...path]
12
+ };
13
+ }
14
+ //#endregion
15
+ export { localeRoot as t };
@@ -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 };