@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
|
@@ -0,0 +1,177 @@
|
|
|
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/po/index.ts
|
|
4
|
+
/** gettext joins msgctxt and msgid with EOT. */
|
|
5
|
+
const CONTEXT_SEPARATOR = "";
|
|
6
|
+
const decodings = {
|
|
7
|
+
n: "\n",
|
|
8
|
+
t: " ",
|
|
9
|
+
r: "\r",
|
|
10
|
+
a: "\x07",
|
|
11
|
+
b: "\b",
|
|
12
|
+
f: "\f",
|
|
13
|
+
v: "\v"
|
|
14
|
+
};
|
|
15
|
+
const encodings = {
|
|
16
|
+
"\\": "\\\\",
|
|
17
|
+
"\"": "\\\"",
|
|
18
|
+
"\n": "\\n",
|
|
19
|
+
" ": "\\t",
|
|
20
|
+
"\r": "\\r"
|
|
21
|
+
};
|
|
22
|
+
/** Reads one `"..."` string starting at `start`; returns its value and the offset after the closing quote. */
|
|
23
|
+
function readString(text, start, end, fail) {
|
|
24
|
+
if (text[start] !== "\"") fail("expected a string");
|
|
25
|
+
let value = "";
|
|
26
|
+
for (let i = start + 1; i < end; i++) {
|
|
27
|
+
const char = text[i];
|
|
28
|
+
if (char === "\"") {
|
|
29
|
+
if (text.slice(i + 1, end).trim() !== "") fail("unexpected text after string");
|
|
30
|
+
return {
|
|
31
|
+
value,
|
|
32
|
+
end: i + 1
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
if (char !== "\\") {
|
|
36
|
+
value += char;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
const escape = /^(?:[0-7]{1,3}|x[0-9a-f]{1,2}|[\s\S])/i.exec(text.slice(i + 1, end))?.[0] ?? "";
|
|
40
|
+
value += /^[0-7]/.test(escape) ? String.fromCharCode(Number.parseInt(escape, 8)) : escape.length > 1 ? String.fromCharCode(Number.parseInt(escape.slice(1), 16)) : decodings[escape] ?? escape;
|
|
41
|
+
i += escape.length;
|
|
42
|
+
}
|
|
43
|
+
return fail("unterminated string");
|
|
44
|
+
}
|
|
45
|
+
function toEntry(text, draft, fail) {
|
|
46
|
+
const field = (name) => draft.fields.find((f) => f.name === name);
|
|
47
|
+
const msgid = field("msgid");
|
|
48
|
+
if (!msgid) return draft.fields.length > 0 ? fail("entry without msgid") : void 0;
|
|
49
|
+
const msgctxt = field("msgctxt");
|
|
50
|
+
const msgstr = field("msgstr") ?? field("msgstr[0]");
|
|
51
|
+
if (!msgstr) return fail(`msgid "${msgid.value}" without msgstr`);
|
|
52
|
+
if (msgid.value === "" && !msgctxt) return void 0;
|
|
53
|
+
const inner = (f) => ({
|
|
54
|
+
start: f.range.start + 1,
|
|
55
|
+
end: f.range.end - 1
|
|
56
|
+
});
|
|
57
|
+
return {
|
|
58
|
+
key: msgctxt ? `${msgctxt.value}${CONTEXT_SEPARATOR}${msgid.value}` : msgid.value,
|
|
59
|
+
value: msgstr.value,
|
|
60
|
+
range: {
|
|
61
|
+
key: inner(msgid),
|
|
62
|
+
value: inner(msgstr)
|
|
63
|
+
},
|
|
64
|
+
source: msgstr.range,
|
|
65
|
+
block: withBlankLines(text, {
|
|
66
|
+
start: draft.start,
|
|
67
|
+
end: draft.end
|
|
68
|
+
})
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function read(text, path) {
|
|
72
|
+
const entries = [];
|
|
73
|
+
let lineNumber = 0;
|
|
74
|
+
const fail = (message) => {
|
|
75
|
+
throw errors.X18N2002({
|
|
76
|
+
path,
|
|
77
|
+
line: lineNumber,
|
|
78
|
+
reason: message
|
|
79
|
+
});
|
|
80
|
+
};
|
|
81
|
+
let draft;
|
|
82
|
+
let field;
|
|
83
|
+
const flush = () => {
|
|
84
|
+
const entry = draft && toEntry(text, draft, fail);
|
|
85
|
+
if (entry) entries.push(entry);
|
|
86
|
+
draft = void 0;
|
|
87
|
+
field = void 0;
|
|
88
|
+
};
|
|
89
|
+
for (const line of splitLines(text)) {
|
|
90
|
+
lineNumber++;
|
|
91
|
+
const offset = line.start + /^\s*/.exec(text.slice(line.start, line.end))[0].length;
|
|
92
|
+
const content = text.slice(offset, line.end);
|
|
93
|
+
const keyword = /^(msgctxt|msgid_plural|msgid|msgstr(?:\[\d+\])?)\s*/.exec(content);
|
|
94
|
+
if (content === "" || content.startsWith("#") && draft?.fields.some((f) => f.name.startsWith("msgstr"))) flush();
|
|
95
|
+
if (content === "") continue;
|
|
96
|
+
if (keyword && (keyword[1] === "msgctxt" || keyword[1] === "msgid") && draft?.fields.some((f) => f.name !== "msgctxt")) flush();
|
|
97
|
+
draft ??= {
|
|
98
|
+
start: line.start,
|
|
99
|
+
end: line.next,
|
|
100
|
+
fields: []
|
|
101
|
+
};
|
|
102
|
+
draft.end = line.next;
|
|
103
|
+
if (content.startsWith("#")) {
|
|
104
|
+
field = void 0;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
const stringStart = keyword ? offset + keyword[0].length : offset;
|
|
108
|
+
if (!keyword && (!field || !content.startsWith("\""))) fail(`unexpected "${content.slice(0, 20)}"`);
|
|
109
|
+
const string = readString(text, stringStart, line.end, fail);
|
|
110
|
+
if (keyword) {
|
|
111
|
+
field = {
|
|
112
|
+
name: keyword[1],
|
|
113
|
+
range: {
|
|
114
|
+
start: stringStart,
|
|
115
|
+
end: string.end
|
|
116
|
+
},
|
|
117
|
+
value: string.value
|
|
118
|
+
};
|
|
119
|
+
draft.fields.push(field);
|
|
120
|
+
} else {
|
|
121
|
+
field.range.end = string.end;
|
|
122
|
+
field.value += string.value;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
flush();
|
|
126
|
+
return entries;
|
|
127
|
+
}
|
|
128
|
+
const quote = (value) => `"${value.replace(/[\\"\n\t\r]/g, (char) => encodings[char])}"`;
|
|
129
|
+
/** One string, or gettext's style of an empty first string and one string per line. */
|
|
130
|
+
function encode(value, eol) {
|
|
131
|
+
if (!value.slice(0, -1).includes("\n")) return quote(value);
|
|
132
|
+
return ["\"\"", ...value.split(/(?<=\n)/).map(quote)].join(eol);
|
|
133
|
+
}
|
|
134
|
+
function entryText(key, value) {
|
|
135
|
+
const index = key.indexOf(CONTEXT_SEPARATOR);
|
|
136
|
+
return `${index === -1 ? "" : `msgctxt ${quote(key.slice(0, index))}\n`}msgid ${encode(key.slice(index + 1), "\n")}\nmsgstr ${encode(value, "\n")}\n`;
|
|
137
|
+
}
|
|
138
|
+
function applyOp(text, op, path) {
|
|
139
|
+
const key = op.path.join(".");
|
|
140
|
+
const entry = read(text, path).findLast((e) => e.key === key);
|
|
141
|
+
if (op.type === "delete") return entry ? splice(text, entry.block, "") : text;
|
|
142
|
+
const value = leafText(op.value);
|
|
143
|
+
if (entry) return splice(text, entry.source, encode(value, detectEol(text)));
|
|
144
|
+
const separated = text.trim() === "" || /\n[ \t]*\r?\n$/.test(text);
|
|
145
|
+
return appendLines(text, `${separated ? "" : "\n"}${entryText(key, value)}`);
|
|
146
|
+
}
|
|
147
|
+
function po() {
|
|
148
|
+
return {
|
|
149
|
+
name: "x18n:format-po",
|
|
150
|
+
formats: [{
|
|
151
|
+
id: "po",
|
|
152
|
+
extensions: ["po", "pot"],
|
|
153
|
+
parse(text, ctx) {
|
|
154
|
+
const tree = {};
|
|
155
|
+
const ranges = /* @__PURE__ */ new Map();
|
|
156
|
+
for (const entry of read(text, ctx.path)) {
|
|
157
|
+
tree[entry.key] = entry.value;
|
|
158
|
+
ranges.set(pathId([entry.key]), entry.range);
|
|
159
|
+
}
|
|
160
|
+
return {
|
|
161
|
+
tree,
|
|
162
|
+
ranges
|
|
163
|
+
};
|
|
164
|
+
},
|
|
165
|
+
edit(text, ops, ctx) {
|
|
166
|
+
let next = text;
|
|
167
|
+
for (const op of ops) next = applyOp(next, op, ctx.path);
|
|
168
|
+
return diffText(text, next);
|
|
169
|
+
},
|
|
170
|
+
serialize(tree, ctx) {
|
|
171
|
+
return [`msgid ""\nmsgstr ""\n"Language: ${ctx.locale}\\n"\n"MIME-Version: 1.0\\n"\n"Content-Type: text/plain; charset=UTF-8\\n"\n"Content-Transfer-Encoding: 8bit\\n"\n`, ...[...walkLeaves(tree)].map((leaf) => entryText(leaf.path.join("."), leafText(leaf.value)))].join("\n");
|
|
172
|
+
}
|
|
173
|
+
}]
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
//#endregion
|
|
177
|
+
export { po as default, po };
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { a as splice, i as leafText, o as splitLines, t as appendLines } from "../shared-C7ceptwi.mjs";
|
|
2
|
+
import { diffText, errors, pathId, walkLeaves } from "@x18n/core";
|
|
3
|
+
//#region src/formats/properties/index.ts
|
|
4
|
+
const escapes = {
|
|
5
|
+
t: " ",
|
|
6
|
+
n: "\n",
|
|
7
|
+
r: "\r",
|
|
8
|
+
f: "\f"
|
|
9
|
+
};
|
|
10
|
+
const isSpace = (char) => char === " " || char === " " || char === "\f";
|
|
11
|
+
function unescape(raw, path) {
|
|
12
|
+
let out = "";
|
|
13
|
+
for (let i = 0; i < raw.length; i++) {
|
|
14
|
+
const char = raw[i];
|
|
15
|
+
if (char !== "\\") {
|
|
16
|
+
out += char;
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
const next = raw[++i];
|
|
20
|
+
if (next === void 0) break;
|
|
21
|
+
if (next === "\r" || next === "\n") {
|
|
22
|
+
if (next === "\r" && raw[i + 1] === "\n") i++;
|
|
23
|
+
while (isSpace(raw[i + 1])) i++;
|
|
24
|
+
} else if (next === "u") {
|
|
25
|
+
const hex = raw.slice(i + 1, i + 5);
|
|
26
|
+
if (!/^[0-9a-f]{4}$/i.test(hex)) throw errors.X18N2002({
|
|
27
|
+
path,
|
|
28
|
+
reason: `malformed \\u escape "\\u${hex}"`
|
|
29
|
+
});
|
|
30
|
+
out += String.fromCharCode(Number.parseInt(hex, 16));
|
|
31
|
+
i += 4;
|
|
32
|
+
} else out += escapes[next] ?? next;
|
|
33
|
+
}
|
|
34
|
+
return out;
|
|
35
|
+
}
|
|
36
|
+
function read(text, path) {
|
|
37
|
+
const entries = [];
|
|
38
|
+
const lines = splitLines(text);
|
|
39
|
+
for (let i = 0; i < lines.length; i++) {
|
|
40
|
+
let p = lines[i].start;
|
|
41
|
+
const lineStart = p;
|
|
42
|
+
while (isSpace(text[p])) p++;
|
|
43
|
+
if (p >= lines[i].end || text[p] === "#" || text[p] === "!") continue;
|
|
44
|
+
while (i < lines.length - 1 && /(?:^|[^\\])(?:\\\\)*\\$/.test(text.slice(lines[i].start, lines[i].end))) i++;
|
|
45
|
+
const end = lines[i].end;
|
|
46
|
+
const keyStart = p;
|
|
47
|
+
while (p < end && !"=: \f".includes(text[p])) p += text[p] === "\\" ? 2 : 1;
|
|
48
|
+
const keyEnd = Math.min(p, end);
|
|
49
|
+
while (isSpace(text[p])) p++;
|
|
50
|
+
if (text[p] === "=" || text[p] === ":") p++;
|
|
51
|
+
while (isSpace(text[p])) p++;
|
|
52
|
+
const valueStart = Math.min(p, end);
|
|
53
|
+
entries.push({
|
|
54
|
+
key: unescape(text.slice(keyStart, keyEnd), path),
|
|
55
|
+
value: unescape(text.slice(valueStart, end), path),
|
|
56
|
+
range: {
|
|
57
|
+
key: {
|
|
58
|
+
start: keyStart,
|
|
59
|
+
end: keyEnd
|
|
60
|
+
},
|
|
61
|
+
value: {
|
|
62
|
+
start: valueStart,
|
|
63
|
+
end
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
line: {
|
|
67
|
+
start: lineStart,
|
|
68
|
+
end: lines[i].next
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
return entries;
|
|
73
|
+
}
|
|
74
|
+
const encodings = {
|
|
75
|
+
"\\": "\\\\",
|
|
76
|
+
" ": "\\t",
|
|
77
|
+
"\n": "\\n",
|
|
78
|
+
"\r": "\\r",
|
|
79
|
+
"\f": "\\f"
|
|
80
|
+
};
|
|
81
|
+
const escapeText = (text) => text.replace(/[\\\n\r\t\f]/g, (char) => encodings[char]);
|
|
82
|
+
const escapeKey = (key) => escapeText(key).replace(/[=: #!]/g, "\\$&");
|
|
83
|
+
const escapeValue = (value) => escapeText(value).replace(/^ /, "\\ ");
|
|
84
|
+
function applyOp(text, op, path) {
|
|
85
|
+
const key = op.path.join(".");
|
|
86
|
+
const entries = read(text, path);
|
|
87
|
+
const entry = entries.findLast((e) => e.key === key);
|
|
88
|
+
if (op.type === "delete") return entry ? splice(text, entry.line, "") : text;
|
|
89
|
+
const value = escapeValue(leafText(op.value));
|
|
90
|
+
if (entry) return splice(text, entry.range.value, value);
|
|
91
|
+
const last = entries.findLast((e) => e.value !== "");
|
|
92
|
+
const separator = last ? text.slice(last.range.key.end, last.range.value.start) : "";
|
|
93
|
+
return appendLines(text, `${escapeKey(key)}${/[=:\s]/.test(separator) ? separator : "="}${value}\n`);
|
|
94
|
+
}
|
|
95
|
+
function properties() {
|
|
96
|
+
return {
|
|
97
|
+
name: "x18n:format-properties",
|
|
98
|
+
formats: [{
|
|
99
|
+
id: "properties",
|
|
100
|
+
extensions: ["properties"],
|
|
101
|
+
parse(text, ctx) {
|
|
102
|
+
const tree = {};
|
|
103
|
+
const ranges = /* @__PURE__ */ new Map();
|
|
104
|
+
for (const entry of read(text, ctx.path)) {
|
|
105
|
+
tree[entry.key] = entry.value;
|
|
106
|
+
ranges.set(pathId([entry.key]), entry.range);
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
tree,
|
|
110
|
+
ranges
|
|
111
|
+
};
|
|
112
|
+
},
|
|
113
|
+
edit(text, ops, ctx) {
|
|
114
|
+
let next = text;
|
|
115
|
+
for (const op of ops) next = applyOp(next, op, ctx.path);
|
|
116
|
+
return diffText(text, next);
|
|
117
|
+
},
|
|
118
|
+
serialize: (tree) => [...walkLeaves(tree)].map((leaf) => `${escapeKey(leaf.path.join("."))}=${escapeValue(leafText(leaf.value))}\n`).join("")
|
|
119
|
+
}]
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
//#endregion
|
|
123
|
+
export { properties as default, properties };
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { a as splice, i as leafText, n as detectEol } from "../shared-C7ceptwi.mjs";
|
|
2
|
+
import { diffText, errors, pathId, walkLeaves } from "@x18n/core";
|
|
3
|
+
//#region src/formats/resx/index.ts
|
|
4
|
+
const entities = {
|
|
5
|
+
lt: "<",
|
|
6
|
+
gt: ">",
|
|
7
|
+
amp: "&",
|
|
8
|
+
quot: "\"",
|
|
9
|
+
apos: "'"
|
|
10
|
+
};
|
|
11
|
+
function decode(raw) {
|
|
12
|
+
return raw.replace(/\r\n?/g, "\n").replace(/<!\[CDATA\[([\s\S]*?)\]\]>|&(#x[\da-f]+|#\d+|\w+);/gi, (match, cdata, name) => {
|
|
13
|
+
if (name === void 0) return cdata ?? match;
|
|
14
|
+
if (name.startsWith("#")) {
|
|
15
|
+
const code = name[1] === "x" ? Number.parseInt(name.slice(2), 16) : Number.parseInt(name.slice(1), 10);
|
|
16
|
+
return code <= 1114111 ? String.fromCodePoint(code) : match;
|
|
17
|
+
}
|
|
18
|
+
return entities[name] ?? match;
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
const escapeText = (text) => text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
22
|
+
const escapeAttribute = (text) => escapeText(text).replace(/"/g, """);
|
|
23
|
+
const isSpace = (char) => char === " " || char === " ";
|
|
24
|
+
/** The range with its indent and line break when it stands on a line of its own. */
|
|
25
|
+
function lineOf(text, range) {
|
|
26
|
+
let start = range.start;
|
|
27
|
+
while (isSpace(text[start - 1])) start--;
|
|
28
|
+
let end = range.end;
|
|
29
|
+
while (isSpace(text[end])) end++;
|
|
30
|
+
if (text[end] === "\r") end++;
|
|
31
|
+
if (text[end] === "\n") end++;
|
|
32
|
+
return (start === 0 || text[start - 1] === "\n") && (end === text.length || text[end - 1] === "\n") ? {
|
|
33
|
+
start,
|
|
34
|
+
end
|
|
35
|
+
} : range;
|
|
36
|
+
}
|
|
37
|
+
/** Comments and CDATA are skipped so the schema sample in the standard header is not read as data. */
|
|
38
|
+
const tokens = /<!--[\s\S]*?-->|<!\[CDATA\[[\s\S]*?\]\]>|<data\b((?:[^>"']|"[^"]*"|'[^']*')*)>/g;
|
|
39
|
+
/** The `<value>` inside a `<data>` whose content starts at `offset`. */
|
|
40
|
+
function valueIn(inner, offset) {
|
|
41
|
+
const match = /<value\s*\/>|<value\s*>([\s\S]*?)<\/value\s*>/d.exec(inner);
|
|
42
|
+
if (!match) return {
|
|
43
|
+
value: "",
|
|
44
|
+
body: void 0,
|
|
45
|
+
emptyValue: void 0
|
|
46
|
+
};
|
|
47
|
+
const [start, end] = match.indices[0];
|
|
48
|
+
const body = match.indices[1];
|
|
49
|
+
if (!body) return {
|
|
50
|
+
value: "",
|
|
51
|
+
body: void 0,
|
|
52
|
+
emptyValue: {
|
|
53
|
+
start: offset + start,
|
|
54
|
+
end: offset + end
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
return {
|
|
58
|
+
value: decode(match[1]),
|
|
59
|
+
body: {
|
|
60
|
+
start: offset + body[0],
|
|
61
|
+
end: offset + body[1]
|
|
62
|
+
},
|
|
63
|
+
emptyValue: void 0
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function read(text, path) {
|
|
67
|
+
const entries = [];
|
|
68
|
+
const objects = /* @__PURE__ */ new Set();
|
|
69
|
+
const closing = /<\/data\s*>/g;
|
|
70
|
+
tokens.lastIndex = 0;
|
|
71
|
+
for (let match = tokens.exec(text); match; match = tokens.exec(text)) {
|
|
72
|
+
const attributes = match[1];
|
|
73
|
+
if (attributes === void 0) continue;
|
|
74
|
+
const start = match.index;
|
|
75
|
+
const open = start + match[0].length;
|
|
76
|
+
const selfClosing = attributes.endsWith("/");
|
|
77
|
+
closing.lastIndex = open;
|
|
78
|
+
const close = selfClosing ? void 0 : closing.exec(text);
|
|
79
|
+
if (close === null) throw errors.X18N2002({
|
|
80
|
+
path,
|
|
81
|
+
reason: "unclosed <data> element"
|
|
82
|
+
});
|
|
83
|
+
const end = close ? close.index + close[0].length : open;
|
|
84
|
+
tokens.lastIndex = end;
|
|
85
|
+
const name = /\sname\s*=\s*(?:"([^"]*)"|'([^']*)')/d.exec(attributes);
|
|
86
|
+
if (!name) continue;
|
|
87
|
+
const key = decode(name[1] ?? name[2]);
|
|
88
|
+
if (/\s(?:type|mimetype)\s*=/.test(attributes)) {
|
|
89
|
+
objects.add(key);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const nameAt = name.indices[1] ?? name.indices[2];
|
|
93
|
+
const inner = close ? text.slice(open, close.index) : "";
|
|
94
|
+
const { value, body, emptyValue } = valueIn(inner, open);
|
|
95
|
+
const line = lineOf(text, {
|
|
96
|
+
start,
|
|
97
|
+
end
|
|
98
|
+
});
|
|
99
|
+
const keyOffset = start + 5;
|
|
100
|
+
entries.push({
|
|
101
|
+
key,
|
|
102
|
+
value,
|
|
103
|
+
range: {
|
|
104
|
+
key: {
|
|
105
|
+
start: keyOffset + nameAt[0],
|
|
106
|
+
end: keyOffset + nameAt[1]
|
|
107
|
+
},
|
|
108
|
+
value: body ?? emptyValue ?? {
|
|
109
|
+
start: open,
|
|
110
|
+
end: open
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
body,
|
|
114
|
+
emptyValue,
|
|
115
|
+
content: close && inner.trim() ? {
|
|
116
|
+
start: open,
|
|
117
|
+
end: close.index
|
|
118
|
+
} : void 0,
|
|
119
|
+
element: {
|
|
120
|
+
start,
|
|
121
|
+
end
|
|
122
|
+
},
|
|
123
|
+
line,
|
|
124
|
+
indent: line.start < start ? text.slice(line.start, start) : ""
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
entries,
|
|
129
|
+
objects
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function dataElement(key, value, indent, eol) {
|
|
133
|
+
return `<data name="${escapeAttribute(key)}" xml:space="preserve">${eol}${indent.repeat(2)}<value>${value}</value>${eol}${indent}</data>`;
|
|
134
|
+
}
|
|
135
|
+
function applyOp(text, op, path) {
|
|
136
|
+
const key = op.path.join(".");
|
|
137
|
+
const { entries, objects } = read(text, path);
|
|
138
|
+
const entry = entries.findLast((e) => e.key === key);
|
|
139
|
+
if (op.type === "delete") return entry ? splice(text, entry.line, "") : text;
|
|
140
|
+
if (!entry && objects.has(key)) throw errors.X18N4008({ reason: `"${key}" in ${path} holds a file or object, not a string` });
|
|
141
|
+
const eol = detectEol(text);
|
|
142
|
+
const value = escapeText(leafText(op.value)).replace(/\n/g, eol);
|
|
143
|
+
if (entry?.body) return splice(text, entry.body, value);
|
|
144
|
+
if (entry?.emptyValue) return splice(text, entry.emptyValue, `<value>${value}</value>`);
|
|
145
|
+
if (entry?.content) {
|
|
146
|
+
const lead = /^\s*/.exec(text.slice(entry.content.start, entry.content.end))[0];
|
|
147
|
+
return splice(text, {
|
|
148
|
+
start: entry.content.start,
|
|
149
|
+
end: entry.content.start
|
|
150
|
+
}, `${lead}<value>${value}</value>`);
|
|
151
|
+
}
|
|
152
|
+
if (entry) return splice(text, entry.element, dataElement(key, value, entry.indent, eol));
|
|
153
|
+
const root = text.lastIndexOf("</root>");
|
|
154
|
+
if (root === -1) throw errors.X18N2002({
|
|
155
|
+
path,
|
|
156
|
+
reason: "missing </root>"
|
|
157
|
+
});
|
|
158
|
+
const indent = entries.at(-1)?.indent ?? [...text.matchAll(/^([ \t]*)<resheader\b/gm)].at(-1)?.[1] ?? " ";
|
|
159
|
+
let at = root;
|
|
160
|
+
while (isSpace(text[at - 1])) at--;
|
|
161
|
+
const lead = at === 0 || text[at - 1] === "\n" ? "" : eol;
|
|
162
|
+
return splice(text, {
|
|
163
|
+
start: at,
|
|
164
|
+
end: at
|
|
165
|
+
}, `${lead}${indent}${dataElement(key, value, indent, eol)}${eol}`);
|
|
166
|
+
}
|
|
167
|
+
const head = `<?xml version="1.0" encoding="utf-8"?>\n<root>\n${[
|
|
168
|
+
["resmimetype", "text/microsoft-resx"],
|
|
169
|
+
["version", "2.0"],
|
|
170
|
+
["reader", "System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"],
|
|
171
|
+
["writer", "System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"]
|
|
172
|
+
].map(([name, value]) => ` <resheader name="${name}">\n <value>${value}</value>\n </resheader>\n`).join("")}`;
|
|
173
|
+
/** .NET `.resx` files: `<data name="key"><value>text</value></data>` inside `<root>`. */
|
|
174
|
+
function resx() {
|
|
175
|
+
return {
|
|
176
|
+
name: "x18n:format-resx",
|
|
177
|
+
formats: [{
|
|
178
|
+
id: "resx",
|
|
179
|
+
extensions: ["resx"],
|
|
180
|
+
parse(text, ctx) {
|
|
181
|
+
const tree = {};
|
|
182
|
+
const ranges = /* @__PURE__ */ new Map();
|
|
183
|
+
for (const entry of read(text, ctx.path).entries) {
|
|
184
|
+
tree[entry.key] = entry.value;
|
|
185
|
+
ranges.set(pathId([entry.key]), entry.range);
|
|
186
|
+
}
|
|
187
|
+
return {
|
|
188
|
+
tree,
|
|
189
|
+
ranges
|
|
190
|
+
};
|
|
191
|
+
},
|
|
192
|
+
edit(text, ops, ctx) {
|
|
193
|
+
let next = text;
|
|
194
|
+
for (const op of ops) next = applyOp(next, op, ctx.path);
|
|
195
|
+
return diffText(text, next);
|
|
196
|
+
},
|
|
197
|
+
serialize: (tree) => `${head}${[...walkLeaves(tree)].map((leaf) => ` ${dataElement(leaf.path.join("."), escapeText(leafText(leaf.value)), " ", "\n")}\n`).join("")}</root>\n`
|
|
198
|
+
}]
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
//#endregion
|
|
202
|
+
export { resx as default, resx };
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { a as splice, s as toLeaf } from "../shared-C7ceptwi.mjs";
|
|
2
|
+
import { applyTextEdits, diffText, errors, pathId } from "@x18n/core";
|
|
3
|
+
import { Document, Scalar, isMap, isNode, isScalar, parseDocument, stringify } from "yaml";
|
|
4
|
+
//#region src/formats/yaml/index.ts
|
|
5
|
+
function scalarTextRange(node) {
|
|
6
|
+
const range = node.range;
|
|
7
|
+
if (!range) return void 0;
|
|
8
|
+
if (isScalar(node) && (node.type === Scalar.QUOTE_DOUBLE || node.type === Scalar.QUOTE_SINGLE)) return {
|
|
9
|
+
start: range[0] + 1,
|
|
10
|
+
end: range[1] - 1
|
|
11
|
+
};
|
|
12
|
+
return {
|
|
13
|
+
start: range[0],
|
|
14
|
+
end: range[1]
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function readMap(node, path, ranges) {
|
|
18
|
+
const out = {};
|
|
19
|
+
if (!isMap(node)) return out;
|
|
20
|
+
for (const pair of node.items) {
|
|
21
|
+
const keyNode = pair.key;
|
|
22
|
+
if (!isScalar(keyNode)) continue;
|
|
23
|
+
const key = String(keyNode.value);
|
|
24
|
+
const childPath = [...path, key];
|
|
25
|
+
const keyRange = scalarTextRange(keyNode);
|
|
26
|
+
const value = isNode(pair.value) ? pair.value : void 0;
|
|
27
|
+
const valueRange = value && value.range ? {
|
|
28
|
+
start: value.range[0],
|
|
29
|
+
end: value.range[1]
|
|
30
|
+
} : void 0;
|
|
31
|
+
if (keyRange) ranges.set(pathId(childPath), {
|
|
32
|
+
key: keyRange,
|
|
33
|
+
value: valueRange ?? keyRange
|
|
34
|
+
});
|
|
35
|
+
if (value === void 0) out[key] = null;
|
|
36
|
+
else if (isMap(value)) out[key] = readMap(value, childPath, ranges);
|
|
37
|
+
else if (isScalar(value)) {
|
|
38
|
+
const leaf = toLeaf(value.value);
|
|
39
|
+
if (leaf !== void 0) out[key] = leaf;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
function parse$1(text, path) {
|
|
45
|
+
const doc = parseDocument(text, {
|
|
46
|
+
keepSourceTokens: true,
|
|
47
|
+
uniqueKeys: false
|
|
48
|
+
});
|
|
49
|
+
if (doc.errors.length > 0) throw errors.X18N2002({
|
|
50
|
+
path,
|
|
51
|
+
reason: doc.errors[0].message
|
|
52
|
+
});
|
|
53
|
+
return doc;
|
|
54
|
+
}
|
|
55
|
+
function scalarText(value, type) {
|
|
56
|
+
if (typeof value !== "string") return stringify(value).trimEnd();
|
|
57
|
+
if (type === Scalar.QUOTE_DOUBLE) return JSON.stringify(value);
|
|
58
|
+
if (type === Scalar.QUOTE_SINGLE) return `'${value.replace(/'/g, "''")}'`;
|
|
59
|
+
return stringify(value, { lineWidth: 0 }).trimEnd();
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Deletes a pair of a block map by its lines, so the comments around it stay where they are. The comment at the end of
|
|
63
|
+
* its last line goes with it. Undefined when the Document API must do it.
|
|
64
|
+
*/
|
|
65
|
+
function removePair(text, doc, path) {
|
|
66
|
+
const map = path.length === 1 ? doc.contents : doc.getIn(path.slice(0, -1), true);
|
|
67
|
+
if (!isMap(map)) return text;
|
|
68
|
+
const pair = map.items.findLast((item) => isScalar(item.key) && String(item.key.value) === path.at(-1));
|
|
69
|
+
const key = pair?.key;
|
|
70
|
+
if (!pair || !isScalar(key)) return text;
|
|
71
|
+
if (map.flow || !key.range || map.items.length === 1 && path.length > 1) return void 0;
|
|
72
|
+
const start = text.lastIndexOf("\n", key.range[0] - 1) + 1;
|
|
73
|
+
if (!/^[ \t]*$/.test(text.slice(start, key.range[0]))) return void 0;
|
|
74
|
+
const valueEnd = isNode(pair.value) && pair.value.range ? pair.value.range[1] : key.range[1];
|
|
75
|
+
const end = text[valueEnd - 1] === "\n" ? valueEnd : valueEnd + /^[ \t]*(?:#.*)?(?:\r?\n)?/.exec(text.slice(valueEnd))[0].length;
|
|
76
|
+
return splice(text, {
|
|
77
|
+
start,
|
|
78
|
+
end
|
|
79
|
+
}, "");
|
|
80
|
+
}
|
|
81
|
+
function indentOf(text) {
|
|
82
|
+
return /^( +)\S/m.exec(text)?.[1]?.length ?? 2;
|
|
83
|
+
}
|
|
84
|
+
function yaml() {
|
|
85
|
+
return {
|
|
86
|
+
name: "x18n:format-yaml",
|
|
87
|
+
formats: [{
|
|
88
|
+
id: "yaml",
|
|
89
|
+
extensions: ["yml", "yaml"],
|
|
90
|
+
parse(text, ctx) {
|
|
91
|
+
const doc = parse$1(text, ctx.path);
|
|
92
|
+
const ranges = /* @__PURE__ */ new Map();
|
|
93
|
+
if (doc.contents === null) return {
|
|
94
|
+
tree: {},
|
|
95
|
+
ranges
|
|
96
|
+
};
|
|
97
|
+
if (!isMap(doc.contents)) throw errors.X18N2003({ path: ctx.path });
|
|
98
|
+
return {
|
|
99
|
+
tree: readMap(doc.contents, [], ranges),
|
|
100
|
+
ranges
|
|
101
|
+
};
|
|
102
|
+
},
|
|
103
|
+
edit(text, ops, ctx) {
|
|
104
|
+
let next = text;
|
|
105
|
+
for (const op of ops) {
|
|
106
|
+
let doc = parse$1(next, ctx.path);
|
|
107
|
+
const current = doc.getIn(op.path, true);
|
|
108
|
+
if (op.type === "set" && isScalar(current) && current.range && current.type !== Scalar.BLOCK_FOLDED && current.type !== Scalar.BLOCK_LITERAL) {
|
|
109
|
+
next = applyTextEdits(next, [{
|
|
110
|
+
range: {
|
|
111
|
+
start: current.range[0],
|
|
112
|
+
end: current.range[1]
|
|
113
|
+
},
|
|
114
|
+
text: scalarText(op.value, current.type)
|
|
115
|
+
}]);
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (op.type === "delete") {
|
|
119
|
+
const removed = removePair(next, doc, op.path);
|
|
120
|
+
if (removed !== void 0) {
|
|
121
|
+
next = removed;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (doc.contents === null) doc = new Document({});
|
|
126
|
+
if (op.type === "set") doc.setIn(op.path, op.value);
|
|
127
|
+
else doc.deleteIn(op.path);
|
|
128
|
+
next = doc.toString({
|
|
129
|
+
indent: indentOf(text),
|
|
130
|
+
lineWidth: 0
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
return diffText(text, next);
|
|
134
|
+
},
|
|
135
|
+
serialize: (tree) => Object.keys(tree).length === 0 ? "{}\n" : stringify(tree, { lineWidth: 0 })
|
|
136
|
+
}]
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
//#endregion
|
|
140
|
+
export { yaml as default, yaml };
|