@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,8 @@
1
+ //#region src/presets/shared/angular-templates.ts
2
+ const angularTemplates = [
3
+ "html",
4
+ "ts",
5
+ "js"
6
+ ];
7
+ //#endregion
8
+ export { angularTemplates as t };
package/dist/checks.d.mts CHANGED
@@ -3,6 +3,7 @@ import { Check, Plugin } from "@x18n/core";
3
3
  export declare const missingKey: Check;
4
4
  export declare const missingTranslation: Check;
5
5
  export declare const staleTranslation: Check;
6
+ export declare const reviewChangesRequested: Check;
6
7
  export declare const emptyTranslation: Check;
7
8
  export declare const redundantTranslation: Check;
8
9
  export declare const keyConflict: Check;
package/dist/checks.mjs CHANGED
@@ -88,6 +88,30 @@ const staleTranslation = {
88
88
  };
89
89
  }
90
90
  };
91
+ const reviewChangesRequested = {
92
+ id: "review-changes-requested",
93
+ defaultSeverity: "info",
94
+ description: "A message with an open \"request change\" review.",
95
+ *run(ctx) {
96
+ for (const thread of ctx.reviews({ open: true })) {
97
+ if (thread.state !== "changes-requested") continue;
98
+ const request = thread.comments.findLast((comment) => comment.type === "request-change");
99
+ const by = request.author?.name ? ` by ${request.author.name}` : "";
100
+ yield {
101
+ key: thread.key,
102
+ locale: thread.locale,
103
+ message: `Change requested${by} for "${thread.key}" in ${thread.locale}${request.text ? `: ${request.text}` : "."}`,
104
+ fixes: [{
105
+ title: "Resolve the review",
106
+ ops: [{
107
+ type: "resolve-review",
108
+ id: request.id
109
+ }]
110
+ }]
111
+ };
112
+ }
113
+ }
114
+ };
91
115
  const emptyTranslation = {
92
116
  id: "empty-translation",
93
117
  defaultSeverity: "warning",
@@ -361,6 +385,7 @@ const allChecks = [
361
385
  keyConflict,
362
386
  missingTranslation,
363
387
  staleTranslation,
388
+ reviewChangesRequested,
364
389
  emptyTranslation,
365
390
  redundantTranslation,
366
391
  unusedKey,
@@ -379,4 +404,4 @@ function recommended() {
379
404
  };
380
405
  }
381
406
  //#endregion
382
- export { allChecks, dynamicKey, emptyTranslation, glossary, hardString, identicalToSource, invalidSyntax, keyConflict, keyStyle, localeCodeConflict, missingKey, missingTranslation, placeholderMismatch, recommended, redundantTranslation, staleTranslation, unusedKey };
407
+ export { allChecks, dynamicKey, emptyTranslation, glossary, hardString, identicalToSource, invalidSyntax, keyConflict, keyStyle, localeCodeConflict, missingKey, missingTranslation, placeholderMismatch, recommended, redundantTranslation, reviewChangesRequested, staleTranslation, unusedKey };
@@ -4,6 +4,7 @@ const textKinds = /* @__PURE__ */ new Set([
4
4
  "jsx-text",
5
5
  "vue-template-text",
6
6
  "svelte-text",
7
+ "astro-text",
7
8
  "html-text"
8
9
  ]);
9
10
  /** Built-in rules, applied in order. A `must-*` wins at once; otherwise the last `should-*` decides. */
@@ -123,11 +124,11 @@ function slugify(text, keyCase = "camel", maxLength = 40) {
123
124
  const defaultTemplates = [
124
125
  {
125
126
  template: "{t('{key}'{args})}",
126
- kinds: ["jsx-text"]
127
+ kinds: ["jsx-text", "astro-text"]
127
128
  },
128
129
  {
129
130
  template: "{attr}={t('{key}'{args})}",
130
- kinds: ["jsx-attribute"]
131
+ kinds: ["jsx-attribute", "astro-attribute"]
131
132
  },
132
133
  {
133
134
  template: "{{ $t('{key}'{args}) }}",
@@ -258,7 +259,10 @@ function quoteKey(before, key) {
258
259
  return inAttribute ? out.replace(/&/g, "&").replace(/"/g, """) : out;
259
260
  }
260
261
  function fillTemplate(template, values) {
261
- return template.replace(/\{(key|attr|args)\}/g, (_, name, at) => name === "key" ? quoteKey(template.slice(0, at), values.key) : values[name]);
262
+ return template.replace(/\{(key|attr|args)\}/g, (_, name, at) => {
263
+ if (name === "key") return quoteKey(template.slice(0, at), values.key);
264
+ return name === "args" && template[at - 1] === "(" ? values.args.replace(/^, /, "") : values[name];
265
+ });
262
266
  }
263
267
  /** Hard strings worth extracting, with proposed keys, values and replacements. */
264
268
  function findCandidates(project, options = {}) {
@@ -23,7 +23,10 @@ export declare const patternFinder: LocaleFinder;
23
23
  export declare function pattern(): Plugin;
24
24
  //#endregion
25
25
  //#region src/finders/sfc-blocks.d.ts
26
- /** Vue `<i18n>` blocks: one resource per block and locale (`locale="en"`, or every top-level locale key). */
26
+ /**
27
+ * Vue `<i18n>` blocks: one resource per block and locale (`locale="en"`, or every top-level locale key).
28
+ * A block with `src` is a resource in the referenced file, so edits go there.
29
+ */
27
30
  export declare function sfcBlocks(): Plugin;
28
31
  /** fluent-vue `<fluent locale="en">` blocks in Vue SFCs, read with the `ftl` format. */
29
32
  export declare function sfcFluentBlocks(): Plugin;
package/dist/finders.mjs CHANGED
@@ -1,3 +1,3 @@
1
1
  import { a as patternFinder, i as pattern, n as guessResources, r as namespaceFromPath, t as compilePattern } from "./finders-CqTGh4OJ.mjs";
2
- import { n as sfcFluentBlocks, t as sfcBlocks } from "./sfc-blocks-BqQ8m9rX.mjs";
2
+ import { n as sfcFluentBlocks, t as sfcBlocks } from "./sfc-blocks-DaGKDZsb.mjs";
3
3
  export { compilePattern, guessResources, namespaceFromPath, pattern, patternFinder, sfcBlocks, sfcFluentBlocks };
@@ -0,0 +1,5 @@
1
+ import { Plugin } from "@x18n/core";
2
+ //#region src/formats/ecmascript/index.d.ts
3
+ export declare function ecmascript(): Plugin;
4
+ //#endregion
5
+ export { ecmascript as default };
@@ -0,0 +1,232 @@
1
+ import { r as indentUnit } from "../shared-C7ceptwi.mjs";
2
+ import { n as literalText, t as editLiteral } from "../literal-gIdB1VGy.mjs";
3
+ import { applyMessageOps, diffText, errors, pathId } from "@x18n/core";
4
+ import { parseSync } from "oxc-parser";
5
+ //#region src/formats/ecmascript/index.ts
6
+ /** Type-only wrappers, `defineX({ ... })` helpers that return their argument, and loaders that only return an object (`defineI18nLocale(() => ({ ... }))`). */
7
+ function unwrap(node) {
8
+ if (node.type === "TSAsExpression" || node.type === "TSSatisfiesExpression" || node.type === "TSTypeAssertion" || node.type === "TSNonNullExpression" || node.type === "ParenthesizedExpression") return unwrap(node.expression);
9
+ if (node.type === "ArrowFunctionExpression" && node.body.type !== "BlockStatement") return unwrap(node.body);
10
+ if ((node.type === "ArrowFunctionExpression" || node.type === "FunctionExpression") && node.body?.type === "BlockStatement") {
11
+ const [only, ...rest] = node.body.body;
12
+ if (only?.type === "ReturnStatement" && only.argument && rest.length === 0) return unwrap(only.argument);
13
+ }
14
+ const argument = node.type === "CallExpression" && node.arguments.length === 1 ? node.arguments[0] : void 0;
15
+ if (node.type === "CallExpression" && node.callee.type === "Identifier" && /^define[A-Z]/.test(node.callee.name) && argument && argument.type !== "SpreadElement") return unwrap(argument);
16
+ return node;
17
+ }
18
+ function findRoot(program, fail) {
19
+ const bindings = /* @__PURE__ */ new Map();
20
+ for (const statement of program.body) {
21
+ if (statement.type !== "VariableDeclaration") continue;
22
+ for (const declarator of statement.declarations) if (declarator.id.type === "Identifier" && declarator.init) bindings.set(declarator.id.name, declarator.init);
23
+ }
24
+ let exported;
25
+ for (const statement of program.body) if (statement.type === "ExportDefaultDeclaration" && statement.declaration.type !== "FunctionDeclaration" && statement.declaration.type !== "ClassDeclaration" && statement.declaration.type !== "TSInterfaceDeclaration") exported = statement.declaration;
26
+ else if (statement.type === "ExpressionStatement" && statement.expression.type === "AssignmentExpression" && isModuleExports(statement.expression.left)) exported = statement.expression.right;
27
+ else if (statement.type === "ExportDefaultDeclaration") fail("X18N2004", statement, "the default export is not an object literal");
28
+ if (!exported) return program.body.length === 0 ? void 0 : fail("X18N2004", program, "no `export default { ... }` or `module.exports = { ... }`");
29
+ let root = unwrap(exported);
30
+ if (root.type === "Identifier" && bindings.has(root.name)) root = unwrap(bindings.get(root.name));
31
+ if (root.type === "ObjectExpression") return root;
32
+ return fail(root.type === "Literal" || root.type === "ArrayExpression" ? "X18N2003" : "X18N2004", root, "the default export is not an object literal");
33
+ }
34
+ function isModuleExports(node) {
35
+ return node.type === "MemberExpression" && !node.computed && node.object.type === "Identifier" && node.object.name === "module" && node.property.type === "Identifier" && node.property.name === "exports";
36
+ }
37
+ function readProperty(property, fail) {
38
+ if (property.type === "SpreadElement" || property.kind !== "init" || property.method || property.shorthand) return fail("X18N2004", property, "only `key: value` properties are static");
39
+ const { key, value } = property;
40
+ if (key.type === "Identifier" && !property.computed) return {
41
+ key: key.name,
42
+ range: {
43
+ start: key.start,
44
+ end: key.end
45
+ },
46
+ value: unwrap(value)
47
+ };
48
+ if (key.type === "Literal" && (typeof key.value === "string" || typeof key.value === "number")) return {
49
+ key: String(key.value),
50
+ range: typeof key.value === "string" ? {
51
+ start: key.start + 1,
52
+ end: key.end - 1
53
+ } : {
54
+ start: key.start,
55
+ end: key.end
56
+ },
57
+ value: unwrap(value)
58
+ };
59
+ return fail("X18N2004", key, "computed keys are not static");
60
+ }
61
+ function readLeaf(node, text) {
62
+ if (node.type === "Literal" && !("regex" in node) && typeof node.value !== "bigint") return {
63
+ value: node.value,
64
+ quote: typeof node.value === "string" ? text[node.start] : void 0
65
+ };
66
+ if (node.type === "TemplateLiteral" && node.expressions.length === 0 && node.quasis[0]?.value.cooked != null) return {
67
+ value: node.quasis[0].value.cooked,
68
+ quote: "`"
69
+ };
70
+ if (node.type === "UnaryExpression" && node.operator === "-" && node.argument.type === "Literal" && typeof node.argument.value === "number") return {
71
+ value: -node.argument.value,
72
+ quote: void 0
73
+ };
74
+ }
75
+ function readObject(node, path, ranges, text, fail) {
76
+ const tree = {};
77
+ const items = [];
78
+ for (const property of node.properties) {
79
+ const { key, range, value } = readProperty(property, fail);
80
+ const childPath = [...path, key];
81
+ const item = {
82
+ key,
83
+ range: {
84
+ start: property.start,
85
+ end: property.end
86
+ }
87
+ };
88
+ if (value.type === "ObjectExpression") {
89
+ ranges.set(pathId(childPath), {
90
+ key: range,
91
+ value: {
92
+ start: value.start,
93
+ end: value.end
94
+ }
95
+ });
96
+ const child = readObject(value, childPath, ranges, text, fail);
97
+ tree[key] = child.tree;
98
+ items.push({
99
+ ...item,
100
+ value: child.literal
101
+ });
102
+ continue;
103
+ }
104
+ const leaf = readLeaf(value, text);
105
+ if (!leaf) return fail("X18N2004", value, `${value.type} is not a static value`);
106
+ ranges.set(pathId(childPath), {
107
+ key: range,
108
+ value: leaf.quote === void 0 ? {
109
+ start: value.start,
110
+ end: value.end
111
+ } : {
112
+ start: value.start + 1,
113
+ end: value.end - 1
114
+ }
115
+ });
116
+ tree[key] = leaf.value;
117
+ items.push({
118
+ ...item,
119
+ value: {
120
+ kind: "scalar",
121
+ range: {
122
+ start: value.start,
123
+ end: value.end
124
+ },
125
+ quote: leaf.quote
126
+ }
127
+ });
128
+ }
129
+ return {
130
+ tree,
131
+ literal: {
132
+ kind: "object",
133
+ range: {
134
+ start: node.start,
135
+ end: node.end
136
+ },
137
+ items
138
+ }
139
+ };
140
+ }
141
+ function readModule(text, path) {
142
+ const fail = (code, node, reason) => {
143
+ throw errors[code]({
144
+ path,
145
+ line: text.slice(0, node.start).split("\n").length,
146
+ reason
147
+ });
148
+ };
149
+ const result = parseSync(path, text, {
150
+ lang: /\.[mc]?ts$/.test(path) ? "ts" : "js",
151
+ sourceType: "module"
152
+ });
153
+ if (result.errors.length > 0) throw errors.X18N2002({
154
+ path,
155
+ reason: result.errors[0].message
156
+ });
157
+ const root = findRoot(result.program, fail);
158
+ const ranges = /* @__PURE__ */ new Map();
159
+ if (!root) return {
160
+ tree: {},
161
+ ranges,
162
+ literal: void 0
163
+ };
164
+ return {
165
+ ...readObject(root, [], ranges, text, fail),
166
+ ranges
167
+ };
168
+ }
169
+ const singleEncodings = {
170
+ "\\": "\\\\",
171
+ "'": "\\'",
172
+ "\n": "\\n",
173
+ "\r": "\\r",
174
+ "\u2028": "\\u2028",
175
+ "\u2029": "\\u2029"
176
+ };
177
+ function literal(value, quote) {
178
+ if (typeof value !== "string") return String(value);
179
+ if (quote === "\"") return JSON.stringify(value);
180
+ if (quote === "`") return `\`${value.replace(/\\|`|\$\{/g, (match) => `\\${match}`)}\``;
181
+ return `'${value.replace(/[\\'\n\r\u2028\u2029]/g, (char) => singleEncodings[char])}'`;
182
+ }
183
+ function syntaxOf(text, indent) {
184
+ return {
185
+ open: "{",
186
+ close: "}",
187
+ separator: ": ",
188
+ key: (key) => /^[a-z_$][\w$]*$/i.test(key) ? key : literal(key, "'"),
189
+ leaf: (value, quote) => literal(value, quote ?? "'"),
190
+ unit: indentUnit(text, indent, " "),
191
+ trailingComma: true
192
+ };
193
+ }
194
+ function moduleText(tree, path, syntax) {
195
+ return `${path.endsWith(".cjs") ? "module.exports =" : "export default"} ${literalText(tree, syntax)}\n`;
196
+ }
197
+ function applyOp(text, op, path, syntax) {
198
+ const root = readModule(text, path).literal;
199
+ return root ? editLiteral(text, root, op, syntax) : moduleText(applyMessageOps({}, [op]), path, syntax);
200
+ }
201
+ function ecmascript() {
202
+ return {
203
+ name: "x18n:format-js",
204
+ formats: [{
205
+ id: "js",
206
+ extensions: [
207
+ "js",
208
+ "mjs",
209
+ "cjs",
210
+ "ts",
211
+ "mts",
212
+ "cts"
213
+ ],
214
+ parse(text, ctx) {
215
+ const { tree, ranges } = readModule(text, ctx.path);
216
+ return {
217
+ tree,
218
+ ranges
219
+ };
220
+ },
221
+ edit(text, ops, ctx) {
222
+ const syntax = syntaxOf(text, ctx.indent);
223
+ let next = text;
224
+ for (const op of ops) next = applyOp(next, op, ctx.path, syntax);
225
+ return diffText(text, next);
226
+ },
227
+ serialize: (tree, ctx) => moduleText(tree, ctx.path, syntaxOf("", ctx.indent))
228
+ }]
229
+ };
230
+ }
231
+ //#endregion
232
+ export { ecmascript as default, ecmascript };
@@ -0,0 +1,5 @@
1
+ import { Plugin } from "@x18n/core";
2
+ //#region src/formats/ftl/index.d.ts
3
+ export declare function ftl(): Plugin;
4
+ //#endregion
5
+ export { ftl as default };
@@ -0,0 +1,234 @@
1
+ import { a as splice, c as withBlankLines, i as leafText, n as detectEol, o as splitLines, t as appendLines } from "../shared-C7ceptwi.mjs";
2
+ import { diffText, errors, pathId, walkLeaves } from "@x18n/core";
3
+ //#region src/formats/ftl/index.ts
4
+ const trimmedEnd = (text, line) => line.start + text.slice(line.start, line.end).trimEnd().length;
5
+ const indentOf = (text, line) => /^[ \t]*/.exec(text.slice(line.start, line.end))[0].length;
6
+ function finishPattern(text, draft) {
7
+ const lines = [...draft.lines];
8
+ while (lines.length > 0 && text.slice(lines.at(-1).start, lines.at(-1).end).trim() === "") lines.pop();
9
+ const indent = Math.min(...lines.filter((l) => text.slice(l.start, l.end).trim() !== "").map((l) => indentOf(text, l)));
10
+ const inline = text.slice(draft.inline.start, draft.inline.end).trimEnd();
11
+ const parts = [...inline ? [inline] : [], ...lines.map((l) => text.slice(Math.min(l.start + indent, l.end), trimmedEnd(text, l)))];
12
+ const first = lines[0];
13
+ if (!inline && !first) return {
14
+ value: void 0,
15
+ range: {
16
+ start: draft.after,
17
+ end: draft.after
18
+ },
19
+ source: {
20
+ start: draft.after,
21
+ end: draft.inline.end
22
+ }
23
+ };
24
+ const start = inline ? draft.inline.start : first.start + indent;
25
+ const end = lines.length > 0 ? trimmedEnd(text, lines.at(-1)) : trimmedEnd(text, draft.inline);
26
+ return {
27
+ value: parts.join("\n"),
28
+ range: {
29
+ start,
30
+ end
31
+ },
32
+ source: {
33
+ start: draft.after,
34
+ end
35
+ }
36
+ };
37
+ }
38
+ function read(text, path) {
39
+ const messages = [];
40
+ let current;
41
+ let commentStart;
42
+ let blanks = [];
43
+ let lineNumber = 0;
44
+ for (const line of splitLines(text)) {
45
+ lineNumber++;
46
+ const content = text.slice(line.start, line.end);
47
+ if (content.trim() === "") {
48
+ blanks.push(line);
49
+ commentStart = void 0;
50
+ continue;
51
+ }
52
+ if (/^[ \t]/.test(content)) {
53
+ if (!current) throw errors.X18N2002({
54
+ path,
55
+ line: lineNumber,
56
+ reason: "indented line outside a message"
57
+ });
58
+ const attribute = /^[ \t]+\.([a-z][\w-]*)[ \t]*=[ \t]*/i.exec(content);
59
+ if (attribute) {
60
+ const nameStart = line.start + attribute[0].indexOf(".") + 1;
61
+ const after = line.start + attribute[0].trimEnd().length;
62
+ current.attributes.push({
63
+ name: attribute[1],
64
+ nameRange: {
65
+ start: nameStart,
66
+ end: nameStart + attribute[1].length
67
+ },
68
+ pattern: {
69
+ after,
70
+ inline: {
71
+ start: line.start + attribute[0].length,
72
+ end: line.end
73
+ },
74
+ lines: []
75
+ },
76
+ lines: {
77
+ start: line.start,
78
+ end: line.next
79
+ }
80
+ });
81
+ } else {
82
+ const last = current.attributes.at(-1);
83
+ (last ?? current).pattern.lines.push(...blanks, line);
84
+ if (last) last.lines.end = line.next;
85
+ }
86
+ current.block.end = line.next;
87
+ blanks = [];
88
+ continue;
89
+ }
90
+ blanks = [];
91
+ current = void 0;
92
+ if (content.startsWith("#")) {
93
+ commentStart = /^#(?: |$)/.test(content) ? commentStart ?? line.start : void 0;
94
+ continue;
95
+ }
96
+ const message = /^(-?[a-z][\w-]*)[ \t]*=[ \t]*/i.exec(content);
97
+ if (!message) throw errors.X18N2002({
98
+ path,
99
+ line: lineNumber,
100
+ reason: "expected a message, a term or a comment"
101
+ });
102
+ current = {
103
+ id: message[1],
104
+ idRange: {
105
+ start: line.start,
106
+ end: line.start + message[1].length
107
+ },
108
+ block: {
109
+ start: commentStart ?? line.start,
110
+ end: line.next
111
+ },
112
+ pattern: {
113
+ after: line.start + message[0].trimEnd().length,
114
+ inline: {
115
+ start: line.start + message[0].length,
116
+ end: line.end
117
+ },
118
+ lines: []
119
+ },
120
+ attributes: []
121
+ };
122
+ commentStart = void 0;
123
+ messages.push(current);
124
+ }
125
+ return messages;
126
+ }
127
+ function encode(value, indent, eol) {
128
+ if (value === "") return " { \"\" }";
129
+ const lines = value.split("\n");
130
+ if (lines.length === 1) return ` ${value}`;
131
+ return lines.map((line) => line === "" ? eol : `${eol}${" ".repeat(indent)}${line}`).join("");
132
+ }
133
+ function splitKey(key) {
134
+ const dot = key.indexOf(".");
135
+ return dot === -1 ? {
136
+ id: key,
137
+ attribute: void 0
138
+ } : {
139
+ id: key.slice(0, dot),
140
+ attribute: key.slice(dot + 1)
141
+ };
142
+ }
143
+ function setValue(text, key, value, messages) {
144
+ const eol = detectEol(text);
145
+ const { id, attribute } = splitKey(key);
146
+ const message = messages.findLast((m) => m.id === id);
147
+ if (!message) return appendLines(text, attribute ? `${id} =\n .${attribute} =${encode(value, 8, "\n")}\n` : `${id} =${encode(value, 4, "\n")}\n`);
148
+ if (!attribute) return splice(text, finishPattern(text, message.pattern).source, encode(value, 4, eol));
149
+ const existing = message.attributes.findLast((a) => a.name === attribute);
150
+ if (existing) return splice(text, finishPattern(text, existing.pattern).source, encode(value, indentOf(text, existing.lines) + 4, eol));
151
+ const end = message.block.end;
152
+ const lead = end === text.length && !text.endsWith("\n") ? eol : "";
153
+ return splice(text, {
154
+ start: end,
155
+ end
156
+ }, `${lead} .${attribute} =${encode(value, 8, eol)}${eol}`);
157
+ }
158
+ function remove(text, key, messages) {
159
+ const { id, attribute } = splitKey(key);
160
+ const message = messages.findLast((m) => m.id === id);
161
+ if (!message) return text;
162
+ const pattern = finishPattern(text, message.pattern);
163
+ if (!attribute) {
164
+ if (pattern.value === void 0) return text;
165
+ return message.attributes.length > 0 ? splice(text, pattern.source, "") : splice(text, withBlankLines(text, message.block), "");
166
+ }
167
+ const existing = message.attributes.findLast((a) => a.name === attribute);
168
+ if (!existing) return text;
169
+ if (pattern.value === void 0 && message.attributes.length === 1) return splice(text, withBlankLines(text, message.block), "");
170
+ return splice(text, existing.lines, "");
171
+ }
172
+ function applyOp(text, op, path) {
173
+ const key = op.path.join(".");
174
+ const messages = read(text, path);
175
+ return op.type === "set" ? setValue(text, key, leafText(op.value), messages) : remove(text, key, messages);
176
+ }
177
+ function serialize(tree) {
178
+ const messages = /* @__PURE__ */ new Map();
179
+ for (const leaf of walkLeaves(tree)) {
180
+ const { id, attribute } = splitKey(leaf.path.join("."));
181
+ const message = messages.get(id) ?? {
182
+ value: void 0,
183
+ attributes: []
184
+ };
185
+ messages.set(id, message);
186
+ if (attribute) message.attributes.push(` .${attribute} =${encode(leafText(leaf.value), 8, "\n")}\n`);
187
+ else message.value = leafText(leaf.value);
188
+ }
189
+ return [...messages].map(([id, m]) => `${id} =${m.value === void 0 ? "" : encode(m.value, 4, "\n")}\n${m.attributes.join("")}`).join("");
190
+ }
191
+ function ftl() {
192
+ return {
193
+ name: "x18n:format-ftl",
194
+ formats: [{
195
+ id: "ftl",
196
+ extensions: ["ftl"],
197
+ parse(text, ctx) {
198
+ const tree = {};
199
+ const ranges = /* @__PURE__ */ new Map();
200
+ for (const message of read(text, ctx.path)) {
201
+ const pattern = finishPattern(text, message.pattern);
202
+ if (pattern.value !== void 0) {
203
+ tree[message.id] = pattern.value;
204
+ ranges.set(pathId([message.id]), {
205
+ key: message.idRange,
206
+ value: pattern.range
207
+ });
208
+ }
209
+ for (const attribute of message.attributes) {
210
+ const key = `${message.id}.${attribute.name}`;
211
+ const value = finishPattern(text, attribute.pattern);
212
+ tree[key] = value.value ?? "";
213
+ ranges.set(pathId([key]), {
214
+ key: attribute.nameRange,
215
+ value: value.range
216
+ });
217
+ }
218
+ }
219
+ return {
220
+ tree,
221
+ ranges
222
+ };
223
+ },
224
+ edit(text, ops, ctx) {
225
+ let next = text;
226
+ for (const op of ops) next = applyOp(next, op, ctx.path);
227
+ return diffText(text, next);
228
+ },
229
+ serialize
230
+ }]
231
+ };
232
+ }
233
+ //#endregion
234
+ export { ftl as default, ftl };
@@ -0,0 +1,5 @@
1
+ import { Plugin } from "@x18n/core";
2
+ //#region src/formats/ini/index.d.ts
3
+ export declare function ini(): Plugin;
4
+ //#endregion
5
+ export { ini as default };