@menapo/vi18n 0.0.0-stage → 0.1.1
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/LICENSE +49 -0
- package/README.md +110 -2
- package/dist/apply-Bre-lY9J.js +228 -0
- package/dist/captured-Bj8bO6vd.js +1034 -0
- package/dist/check-BIKEuu6B.js +201 -0
- package/dist/check-HsmmpJZg.js +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +41 -0
- package/dist/codec-DuvU2VQy.js +207 -0
- package/dist/collect-Cx4_SKOe.js +13 -0
- package/dist/collisions-CfpZU8e4.js +217 -0
- package/dist/compile-BaDh0cHG.js +245 -0
- package/dist/compile-Cc97JaW2.js +2502 -0
- package/dist/concurrency-C9UUWuDC.js +96 -0
- package/dist/concurrency-DONL7t4V.js +1 -0
- package/dist/config.d.ts +21 -0
- package/dist/config.js +39 -0
- package/dist/dir-zw6bGMKQ.js +290 -0
- package/dist/duplicates-yuRljQ6h.js +185 -0
- package/dist/engine-C2ENBRJe.js +531 -0
- package/dist/engine.d.ts +43 -0
- package/dist/engine.js +1241 -0
- package/dist/exclude-Du-Zd_qI.js +26 -0
- package/dist/export-C9Sy6UWv.js +103 -0
- package/dist/extract-C3S8btx0.js +298 -0
- package/dist/formats-9unqj_fZ.js +781 -0
- package/dist/i18next-9HAoxwUI.js +1 -0
- package/dist/i18next-BEJgv_S8.js +1596 -0
- package/dist/import-nANvQutw.js +225 -0
- package/dist/index.d.ts +156 -0
- package/dist/index.js +239 -0
- package/dist/init-C2ScuW6a.js +792 -0
- package/dist/library-D5CWvTQs.js +1882 -0
- package/dist/lingui-zUiCl9Um.js +833 -0
- package/dist/lint.d.ts +105 -0
- package/dist/lint.js +971 -0
- package/dist/load-CQRz4PpM.js +2950 -0
- package/dist/load-gW-oJt1Q.js +747 -0
- package/dist/locales-DX4MOBFE.js +827 -0
- package/dist/lsp-RMMMc88P.js +31 -0
- package/dist/match-BGr8EXB3.js +89 -0
- package/dist/mcp-CHVwBoRP.js +28 -0
- package/dist/merge-driver-BTX87aVm.js +422 -0
- package/dist/messages-BfjIExyB.js +166 -0
- package/dist/migrate-IpLDMXmq.js +1484 -0
- package/dist/migrate-rCQuOGsf.js +1 -0
- package/dist/openai-CTaMDcl-.js +346 -0
- package/dist/output-DWE3SIUA.js +164 -0
- package/dist/parse.d.ts +62 -0
- package/dist/parse.js +156 -0
- package/dist/pipeline-C9Dbotkh.js +2031 -0
- package/dist/pipeline-DbX6rlo5.js +1 -0
- package/dist/project-Bw9UPIdx.js +1046 -0
- package/dist/project-C4GpaN-n.js +381 -0
- package/dist/pseudo-DRv30iN5.js +145 -0
- package/dist/raw-C4Y6vyGY.js +4708 -0
- package/dist/react-intl-BP0u-D5m.js +519 -0
- package/dist/restore-CiYv-jxS.js +175 -0
- package/dist/rewrite-EsIw_lsq.js +803 -0
- package/dist/rich-DCUKl5HM.js +97 -0
- package/dist/rolldown-runtime-D7D4PA-g.js +13 -0
- package/dist/run-BEcnBbu-.js +694 -0
- package/dist/runtime-DTMmliT1.js +64 -0
- package/dist/scan-worker.d.ts +62 -0
- package/dist/scan-worker.js +1 -0
- package/dist/scoped-BMYpSthj.js +44 -0
- package/dist/server-Da1Ot9IN.js +10485 -0
- package/dist/server-deBOq2vq.js +11823 -0
- package/dist/server.d.ts +169 -0
- package/dist/server.js +1234 -0
- package/dist/sitemap-DJXzKoza.js +159 -0
- package/dist/sources-D19czDSA.js +2242 -0
- package/dist/status-zr4jY59Y.js +149 -0
- package/dist/stdio-CVaJfxA7.js +29 -0
- package/dist/stdio-FPOKQAgu.js +95 -0
- package/dist/sync-B9V2KFwb.js +658 -0
- package/dist/t-DC-c9zE5.js +454 -0
- package/dist/test.d.ts +116 -0
- package/dist/test.js +319 -0
- package/dist/translate-CQnJfLjO.js +299 -0
- package/dist/ts-BaU5yOf0.js +4863 -0
- package/dist/types-CgUCkFU1.d.ts +707 -0
- package/dist/types-D3QGM8XO.js +146 -0
- package/dist/types-DsdWBG0b.d.ts +558 -0
- package/dist/types-DtvQ7UdD.js +1948 -0
- package/dist/types-wSh9fCBY.js +11 -0
- package/dist/units-CQ5Q99RC.js +150 -0
- package/dist/url-CRfCOhUI.js +172 -0
- package/dist/url-CsBkRaJy.js +225 -0
- package/dist/validate-BxpupdAf.js +2615 -0
- package/dist/validate-D6DJepZv.js +365 -0
- package/dist/version-Byqi2guX.js +28 -0
- package/dist/vite-scan-CT_vsp4g.js +242 -0
- package/dist/vite.d.ts +34 -0
- package/dist/vite.js +7558 -0
- package/dist/vue-i18n-g0iGy6CZ.js +651 -0
- package/dist/which-BOemZ5Q8.js +116 -0
- package/dist/worker-84U6uYB3.js +1656 -0
- package/package.json +97 -3
- package/schemas/catalog.json +57 -0
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
import { o as isDiagnosticError } from "./types-D3QGM8XO.js";
|
|
2
|
+
import { n as printICU } from "./codec-DuvU2VQy.js";
|
|
3
|
+
import { l as langFromId, u as parseModule } from "./worker-84U6uYB3.js";
|
|
4
|
+
import { a as attributeExpression, b as stringValue, g as jsxNameNodes, h as jsxName, i as analyzeModule, l as identifierName, o as attributeString, p as jsxAttributes, s as childEntries, t as ModuleBuilder, u as inJsxChildren, v as memberParts, x as unwrap, y as objectProperties } from "./rewrite-EsIw_lsq.js";
|
|
5
|
+
import { a as annotateHandlers, g as formatjsId, h as formatjsAstToIcu, l as reactTypes, t as MODULE_EXTENSIONS, v as parseJson } from "./i18next-BEJgv_S8.js";
|
|
6
|
+
import { extname } from "node:path";
|
|
7
|
+
import { readFileSync } from "node:fs";
|
|
8
|
+
//#region src/cli/commands/migrate/react-intl.ts
|
|
9
|
+
/**
|
|
10
|
+
* `vi18n migrate --from react-intl` (INTERNALS §2.14): react-intl and
|
|
11
|
+
* `@formatjs/intl`.
|
|
12
|
+
*
|
|
13
|
+
* Messages are ICU already. The source text is the code's `defaultMessage`
|
|
14
|
+
* (the source catalog's text where a descriptor has none); translations are
|
|
15
|
+
* the catalogs' texts by id: an explicit `id`, else the id
|
|
16
|
+
* `babel-plugin-formatjs` generates (`[sha512:contenthash:base64:6]`).
|
|
17
|
+
* Catalogs are JSON: `{ id: "text" }`, `{ id: { defaultMessage | message |
|
|
18
|
+
* string, description } }`, or `formatjs compile --ast` output.
|
|
19
|
+
* `<FormattedMessage>` and `intl.formatMessage()` become `t()` (`rich()`
|
|
20
|
+
* when values hold elements or tag handlers), `defineMessages` /
|
|
21
|
+
* `defineMessage` become `msg()`, `description` a `context` note.
|
|
22
|
+
* `useIntl()` bindings, imports and `<IntlProvider>` go once unused.
|
|
23
|
+
*/
|
|
24
|
+
function isOldSource(source) {
|
|
25
|
+
return source === "react-intl" || source.startsWith("react-intl/") || source === "@formatjs/intl";
|
|
26
|
+
}
|
|
27
|
+
/** `fmt` counterparts of the formatting components and `intl.format*` methods. */
|
|
28
|
+
const FORMATTERS = {
|
|
29
|
+
FormattedNumber: "fmt.number(value, options)",
|
|
30
|
+
FormattedDate: "fmt.date(value, options)",
|
|
31
|
+
FormattedTime: "fmt.time(value, options)",
|
|
32
|
+
FormattedDateParts: "fmt.date(value, options)",
|
|
33
|
+
FormattedTimeParts: "fmt.time(value, options)",
|
|
34
|
+
FormattedRelativeTime: "fmt.relative(value, options)",
|
|
35
|
+
FormattedList: "fmt.list(values, options)",
|
|
36
|
+
FormattedDisplayName: "Intl.DisplayNames",
|
|
37
|
+
FormattedPlural: "an ICU plural: t(\"{n, plural, one {…} other {…}}\", { n })",
|
|
38
|
+
formatNumber: "fmt.number",
|
|
39
|
+
formatDate: "fmt.date",
|
|
40
|
+
formatTime: "fmt.time",
|
|
41
|
+
formatRelativeTime: "fmt.relative",
|
|
42
|
+
formatList: "fmt.list",
|
|
43
|
+
formatDisplayName: "Intl.DisplayNames",
|
|
44
|
+
formatPlural: "an ICU plural"
|
|
45
|
+
};
|
|
46
|
+
function createReactIntlAdapter() {
|
|
47
|
+
/** locale → id → text. */
|
|
48
|
+
const catalogs = /* @__PURE__ */ new Map();
|
|
49
|
+
const referenced = /* @__PURE__ */ new Set();
|
|
50
|
+
let sourceLocale = "";
|
|
51
|
+
const readCatalog = (file, env) => {
|
|
52
|
+
let text;
|
|
53
|
+
try {
|
|
54
|
+
text = readFileSync(file.file, "utf8");
|
|
55
|
+
} catch (error) {
|
|
56
|
+
env.report("catalog-parse", `cannot read: ${error.message}`, { file: file.file }, "error");
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const parsed = parseJson(text);
|
|
60
|
+
if (parsed.problem !== null) {
|
|
61
|
+
env.report("catalog-parse", parsed.problem.message, {
|
|
62
|
+
file: file.file,
|
|
63
|
+
line: parsed.problem.line
|
|
64
|
+
}, "error");
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const value = parsed.value;
|
|
68
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
69
|
+
env.report("catalog-parse", "a react-intl catalog is an object of messages by id", { file: file.file }, "error");
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
let texts = catalogs.get(file.locale);
|
|
73
|
+
if (texts === void 0) catalogs.set(file.locale, texts = /* @__PURE__ */ new Map());
|
|
74
|
+
const lines = text.split("\n");
|
|
75
|
+
const lineOf = (id) => {
|
|
76
|
+
const needle = `${JSON.stringify(id)}:`;
|
|
77
|
+
return lines.findIndex((l) => l.includes(needle)) + 1;
|
|
78
|
+
};
|
|
79
|
+
for (const [id, entry] of Object.entries(value)) {
|
|
80
|
+
const line = lineOf(id);
|
|
81
|
+
let message = entry;
|
|
82
|
+
let description = null;
|
|
83
|
+
if (typeof entry === "object" && entry !== null && !Array.isArray(entry)) {
|
|
84
|
+
const record = entry;
|
|
85
|
+
message = record.defaultMessage ?? record.message ?? record.string ?? record.translation;
|
|
86
|
+
description = describe(record.description);
|
|
87
|
+
}
|
|
88
|
+
if (typeof message === "string") texts.set(id, {
|
|
89
|
+
text: { icu: message },
|
|
90
|
+
description,
|
|
91
|
+
file: file.file,
|
|
92
|
+
line
|
|
93
|
+
});
|
|
94
|
+
else if (Array.isArray(message)) {
|
|
95
|
+
const nodes = formatjsAstToIcu(message);
|
|
96
|
+
texts.set(id, {
|
|
97
|
+
text: nodes === null ? {
|
|
98
|
+
unconverted: JSON.stringify(message),
|
|
99
|
+
reason: "a compiled message vi18n cannot print",
|
|
100
|
+
file: file.file,
|
|
101
|
+
line
|
|
102
|
+
} : { icu: printICU(nodes) },
|
|
103
|
+
description,
|
|
104
|
+
file: file.file,
|
|
105
|
+
line
|
|
106
|
+
});
|
|
107
|
+
} else env.report("migrate-unconverted", `${id} holds no message text; it was skipped`, {
|
|
108
|
+
file: file.file,
|
|
109
|
+
line
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
/** A message for a descriptor; its registry key, or why there is none. */
|
|
114
|
+
const define = (env, descriptor, file, line) => {
|
|
115
|
+
const named = descriptor.id !== null;
|
|
116
|
+
if (descriptor.id === null && descriptor.message === null) return { problem: "a descriptor without id or defaultMessage" };
|
|
117
|
+
const id = descriptor.id ?? formatjsId(descriptor.message, descriptor.hashed ?? descriptor.description);
|
|
118
|
+
referenced.add(id);
|
|
119
|
+
const known = env.message(id);
|
|
120
|
+
if (known !== void 0) {
|
|
121
|
+
let same = descriptor.message === null;
|
|
122
|
+
try {
|
|
123
|
+
same ||= env.codec.canonicalize(descriptor.message) === known.canonical;
|
|
124
|
+
} catch {
|
|
125
|
+
same = false;
|
|
126
|
+
}
|
|
127
|
+
if (!same) return { problem: `id ${id} has another defaultMessage at an earlier site (${JSON.stringify(known.source)})` };
|
|
128
|
+
return { key: id };
|
|
129
|
+
}
|
|
130
|
+
const fromCatalog = catalogs.get(sourceLocale)?.get(id);
|
|
131
|
+
let source = descriptor.message;
|
|
132
|
+
if (source === null) {
|
|
133
|
+
if (fromCatalog === void 0) return { problem: `${id} has no defaultMessage and is in no ${sourceLocale} catalog` };
|
|
134
|
+
if (!("icu" in fromCatalog.text)) return { problem: `${id}: ${fromCatalog.text.reason}` };
|
|
135
|
+
source = fromCatalog.text.icu;
|
|
136
|
+
} else if (fromCatalog !== void 0 && "icu" in fromCatalog.text && fromCatalog.text.icu !== source) env.report("migrate-manual", `${id}: the ${sourceLocale} catalog has ${JSON.stringify(fromCatalog.text.icu)}, the code's defaultMessage ${JSON.stringify(source)}; the defaultMessage was kept`, {
|
|
137
|
+
file: fromCatalog.file,
|
|
138
|
+
line: fromCatalog.line
|
|
139
|
+
});
|
|
140
|
+
const translations = /* @__PURE__ */ new Map();
|
|
141
|
+
for (const [locale, texts] of catalogs) {
|
|
142
|
+
if (locale === sourceLocale) continue;
|
|
143
|
+
const translated = texts.get(id);
|
|
144
|
+
if (translated !== void 0) translations.set(locale, translated.text);
|
|
145
|
+
}
|
|
146
|
+
return env.define({
|
|
147
|
+
key: id,
|
|
148
|
+
named,
|
|
149
|
+
source,
|
|
150
|
+
context: descriptor.description ?? fromCatalog?.description ?? null,
|
|
151
|
+
hint: null,
|
|
152
|
+
translations,
|
|
153
|
+
file: descriptor.message === null && fromCatalog !== void 0 ? fromCatalog.file : file,
|
|
154
|
+
line: descriptor.message === null && fromCatalog !== void 0 ? fromCatalog.line : line
|
|
155
|
+
}) ? { key: id } : { problem: `${id} could not be converted` };
|
|
156
|
+
};
|
|
157
|
+
const planModule = (file, text, env) => {
|
|
158
|
+
let program;
|
|
159
|
+
try {
|
|
160
|
+
program = parseModule(text, langFromId(file), file).program;
|
|
161
|
+
} catch (error) {
|
|
162
|
+
if (!isDiagnosticError(error)) throw error;
|
|
163
|
+
env.diagnostics.push({
|
|
164
|
+
...error.diagnostic,
|
|
165
|
+
file: env.rel(file)
|
|
166
|
+
});
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
const info = analyzeModule(program);
|
|
170
|
+
const builder = new ModuleBuilder({
|
|
171
|
+
file,
|
|
172
|
+
text,
|
|
173
|
+
info,
|
|
174
|
+
isOldSource
|
|
175
|
+
});
|
|
176
|
+
const tsx = extname(file) === ".tsx";
|
|
177
|
+
const lineOf = (offset) => text.slice(0, offset).split("\n").length;
|
|
178
|
+
const imported = (name) => {
|
|
179
|
+
const locals = /* @__PURE__ */ new Set();
|
|
180
|
+
for (const declaration of info.imports) {
|
|
181
|
+
if (!isOldSource(declaration.source)) continue;
|
|
182
|
+
for (const s of declaration.specifiers) if (s.imported === name) locals.add(s.local);
|
|
183
|
+
}
|
|
184
|
+
return locals;
|
|
185
|
+
};
|
|
186
|
+
const has = (name, local) => imported(name).has(local);
|
|
187
|
+
const useIntl = imported("useIntl");
|
|
188
|
+
const intlBindings = /* @__PURE__ */ new Set();
|
|
189
|
+
for (const binding of info.bindings) {
|
|
190
|
+
const init = binding.declarator?.init;
|
|
191
|
+
if (init === null || init === void 0) continue;
|
|
192
|
+
const call = unwrap(init);
|
|
193
|
+
const callee = call.type === "CallExpression" ? identifierName(call.callee) : null;
|
|
194
|
+
if (callee === null || !useIntl.has(callee)) continue;
|
|
195
|
+
builder.hooks.push(binding);
|
|
196
|
+
intlBindings.add(binding);
|
|
197
|
+
}
|
|
198
|
+
/** Locals bound to `defineMessages()` / `defineMessage()` results: their members are `msg()` refs now. */
|
|
199
|
+
const messageTables = /* @__PURE__ */ new Set();
|
|
200
|
+
for (const binding of info.bindings) {
|
|
201
|
+
const init = binding.declarator?.init;
|
|
202
|
+
if (init === null || init === void 0) continue;
|
|
203
|
+
const call = unwrap(init);
|
|
204
|
+
const callee = call.type === "CallExpression" ? identifierName(call.callee) : null;
|
|
205
|
+
if (callee !== null && (has("defineMessages", callee) || has("defineMessage", callee))) messageTables.add(binding.name);
|
|
206
|
+
}
|
|
207
|
+
const relativeImports = /* @__PURE__ */ new Set();
|
|
208
|
+
for (const declaration of info.imports) {
|
|
209
|
+
if (!declaration.source.startsWith(".")) continue;
|
|
210
|
+
for (const s of declaration.specifiers) relativeImports.add(s.local);
|
|
211
|
+
}
|
|
212
|
+
/** A descriptor object literal, or null when a field is not static. */
|
|
213
|
+
const readDescriptor = (node) => {
|
|
214
|
+
const props = objectProperties(node);
|
|
215
|
+
if (props === null || props.spreads.length > 0) return null;
|
|
216
|
+
const field = (name) => {
|
|
217
|
+
const property = props.properties.get(name);
|
|
218
|
+
if (property === void 0) return null;
|
|
219
|
+
const value = unwrap(property.value);
|
|
220
|
+
if (name === "description" && value.type === "ObjectExpression") return describeNode(value)?.note;
|
|
221
|
+
const s = stringValue(value);
|
|
222
|
+
return s === null ? void 0 : s;
|
|
223
|
+
};
|
|
224
|
+
const id = field("id");
|
|
225
|
+
const message = field("defaultMessage");
|
|
226
|
+
const description = field("description");
|
|
227
|
+
if (id === void 0 || message === void 0 || description === void 0) return null;
|
|
228
|
+
const object = props.properties.get("description");
|
|
229
|
+
return {
|
|
230
|
+
id,
|
|
231
|
+
message,
|
|
232
|
+
description,
|
|
233
|
+
hashed: object !== void 0 && unwrap(object.value).type === "ObjectExpression" ? describeNode(unwrap(object.value))?.json ?? null : description
|
|
234
|
+
};
|
|
235
|
+
};
|
|
236
|
+
/** Whether an expression is a message the migrated code holds as a `msg()` ref. */
|
|
237
|
+
const isMessageRef = (node) => {
|
|
238
|
+
let root = unwrap(node);
|
|
239
|
+
while (root.type === "MemberExpression") root = unwrap(root.object);
|
|
240
|
+
const name = identifierName(root);
|
|
241
|
+
return name !== null && (messageTables.has(name) || relativeImports.has(name));
|
|
242
|
+
};
|
|
243
|
+
/** Whether values need `rich()`: an element or a function among them. */
|
|
244
|
+
const needsRich = (values, key) => {
|
|
245
|
+
if (key !== null) {
|
|
246
|
+
const message = env.message(key);
|
|
247
|
+
if (message !== void 0 && hasTag(env.codec.parse(message.canonical))) return true;
|
|
248
|
+
}
|
|
249
|
+
if (values === null) return false;
|
|
250
|
+
const props = objectProperties(values);
|
|
251
|
+
if (props === null) return false;
|
|
252
|
+
return props.order.some((p) => {
|
|
253
|
+
if (p.type === "SpreadElement") return false;
|
|
254
|
+
const value = unwrap(p.value);
|
|
255
|
+
return value.type === "ArrowFunctionExpression" || value.type === "FunctionExpression" || value.type === "JSXElement" || value.type === "JSXFragment";
|
|
256
|
+
});
|
|
257
|
+
};
|
|
258
|
+
const call = (r, message, values, rich, types) => {
|
|
259
|
+
const first = "key" in message ? r.message(message.key) : r.src(message.ref.start, message.ref.end);
|
|
260
|
+
const rest = values === null ? "" : `, ${r.src(values.start, values.end)}`;
|
|
261
|
+
if (!rich) return `${r.fn.t}(${first}${rest})`;
|
|
262
|
+
return `${r.fn.rich}${types === null ? "" : `<${types.node}>`}(${first}${rest})`;
|
|
263
|
+
};
|
|
264
|
+
const formattedMessage = (element) => {
|
|
265
|
+
const attributes = jsxAttributes(element);
|
|
266
|
+
const attr = (name) => attributes.named.get(name)?.value ?? null;
|
|
267
|
+
if (attributes.named.has("children") || element.children.length > 0) {
|
|
268
|
+
builder.note(element.start, "<FormattedMessage> with a render function child was not rewritten: call t() and render the string");
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
let message = null;
|
|
272
|
+
if (attributes.spreads.length === 1 && !attributes.named.has("id") && !attributes.named.has("defaultMessage")) {
|
|
273
|
+
const spread = attributes.spreads[0].argument;
|
|
274
|
+
if (isMessageRef(spread)) message = { ref: spread };
|
|
275
|
+
} else if (attributes.spreads.length === 0) {
|
|
276
|
+
const read = (name) => attributes.named.has(name) ? attributeString(attr(name)) ?? void 0 : null;
|
|
277
|
+
let description = read("description");
|
|
278
|
+
const descriptionExpr = attributeExpression(attr("description"));
|
|
279
|
+
let hashed = description;
|
|
280
|
+
if (description === void 0 && descriptionExpr !== null && unwrap(descriptionExpr).type === "ObjectExpression") {
|
|
281
|
+
const described = describeNode(unwrap(descriptionExpr));
|
|
282
|
+
description = described?.note;
|
|
283
|
+
hashed = described?.json;
|
|
284
|
+
}
|
|
285
|
+
const id = read("id");
|
|
286
|
+
const defaultMessage = read("defaultMessage");
|
|
287
|
+
if (id !== void 0 && defaultMessage !== void 0 && description !== void 0) {
|
|
288
|
+
const defined = define(env, {
|
|
289
|
+
id,
|
|
290
|
+
message: defaultMessage,
|
|
291
|
+
description,
|
|
292
|
+
hashed: hashed ?? null
|
|
293
|
+
}, file, lineOf(element.start));
|
|
294
|
+
if ("problem" in defined) {
|
|
295
|
+
builder.note(element.start, `${defined.problem}; <FormattedMessage> was not rewritten`);
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
message = defined;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
if (message === null) {
|
|
302
|
+
builder.note(element.start, "<FormattedMessage> with a computed id or defaultMessage was not rewritten");
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
const values = attributeExpression(attr("values"));
|
|
306
|
+
if (attributes.named.has("values") && (values === null || objectProperties(values) === null)) {
|
|
307
|
+
builder.note(element.start, "<FormattedMessage values={…}> is not an object literal; not rewritten");
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
const tagName = attributes.named.has("tagName") ? attributeString(attr("tagName")) : null;
|
|
311
|
+
if (attributes.named.has("tagName") && (tagName === null || !/^[a-z][a-z0-9-]*$/.test(tagName))) {
|
|
312
|
+
builder.note(element.start, "<FormattedMessage tagName={Component}> was not rewritten: wrap the t() call yourself");
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
const rich = needsRich(values, "key" in message ? message.key : null);
|
|
316
|
+
const types = rich && tsx ? reactTypes(builder) : null;
|
|
317
|
+
if (types !== null && values !== null) annotateHandlers(builder, values, types.node);
|
|
318
|
+
const nested = inJsxChildren(info, element);
|
|
319
|
+
const resolved = message;
|
|
320
|
+
builder.site(element, (r) => {
|
|
321
|
+
const code = call(r, resolved, values, rich, types);
|
|
322
|
+
if (tagName !== null) return `<${tagName}>{${code}}</${tagName}>`;
|
|
323
|
+
return nested ? `{${code}}` : code;
|
|
324
|
+
}, {
|
|
325
|
+
keys: "key" in message ? [message.key] : [],
|
|
326
|
+
uses: [rich ? "rich" : "t"],
|
|
327
|
+
consume: jsxNameNodes(element)
|
|
328
|
+
});
|
|
329
|
+
};
|
|
330
|
+
/** `intl.formatMessage(descriptor, values)` and a destructured `formatMessage(…)`. */
|
|
331
|
+
const formatMessage = (node) => {
|
|
332
|
+
const callee = unwrap(node.callee);
|
|
333
|
+
const member = memberParts(callee);
|
|
334
|
+
let consume = [];
|
|
335
|
+
if (member !== null && member.property === "formatMessage") {
|
|
336
|
+
const object = unwrap(member.object);
|
|
337
|
+
if (object.type === "Identifier") consume = [object];
|
|
338
|
+
} else if (callee.type === "Identifier" && [...intlBindings].some((b) => b.name === callee.name)) consume = [callee];
|
|
339
|
+
else return false;
|
|
340
|
+
const args = node.arguments;
|
|
341
|
+
const first = args[0];
|
|
342
|
+
const values = args[1] ?? null;
|
|
343
|
+
if (first === void 0 || first.type === "SpreadElement" || args.length > 2) return false;
|
|
344
|
+
let message = null;
|
|
345
|
+
if (unwrap(first).type === "ObjectExpression") {
|
|
346
|
+
const descriptor = readDescriptor(unwrap(first));
|
|
347
|
+
if (descriptor === null) {
|
|
348
|
+
builder.note(node.start, "formatMessage() with a computed descriptor was not rewritten");
|
|
349
|
+
return true;
|
|
350
|
+
}
|
|
351
|
+
const defined = define(env, descriptor, file, lineOf(node.start));
|
|
352
|
+
if ("problem" in defined) {
|
|
353
|
+
builder.note(node.start, `${defined.problem}; formatMessage() was not rewritten`);
|
|
354
|
+
return true;
|
|
355
|
+
}
|
|
356
|
+
message = defined;
|
|
357
|
+
} else if (isMessageRef(first)) message = { ref: first };
|
|
358
|
+
if (message === null) {
|
|
359
|
+
builder.note(node.start, "formatMessage() with a descriptor vi18n migrate cannot trace was not rewritten: pass a msg() descriptor to t()");
|
|
360
|
+
return true;
|
|
361
|
+
}
|
|
362
|
+
if (values !== null && unwrap(values).type !== "ObjectExpression" && needsRich(null, "key" in message ? message.key : null)) {
|
|
363
|
+
builder.note(node.start, "formatMessage() with rich values that are not an object literal was not rewritten");
|
|
364
|
+
return true;
|
|
365
|
+
}
|
|
366
|
+
const rich = needsRich(values, "key" in message ? message.key : null);
|
|
367
|
+
const types = rich && tsx ? reactTypes(builder) : null;
|
|
368
|
+
if (types !== null && values !== null) annotateHandlers(builder, values, types.node);
|
|
369
|
+
const resolved = message;
|
|
370
|
+
builder.site(node, (r) => call(r, resolved, values, rich, types), {
|
|
371
|
+
keys: "key" in message ? [message.key] : [],
|
|
372
|
+
uses: [rich ? "rich" : "t"],
|
|
373
|
+
consume
|
|
374
|
+
});
|
|
375
|
+
return true;
|
|
376
|
+
};
|
|
377
|
+
/** `defineMessages({ … })` → `{ key: msg(…) }`, `defineMessage({ … })` → `msg(…)`. */
|
|
378
|
+
const defineCall = (node, kind) => {
|
|
379
|
+
const callee = unwrap(node.callee);
|
|
380
|
+
const arg = node.arguments[0];
|
|
381
|
+
if (arg === void 0) return;
|
|
382
|
+
if (kind === "defineMessage") {
|
|
383
|
+
const descriptor = readDescriptor(unwrap(arg));
|
|
384
|
+
if (descriptor === null) {
|
|
385
|
+
builder.note(node.start, "defineMessage() with a computed descriptor was not rewritten");
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
const defined = define(env, descriptor, file, lineOf(node.start));
|
|
389
|
+
if ("problem" in defined) {
|
|
390
|
+
builder.note(node.start, `${defined.problem}; defineMessage() was not rewritten`);
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
builder.site(node, (r) => `${r.fn.msg}(${r.message(defined.key)})`, {
|
|
394
|
+
keys: [defined.key],
|
|
395
|
+
uses: ["msg"],
|
|
396
|
+
consume: [callee]
|
|
397
|
+
});
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
const props = objectProperties(arg);
|
|
401
|
+
if (props === null || props.spreads.length > 0) {
|
|
402
|
+
builder.note(node.start, "defineMessages() with spreads or computed keys was not rewritten");
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
const entries = [];
|
|
406
|
+
for (const property of props.order) {
|
|
407
|
+
const descriptor = readDescriptor(unwrap(property.value));
|
|
408
|
+
const defined = descriptor === null ? null : define(env, descriptor, file, lineOf(property.start));
|
|
409
|
+
if (defined === null || "problem" in defined) {
|
|
410
|
+
builder.note(property.start, `defineMessages(): ${defined === null ? "a computed descriptor" : defined.problem}; the table was not rewritten`);
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
entries.push({
|
|
414
|
+
property,
|
|
415
|
+
key: defined.key
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
builder.site(node, (r) => {
|
|
419
|
+
const lines = entries.map(({ property, key }) => {
|
|
420
|
+
const name = text.slice(property.key.start, property.key.end);
|
|
421
|
+
return `${property.computed === true ? `[${name}]` : name}: ${r.fn.msg}(${r.message(key)})`;
|
|
422
|
+
});
|
|
423
|
+
if (!text.slice(arg.start, arg.end).includes("\n")) return `{ ${lines.join(", ")} }`;
|
|
424
|
+
const indent = /\n([ \t]*)\S/.exec(text.slice(arg.start, arg.end))?.[1] ?? " ";
|
|
425
|
+
const closing = /\n([ \t]*)\}$/.exec(text.slice(arg.start, arg.end))?.[1] ?? "";
|
|
426
|
+
return `{\n${lines.map((l) => `${indent}${l},`).join("\n")}\n${closing}}`;
|
|
427
|
+
}, {
|
|
428
|
+
keys: entries.map((e) => e.key),
|
|
429
|
+
uses: ["msg"],
|
|
430
|
+
consume: [callee]
|
|
431
|
+
});
|
|
432
|
+
};
|
|
433
|
+
const unwrapProvider = (element) => {
|
|
434
|
+
const opening = element.openingElement;
|
|
435
|
+
const closing = element.closingElement;
|
|
436
|
+
const children = element.children.filter((c) => !(c.type === "JSXText" && c.value.trim() === ""));
|
|
437
|
+
builder.consumeRange(opening);
|
|
438
|
+
builder.consume(jsxNameNodes(element));
|
|
439
|
+
const nested = inJsxChildren(info, element);
|
|
440
|
+
builder.replace(element, (r) => {
|
|
441
|
+
if (closing === null) return nested ? "" : "null";
|
|
442
|
+
if (nested) return r.src(opening.end, closing.start).trim();
|
|
443
|
+
if (children.length === 1 && children[0].type === "JSXElement") return r.src(children[0].start, children[0].end);
|
|
444
|
+
return `<>${r.src(opening.end, closing.start)}</>`;
|
|
445
|
+
});
|
|
446
|
+
};
|
|
447
|
+
const visit = (node) => {
|
|
448
|
+
if (node.type === "JSXElement") {
|
|
449
|
+
const name = jsxName(node);
|
|
450
|
+
if (has("FormattedMessage", name)) formattedMessage(node);
|
|
451
|
+
else if (has("IntlProvider", name) || has("RawIntlProvider", name)) unwrapProvider(node);
|
|
452
|
+
else {
|
|
453
|
+
const imported_ = Object.keys(FORMATTERS).find((f) => has(f, name));
|
|
454
|
+
if (imported_ !== void 0) builder.note(node.start, `<${name}> is not rewritten: render ${FORMATTERS[imported_]} from @menapo/vi18n`);
|
|
455
|
+
}
|
|
456
|
+
} else if (node.type === "CallExpression") {
|
|
457
|
+
const callee = identifierName(node.callee);
|
|
458
|
+
if (callee !== null && has("defineMessages", callee)) defineCall(node, "defineMessages");
|
|
459
|
+
else if (callee !== null && has("defineMessage", callee)) defineCall(node, "defineMessage");
|
|
460
|
+
else if (callee !== null && has("injectIntl", callee)) builder.note(node.start, "injectIntl() injects intl as a prop: drop it and call t() from @menapo/vi18n");
|
|
461
|
+
else if (callee !== null && (has("createIntl", callee) || has("createIntlCache", callee))) builder.note(node.start, `${callee}(): use createTranslator(locale) or withLocale() from @menapo/vi18n/server`);
|
|
462
|
+
else if (!formatMessage(node)) {
|
|
463
|
+
const member = memberParts(node.callee);
|
|
464
|
+
if (member !== null && member.property in FORMATTERS && member.property.startsWith("format")) {
|
|
465
|
+
const object = identifierName(member.object);
|
|
466
|
+
if (object !== null && [...intlBindings].some((b) => b.name === object)) builder.note(node.start, `intl.${member.property}() is not rewritten: use ${FORMATTERS[member.property]} from @menapo/vi18n`);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
for (const [, child] of childEntries(node)) visit(child);
|
|
471
|
+
};
|
|
472
|
+
visit(program);
|
|
473
|
+
return builder.finish();
|
|
474
|
+
};
|
|
475
|
+
return {
|
|
476
|
+
catalogExtensions: [".json"],
|
|
477
|
+
catalogsRequired: false,
|
|
478
|
+
extensions: MODULE_EXTENSIONS,
|
|
479
|
+
mentions: (text) => /react-intl|@formatjs\/intl|formatMessage/.test(text),
|
|
480
|
+
load(files, env) {
|
|
481
|
+
sourceLocale = env.config.sourceLocale;
|
|
482
|
+
for (const file of files) readCatalog(file, env);
|
|
483
|
+
},
|
|
484
|
+
planFile: planModule,
|
|
485
|
+
unusedKeys() {
|
|
486
|
+
return [...catalogs.get(sourceLocale)?.keys() ?? []].filter((id) => !referenced.has(id));
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
function hasTag(nodes) {
|
|
491
|
+
return nodes.some((node) => node.type === "tag" || (node.type === "plural" || node.type === "select") && node.options.some((o) => hasTag(o.value)));
|
|
492
|
+
}
|
|
493
|
+
/** A FormatJS `description` as one note: a string as is, an object as `key: value` pairs. */
|
|
494
|
+
function describe(value) {
|
|
495
|
+
if (typeof value === "string") return value === "" ? null : value;
|
|
496
|
+
if (typeof value !== "object" || value === null) return null;
|
|
497
|
+
const parts = Object.entries(value).map(([k, v]) => `${k}: ${typeof v === "string" ? v : JSON.stringify(v)}`);
|
|
498
|
+
return parts.length === 0 ? null : parts.join("; ");
|
|
499
|
+
}
|
|
500
|
+
/** An object `description` as a note, and as the stable JSON FormatJS hashes. */
|
|
501
|
+
function describeNode(node) {
|
|
502
|
+
const props = objectProperties(node);
|
|
503
|
+
if (props === null || props.spreads.length > 0) return void 0;
|
|
504
|
+
const out = {};
|
|
505
|
+
for (const [key, property] of props.properties) {
|
|
506
|
+
const value = unwrap(property.value);
|
|
507
|
+
const s = stringValue(value);
|
|
508
|
+
if (s !== null) out[key] = s;
|
|
509
|
+
else if (value.type === "Literal") out[key] = value.value;
|
|
510
|
+
else return void 0;
|
|
511
|
+
}
|
|
512
|
+
const sorted = Object.fromEntries(Object.keys(out).sort().map((k) => [k, out[k]]));
|
|
513
|
+
return {
|
|
514
|
+
note: describe(out),
|
|
515
|
+
json: JSON.stringify(sorted)
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
//#endregion
|
|
519
|
+
export { createReactIntlAdapter };
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { s as parseIdScheme } from "./validate-BxpupdAf.js";
|
|
2
|
+
import { t as createCodec } from "./codec-DuvU2VQy.js";
|
|
3
|
+
import { d as parseCatalogBytes, m as updateCatalogFile } from "./library-D5CWvTQs.js";
|
|
4
|
+
import { t as VERSION } from "./version-Byqi2guX.js";
|
|
5
|
+
import { c as sourceIndexOf } from "./units-CQ5Q99RC.js";
|
|
6
|
+
import { i as rootRelative, r as plural } from "./output-DWE3SIUA.js";
|
|
7
|
+
import { t as EXIT } from "./types-wSh9fCBY.js";
|
|
8
|
+
import { a as loadCatalogs, o as loadProject, p as targetLocales, t as CliExit, u as readErrors } from "./project-C4GpaN-n.js";
|
|
9
|
+
import { n as applyRestore } from "./apply-Bre-lY9J.js";
|
|
10
|
+
import { basename, dirname } from "node:path";
|
|
11
|
+
import { promisify } from "node:util";
|
|
12
|
+
import { execFile } from "node:child_process";
|
|
13
|
+
//#region src/cli/commands/restore.ts
|
|
14
|
+
/**
|
|
15
|
+
* `vi18n restore --from <git-rev> [--locale l]… [--dry-run]` (PLAN §4.4,
|
|
16
|
+
* §5.19, INTERNALS §2.6.9): fills entries that are empty now from that
|
|
17
|
+
* revision's catalogs, by exact source text (generated ids) or manual id
|
|
18
|
+
* with the same source text. It undoes what `vi18n extract` rightly did
|
|
19
|
+
* when the code changed back: a reverted text, or a merge whose branch
|
|
20
|
+
* deleted an entry the other branch still uses. Only empty entries are
|
|
21
|
+
* filled, so nothing anyone translated since is overwritten.
|
|
22
|
+
*/
|
|
23
|
+
const run = promisify(execFile);
|
|
24
|
+
/** Catalogs are text, but a large one at an old revision must not hit the default 1 MB buffer. */
|
|
25
|
+
const MAX_BUFFER = 1073741824;
|
|
26
|
+
function gitError(message) {
|
|
27
|
+
return new CliExit(EXIT.failure, [{
|
|
28
|
+
code: "restore-git",
|
|
29
|
+
severity: "error",
|
|
30
|
+
message
|
|
31
|
+
}]);
|
|
32
|
+
}
|
|
33
|
+
/** The revision as git sees it; a typo or a missing repository ends the command. */
|
|
34
|
+
async function verifyRevision(cwd, rev) {
|
|
35
|
+
try {
|
|
36
|
+
await run("git", [
|
|
37
|
+
"rev-parse",
|
|
38
|
+
"--verify",
|
|
39
|
+
"--quiet",
|
|
40
|
+
`${rev}^{commit}`
|
|
41
|
+
], { cwd });
|
|
42
|
+
} catch (error) {
|
|
43
|
+
const failure = error;
|
|
44
|
+
if (failure.code === "ENOENT") throw gitError("git is not installed or not on PATH");
|
|
45
|
+
const stderr = String(failure.stderr ?? "").trim();
|
|
46
|
+
throw gitError(/not a git repository/i.test(stderr) ? `${cwd} is not inside a git repository` : `${rev} is not a commit of this repository`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/** A file's bytes at a revision; null when it did not exist there. */
|
|
50
|
+
async function showAt(rev, path) {
|
|
51
|
+
try {
|
|
52
|
+
const { stdout } = await run("git", ["show", `${rev}:./${basename(path)}`], {
|
|
53
|
+
cwd: dirname(path),
|
|
54
|
+
encoding: "buffer",
|
|
55
|
+
maxBuffer: MAX_BUFFER
|
|
56
|
+
});
|
|
57
|
+
return stdout;
|
|
58
|
+
} catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function oldCatalog(bytes, options) {
|
|
63
|
+
const read = parseCatalogBytes(bytes, options);
|
|
64
|
+
return {
|
|
65
|
+
catalog: read.catalog,
|
|
66
|
+
error: read.diagnostics.find((diagnostic) => diagnostic.severity === "error") ?? null
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
async function restore(ctx, flags) {
|
|
70
|
+
const project = await loadProject(ctx);
|
|
71
|
+
const { config, codec, layout } = project;
|
|
72
|
+
const locales = targetLocales(config, flags.locales);
|
|
73
|
+
await verifyRevision(config.root, flags.from);
|
|
74
|
+
const loaded = await loadCatalogs(project);
|
|
75
|
+
const unreadable = readErrors(loaded.diagnostics);
|
|
76
|
+
if (unreadable.length > 0) throw new CliExit(EXIT.failure, unreadable);
|
|
77
|
+
const sourceLoaded = loaded.set.get(config.sourceLocale);
|
|
78
|
+
const context = {
|
|
79
|
+
layout,
|
|
80
|
+
codec,
|
|
81
|
+
sourceIndex: sourceIndexOf(sourceLoaded?.catalog ?? null, layout, codec)
|
|
82
|
+
};
|
|
83
|
+
let oldSource = null;
|
|
84
|
+
if (layout === "id" && sourceLoaded !== void 0) {
|
|
85
|
+
const bytes = await showAt(flags.from, sourceLoaded.path);
|
|
86
|
+
if (bytes !== null) {
|
|
87
|
+
const read = oldCatalog(bytes, {
|
|
88
|
+
locale: config.sourceLocale,
|
|
89
|
+
layout,
|
|
90
|
+
isSourceLocale: true
|
|
91
|
+
});
|
|
92
|
+
if (read.error === null) oldSource = read.catalog;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const data = {
|
|
96
|
+
from: flags.from,
|
|
97
|
+
dryRun: flags.dryRun,
|
|
98
|
+
locales: [],
|
|
99
|
+
restored: []
|
|
100
|
+
};
|
|
101
|
+
let failed = false;
|
|
102
|
+
for (const locale of locales) {
|
|
103
|
+
const target = loaded.set.get(locale);
|
|
104
|
+
if (target === void 0 || target.catalog === null) continue;
|
|
105
|
+
const file = rootRelative(config.root, target.path);
|
|
106
|
+
const bytes = await showAt(flags.from, target.path);
|
|
107
|
+
if (bytes === null) {
|
|
108
|
+
ctx.out.note(`vi18n restore: ${file} did not exist at ${flags.from}`);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
const old = oldCatalog(bytes, {
|
|
112
|
+
locale,
|
|
113
|
+
layout,
|
|
114
|
+
isSourceLocale: false
|
|
115
|
+
});
|
|
116
|
+
if (old.error !== null) {
|
|
117
|
+
failed = true;
|
|
118
|
+
ctx.out.report([{
|
|
119
|
+
...old.error,
|
|
120
|
+
code: "restore-git",
|
|
121
|
+
file: target.path,
|
|
122
|
+
locale,
|
|
123
|
+
message: `${file} at ${flags.from} cannot be read: ${old.error.message}`
|
|
124
|
+
}]);
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
const length = parseIdScheme(old.catalog.header.idScheme ?? "")?.length ?? codec.idLength;
|
|
128
|
+
const oldCodec = length === codec.idLength ? codec : createCodec(length);
|
|
129
|
+
const oldContext = {
|
|
130
|
+
layout,
|
|
131
|
+
codec: oldCodec,
|
|
132
|
+
sourceIndex: sourceIndexOf(oldSource, layout, oldCodec)
|
|
133
|
+
};
|
|
134
|
+
const apply = (catalog) => applyRestore({
|
|
135
|
+
catalog,
|
|
136
|
+
context,
|
|
137
|
+
old: old.catalog,
|
|
138
|
+
oldContext,
|
|
139
|
+
locale
|
|
140
|
+
});
|
|
141
|
+
let outcome = null;
|
|
142
|
+
if (flags.dryRun) outcome = apply(target.catalog);
|
|
143
|
+
else await updateCatalogFile(target.path, target.hash, (current) => {
|
|
144
|
+
if (current === null) return null;
|
|
145
|
+
outcome = apply(current);
|
|
146
|
+
return outcome.catalog === current ? null : outcome.catalog;
|
|
147
|
+
}, {
|
|
148
|
+
locale,
|
|
149
|
+
layout,
|
|
150
|
+
isSourceLocale: false,
|
|
151
|
+
version: VERSION
|
|
152
|
+
});
|
|
153
|
+
if (outcome === null) continue;
|
|
154
|
+
ctx.out.report(outcome.diagnostics.map((diagnostic) => ({
|
|
155
|
+
...diagnostic,
|
|
156
|
+
file: target.path
|
|
157
|
+
})));
|
|
158
|
+
data.locales.push({
|
|
159
|
+
locale,
|
|
160
|
+
file,
|
|
161
|
+
restored: outcome.restored.length
|
|
162
|
+
});
|
|
163
|
+
for (const entry of outcome.restored) data.restored.push({
|
|
164
|
+
locale,
|
|
165
|
+
...entry
|
|
166
|
+
});
|
|
167
|
+
ctx.out.result(`${locale}: ${plural(outcome.restored.length, "entry", "entries")} ${flags.dryRun ? "to restore" : "restored"} from ${flags.from}`);
|
|
168
|
+
}
|
|
169
|
+
return {
|
|
170
|
+
exitCode: failed ? EXIT.failure : EXIT.ok,
|
|
171
|
+
data
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
//#endregion
|
|
175
|
+
export { restore };
|