@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.
Files changed (100) hide show
  1. package/LICENSE +49 -0
  2. package/README.md +110 -2
  3. package/dist/apply-Bre-lY9J.js +228 -0
  4. package/dist/captured-Bj8bO6vd.js +1034 -0
  5. package/dist/check-D34P-S--.js +1 -0
  6. package/dist/check-DyrAbv73.js +201 -0
  7. package/dist/cli.d.ts +1 -0
  8. package/dist/cli.js +41 -0
  9. package/dist/codec-DuvU2VQy.js +207 -0
  10. package/dist/collect-Cx4_SKOe.js +13 -0
  11. package/dist/collisions-CfpZU8e4.js +217 -0
  12. package/dist/compile-B5Mz6WJm.js +245 -0
  13. package/dist/compile-Cc97JaW2.js +2502 -0
  14. package/dist/concurrency-C9UUWuDC.js +96 -0
  15. package/dist/concurrency-DONL7t4V.js +1 -0
  16. package/dist/config.d.ts +21 -0
  17. package/dist/config.js +39 -0
  18. package/dist/dir-zw6bGMKQ.js +290 -0
  19. package/dist/duplicates-yuRljQ6h.js +185 -0
  20. package/dist/engine-B7p40M__.js +531 -0
  21. package/dist/engine.d.ts +43 -0
  22. package/dist/engine.js +1241 -0
  23. package/dist/exclude-Du-Zd_qI.js +26 -0
  24. package/dist/export-DGsMbcTZ.js +103 -0
  25. package/dist/extract-Dujlsgnk.js +298 -0
  26. package/dist/formats-KPIg-08V.js +781 -0
  27. package/dist/i18next-CrUnKFfh.js +1596 -0
  28. package/dist/i18next-Rgo-v9l0.js +1 -0
  29. package/dist/import-Cwdhcywt.js +225 -0
  30. package/dist/index.d.ts +156 -0
  31. package/dist/index.js +239 -0
  32. package/dist/init-BecCTvKi.js +792 -0
  33. package/dist/library-D5CWvTQs.js +1882 -0
  34. package/dist/lingui--PcOOYry.js +833 -0
  35. package/dist/lint.d.ts +105 -0
  36. package/dist/lint.js +971 -0
  37. package/dist/load-CQRz4PpM.js +2950 -0
  38. package/dist/load-gW-oJt1Q.js +747 -0
  39. package/dist/locales-DX4MOBFE.js +827 -0
  40. package/dist/lsp-Cq1Nuu-Q.js +31 -0
  41. package/dist/match-BGr8EXB3.js +89 -0
  42. package/dist/mcp-Cf0T7qPW.js +28 -0
  43. package/dist/merge-driver-CpiiSVIc.js +422 -0
  44. package/dist/messages-Cxa-5tR0.js +166 -0
  45. package/dist/migrate-CntqvrW4.js +1 -0
  46. package/dist/migrate-dPgq9uzy.js +1484 -0
  47. package/dist/openai-CTaMDcl-.js +346 -0
  48. package/dist/output-DsaHKHEt.js +164 -0
  49. package/dist/parse.d.ts +62 -0
  50. package/dist/parse.js +156 -0
  51. package/dist/pipeline-C7g11THL.js +2031 -0
  52. package/dist/pipeline-wxS0SApG.js +1 -0
  53. package/dist/project-Dwx7iPm6.js +381 -0
  54. package/dist/project-pXipT8iG.js +1046 -0
  55. package/dist/pseudo-DRv30iN5.js +145 -0
  56. package/dist/raw-C4Y6vyGY.js +4708 -0
  57. package/dist/react-intl-CaBVwJ9u.js +519 -0
  58. package/dist/restore-Btq66Tid.js +175 -0
  59. package/dist/rewrite-CiTRAICl.js +803 -0
  60. package/dist/rich-DCUKl5HM.js +97 -0
  61. package/dist/rolldown-runtime-D7D4PA-g.js +13 -0
  62. package/dist/run-DYD_VXKy.js +694 -0
  63. package/dist/runtime-DTMmliT1.js +64 -0
  64. package/dist/scan-worker.d.ts +62 -0
  65. package/dist/scan-worker.js +1 -0
  66. package/dist/scoped-BMYpSthj.js +44 -0
  67. package/dist/server-BDkykMjb.js +11823 -0
  68. package/dist/server-qv3mz70d.js +10485 -0
  69. package/dist/server.d.ts +169 -0
  70. package/dist/server.js +1234 -0
  71. package/dist/sitemap-DV3o8QD7.js +159 -0
  72. package/dist/sources-D19czDSA.js +2242 -0
  73. package/dist/status-DZArW5aE.js +149 -0
  74. package/dist/stdio-CVaJfxA7.js +29 -0
  75. package/dist/stdio-FPOKQAgu.js +95 -0
  76. package/dist/sync-CcVzger_.js +658 -0
  77. package/dist/t-DC-c9zE5.js +454 -0
  78. package/dist/test.d.ts +116 -0
  79. package/dist/test.js +319 -0
  80. package/dist/translate-DZR3MkSA.js +299 -0
  81. package/dist/ts-BaU5yOf0.js +4863 -0
  82. package/dist/types-CgUCkFU1.d.ts +707 -0
  83. package/dist/types-D3QGM8XO.js +146 -0
  84. package/dist/types-DsdWBG0b.d.ts +558 -0
  85. package/dist/types-DtvQ7UdD.js +1948 -0
  86. package/dist/types-wSh9fCBY.js +11 -0
  87. package/dist/units-CQ5Q99RC.js +150 -0
  88. package/dist/url-CRfCOhUI.js +172 -0
  89. package/dist/url-CsBkRaJy.js +225 -0
  90. package/dist/validate-BxpupdAf.js +2615 -0
  91. package/dist/validate-D6DJepZv.js +365 -0
  92. package/dist/version-BlyLGlOa.js +28 -0
  93. package/dist/vite-scan-B_YW4X_a.js +242 -0
  94. package/dist/vite.d.ts +34 -0
  95. package/dist/vite.js +7558 -0
  96. package/dist/vue-i18n-CEMnpv7k.js +651 -0
  97. package/dist/which-C6PFI1Dm.js +116 -0
  98. package/dist/worker-84U6uYB3.js +1656 -0
  99. package/package.json +97 -3
  100. 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 };