@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 @@
|
|
|
1
|
+
export { t as MAX_RETRIES, n as catalogPersister, r as createProvider, i as estimateUsage, a as firstRequests, o as planTranslations, s as replayProvider, c as runTranslations } from "./pipeline-C7g11THL.js";
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
import { o as isDiagnosticError } from "./types-D3QGM8XO.js";
|
|
2
|
+
import { a as isManualId } from "./validate-BxpupdAf.js";
|
|
3
|
+
import { t as createCodec } from "./codec-DuvU2VQy.js";
|
|
4
|
+
import { C as usableTranslation, S as entryKey, i as libraryTranslation, l as loadCatalogSet, o as catalogLayout, s as catalogPath } from "./library-D5CWvTQs.js";
|
|
5
|
+
import { n as scanProject } from "./project-pXipT8iG.js";
|
|
6
|
+
import { n as loadConfig } from "./load-gW-oJt1Q.js";
|
|
7
|
+
import { i as genFileUpToDate, n as syncCatalogs, r as GEN_FILE } from "./sync-CcVzger_.js";
|
|
8
|
+
import { r as resolveDuplicates, t as duplicateDiagnostics } from "./duplicates-yuRljQ6h.js";
|
|
9
|
+
import { i as rootRelative, r as plural } from "./output-DsaHKHEt.js";
|
|
10
|
+
import { t as EXIT } from "./types-wSh9fCBY.js";
|
|
11
|
+
import { dirname, join } from "node:path";
|
|
12
|
+
import { readFile } from "node:fs/promises";
|
|
13
|
+
//#region src/cli/project.ts
|
|
14
|
+
/**
|
|
15
|
+
* What several commands share: loading the config (exit 2 without one, so a
|
|
16
|
+
* typo in `--cwd` never "succeeds" on an empty project), the cached include
|
|
17
|
+
* scan, the catalog set, the referenced messages (code plus `keep` ids), each
|
|
18
|
+
* locale's own-entry status, and the drift `extract --check` and `compile`
|
|
19
|
+
* refuse to run past.
|
|
20
|
+
*/
|
|
21
|
+
/** Ends a command early with an exit code; `run` reports the diagnostics and prints the envelope. */
|
|
22
|
+
var CliExit = class extends Error {
|
|
23
|
+
exitCode;
|
|
24
|
+
diagnostics;
|
|
25
|
+
data;
|
|
26
|
+
constructor(exitCode, diagnostics = [], data = null) {
|
|
27
|
+
super(diagnostics[0]?.message ?? `exit ${exitCode}`);
|
|
28
|
+
this.exitCode = exitCode;
|
|
29
|
+
this.diagnostics = diagnostics;
|
|
30
|
+
this.data = data;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
async function loadProject(ctx) {
|
|
34
|
+
let config;
|
|
35
|
+
try {
|
|
36
|
+
config = await loadConfig({
|
|
37
|
+
cwd: ctx.global.cwd,
|
|
38
|
+
configFile: ctx.global.config
|
|
39
|
+
});
|
|
40
|
+
} catch (error) {
|
|
41
|
+
if (isDiagnosticError(error)) throw new CliExit(EXIT.usage, [error.diagnostic]);
|
|
42
|
+
throw new CliExit(EXIT.usage, [{
|
|
43
|
+
code: "config-invalid",
|
|
44
|
+
severity: "error",
|
|
45
|
+
message: `vi18n.config.ts could not be loaded: ${error instanceof Error ? error.message : String(error)}`
|
|
46
|
+
}]);
|
|
47
|
+
}
|
|
48
|
+
if (config.configFile === null) throw new CliExit(EXIT.usage, [{
|
|
49
|
+
code: "config-not-found",
|
|
50
|
+
severity: "error",
|
|
51
|
+
message: `no vi18n.config.ts in ${ctx.global.cwd} or any directory above it up to the workspace root; run vi18n init`
|
|
52
|
+
}]);
|
|
53
|
+
ctx.out.setRoot(config.root);
|
|
54
|
+
ctx.out.addSecret(ctx.io.env[config.ai.apiKeyEnv]);
|
|
55
|
+
return {
|
|
56
|
+
config,
|
|
57
|
+
codec: createCodec(config.ids.length),
|
|
58
|
+
layout: catalogLayout(config)
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The cached include scan, or with `vite` the scan through the project's
|
|
63
|
+
* Vite config (`extract --vite`); an aborted scan (parse error, missing
|
|
64
|
+
* compiler, empty root) ends the command.
|
|
65
|
+
*/
|
|
66
|
+
async function scan(project, options = {}) {
|
|
67
|
+
const result = options.vite === true ? await (await import("./vite-scan-B_YW4X_a.js")).scanWithVite(project.config) : await scanProject(project.config, project.codec, { cache: true });
|
|
68
|
+
if (result.aborted) throw new CliExit(EXIT.failure, result.diagnostics.filter(isAbortCause));
|
|
69
|
+
return result;
|
|
70
|
+
}
|
|
71
|
+
function isAbortCause(diagnostic) {
|
|
72
|
+
return diagnostic.severity === "error";
|
|
73
|
+
}
|
|
74
|
+
async function loadCatalogs(project) {
|
|
75
|
+
return loadCatalogSet(project.config, project.codec);
|
|
76
|
+
}
|
|
77
|
+
/** Every occurrence with its location, as `findCollisions` wants them (aggregation keeps only the first text of an id). */
|
|
78
|
+
function occurrencesOf(scanned) {
|
|
79
|
+
const list = [];
|
|
80
|
+
for (const file of scanned.files) for (const occurrence of file.occurrences) list.push({
|
|
81
|
+
id: occurrence.id,
|
|
82
|
+
manual: occurrence.manual,
|
|
83
|
+
canonical: occurrence.canonical,
|
|
84
|
+
sourceHash: occurrence.sourceHash,
|
|
85
|
+
label: occurrence.label,
|
|
86
|
+
source: occurrence.source,
|
|
87
|
+
locations: [{
|
|
88
|
+
file: occurrence.file,
|
|
89
|
+
line: occurrence.line
|
|
90
|
+
}]
|
|
91
|
+
});
|
|
92
|
+
return list;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* The messages a locale must cover: the scan's, plus `keep` ids no code
|
|
96
|
+
* declares, whose text comes from the source locale's entry (else any
|
|
97
|
+
* locale's), as `compileLocales` resolves them. A `keep` id with no entry
|
|
98
|
+
* anywhere is `keep-missing-entry`.
|
|
99
|
+
*/
|
|
100
|
+
function referencedMessages(project, messages, set) {
|
|
101
|
+
const { config, codec, layout } = project;
|
|
102
|
+
const identities = /* @__PURE__ */ new Map();
|
|
103
|
+
for (const [id, message] of messages) identities.set(id, message);
|
|
104
|
+
const diagnostics = [];
|
|
105
|
+
const wanted = config.extraction.keep.filter((id) => !identities.has(id));
|
|
106
|
+
if (wanted.length === 0) return {
|
|
107
|
+
identities,
|
|
108
|
+
diagnostics
|
|
109
|
+
};
|
|
110
|
+
const order = [config.sourceLocale, ...config.locales.filter((l) => l !== config.sourceLocale)];
|
|
111
|
+
for (const id of wanted) {
|
|
112
|
+
let text = null;
|
|
113
|
+
for (const locale of order) {
|
|
114
|
+
const catalog = set.get(locale)?.catalog;
|
|
115
|
+
if (catalog == null) continue;
|
|
116
|
+
for (const entry of catalog.entries) if (entry.source !== null && entryKey(entry, layout, codec) === id) {
|
|
117
|
+
text = entry.source;
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
if (text !== null) break;
|
|
121
|
+
}
|
|
122
|
+
if (text === null) {
|
|
123
|
+
diagnostics.push({
|
|
124
|
+
code: "keep-missing-entry",
|
|
125
|
+
severity: "error",
|
|
126
|
+
message: `extraction.keep lists ${id}, but no code declares it and no catalog has an entry for it; add msg({ id: "${id}", message: "…" }) to the code or remove it from keep`,
|
|
127
|
+
id
|
|
128
|
+
});
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
const canonical = codec.canonicalize(text);
|
|
133
|
+
identities.set(id, {
|
|
134
|
+
id,
|
|
135
|
+
manual: isManualId(id),
|
|
136
|
+
canonical,
|
|
137
|
+
sourceHash: codec.sourceHash(canonical),
|
|
138
|
+
label: codec.label(canonical)
|
|
139
|
+
});
|
|
140
|
+
} catch (error) {
|
|
141
|
+
if (!isDiagnosticError(error)) throw error;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
identities,
|
|
146
|
+
diagnostics
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* The locale's **own** entries against the referenced messages: the one
|
|
151
|
+
* definition (`usableTranslation`) that compilation, `check` and `status`
|
|
152
|
+
* share, without walking fallbacks — a fallback hides a gap, it does not
|
|
153
|
+
* fill it.
|
|
154
|
+
*/
|
|
155
|
+
function ownStatus(project, locale, loaded, identities) {
|
|
156
|
+
const { codec, layout, config } = project;
|
|
157
|
+
const status = {
|
|
158
|
+
locale,
|
|
159
|
+
total: identities.size,
|
|
160
|
+
translated: 0,
|
|
161
|
+
ai: 0,
|
|
162
|
+
missing: 0,
|
|
163
|
+
reasons: /* @__PURE__ */ new Map(),
|
|
164
|
+
invalid: [],
|
|
165
|
+
diagnostics: [],
|
|
166
|
+
schemeMismatch: false,
|
|
167
|
+
entries: /* @__PURE__ */ new Map()
|
|
168
|
+
};
|
|
169
|
+
const read = loaded?.catalog ?? null;
|
|
170
|
+
const index = /* @__PURE__ */ new Map();
|
|
171
|
+
if (read !== null) {
|
|
172
|
+
const resolved = resolveDuplicates(read, codec);
|
|
173
|
+
const file = rootRelative(config.root, loaded?.path ?? catalogPath(config, locale));
|
|
174
|
+
status.diagnostics.push(...duplicateDiagnostics(resolved.duplicates, file));
|
|
175
|
+
for (const entry of resolved.catalog.entries) {
|
|
176
|
+
const key = entryKey(entry, layout, codec);
|
|
177
|
+
if (key !== null && !index.has(key)) index.set(key, entry);
|
|
178
|
+
}
|
|
179
|
+
status.schemeMismatch = layout !== "source" && read.header.idScheme !== null && read.header.idScheme !== codec.idScheme;
|
|
180
|
+
}
|
|
181
|
+
for (const [id, identity] of identities) {
|
|
182
|
+
if (status.schemeMismatch && (layout === "id" || !identity.manual)) {
|
|
183
|
+
status.missing++;
|
|
184
|
+
status.reasons.set(id, "scheme-mismatch");
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
const entry = index.get(id);
|
|
188
|
+
const library = () => {
|
|
189
|
+
const found = libraryTranslation(loaded, identity, layout, codec);
|
|
190
|
+
if (found === null) return false;
|
|
191
|
+
if (found.state === "ai") status.ai++;
|
|
192
|
+
else status.translated++;
|
|
193
|
+
return true;
|
|
194
|
+
};
|
|
195
|
+
if (entry === void 0) {
|
|
196
|
+
if (library()) continue;
|
|
197
|
+
status.missing++;
|
|
198
|
+
status.reasons.set(id, "no-entry");
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
status.entries.set(id, entry);
|
|
202
|
+
const usable = usableTranslation(entry, layout, identity, codec, locale);
|
|
203
|
+
if (usable.ok) {
|
|
204
|
+
if (entry.state === "ai") status.ai++;
|
|
205
|
+
else status.translated++;
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
if (entry.translation === "" && library()) continue;
|
|
209
|
+
status.missing++;
|
|
210
|
+
status.reasons.set(id, usable.reason);
|
|
211
|
+
if (usable.reason === "invalid" || usable.reason === "fuzzy") status.invalid.push({
|
|
212
|
+
id,
|
|
213
|
+
reason: usable.reason,
|
|
214
|
+
line: entry.line
|
|
215
|
+
});
|
|
216
|
+
status.diagnostics.push(...usable.diagnostics);
|
|
217
|
+
}
|
|
218
|
+
return status;
|
|
219
|
+
}
|
|
220
|
+
/** (translated + ai) / total × 100 with one decimal; an empty locale has nothing left to do. */
|
|
221
|
+
function percent(done, total) {
|
|
222
|
+
if (total === 0) return 100;
|
|
223
|
+
return Math.round(done / total * 1e3) / 10;
|
|
224
|
+
}
|
|
225
|
+
/** Unused-entry warnings listed per locale; the rest is counted in the drift message. */
|
|
226
|
+
const UNUSED_LISTED = 20;
|
|
227
|
+
/**
|
|
228
|
+
* What `vi18n extract` would change, without writing (`extract --check`,
|
|
229
|
+
* and `compile`'s refusal). The safety limit does not apply: drift is drift
|
|
230
|
+
* whether or not extract would need `--yes` to fix it.
|
|
231
|
+
*/
|
|
232
|
+
async function computeDrift(project, messages, set) {
|
|
233
|
+
const { config, codec, layout } = project;
|
|
234
|
+
const sync = syncCatalogs({
|
|
235
|
+
config,
|
|
236
|
+
codec,
|
|
237
|
+
messages,
|
|
238
|
+
catalogs: set,
|
|
239
|
+
options: {
|
|
240
|
+
mode: "full",
|
|
241
|
+
yes: true
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
const diagnostics = [];
|
|
245
|
+
const keep = new Set(config.extraction.keep);
|
|
246
|
+
const changed = new Set(sync.changed);
|
|
247
|
+
for (const locale of config.locales) {
|
|
248
|
+
const loaded = set.get(locale);
|
|
249
|
+
const path = loaded?.path ?? catalogPath(config, locale);
|
|
250
|
+
const file = rootRelative(config.root, path);
|
|
251
|
+
const read = loaded?.catalog ?? null;
|
|
252
|
+
let wrapped = 0;
|
|
253
|
+
let unused = 0;
|
|
254
|
+
if (read !== null) {
|
|
255
|
+
if (read.header.idScheme !== null && read.header.idScheme !== codec.idScheme) diagnostics.push({
|
|
256
|
+
code: "id-scheme-mismatch",
|
|
257
|
+
severity: "error",
|
|
258
|
+
message: `${file} records ${read.header.idScheme}, but ids.length ${config.ids.length} gives ${codec.idScheme}; run vi18n extract to re-key it (translations are kept)`,
|
|
259
|
+
file,
|
|
260
|
+
locale
|
|
261
|
+
});
|
|
262
|
+
for (const entry of read.entries) {
|
|
263
|
+
if (entry.wrapped) {
|
|
264
|
+
wrapped++;
|
|
265
|
+
diagnostics.push({
|
|
266
|
+
code: "catalog-wrapped",
|
|
267
|
+
severity: "warning",
|
|
268
|
+
message: "a string is wrapped over several lines; vi18n extract rewrites it on one line",
|
|
269
|
+
file,
|
|
270
|
+
line: entry.line,
|
|
271
|
+
locale
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
if (entry.foreignContext !== null) {
|
|
275
|
+
diagnostics.push({
|
|
276
|
+
code: "catalog-foreign-context",
|
|
277
|
+
severity: "warning",
|
|
278
|
+
message: `msgctxt "${entry.foreignContext}" is not a manual id (no "."), so no message matches this entry; give it a manual id with a "." or remove it`,
|
|
279
|
+
file,
|
|
280
|
+
line: entry.line,
|
|
281
|
+
locale
|
|
282
|
+
});
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
const key = entryKey(entry, layout, codec);
|
|
286
|
+
if (key === null || messages.has(key) || keep.has(key)) continue;
|
|
287
|
+
unused++;
|
|
288
|
+
if (unused <= UNUSED_LISTED) diagnostics.push({
|
|
289
|
+
code: "catalog-unused-entry",
|
|
290
|
+
severity: "warning",
|
|
291
|
+
message: `${key} is not referenced by the code; vi18n extract deletes it`,
|
|
292
|
+
file,
|
|
293
|
+
line: entry.line,
|
|
294
|
+
locale,
|
|
295
|
+
id: key
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if (!changed.has(locale)) continue;
|
|
300
|
+
diagnostics.push({
|
|
301
|
+
code: "catalog-drift",
|
|
302
|
+
severity: "error",
|
|
303
|
+
message: read === null ? `${file} does not exist yet; run vi18n extract` : `${file} is out of date (${driftSummary(sync.stats[locale], wrapped, unused)}); run vi18n extract`,
|
|
304
|
+
file,
|
|
305
|
+
locale
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
const gen = config.configFile === null ? null : await genDrift(config);
|
|
309
|
+
if (gen !== null) {
|
|
310
|
+
const path = join(dirname(config.configFile), GEN_FILE);
|
|
311
|
+
diagnostics.push({
|
|
312
|
+
code: "catalog-drift",
|
|
313
|
+
severity: "error",
|
|
314
|
+
message: gen === "missing" ? `${GEN_FILE} does not exist yet; run vi18n extract` : `${GEN_FILE} differs from what vi18n extract writes for the configured locales (edited by hand, or the locales changed); run vi18n extract`,
|
|
315
|
+
file: rootRelative(config.root, path)
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
return {
|
|
319
|
+
sync,
|
|
320
|
+
locales: sync.changed,
|
|
321
|
+
gen: gen !== null,
|
|
322
|
+
diagnostics
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
function driftSummary(stats, wrapped, unused) {
|
|
326
|
+
const parts = [];
|
|
327
|
+
if (stats !== void 0) {
|
|
328
|
+
if (stats.added > 0) parts.push(`${stats.added} to add`);
|
|
329
|
+
if (stats.deleted > 0) parts.push(`${stats.deleted} to delete`);
|
|
330
|
+
if (stats.cleared > 0) parts.push(`${stats.cleared} to clear`);
|
|
331
|
+
if (stats.moved > 0) parts.push(`${stats.moved} to move`);
|
|
332
|
+
if (stats.rekeyed > 0) parts.push(`${stats.rekeyed} to re-key`);
|
|
333
|
+
if (stats.updated > 0) parts.push(`${stats.updated} to update`);
|
|
334
|
+
}
|
|
335
|
+
if (wrapped > 0) parts.push(plural(wrapped, "wrapped entry", "wrapped entries"));
|
|
336
|
+
if (unused > UNUSED_LISTED) parts.push(`${unused} unused entries, ${UNUSED_LISTED} listed`);
|
|
337
|
+
return parts.length === 0 ? "formatting or header" : parts.join(", ");
|
|
338
|
+
}
|
|
339
|
+
/** Null when `vi18n.gen.ts` is up to date (an earlier release's content for the same locales counts). */
|
|
340
|
+
async function genDrift(config) {
|
|
341
|
+
const path = join(dirname(config.configFile), GEN_FILE);
|
|
342
|
+
try {
|
|
343
|
+
return genFileUpToDate(config, await readFile(path, "utf8")) ? null : "stale";
|
|
344
|
+
} catch (error) {
|
|
345
|
+
if (error.code === "ENOENT") return "missing";
|
|
346
|
+
throw error;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
/** Catalog read errors make every writer refuse: rewriting a file vi18n cannot fully read drops what it skipped. */
|
|
350
|
+
function readErrors(diagnostics) {
|
|
351
|
+
return diagnostics.filter((d) => d.code === "catalog-parse" && d.severity === "error");
|
|
352
|
+
}
|
|
353
|
+
/** Target locales from `--locale` (default: every configured locale but the source); unknown ones are usage errors. */
|
|
354
|
+
function targetLocales(config, requested) {
|
|
355
|
+
const targets = config.locales.filter((locale) => locale !== config.sourceLocale);
|
|
356
|
+
if (requested.length === 0) return targets;
|
|
357
|
+
const out = [];
|
|
358
|
+
for (const locale of requested) {
|
|
359
|
+
if (locale === config.sourceLocale) throw new CliExit(EXIT.usage, [{
|
|
360
|
+
code: "config-invalid",
|
|
361
|
+
severity: "error",
|
|
362
|
+
message: `--locale ${locale} is the source locale, which is compiled from code and has nothing to translate`
|
|
363
|
+
}]);
|
|
364
|
+
if (!targets.includes(locale)) throw new CliExit(EXIT.usage, [{
|
|
365
|
+
code: "config-invalid",
|
|
366
|
+
severity: "error",
|
|
367
|
+
message: `--locale ${locale} is not in locales (${config.locales.join(", ")})`
|
|
368
|
+
}]);
|
|
369
|
+
if (!out.includes(locale)) out.push(locale);
|
|
370
|
+
}
|
|
371
|
+
return out;
|
|
372
|
+
}
|
|
373
|
+
function hasErrors(diagnostics) {
|
|
374
|
+
return diagnostics.some((diagnostic) => diagnostic.severity === "error");
|
|
375
|
+
}
|
|
376
|
+
/** UTF-16 code-unit order, as every vi18n file uses. */
|
|
377
|
+
function compareText(a, b) {
|
|
378
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
379
|
+
}
|
|
380
|
+
//#endregion
|
|
381
|
+
export { loadCatalogs as a, ownStatus as c, referencedMessages as d, scan as f, hasErrors as i, percent as l, compareText as n, loadProject as o, targetLocales as p, computeDrift as r, occurrencesOf as s, CliExit as t, readErrors as u };
|