@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.
- package/dist/angular-templates-DqIOGfzT.mjs +8 -0
- package/dist/checks.d.mts +1 -0
- package/dist/checks.mjs +26 -1
- package/dist/extractors.mjs +7 -3
- package/dist/finders.d.mts +4 -1
- package/dist/finders.mjs +1 -1
- package/dist/formats/ecmascript.d.mts +5 -0
- package/dist/formats/ecmascript.mjs +232 -0
- package/dist/formats/ftl.d.mts +5 -0
- package/dist/formats/ftl.mjs +234 -0
- package/dist/formats/ini.d.mts +5 -0
- package/dist/formats/ini.mjs +158 -0
- package/dist/formats/json.d.mts +6 -0
- package/dist/formats/json.mjs +10 -0
- package/dist/formats/json5.d.mts +5 -0
- package/dist/formats/json5.mjs +143 -0
- package/dist/formats/jsonc.d.mts +6 -0
- package/dist/formats/jsonc.mjs +10 -0
- package/dist/formats/php.d.mts +5 -0
- package/dist/formats/php.mjs +198 -0
- package/dist/formats/po.d.mts +5 -0
- package/dist/formats/po.mjs +177 -0
- package/dist/formats/properties.d.mts +5 -0
- package/dist/formats/properties.mjs +123 -0
- package/dist/formats/resx.d.mts +6 -0
- package/dist/formats/resx.mjs +202 -0
- package/dist/formats/yaml.d.mts +5 -0
- package/dist/formats/yaml.mjs +140 -0
- package/dist/formats.d.mts +10 -34
- package/dist/formats.mjs +61 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +30 -22
- package/dist/{vue-CF4pe-Yr.mjs → js-CV-ECNJG.mjs} +67 -222
- package/dist/json-CGNSGS97.mjs +109 -0
- package/dist/json-CyjI8bjp.d.mts +8 -0
- package/dist/jsx-templates-B1J0no3i.mjs +17 -0
- package/dist/literal-gIdB1VGy.mjs +125 -0
- package/dist/locale-root-C_rch3IQ.mjs +15 -0
- package/dist/manifests.d.mts +8 -0
- package/dist/manifests.mjs +66 -2
- package/dist/placeholders.d.mts +11 -0
- package/dist/placeholders.mjs +28 -1
- package/dist/plugin-Cb1b0axD.mjs +83 -0
- package/dist/plural-forms-B7_QX4nG.mjs +12 -0
- package/dist/presets/cargo-i18n.d.mts +6 -0
- package/dist/presets/cargo-i18n.mjs +28 -0
- package/dist/presets/chrome-extension.d.mts +6 -0
- package/dist/presets/chrome-extension.mjs +52 -0
- package/dist/presets/dotnet-resx.d.mts +6 -0
- package/dist/presets/dotnet-resx.mjs +64 -0
- package/dist/presets/easy-localization.d.mts +6 -0
- package/dist/presets/easy-localization.mjs +36 -0
- package/dist/presets/ember-intl.d.mts +5 -0
- package/dist/presets/ember-intl.mjs +31 -0
- package/dist/presets/fluent-react.d.mts +5 -0
- package/dist/presets/fluent-react.mjs +46 -0
- package/dist/presets/fluent-vue.d.mts +5 -0
- package/dist/presets/fluent-vue.mjs +51 -0
- package/dist/presets/flutter-translate.d.mts +6 -0
- package/dist/presets/flutter-translate.mjs +30 -0
- package/dist/presets/flutter.d.mts +6 -0
- package/dist/presets/flutter.mjs +21 -0
- package/dist/presets/general.d.mts +6 -0
- package/dist/presets/general.mjs +17 -0
- package/dist/presets/globalize.d.mts +5 -0
- package/dist/presets/globalize.mjs +25 -0
- package/dist/presets/gravity-ui-i18n.d.mts +6 -0
- package/dist/presets/gravity-ui-i18n.mjs +41 -0
- package/dist/presets/i18n-tag.d.mts +5 -0
- package/dist/presets/i18n-tag.mjs +21 -0
- package/dist/presets/i18next-shopify.d.mts +6 -0
- package/dist/presets/i18next-shopify.mjs +23 -0
- package/dist/presets/i18next.d.mts +5 -0
- package/dist/presets/i18next.mjs +10 -0
- package/dist/presets/i18nice.d.mts +6 -0
- package/dist/presets/i18nice.mjs +29 -0
- package/dist/presets/jekyll.d.mts +5 -0
- package/dist/presets/jekyll.mjs +23 -0
- package/dist/presets/laravel-vue-i18n.d.mts +6 -0
- package/dist/presets/laravel-vue-i18n.mjs +54 -0
- package/dist/presets/laravel.d.mts +6 -0
- package/dist/presets/laravel.mjs +23 -0
- package/dist/presets/lingui.d.mts +5 -0
- package/dist/presets/lingui.mjs +61 -0
- package/dist/presets/next-international.d.mts +5 -0
- package/dist/presets/next-international.mjs +27 -0
- package/dist/presets/next-intl.d.mts +5 -0
- package/dist/presets/next-intl.mjs +55 -0
- package/dist/presets/next-translate.d.mts +5 -0
- package/dist/presets/next-translate.mjs +53 -0
- package/dist/presets/ngx-translate.d.mts +5 -0
- package/dist/presets/ngx-translate.mjs +41 -0
- package/dist/presets/nuxt-i18n-micro.d.mts +5 -0
- package/dist/presets/nuxt-i18n-micro.mjs +81 -0
- package/dist/presets/nuxt-i18n.d.mts +5 -0
- package/dist/presets/nuxt-i18n.mjs +23 -0
- package/dist/presets/paraglide.d.mts +5 -0
- package/dist/presets/paraglide.mjs +38 -0
- package/dist/presets/php-gettext.d.mts +6 -0
- package/dist/presets/php-gettext.mjs +27 -0
- package/dist/presets/php-joomla.d.mts +6 -0
- package/dist/presets/php-joomla.mjs +22 -0
- package/dist/presets/polyglot.d.mts +5 -0
- package/dist/presets/polyglot.mjs +23 -0
- package/dist/presets/python-gettext.d.mts +6 -0
- package/dist/presets/python-gettext.mjs +44 -0
- package/dist/presets/react-i18next.d.mts +5 -0
- package/dist/presets/react-i18next.mjs +67 -0
- package/dist/presets/react-intl.d.mts +5 -0
- package/dist/presets/react-intl.mjs +47 -0
- package/dist/presets/ruby-i18n-js.d.mts +5 -0
- package/dist/presets/ruby-i18n-js.mjs +19 -0
- package/dist/presets/ruby-rails.d.mts +7 -0
- package/dist/presets/ruby-rails.mjs +44 -0
- package/dist/presets/solid-primitives-i18n.d.mts +5 -0
- package/dist/presets/solid-primitives-i18n.mjs +29 -0
- package/dist/presets/svelte-i18n.d.mts +5 -0
- package/dist/presets/svelte-i18n.mjs +33 -0
- package/dist/presets/svelte-i18next.d.mts +5 -0
- package/dist/presets/svelte-i18next.mjs +27 -0
- package/dist/presets/transloco.d.mts +5 -0
- package/dist/presets/transloco.mjs +44 -0
- package/dist/presets/typesafe-i18n.d.mts +6 -0
- package/dist/presets/typesafe-i18n.mjs +61 -0
- package/dist/presets/ui5.d.mts +5 -0
- package/dist/presets/ui5.mjs +35 -0
- package/dist/presets/vscode-extension.d.mts +5 -0
- package/dist/presets/vscode-extension.mjs +53 -0
- package/dist/presets/vue-i18n.d.mts +5 -0
- package/dist/presets/vue-i18n.mjs +59 -0
- package/dist/presets.d.mts +7 -54
- package/dist/presets.mjs +346 -1
- package/dist/reporters/github.d.mts +6 -0
- package/dist/reporters/github.mjs +27 -0
- package/dist/reporters/json.d.mts +6 -0
- package/dist/reporters/json.mjs +24 -0
- package/dist/reporters/markdown.d.mts +6 -0
- package/dist/reporters/markdown.mjs +37 -0
- package/dist/reporters/pretty.d.mts +6 -0
- package/dist/reporters/pretty.mjs +64 -0
- package/dist/reporters/sarif.d.mts +6 -0
- package/dist/reporters/sarif.mjs +48 -0
- package/dist/reporters.d.mts +5 -12
- package/dist/reporters.mjs +8 -218
- package/dist/reviews.d.mts +6 -0
- package/dist/reviews.mjs +35 -0
- package/dist/scanners/astro.d.mts +6 -0
- package/dist/scanners/astro.mjs +256 -0
- package/dist/scanners/js.d.mts +6 -0
- package/dist/scanners/js.mjs +33 -0
- package/dist/scanners/regex-scopes.d.mts +14 -0
- package/dist/scanners/regex-scopes.mjs +41 -0
- package/dist/scanners/regex.d.mts +9 -0
- package/dist/{regex-AYVhuSb6.mjs → scanners/regex.mjs} +2 -40
- package/dist/scanners/svelte.d.mts +6 -0
- package/dist/{scanners-DhnnVp7s.mjs → scanners/svelte.mjs} +3 -3
- package/dist/scanners/vue.d.mts +6 -0
- package/dist/scanners/vue.mjs +172 -0
- package/dist/scanners.d.mts +9 -50
- package/dist/scanners.mjs +36 -4
- package/dist/sfc-blocks-DaGKDZsb.mjs +213 -0
- package/dist/shared-C7ceptwi.mjs +55 -0
- package/dist/shared-D-tbsYmc.mjs +30 -0
- package/dist/shared-D4NJBKEP.mjs +17 -0
- package/dist/shared-Tq06G0gp.mjs +6 -0
- package/dist/shared-meYeVckh.mjs +11 -0
- package/dist/vue-compiler-0sfLIiiL.mjs +8 -0
- package/package.json +68 -3
- package/dist/formats-CFkM5co0.mjs +0 -1370
- package/dist/presets-Co5SlnKr.mjs +0 -1187
- package/dist/sfc-blocks-BqQ8m9rX.mjs +0 -161
- package/dist/yaml-plugin.d.mts +0 -8
- package/dist/yaml-plugin.mjs +0 -68
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 };
|
package/dist/extractors.mjs
CHANGED
|
@@ -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) =>
|
|
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 = {}) {
|
package/dist/finders.d.mts
CHANGED
|
@@ -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
|
-
/**
|
|
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-
|
|
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,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,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 };
|