@menapo/vi18n 0.0.0-stage → 0.1.0
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-D34P-S--.js +1 -0
- package/dist/check-DyrAbv73.js +201 -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-B5Mz6WJm.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-B7p40M__.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-DGsMbcTZ.js +103 -0
- package/dist/extract-Dujlsgnk.js +298 -0
- package/dist/formats-KPIg-08V.js +781 -0
- package/dist/i18next-CrUnKFfh.js +1596 -0
- package/dist/i18next-Rgo-v9l0.js +1 -0
- package/dist/import-Cwdhcywt.js +225 -0
- package/dist/index.d.ts +156 -0
- package/dist/index.js +239 -0
- package/dist/init-BecCTvKi.js +792 -0
- package/dist/library-D5CWvTQs.js +1882 -0
- package/dist/lingui--PcOOYry.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-Cq1Nuu-Q.js +31 -0
- package/dist/match-BGr8EXB3.js +89 -0
- package/dist/mcp-Cf0T7qPW.js +28 -0
- package/dist/merge-driver-CpiiSVIc.js +422 -0
- package/dist/messages-Cxa-5tR0.js +166 -0
- package/dist/migrate-CntqvrW4.js +1 -0
- package/dist/migrate-dPgq9uzy.js +1484 -0
- package/dist/openai-CTaMDcl-.js +346 -0
- package/dist/output-DsaHKHEt.js +164 -0
- package/dist/parse.d.ts +62 -0
- package/dist/parse.js +156 -0
- package/dist/pipeline-C7g11THL.js +2031 -0
- package/dist/pipeline-wxS0SApG.js +1 -0
- package/dist/project-Dwx7iPm6.js +381 -0
- package/dist/project-pXipT8iG.js +1046 -0
- package/dist/pseudo-DRv30iN5.js +145 -0
- package/dist/raw-C4Y6vyGY.js +4708 -0
- package/dist/react-intl-CaBVwJ9u.js +519 -0
- package/dist/restore-Btq66Tid.js +175 -0
- package/dist/rewrite-CiTRAICl.js +803 -0
- package/dist/rich-DCUKl5HM.js +97 -0
- package/dist/rolldown-runtime-D7D4PA-g.js +13 -0
- package/dist/run-DYD_VXKy.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-BDkykMjb.js +11823 -0
- package/dist/server-qv3mz70d.js +10485 -0
- package/dist/server.d.ts +169 -0
- package/dist/server.js +1234 -0
- package/dist/sitemap-DV3o8QD7.js +159 -0
- package/dist/sources-D19czDSA.js +2242 -0
- package/dist/status-DZArW5aE.js +149 -0
- package/dist/stdio-CVaJfxA7.js +29 -0
- package/dist/stdio-FPOKQAgu.js +95 -0
- package/dist/sync-CcVzger_.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-DZR3MkSA.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-BlyLGlOa.js +28 -0
- package/dist/vite-scan-B_YW4X_a.js +242 -0
- package/dist/vite.d.ts +34 -0
- package/dist/vite.js +7558 -0
- package/dist/vue-i18n-CEMnpv7k.js +651 -0
- package/dist/which-C6PFI1Dm.js +116 -0
- package/dist/worker-84U6uYB3.js +1656 -0
- package/package.json +97 -3
- package/schemas/catalog.json +57 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { o as isValidManualId } from "./validate-BxpupdAf.js";
|
|
2
|
+
//#region src/core/config/exclude.ts
|
|
3
|
+
/**
|
|
4
|
+
* `ai.exclude` entries (PLAN §4.6, §5.20; INTERNALS §2.15): a configured
|
|
5
|
+
* locale, a message id, or a glob over the config-relative files that use a
|
|
6
|
+
* message. One list keeps the config short, so the kind is read from the
|
|
7
|
+
* entry: the loader rejects what fits none, and `vi18n translate` matches
|
|
8
|
+
* with the same reading.
|
|
9
|
+
*/
|
|
10
|
+
/** Everything a message id may hold: base64url, plus a manual id's `.` and `:`. */
|
|
11
|
+
const ID_CHARACTERS = /^[A-Za-z0-9_:.-]+$/;
|
|
12
|
+
const BASE64URL = /^[A-Za-z0-9_-]+$/;
|
|
13
|
+
/**
|
|
14
|
+
* The kind of an entry, or null when it is none. A glob is anything with a
|
|
15
|
+
* character no id or locale has, and must contain `/`, `*` or `?`: a bare
|
|
16
|
+
* `legal.tsx` reads as the manual id it also is, so a file at the root is
|
|
17
|
+
* written `./legal.tsx` or `**\/legal.tsx`.
|
|
18
|
+
*/
|
|
19
|
+
function excludeKind(entry, locales, idLength) {
|
|
20
|
+
if (!ID_CHARACTERS.test(entry)) return /[/*?]/.test(entry) ? "glob" : null;
|
|
21
|
+
if (locales.includes(entry)) return "locale";
|
|
22
|
+
if (isValidManualId(entry)) return "id";
|
|
23
|
+
return entry.length === idLength && BASE64URL.test(entry) ? "id" : null;
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
export { excludeKind as t };
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { c as sourceIndexOf, n as EXTENSIONS, o as exchangeUnits } from "./units-CQ5Q99RC.js";
|
|
2
|
+
import { r as plural } from "./output-DsaHKHEt.js";
|
|
3
|
+
import { t as EXIT } from "./types-wSh9fCBY.js";
|
|
4
|
+
import { a as loadCatalogs, o as loadProject, t as CliExit, u as readErrors } from "./project-Dwx7iPm6.js";
|
|
5
|
+
import { n as serializeExport } from "./formats-KPIg-08V.js";
|
|
6
|
+
import { join, resolve } from "node:path";
|
|
7
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
8
|
+
//#region src/cli/commands/export.ts
|
|
9
|
+
/**
|
|
10
|
+
* `vi18n export --format po|json|flat|xliff2|arb|csv [--locale l]… [--out dir]
|
|
11
|
+
* [--missing] [--ai]` (PLAN §4.4, §5.19, INTERNALS §2.6.9): each locale's
|
|
12
|
+
* catalog in an exchange format, for a TMS or a translation agency. It reads
|
|
13
|
+
* the catalogs as they are, so run `vi18n extract` first to send what the
|
|
14
|
+
* code uses now.
|
|
15
|
+
*/
|
|
16
|
+
async function exportCatalogs(ctx, flags) {
|
|
17
|
+
const project = await loadProject(ctx);
|
|
18
|
+
const { config, codec, layout } = project;
|
|
19
|
+
const filtered = flags.missing || flags.ai;
|
|
20
|
+
const locales = exportLocales(config.locales, config.sourceLocale, flags.locales, filtered);
|
|
21
|
+
if (flags.out === null && locales.length !== 1) throw new CliExit(EXIT.usage, [{
|
|
22
|
+
code: "config-invalid",
|
|
23
|
+
severity: "error",
|
|
24
|
+
message: `${plural(locales.length, "locale")} to export: pass --out <dir> for one file per locale, or one --locale for stdout`
|
|
25
|
+
}]);
|
|
26
|
+
const loaded = await loadCatalogs(project);
|
|
27
|
+
const unreadable = readErrors(loaded.diagnostics);
|
|
28
|
+
if (unreadable.length > 0) throw new CliExit(EXIT.failure, unreadable);
|
|
29
|
+
const context = {
|
|
30
|
+
layout,
|
|
31
|
+
codec,
|
|
32
|
+
sourceIndex: sourceIndexOf(loaded.set.get(config.sourceLocale)?.catalog ?? null, layout, codec)
|
|
33
|
+
};
|
|
34
|
+
const data = {
|
|
35
|
+
format: flags.format,
|
|
36
|
+
files: []
|
|
37
|
+
};
|
|
38
|
+
const out = flags.out === null ? null : resolve(ctx.global.cwd, flags.out);
|
|
39
|
+
if (out !== null) await mkdir(out, { recursive: true });
|
|
40
|
+
for (const locale of locales) {
|
|
41
|
+
const isSourceLocale = locale === config.sourceLocale;
|
|
42
|
+
const catalog = loaded.set.get(locale)?.catalog ?? null;
|
|
43
|
+
const { units, unsourced } = exchangeUnits({
|
|
44
|
+
catalog,
|
|
45
|
+
context,
|
|
46
|
+
locale,
|
|
47
|
+
isSourceLocale
|
|
48
|
+
});
|
|
49
|
+
const selected = filtered && !isSourceLocale ? units.filter((unit) => flags.missing && !unit.usable || flags.ai && unit.state === "ai" && unit.translation !== "") : units;
|
|
50
|
+
if (unsourced > 0) ctx.out.note(`vi18n export: ${locale}: left out ${plural(unsourced, "entry", "entries")} whose source text the catalogs do not hold; run vi18n extract`);
|
|
51
|
+
const text = serializeExport(flags.format, selected, {
|
|
52
|
+
locale,
|
|
53
|
+
sourceLocale: config.sourceLocale,
|
|
54
|
+
isSourceLocale,
|
|
55
|
+
idScheme: codec.idScheme,
|
|
56
|
+
msgid: config.catalogs.msgid,
|
|
57
|
+
header: catalog?.header ?? null,
|
|
58
|
+
layout
|
|
59
|
+
});
|
|
60
|
+
if (out === null) {
|
|
61
|
+
data.files.push({
|
|
62
|
+
locale,
|
|
63
|
+
file: null,
|
|
64
|
+
entries: selected.length
|
|
65
|
+
});
|
|
66
|
+
if (ctx.global.json) data.content = text;
|
|
67
|
+
else ctx.io.stdout(ctx.out.redact(text));
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
const file = join(out, `${locale}${EXTENSIONS[flags.format]}`);
|
|
71
|
+
await writeFile(file, text);
|
|
72
|
+
const shown = ctx.out.display(file);
|
|
73
|
+
data.files.push({
|
|
74
|
+
locale,
|
|
75
|
+
file: shown,
|
|
76
|
+
entries: selected.length
|
|
77
|
+
});
|
|
78
|
+
ctx.out.result(`${locale} → ${shown} (${plural(selected.length, "entry", "entries")})`);
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
exitCode: EXIT.ok,
|
|
82
|
+
data
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* `--locale` as given (each must be configured), else every locale; with a
|
|
87
|
+
* filter, every target locale, since the source locale has nothing missing.
|
|
88
|
+
*/
|
|
89
|
+
function exportLocales(configured, sourceLocale, requested, filtered) {
|
|
90
|
+
if (requested.length === 0) return filtered ? configured.filter((locale) => locale !== sourceLocale) : [...configured];
|
|
91
|
+
const out = [];
|
|
92
|
+
for (const locale of requested) {
|
|
93
|
+
if (!configured.includes(locale)) throw new CliExit(EXIT.usage, [{
|
|
94
|
+
code: "config-invalid",
|
|
95
|
+
severity: "error",
|
|
96
|
+
message: `--locale ${locale} is not in locales (${configured.join(", ")})`
|
|
97
|
+
}]);
|
|
98
|
+
if (!out.includes(locale)) out.push(locale);
|
|
99
|
+
}
|
|
100
|
+
return out;
|
|
101
|
+
}
|
|
102
|
+
//#endregion
|
|
103
|
+
export { exportCatalogs };
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import { m as updateCatalogFile, n as libraryCoverage, s as catalogPath } from "./library-D5CWvTQs.js";
|
|
2
|
+
import { n as scanProject, r as isIncluded } from "./project-pXipT8iG.js";
|
|
3
|
+
import { t as VERSION } from "./version-BlyLGlOa.js";
|
|
4
|
+
import { a as writeGenFile, n as syncCatalogs, t as syncCatalog } from "./sync-CcVzger_.js";
|
|
5
|
+
import { t as findCollisions } from "./collisions-CfpZU8e4.js";
|
|
6
|
+
import { a as table, i as rootRelative, r as plural } from "./output-DsaHKHEt.js";
|
|
7
|
+
import { t as EXIT } from "./types-wSh9fCBY.js";
|
|
8
|
+
import { a as loadCatalogs, f as scan, i as hasErrors, o as loadProject, r as computeDrift, s as occurrencesOf, u as readErrors } from "./project-Dwx7iPm6.js";
|
|
9
|
+
import { dirname, join, resolve } from "node:path";
|
|
10
|
+
import { watch } from "node:fs";
|
|
11
|
+
import { stat } from "node:fs/promises";
|
|
12
|
+
async function extract(ctx, flags) {
|
|
13
|
+
const project = await loadProject(ctx);
|
|
14
|
+
if (flags.watch) return watchMode(ctx, project);
|
|
15
|
+
const scanned = await scan(project, { vite: flags.vite });
|
|
16
|
+
ctx.out.report(scanned.diagnostics);
|
|
17
|
+
const loaded = await loadCatalogs(project);
|
|
18
|
+
ctx.out.report(loaded.diagnostics);
|
|
19
|
+
const data = {
|
|
20
|
+
check: flags.check,
|
|
21
|
+
wrote: [],
|
|
22
|
+
stats: {},
|
|
23
|
+
blocked: []
|
|
24
|
+
};
|
|
25
|
+
if (readErrors(loaded.diagnostics).length > 0) return {
|
|
26
|
+
exitCode: EXIT.failure,
|
|
27
|
+
data
|
|
28
|
+
};
|
|
29
|
+
if (hasErrors(scanned.diagnostics)) {
|
|
30
|
+
ctx.out.result(flags.check ? "vi18n extract --check: the code has errors; fix them before the catalogs are compared" : "nothing written: the code has errors; fix them and rerun vi18n extract");
|
|
31
|
+
return {
|
|
32
|
+
exitCode: EXIT.failure,
|
|
33
|
+
data
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
if (flags.check) {
|
|
37
|
+
const drift = await computeDrift(project, scanned.messages, loaded.set);
|
|
38
|
+
data.stats = drift.sync.stats;
|
|
39
|
+
ctx.out.report(collisions(project, scanned, loaded.set, drift.sync));
|
|
40
|
+
ctx.out.report(drift.diagnostics);
|
|
41
|
+
const failed = hasErrors(ctx.out.diagnostics);
|
|
42
|
+
ctx.out.result(failed ? `vi18n extract --check: ${drift.locales.length + (drift.gen ? 1 : 0)} out-of-date file(s)` : "vi18n extract --check: catalogs are up to date");
|
|
43
|
+
return {
|
|
44
|
+
exitCode: failed ? EXIT.failure : EXIT.ok,
|
|
45
|
+
data
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
const options = {
|
|
49
|
+
mode: "full",
|
|
50
|
+
yes: flags.yes
|
|
51
|
+
};
|
|
52
|
+
const { config, codec } = project;
|
|
53
|
+
const sync = syncCatalogs({
|
|
54
|
+
config,
|
|
55
|
+
codec,
|
|
56
|
+
messages: scanned.messages,
|
|
57
|
+
catalogs: loaded.set,
|
|
58
|
+
options
|
|
59
|
+
});
|
|
60
|
+
data.stats = sync.stats;
|
|
61
|
+
ctx.out.report(sync.diagnostics);
|
|
62
|
+
const collided = collisions(project, scanned, loaded.set, sync);
|
|
63
|
+
ctx.out.report(collided);
|
|
64
|
+
if (hasErrors(collided)) return {
|
|
65
|
+
exitCode: EXIT.failure,
|
|
66
|
+
data
|
|
67
|
+
};
|
|
68
|
+
if (sync.blocked.length > 0) {
|
|
69
|
+
data.blocked = sync.blocked;
|
|
70
|
+
ctx.out.result(table([
|
|
71
|
+
"locale",
|
|
72
|
+
"removals",
|
|
73
|
+
"limit"
|
|
74
|
+
], sync.blocked.map((row) => [
|
|
75
|
+
row.locale,
|
|
76
|
+
row.removals,
|
|
77
|
+
row.limit
|
|
78
|
+
])));
|
|
79
|
+
ctx.out.result("nothing written: rerun with --yes if the removals are intended");
|
|
80
|
+
return {
|
|
81
|
+
exitCode: EXIT.failure,
|
|
82
|
+
data
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
data.wrote = await writeCatalogs(project, loaded.set, sync, scanned, options);
|
|
86
|
+
if (await writeGenFile(config)) data.wrote.push(rootRelative(config.root, join(dirname(config.configFile), "vi18n.gen.ts")));
|
|
87
|
+
printStats(ctx, data);
|
|
88
|
+
return {
|
|
89
|
+
exitCode: hasErrors(ctx.out.diagnostics) ? EXIT.failure : EXIT.ok,
|
|
90
|
+
data
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Collisions among the scan's texts and against the catalogs, ignoring the
|
|
95
|
+
* entries this run deletes (an entry whose text left the code cannot
|
|
96
|
+
* collide with the new text that took its id).
|
|
97
|
+
*/
|
|
98
|
+
function collisions(project, scanned, set, sync) {
|
|
99
|
+
return findCollisions(occurrencesOf(scanned), set, project.codec, {
|
|
100
|
+
referenced: new Set(project.config.extraction.keep),
|
|
101
|
+
ignoreKeys: new Set(sync.deletedKeys)
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Writes each changed locale with compare-and-swap: if the file changed
|
|
106
|
+
* since it was read (an editor, the dev server), the locale's sync re-runs
|
|
107
|
+
* on what is there now instead of overwriting it.
|
|
108
|
+
*/
|
|
109
|
+
async function writeCatalogs(project, set, sync, scanned, options) {
|
|
110
|
+
const { config, codec, layout } = project;
|
|
111
|
+
const sourceCatalog = set.get(config.sourceLocale)?.catalog ?? null;
|
|
112
|
+
const coverage = libraryCoverage(set, scanned.messages, config, codec);
|
|
113
|
+
return (await Promise.all(sync.changed.map(async (locale) => {
|
|
114
|
+
const loaded = set.get(locale);
|
|
115
|
+
const path = loaded?.path ?? catalogPath(config, locale);
|
|
116
|
+
const precomputed = sync.catalogs.get(locale) ?? null;
|
|
117
|
+
let first = true;
|
|
118
|
+
return (await updateCatalogFile(path, loaded?.hash ?? null, (current) => {
|
|
119
|
+
if (first && current === null && loaded?.catalog == null) {
|
|
120
|
+
first = false;
|
|
121
|
+
return precomputed;
|
|
122
|
+
}
|
|
123
|
+
first = false;
|
|
124
|
+
return syncCatalog({
|
|
125
|
+
config,
|
|
126
|
+
codec,
|
|
127
|
+
messages: scanned.messages,
|
|
128
|
+
locale,
|
|
129
|
+
catalog: current,
|
|
130
|
+
sourceCatalog,
|
|
131
|
+
options,
|
|
132
|
+
coverage
|
|
133
|
+
}).catalog;
|
|
134
|
+
}, {
|
|
135
|
+
locale,
|
|
136
|
+
layout,
|
|
137
|
+
isSourceLocale: locale === config.sourceLocale,
|
|
138
|
+
version: VERSION
|
|
139
|
+
})).written ? rootRelative(config.root, path) : null;
|
|
140
|
+
}))).filter((file) => file !== null);
|
|
141
|
+
}
|
|
142
|
+
function printStats(ctx, data) {
|
|
143
|
+
const rows = Object.entries(data.stats).map(([locale, s]) => [
|
|
144
|
+
locale,
|
|
145
|
+
s.added,
|
|
146
|
+
s.deleted,
|
|
147
|
+
s.cleared,
|
|
148
|
+
s.moved,
|
|
149
|
+
s.rekeyed,
|
|
150
|
+
s.updated
|
|
151
|
+
]);
|
|
152
|
+
if (rows.length > 0) ctx.out.result(table([
|
|
153
|
+
"locale",
|
|
154
|
+
"added",
|
|
155
|
+
"deleted",
|
|
156
|
+
"cleared",
|
|
157
|
+
"moved",
|
|
158
|
+
"rekeyed",
|
|
159
|
+
"updated"
|
|
160
|
+
], rows));
|
|
161
|
+
ctx.out.result(data.wrote.length === 0 ? "catalogs are up to date" : `wrote ${plural(data.wrote.length, "file")}: ${data.wrote.join(", ")}`);
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Add-only sync now and after every quiet period following a change under
|
|
165
|
+
* an include root, until the signal aborts (SIGINT in the binary). Each pass
|
|
166
|
+
* re-runs the cached whole-include scan: only changed files are parsed, and
|
|
167
|
+
* collisions are checked against every module, which a scan of the changed
|
|
168
|
+
* files alone could not do.
|
|
169
|
+
*/
|
|
170
|
+
async function watchMode(ctx, project) {
|
|
171
|
+
const { config } = project;
|
|
172
|
+
const data = {
|
|
173
|
+
check: false,
|
|
174
|
+
wrote: [],
|
|
175
|
+
stats: {},
|
|
176
|
+
blocked: []
|
|
177
|
+
};
|
|
178
|
+
const signal = ctx.io.signal;
|
|
179
|
+
const wrote = /* @__PURE__ */ new Set();
|
|
180
|
+
const pass = async () => {
|
|
181
|
+
const scanned = await scanProject(config, project.codec, { cache: true });
|
|
182
|
+
ctx.out.report(scanned.diagnostics);
|
|
183
|
+
if (scanned.aborted) return;
|
|
184
|
+
const loaded = await loadCatalogs(project);
|
|
185
|
+
ctx.out.report(loaded.diagnostics);
|
|
186
|
+
if (readErrors(loaded.diagnostics).length > 0) return;
|
|
187
|
+
const options = {
|
|
188
|
+
mode: "add-only",
|
|
189
|
+
yes: false
|
|
190
|
+
};
|
|
191
|
+
const sync = syncCatalogs({
|
|
192
|
+
config,
|
|
193
|
+
codec: project.codec,
|
|
194
|
+
messages: scanned.messages,
|
|
195
|
+
catalogs: loaded.set,
|
|
196
|
+
options
|
|
197
|
+
});
|
|
198
|
+
ctx.out.report(sync.diagnostics);
|
|
199
|
+
const collided = collisions(project, scanned, loaded.set, sync);
|
|
200
|
+
ctx.out.report(collided);
|
|
201
|
+
if (hasErrors(collided)) return;
|
|
202
|
+
const files = await writeCatalogs(project, loaded.set, sync, scanned, options);
|
|
203
|
+
for (const [locale, stats] of Object.entries(sync.stats)) {
|
|
204
|
+
const total = data.stats[locale] ??= {
|
|
205
|
+
added: 0,
|
|
206
|
+
deleted: 0,
|
|
207
|
+
cleared: 0,
|
|
208
|
+
moved: 0,
|
|
209
|
+
rekeyed: 0,
|
|
210
|
+
updated: 0
|
|
211
|
+
};
|
|
212
|
+
total.added += stats.added;
|
|
213
|
+
total.updated += stats.updated;
|
|
214
|
+
}
|
|
215
|
+
for (const file of files) wrote.add(file);
|
|
216
|
+
if (files.length > 0) ctx.out.note(`vi18n: updated ${files.join(", ")}`);
|
|
217
|
+
};
|
|
218
|
+
await writeGenFile(config);
|
|
219
|
+
const roots = await watchRoots(config.project.include);
|
|
220
|
+
const watchers = [];
|
|
221
|
+
let timer = null;
|
|
222
|
+
let running = Promise.resolve();
|
|
223
|
+
let pending = false;
|
|
224
|
+
let stopped = false;
|
|
225
|
+
let first = true;
|
|
226
|
+
const schedule = () => {
|
|
227
|
+
if (timer !== null) clearTimeout(timer);
|
|
228
|
+
timer = setTimeout(() => {
|
|
229
|
+
timer = null;
|
|
230
|
+
if (!pending) return;
|
|
231
|
+
pending = false;
|
|
232
|
+
running = running.then(pass).catch((error) => {
|
|
233
|
+
ctx.out.note(`vi18n: ${error instanceof Error ? error.message : String(error)}`);
|
|
234
|
+
});
|
|
235
|
+
}, 200);
|
|
236
|
+
};
|
|
237
|
+
for (const root of roots) {
|
|
238
|
+
const onEvent = (_event, name) => {
|
|
239
|
+
if (stopped) return;
|
|
240
|
+
const file = name === null ? null : resolve(root.dir, name.toString());
|
|
241
|
+
if (file !== null && root.file !== null && file !== root.file) return;
|
|
242
|
+
if (file !== null && !isIncluded(file, config)) return;
|
|
243
|
+
pending = true;
|
|
244
|
+
if (!first) schedule();
|
|
245
|
+
};
|
|
246
|
+
try {
|
|
247
|
+
watchers.push(root.file === null ? watch(root.dir, { recursive: true }, onEvent) : watch(root.dir, {}, onEvent));
|
|
248
|
+
} catch (error) {
|
|
249
|
+
ctx.out.note(`vi18n: cannot watch ${root.dir}: ${error instanceof Error ? error.message : String(error)}`);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
try {
|
|
253
|
+
await pass();
|
|
254
|
+
} catch (error) {
|
|
255
|
+
stopped = true;
|
|
256
|
+
for (const watcher of watchers) watcher.close();
|
|
257
|
+
throw error;
|
|
258
|
+
}
|
|
259
|
+
first = false;
|
|
260
|
+
if (pending) schedule();
|
|
261
|
+
await new Promise((done) => {
|
|
262
|
+
const stop = () => {
|
|
263
|
+
if (stopped) return;
|
|
264
|
+
stopped = true;
|
|
265
|
+
if (timer !== null) clearTimeout(timer);
|
|
266
|
+
for (const watcher of watchers) watcher.close();
|
|
267
|
+
running.then(done, done);
|
|
268
|
+
};
|
|
269
|
+
if (signal === void 0) return;
|
|
270
|
+
if (signal.aborted) stop();
|
|
271
|
+
else signal.addEventListener("abort", stop, { once: true });
|
|
272
|
+
});
|
|
273
|
+
return finishWatch(data, wrote);
|
|
274
|
+
}
|
|
275
|
+
function finishWatch(data, wrote) {
|
|
276
|
+
data.wrote = [...wrote].sort();
|
|
277
|
+
return {
|
|
278
|
+
exitCode: EXIT.ok,
|
|
279
|
+
data
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
/** Directories to watch: each include directory recursively, a file's parent for a file entry. */
|
|
283
|
+
async function watchRoots(include) {
|
|
284
|
+
const roots = [];
|
|
285
|
+
for (const entry of include) try {
|
|
286
|
+
const info = await stat(entry);
|
|
287
|
+
roots.push(info.isDirectory() ? {
|
|
288
|
+
dir: entry,
|
|
289
|
+
file: null
|
|
290
|
+
} : {
|
|
291
|
+
dir: dirname(entry),
|
|
292
|
+
file: entry
|
|
293
|
+
});
|
|
294
|
+
} catch {}
|
|
295
|
+
return roots;
|
|
296
|
+
}
|
|
297
|
+
//#endregion
|
|
298
|
+
export { extract };
|