@verbaly/studio 0.54.0 → 0.55.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/README.md CHANGED
@@ -14,9 +14,10 @@
14
14
  Your JSON catalogs are the database and git is the history. Studio is a window onto them, which is
15
15
  why it cannot fall out of sync with your code: it does not own anything.
16
16
 
17
- > **This release ships the server and the state, not the interface.** Every route below is real and
18
- > tested; the panel that will consume them is not written yet. Useful today as `--json`, as a local
19
- > API for a script, and as the thing the interface will be built against.
17
+ > **The panel is here.** `npx verbaly-studio` opens a page that shows every message in your project,
18
+ > in as many languages as you tick, with its state on each one. You edit in the row itself, you mark
19
+ > what a machine wrote as read, you read what `verbaly doctor` found, and you run the two commands
20
+ > that change your catalogs: finding new text in your code, and translating what is missing.
20
21
 
21
22
  > **Studio edits translations. It never edits the source text.** The source lives in your code and
22
23
  > the key derives from it, so editing it here would be undone by the next `verbaly extract`. A write
@@ -31,9 +32,9 @@ pnpm add -D @verbaly/studio
31
32
  ```bash
32
33
  npx verbaly-studio
33
34
 
34
- # Verbaly Studio http://127.0.0.1:4747/api/state?t=kQ7pVn2XsL4b
35
+ # Verbaly Studio http://127.0.0.1:4747/?t=kQ7pVn2XsL4b
35
36
  # Project /home/you/app
36
- # Catalog 1479 messages · 212 untranslated · 187 drafts
37
+ # Catalog 1587 messages · 212 untranslated · 187 drafts
37
38
  #
38
39
  # Ctrl+C to stop
39
40
  ```
@@ -45,7 +46,7 @@ npx verbaly-studio
45
46
  | `--json` | print the whole state to stdout and exit, no server |
46
47
 
47
48
  `--json` is the panel without the panel: the same object the interface reads, so it can be piped,
48
- diffed and scripted.
49
+ diffed and scripted. Opening the printed url gets you the screen instead.
49
50
 
50
51
  ## 🔒 Security, which is not optional for a server that writes files
51
52
 
@@ -71,8 +72,13 @@ keep in step. Every answer is JSON, including the errors, which are `{ "error":
71
72
  | `GET /api/health` | what `verbaly doctor` reports |
72
73
  | `PUT /api/message/:locale/:key` | write a translation **and clear its draft flag**; body `{ "text": "…" }` |
73
74
  | `POST /api/approve` | approve drafts; body `{ "locale": "es", "keys": ["…"] }`, and the whole locale when `keys` is omitted |
75
+ | `POST /api/extract` | read your code and add what your catalogs do not have; answers in the request, because it is local and free |
76
+ | `GET /api/translate` | **the bill**: how many messages and in which languages, without calling the provider |
77
+ | `POST /api/translate` | start the run and get back a job; body `{ "locales": ["es"] }` |
78
+ | `GET /api/job/:id` | how that run is going, to ask once a second |
79
+ | `GET /api/job` | the run in progress, or `null`; a reloaded page asks this instead of losing the bar |
74
80
 
75
- `GET /api/state` is unpaginated on purpose: the biggest catalog we know is 1479 messages, and over
81
+ `GET /api/state` is unpaginated on purpose: the biggest catalog we know is 1587 messages, and over
76
82
  localhost that is instant. Its `problems` array is why nothing Studio reads can take the server
77
83
  down: a catalog it cannot parse, a drafts sidecar it cannot parse, a source file Babel cannot read,
78
84
  and a `--root` with no catalogs in it are all reported there, by a path relative to your project,
@@ -92,6 +98,16 @@ approving itself. Studio is the human's: it exists to put the source text and th
92
98
  front of a person. The token is what keeps that true, so treat it as the thing that says a person is
93
99
  here, and do not paste it into an agent's prompt.
94
100
 
101
+ **Translating is the one thing here that spends money, so the panel shows the bill first.** The
102
+ plan is its own route, which is why looking at the cost cannot start the spending: it says how
103
+ many messages and in which languages, and nothing is called until you say go. The run itself is a
104
+ job you ask about rather than a stream you listen to, because `verbaly translate` writes partial
105
+ results on purpose and a reload must not lose them. **One run at a time**: two runs over the same
106
+ catalogs is a race over the same files.
107
+
108
+ **What a machine writes stays a draft**, here as everywhere else. The panel does not get to change
109
+ that rule, so a finished run leaves you a list to read, not a job marked done.
110
+
95
111
  ## 🔎 Triage: which machine translations are worth reading
96
112
 
97
113
  Adding a language writes hundreds of drafts at once, and reading them one by one is a wall. Walls
@@ -117,7 +133,7 @@ an unfinished translation is not a second opinion.
117
133
  changed because of what the measurement said.** `digits` was reading the comma after a number as
118
134
  part of it, and it was flagging a number the translation added where the source had none, which is
119
135
  what "refresh" becoming "F5" looks like. Those were **10 of 10** of its hits, all correct text, so
120
- both are now excluded. The result is **18 of 1479 messages** flagged per language.
136
+ both are now excluded. The result is **23 of 1587 messages** flagged in Spanish and 22 in Portuguese.
121
137
 
122
138
  ## 🧩 Programmatic API
123
139
 
package/dist/cli.js CHANGED
@@ -1,9 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import { parseArgs } from "node:util";
3
- import { DRAFTS_FILE, check, clearDrafts, collectOrigins, counted, doctor, effectiveDrafts, extractProject, formatCliError, loadConfig, loadDrafts, readCatalog, saveDrafts, status, targetLocales, validateMessage, validatePair, writeCatalog } from "@verbaly/compiler";
4
- import { existsSync } from "node:fs";
5
- import { relative } from "node:path";
3
+ import { DRAFTS_FILE, check, clearDrafts, collectOrigins, counted, doctor, effectiveDrafts, extractProject, formatCliError, loadCatalogs, loadConfig, loadDrafts, markDrafts, readCatalog, resolveProvider, saveDrafts, status, syncCatalogs, targetLocales, translateCatalogs, validateMessage, validatePair, writeCatalog, writeDts } from "@verbaly/compiler";
4
+ import { existsSync, readFileSync, statSync } from "node:fs";
5
+ import { join, normalize, relative, resolve } from "node:path";
6
6
  import { createServer } from "node:http";
7
+ import { fileURLToPath } from "node:url";
7
8
  import { randomBytes, timingSafeEqual } from "node:crypto";
8
9
  //#region src/http.ts
9
10
  var HttpError = class extends Error {
@@ -195,13 +196,138 @@ function writeMessage(cfg, locale, key, text) {
195
196
  function approve(cfg, locale, keys) {
196
197
  if (!cfg.locales.includes(locale)) throw badRequest(`${locale} is not one of this project's locales`);
197
198
  const drafts = loadDrafts(cfg);
198
- const before = (drafts[locale] ?? []).length;
199
+ const had = drafts[locale] ?? [];
200
+ const cleared = keys ? had.filter((key) => keys.includes(key)) : [...had];
199
201
  clearDrafts(drafts, locale, keys);
200
- const after = (drafts[locale] ?? []).length;
201
202
  saveDrafts(cfg, drafts);
202
203
  return {
203
204
  locale,
204
- approved: before - after
205
+ approved: cleared.length,
206
+ keys: cleared
207
+ };
208
+ }
209
+ function unapprove(cfg, locale, keys) {
210
+ if (!cfg.locales.includes(locale)) throw badRequest(`${locale} is not one of this project's locales`);
211
+ const drafts = loadDrafts(cfg);
212
+ markDrafts(drafts, locale, keys);
213
+ saveDrafts(cfg, drafts);
214
+ return {
215
+ locale,
216
+ approved: keys.length,
217
+ keys
218
+ };
219
+ }
220
+ //#endregion
221
+ //#region src/jobs.ts
222
+ let current = null;
223
+ let counter = 0;
224
+ function running() {
225
+ return current?.state === "running" ? current : null;
226
+ }
227
+ function read(id) {
228
+ if (!current || current.id !== id) throw new HttpError(404, `no job called "${id}"`);
229
+ return current;
230
+ }
231
+ function start(kind, total) {
232
+ if (running()) throw badRequest("a job is already running, wait for it or read its progress");
233
+ counter += 1;
234
+ current = {
235
+ id: `${kind}-${counter}`,
236
+ kind,
237
+ state: "running",
238
+ total,
239
+ done: 0
240
+ };
241
+ return current;
242
+ }
243
+ function advance(id, keys, locale, error) {
244
+ if (current?.id !== id) return;
245
+ current.done = Math.min(current.done + keys, current.total);
246
+ current.locale = locale;
247
+ if (error) current.message = error;
248
+ }
249
+ function finish(id, state, patch = {}) {
250
+ if (current?.id === id) Object.assign(current, patch, { state });
251
+ }
252
+ //#endregion
253
+ //#region src/actions.ts
254
+ async function runExtract(cfg) {
255
+ if (!cfg.include.length) throw badRequest("source scanning is off (include: []), so there is nothing to extract");
256
+ const registry = await extractProject(cfg);
257
+ const catalogs = loadCatalogs(cfg);
258
+ const result = syncCatalogs(cfg, catalogs, registry);
259
+ for (const locale of cfg.locales) writeCatalog(cfg, locale, catalogs[locale] ?? {});
260
+ if (cfg.dts !== false) writeDts(cfg, catalogs[cfg.sourceLocale] ?? {}, cfg.dts);
261
+ const found = result.added[cfg.sourceLocale]?.length ?? 0;
262
+ return {
263
+ added: result.added,
264
+ found,
265
+ messages: registry.messages().size
266
+ };
267
+ }
268
+ async function planTranslate(cfg, locales) {
269
+ const provider = await resolveProvider(cfg);
270
+ const catalogs = loadCatalogs(cfg);
271
+ const result = await translateCatalogs(cfg, catalogs, provider, {
272
+ locales,
273
+ dryRun: true
274
+ });
275
+ const total = Object.values(result.pending).reduce((n, keys) => n + keys.length, 0);
276
+ return {
277
+ pending: result.pending,
278
+ total
279
+ };
280
+ }
281
+ async function startTranslate(cfg, locales) {
282
+ const provider = await resolveProvider(cfg);
283
+ const job = start("translate", (await planTranslate(cfg, locales)).total);
284
+ const id = job.id;
285
+ (async () => {
286
+ try {
287
+ const catalogs = loadCatalogs(cfg);
288
+ const result = await translateCatalogs(cfg, catalogs, provider, {
289
+ locales,
290
+ batchSize: cfg.translate.batchSize,
291
+ concurrency: cfg.translate.concurrency,
292
+ retries: cfg.translate.retries,
293
+ origins: await collectOrigins(cfg),
294
+ onProgress: (p) => advance(id, p.keys, p.locale, p.error)
295
+ });
296
+ for (const [locale, keys] of Object.entries(result.translated)) if (keys.length) writeCatalog(cfg, locale, catalogs[locale] ?? {});
297
+ const drafts = loadDrafts(cfg);
298
+ for (const [locale, keys] of Object.entries(result.translated)) markDrafts(drafts, locale, keys);
299
+ saveDrafts(cfg, drafts);
300
+ finish(id, result.failed.length ? "failed" : "done", {
301
+ result,
302
+ message: result.failed.length ? `${result.failed.length} batches did not answer` : void 0
303
+ });
304
+ } catch (error) {
305
+ finish(id, "failed", { message: error instanceof Error ? error.message : String(error) });
306
+ }
307
+ })();
308
+ return job;
309
+ }
310
+ //#endregion
311
+ //#region src/assets.ts
312
+ const UI = resolve(fileURLToPath(new URL(".", import.meta.url)), "ui");
313
+ const TYPES = {
314
+ ".html": "text/html; charset=utf-8",
315
+ ".js": "text/javascript; charset=utf-8",
316
+ ".css": "text/css; charset=utf-8",
317
+ ".svg": "image/svg+xml",
318
+ ".woff2": "font/woff2",
319
+ ".json": "application/json; charset=utf-8",
320
+ ".ico": "image/x-icon"
321
+ };
322
+ const uiBuilt = () => existsSync(join(UI, "index.html"));
323
+ function readAsset(pathname) {
324
+ const target = resolve(join(UI, normalize(pathname === "/" ? "/index.html" : pathname)));
325
+ if (target !== UI && !target.startsWith(UI + (process.platform === "win32" ? "\\" : "/"))) return;
326
+ if (!existsSync(target) || !statSync(target).isFile()) return void 0;
327
+ const dot = target.lastIndexOf(".");
328
+ return {
329
+ body: readFileSync(target),
330
+ type: TYPES[target.slice(dot).toLowerCase()] ?? "application/octet-stream"
205
331
  };
206
332
  }
207
333
  //#endregion
@@ -298,6 +424,7 @@ function createStudioApp(cfg, port, token) {
298
424
  } catch {
299
425
  throw badRequest("the request target is not a valid url");
300
426
  }
427
+ const api = url.pathname.startsWith("/api/");
301
428
  const verdict = guardRequest({
302
429
  host: req.headers.host,
303
430
  origin: req.headers.origin
@@ -305,10 +432,22 @@ function createStudioApp(cfg, port, token) {
305
432
  port,
306
433
  token
307
434
  });
308
- if (!verdict.ok) {
435
+ if (!verdict.ok && (api || verdict.status !== 401)) {
309
436
  send(res, verdict.status, { error: verdict.reason });
310
437
  return;
311
438
  }
439
+ if (!api && req.method === "GET") {
440
+ const asset = readAsset(url.pathname);
441
+ if (asset) {
442
+ res.writeHead(200, {
443
+ "content-type": asset.type,
444
+ "cache-control": "no-store"
445
+ });
446
+ res.end(asset.body);
447
+ return;
448
+ }
449
+ if (url.pathname === "/" && !uiBuilt()) throw new HttpError(500, "the panel is not in this install, so only /api/* answers here");
450
+ }
312
451
  if (req.method === "GET" && url.pathname === "/api/state") {
313
452
  send(res, 200, await buildState(cfg));
314
453
  return;
@@ -330,9 +469,37 @@ function createStudioApp(cfg, port, token) {
330
469
  const body = await readJson(req);
331
470
  if (typeof body.locale !== "string") throw badRequest("body needs a \"locale\" string");
332
471
  const keys = Array.isArray(body.keys) ? body.keys.filter((k) => typeof k === "string") : void 0;
472
+ if (body.undo === true) {
473
+ if (!keys?.length) throw badRequest("undoing needs the \"keys\" it should put back");
474
+ send(res, 200, unapprove(cfg, body.locale, keys));
475
+ return;
476
+ }
333
477
  send(res, 200, approve(cfg, body.locale, keys));
334
478
  return;
335
479
  }
480
+ if (req.method === "POST" && url.pathname === "/api/extract") {
481
+ send(res, 200, await runExtract(cfg));
482
+ return;
483
+ }
484
+ if (req.method === "GET" && url.pathname === "/api/translate") {
485
+ const locales = url.searchParams.getAll("locale");
486
+ send(res, 200, await planTranslate(cfg, locales.length ? locales : void 0));
487
+ return;
488
+ }
489
+ if (req.method === "POST" && url.pathname === "/api/translate") {
490
+ const body = await readJson(req);
491
+ const locales = Array.isArray(body.locales) ? body.locales.filter((l) => typeof l === "string") : void 0;
492
+ send(res, 200, await startTranslate(cfg, locales?.length ? locales : void 0));
493
+ return;
494
+ }
495
+ if (req.method === "GET" && url.pathname === "/api/job") {
496
+ send(res, 200, running());
497
+ return;
498
+ }
499
+ if (req.method === "GET" && url.pathname.startsWith("/api/job/")) {
500
+ send(res, 200, read(segment(url.pathname.slice(9), "job id")));
501
+ return;
502
+ }
336
503
  send(res, 404, { error: `[verbaly] no route for ${req.method} ${url.pathname}` });
337
504
  } catch (error) {
338
505
  send(res, error instanceof HttpError ? error.status : 500, { error: scrub(error instanceof Error ? error.message : String(error), cfg.root) });
@@ -367,7 +534,7 @@ async function startStudio(cfg, options = {}) {
367
534
  server,
368
535
  port,
369
536
  token,
370
- url: `http://127.0.0.1:${port}/api/state?t=${token}`,
537
+ url: `http://127.0.0.1:${port}/?t=${token}`,
371
538
  close: () => new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve()))
372
539
  };
373
540
  }
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","names":[],"sources":["../src/http.ts","../src/triage.ts","../src/api.ts","../src/guard.ts","../src/server.ts","../src/cli.ts"],"sourcesContent":["// A client mistake is not a server fault, so the status rides with the error, not a blanket 500.\nexport class HttpError extends Error {\n readonly status: number;\n\n constructor(status: number, message: string) {\n super(message.startsWith('[verbaly]') ? message : `[verbaly] ${message}`);\n this.status = status;\n this.name = 'HttpError';\n }\n}\n\nexport const badRequest = (message: string) => new HttpError(400, message);\n\n// The absolute path of someone's project is not the browser's business.\nexport function scrub(message: string, root: string): string {\n return root ? message.split(root).join('.') : message;\n}\n","import type { Catalog } from '@verbaly/compiler';\n\nexport type TriageSignal = 'divergent' | 'collision' | 'echo' | 'digits' | 'code' | 'url';\n\nexport interface TriageReason {\n signal: TriageSignal;\n text: string;\n}\n\nexport type TriageResult = Record<string, TriageReason[]>;\n\n// check() already caught the structure, so these only point at the few worth reading first.\nexport interface TriageInput {\n source: Catalog;\n target: Catalog;\n reviewed: Catalog[];\n}\n\nconst DIGITS = /\\d[\\d.,]*/g;\nconst URLS = /https?:\\/\\/[^\\s<)\"']+/g;\nconst CODE = /<code>([^<]+)<\\/code>/g;\n\nfunction bag(text: string, re: RegExp, normalize?: (v: string) => string): string[] {\n const found = text.match(re) ?? [];\n return (normalize ? found.map(normalize) : found).sort();\n}\n\nfunction same(a: string[], b: string[]): boolean {\n return a.length === b.length && a.every((v, i) => v === b[i]);\n}\n\n// A number or a url at the end of a clause takes the punctuation with it, and that is not a signal.\nconst trimPunct = (v: string) => v.replace(/[.,;:!?)\\]]+$/, '');\n// A separator is idiomatic (3.09 is 3,09 in Spanish); canonizing keeps 1.5 apart from 15.\nconst bareDigits = (v: string) => trimPunct(v).replaceAll(/[.,]/g, '.');\n\nfunction push(out: TriageResult, key: string, reason: TriageReason): void {\n (out[key] ??= []).push(reason);\n}\n\nexport function triage({ source, target, reviewed }: TriageInput): TriageResult {\n // null-prototype: a source text of \"__proto__\" or \"toString\" is a string, not a member of Object\n const out: TriageResult = Object.create(null);\n const keys = Object.keys(source);\n\n // One source text that two humans rendered two ways is ambiguous, so the machine had no chance.\n const bySource: Record<string, string[]> = Object.create(null);\n for (const key of keys) {\n const text = source[key]?.trim();\n if (text) (bySource[text] ??= []).push(key);\n }\n for (const [text, group] of Object.entries(bySource)) {\n if (group.length < 2) continue;\n const divergent = reviewed.some(\n (catalog) => new Set(group.map((k) => catalog[k]?.trim() ?? '').filter(Boolean)).size > 1,\n );\n if (divergent) {\n for (const key of group) {\n push(out, key, { signal: 'divergent', text: `\"${text}\" was already translated two ways` });\n }\n }\n // The same ambiguity seen from the result instead of from the history.\n const targets = new Set(group.map((key) => target[key]?.trim() ?? '').filter(Boolean));\n if (!divergent && targets.size > 1) {\n for (const key of group) {\n push(out, key, { signal: 'collision', text: `\"${text}\" came back as two different translations` });\n }\n }\n }\n\n for (const key of keys) {\n const src = source[key]?.trim() ?? '';\n const trg = target[key]?.trim() ?? '';\n if (!src || !trg) continue;\n\n // Only the locales with an opinion on this key vote: alone it fires on every do-not-translate.\n const opinions = reviewed.map((c) => c[key]?.trim()).filter((v) => v !== undefined && v !== '');\n if (trg === src && opinions.length > 0 && opinions.every((v) => v !== src)) {\n push(out, key, { signal: 'echo', text: 'still identical to the source' });\n }\n\n // a translation that adds a number is idiomatic (\"refresh\" is \"F5\"); one that changes it is not\n const srcDigits = bag(src, DIGITS, bareDigits);\n if (srcDigits.length > 0 && !same(srcDigits, bag(trg, DIGITS, bareDigits))) {\n push(out, key, { signal: 'digits', text: 'the numbers do not match the source' });\n }\n if (!same(bag(src, URLS, trimPunct), bag(trg, URLS, trimPunct))) {\n push(out, key, { signal: 'url', text: 'a URL changed' });\n }\n if (!same(bag(src, CODE), bag(trg, CODE))) {\n push(out, key, { signal: 'code', text: 'the contents of a <code> span changed' });\n }\n }\n\n return out;\n}\n","import { existsSync } from 'node:fs';\nimport { relative } from 'node:path';\nimport {\n check,\n clearDrafts,\n collectOrigins,\n doctor,\n effectiveDrafts,\n extractProject,\n loadDrafts,\n readCatalog,\n saveDrafts,\n status,\n targetLocales,\n validateMessage,\n validatePair,\n writeCatalog,\n} from '@verbaly/compiler';\nimport { DRAFTS_FILE } from '@verbaly/compiler';\nimport type {\n Catalog,\n Catalogs,\n CheckResult,\n DoctorResult,\n Drafts,\n ResolvedConfig,\n StatusResult,\n} from '@verbaly/compiler';\nimport { badRequest } from './http';\nimport { triage, type TriageResult } from './triage';\n\nexport interface StudioProblem {\n scope: string;\n message: string;\n}\n\nexport interface StudioState {\n root: string;\n dir: string;\n sourceLocale: string;\n locales: string[];\n scanning: boolean;\n catalogs: Catalogs;\n origins: Record<string, string[]>;\n status: StatusResult;\n check: CheckResult;\n drafts: Drafts;\n triage: Record<string, TriageResult>;\n problems: StudioProblem[];\n}\n\n// A locale nobody can parse must not take the panel down: Studio is what you open to fix it.\nfunction readAll(cfg: ResolvedConfig): { catalogs: Catalogs; problems: StudioProblem[] } {\n const catalogs: Catalogs = {};\n const problems: StudioProblem[] = [];\n // an empty green panel and a wrong --root look identical, and only this line tells them apart\n if (!existsSync(cfg.dir)) {\n const dir = relative(cfg.root, cfg.dir).replaceAll('\\\\', '/');\n problems.push({ scope: dir, message: '[verbaly] no catalogs here, run npx verbaly init or check --root' });\n }\n for (const locale of cfg.locales) {\n try {\n catalogs[locale] = readCatalog(cfg, locale);\n } catch (error) {\n catalogs[locale] = {};\n problems.push({\n scope: locale,\n message: error instanceof Error ? error.message : String(error),\n });\n }\n }\n return { catalogs, problems };\n}\n\n// A sidecar nobody can parse is the other file Studio exists to fix, so it degrades like a catalog.\nfunction readDrafts(cfg: ResolvedConfig): { drafts: Drafts; problems: StudioProblem[] } {\n try {\n return { drafts: loadDrafts(cfg), problems: [] };\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return { drafts: {}, problems: [{ scope: DRAFTS_FILE, message }] };\n }\n}\n\n// check() reports the file a stray key was used in as an absolute path, origins already do not.\nfunction relativize(cfg: ResolvedConfig, result: CheckResult): CheckResult {\n return {\n ...result,\n unknown: result.unknown.map((entry) => ({\n ...entry,\n files: entry.files.map((file) => relative(cfg.root, file).replaceAll('\\\\', '/')),\n })),\n };\n}\n\n// One shot and unpaginated: the biggest catalog we know is 1479 messages, instant over localhost.\nexport async function buildState(cfg: ResolvedConfig): Promise<StudioState> {\n const { catalogs, problems } = readAll(cfg);\n const registry = await extractProject(cfg);\n const sidecar = readDrafts(cfg);\n problems.push(...sidecar.problems);\n const live = effectiveDrafts(sidecar.drafts, catalogs);\n const source = (catalogs[cfg.sourceLocale] ?? {}) as Catalog;\n const targets = targetLocales(cfg);\n\n for (const { file, message } of registry.parseErrors()) {\n problems.push({ scope: relative(cfg.root, file).replaceAll('\\\\', '/'), message });\n }\n\n const perLocale: Record<string, TriageResult> = {};\n for (const locale of targets) {\n perLocale[locale] = triage({\n source,\n target: (catalogs[locale] ?? {}) as Catalog,\n // a locale still carrying drafts has not been reviewed, so it cannot be the control\n reviewed: targets\n .filter((other) => other !== locale && !(live[other] ?? []).length)\n .map((other) => (catalogs[other] ?? {}) as Catalog),\n });\n }\n\n return {\n root: cfg.root,\n dir: cfg.dir,\n sourceLocale: cfg.sourceLocale,\n locales: cfg.locales,\n // with include: [] nothing is scanned, so an empty origins map is off, not \"none found\"\n scanning: cfg.include.length > 0,\n catalogs,\n origins: await collectOrigins(cfg, registry),\n status: status(cfg, catalogs, registry, live),\n check: relativize(cfg, check(cfg, catalogs, registry)),\n drafts: live,\n triage: perLocale,\n problems,\n };\n}\n\nexport function health(cfg: ResolvedConfig): Promise<DoctorResult> {\n return doctor(cfg);\n}\n\nexport interface WriteMessageResult {\n locale: string;\n key: string;\n clearedDraft: boolean;\n}\n\n// A human wrote it, so it is reviewed: the rule import already applies in run.ts:447.\nexport function writeMessage(\n cfg: ResolvedConfig,\n locale: string,\n key: string,\n text: string,\n): WriteMessageResult {\n if (locale === cfg.sourceLocale) {\n throw badRequest('the source text lives in your code, Studio never edits it');\n }\n if (!cfg.locales.includes(locale)) {\n throw badRequest(`${locale} is not one of this project's locales`);\n }\n const source = readCatalog(cfg, cfg.sourceLocale);\n // hasOwn, not `in`: `in` walks the prototype, so toString and __proto__ would pass this guard\n if (!Object.hasOwn(source, key)) {\n throw badRequest(`\"${key}\" is not in the source catalog, so Studio will not create it`);\n }\n // '' is untranslated everywhere else, so whitespace is that and not a translation to validate\n const value = text.trim() ? text : '';\n // the same two calls check() makes: validatePair alone misses a plural block with no other case\n const issues = value\n ? [...validateMessage(value, locale), ...validatePair(source[key] as string, value)]\n : [];\n const errors = issues.filter((issue) => issue.severity === 'error');\n if (errors.length) {\n throw badRequest(`${errors.map((issue) => issue.message).join('; ')}`);\n }\n\n const catalog = { ...readCatalog(cfg, locale), [key]: value };\n writeCatalog(cfg, locale, catalog);\n\n const drafts = loadDrafts(cfg);\n const clearedDraft = (drafts[locale] ?? []).includes(key);\n clearDrafts(drafts, locale, [key]);\n saveDrafts(cfg, drafts);\n return { locale, key, clearedDraft };\n}\n\nexport interface ApproveResult {\n locale: string;\n approved: number;\n}\n\n// The sidecar is re-read here: saveDrafts writes every locale, so a stale copy drops another's.\nexport function approve(cfg: ResolvedConfig, locale: string, keys?: string[]): ApproveResult {\n if (!cfg.locales.includes(locale)) {\n throw badRequest(`${locale} is not one of this project's locales`);\n }\n const drafts = loadDrafts(cfg);\n const before = (drafts[locale] ?? []).length;\n clearDrafts(drafts, locale, keys);\n const after = (drafts[locale] ?? []).length;\n saveDrafts(cfg, drafts);\n return { locale, approved: before - after };\n}\n","import { randomBytes, timingSafeEqual } from 'node:crypto';\n\nexport interface GuardOptions {\n port: number;\n token: string;\n}\n\n// A local server that writes files is reachable from any page the browser has open.\nexport function newToken(): string {\n return randomBytes(9).toString('base64url');\n}\n\n// Only the two names that resolve to this machine. A DNS rebind arrives with a foreign Host.\nexport function hostAllowed(host: string | undefined, port: number): boolean {\n // a browser omits the port when it is the scheme default, so :80 would 403 every real request\n const names = port === 80 ? ['127.0.0.1', 'localhost'] : [];\n return [`127.0.0.1:${port}`, `localhost:${port}`, ...names].includes(host ?? '');\n}\n\n// No Origin at all is a same-origin navigation or a curl, both fine; a foreign one never is.\nexport function originAllowed(origin: string | undefined, port: number): boolean {\n if (origin === undefined) return true;\n return origin === `http://127.0.0.1:${port}` || origin === `http://localhost:${port}`;\n}\n\n// Compared in constant time so the token cannot be guessed one character at a time.\nexport function tokenMatches(given: string | null, expected: string): boolean {\n if (given === null) return false;\n const a = Buffer.from(given);\n const b = Buffer.from(expected);\n return a.length === b.length && timingSafeEqual(a, b);\n}\n\nexport interface GuardVerdict {\n ok: boolean;\n status: number;\n reason: string;\n}\n\nconst PASS: GuardVerdict = { ok: true, status: 200, reason: '' };\n\nexport function guardRequest(\n headers: { host?: string; origin?: string },\n url: URL,\n { port, token }: GuardOptions,\n): GuardVerdict {\n if (!hostAllowed(headers.host, port)) {\n return { ok: false, status: 403, reason: '[verbaly] unexpected Host header' };\n }\n if (!originAllowed(headers.origin, port)) {\n return { ok: false, status: 403, reason: '[verbaly] cross-origin request' };\n }\n if (!tokenMatches(url.searchParams.get('t'), token)) {\n return { ok: false, status: 401, reason: '[verbaly] missing or wrong token, open the printed url' };\n }\n return PASS;\n}\n","import { createServer, type IncomingMessage, type Server, type ServerResponse } from 'node:http';\nimport type { AddressInfo } from 'node:net';\nimport type { ResolvedConfig } from '@verbaly/compiler';\nimport { approve, buildState, health, writeMessage } from './api';\nimport { guardRequest, newToken } from './guard';\nimport { HttpError, badRequest, scrub } from './http';\n\nexport interface StudioServer {\n server: Server;\n port: number;\n token: string;\n url: string;\n close: () => Promise<void>;\n}\n\nexport interface StartOptions {\n port?: number;\n token?: string;\n}\n\nconst DEFAULT_PORT = 4747;\nconst MAX_BODY = 1_000_000;\n\nfunction send(res: ServerResponse, status: number, body: unknown): void {\n res.writeHead(status, {\n 'content-type': 'application/json; charset=utf-8',\n 'cache-control': 'no-store',\n });\n res.end(JSON.stringify(body));\n}\n\nasync function readJson(req: IncomingMessage): Promise<Record<string, unknown>> {\n const chunks: Buffer[] = [];\n let size = 0;\n for await (const chunk of req) {\n size += (chunk as Buffer).length;\n if (size > MAX_BODY) throw new HttpError(413, 'request body too large');\n chunks.push(chunk as Buffer);\n }\n if (!chunks.length) return {};\n let parsed: unknown;\n try {\n parsed = JSON.parse(Buffer.concat(chunks).toString('utf8'));\n } catch {\n throw badRequest('body is not valid JSON');\n }\n // null and an array both pass typeof 'object', and reading .text off them is a 500\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw badRequest('body must be a JSON object');\n }\n return parsed as Record<string, unknown>;\n}\n\nfunction segment(raw: string | undefined, what: string): string {\n let value: string;\n try {\n value = decodeURIComponent(raw ?? '');\n } catch {\n throw badRequest(`the ${what} in the url is not valid percent-encoding`);\n }\n if (!value) throw badRequest(`the url is missing a ${what}`);\n return value;\n}\n\n// One page, JSON on every route: a framework would only add a dependency to a local tool.\nexport function createStudioApp(cfg: ResolvedConfig, port: number, token: string) {\n return async function handle(req: IncomingMessage, res: ServerResponse): Promise<void> {\n // The url is built inside the try: `GET //` throws, and out here that would kill the process.\n try {\n let url: URL;\n try {\n url = new URL(req.url ?? '/', `http://127.0.0.1:${port}`);\n } catch {\n throw badRequest('the request target is not a valid url');\n }\n\n const verdict = guardRequest({ host: req.headers.host, origin: req.headers.origin }, url, {\n port,\n token,\n });\n if (!verdict.ok) {\n send(res, verdict.status, { error: verdict.reason });\n return;\n }\n\n if (req.method === 'GET' && url.pathname === '/api/state') {\n send(res, 200, await buildState(cfg));\n return;\n }\n if (req.method === 'GET' && url.pathname === '/api/health') {\n send(res, 200, await health(cfg));\n return;\n }\n if (req.method === 'PUT' && url.pathname.startsWith('/api/message/')) {\n const rest = url.pathname.slice('/api/message/'.length).split('/');\n const locale = segment(rest[0], 'locale');\n const key = segment(rest.slice(1).join('/'), 'key');\n const body = await readJson(req);\n if (typeof body.text !== 'string') throw badRequest('body needs a \"text\" string');\n send(res, 200, writeMessage(cfg, locale, key, body.text));\n return;\n }\n if (req.method === 'POST' && url.pathname === '/api/approve') {\n const body = await readJson(req);\n if (typeof body.locale !== 'string') throw badRequest('body needs a \"locale\" string');\n const keys = Array.isArray(body.keys)\n ? body.keys.filter((k): k is string => typeof k === 'string')\n : undefined;\n send(res, 200, approve(cfg, body.locale, keys));\n return;\n }\n send(res, 404, { error: `[verbaly] no route for ${req.method} ${url.pathname}` });\n } catch (error) {\n const status = error instanceof HttpError ? error.status : 500;\n const message = error instanceof Error ? error.message : String(error);\n send(res, status, { error: scrub(message, cfg.root) });\n }\n };\n}\n\n// A busy port climbs instead of failing: this is a local tool, not a service on a fixed address.\nfunction listen(server: Server, port: number, attemptsLeft: number): Promise<number> {\n return new Promise((resolve, reject) => {\n const onError = (error: NodeJS.ErrnoException) => {\n if (error.code === 'EADDRINUSE' && attemptsLeft > 0) {\n server.removeListener('error', onError);\n resolve(listen(server, port + 1, attemptsLeft - 1));\n return;\n }\n reject(error);\n };\n server.once('error', onError);\n server.listen(port, '127.0.0.1', () => {\n server.removeListener('error', onError);\n // port 0 asks the system to pick, so the bound address is the only place the answer is\n const bound = server.address() as AddressInfo | string | null;\n resolve(typeof bound === 'object' && bound !== null ? bound.port : port);\n });\n });\n}\n\nexport async function startStudio(\n cfg: ResolvedConfig,\n options: StartOptions = {},\n): Promise<StudioServer> {\n const token = options.token ?? newToken();\n const server = createServer();\n // The handler is attached before listen so no request can arrive without one.\n let port = options.port ?? DEFAULT_PORT;\n server.on('request', (req, res) => void createStudioApp(cfg, port, token)(req, res));\n port = await listen(server, port, 20);\n\n return {\n server,\n port,\n token,\n url: `http://127.0.0.1:${port}/api/state?t=${token}`,\n close: () =>\n new Promise<void>((resolve, reject) =>\n server.close((error) => (error ? reject(error) : resolve())),\n ),\n };\n}\n","#!/usr/bin/env node\nimport { parseArgs } from 'node:util';\nimport { counted, formatCliError, loadConfig } from '@verbaly/compiler';\nimport { buildState } from './api';\nimport { startStudio } from './server';\n\ntry {\n const { values } = parseArgs({\n options: {\n root: { type: 'string' },\n port: { type: 'string' },\n json: { type: 'boolean' },\n },\n });\n const port = values.port === undefined ? undefined : Number(values.port);\n if (port !== undefined && !Number.isInteger(port)) {\n throw new Error(`[verbaly] --port needs a number, got \"${values.port}\"`);\n }\n const cfg = await loadConfig(values.root ?? process.cwd());\n\n // --json is the state the interface reads, on stdout, before any interface exists.\n if (values.json) {\n console.log(JSON.stringify(await buildState(cfg), null, 2));\n } else {\n const studio = await startStudio(cfg, { port });\n const state = await buildState(cfg);\n const missing = state.status.locales.reduce((n, l) => n + (l.total - l.translated), 0);\n const drafts = Object.values(state.drafts).reduce((n, keys) => n + keys.length, 0);\n console.log(`\\n Verbaly Studio ${studio.url}`);\n console.log(` Project ${cfg.root}`);\n console.log(\n ` Catalog ${counted(state.status.messages, 'message')} · ` +\n `${missing} untranslated · ${counted(drafts, 'draft')}`,\n );\n for (const problem of state.problems) console.log(` ! ${problem.scope}: ${problem.message}`);\n console.log('\\n Ctrl+C to stop\\n');\n }\n} catch (error) {\n console.error(formatCliError(error));\n process.exitCode = 1;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","names":["readJob"],"sources":["../src/http.ts","../src/triage.ts","../src/api.ts","../src/jobs.ts","../src/actions.ts","../src/assets.ts","../src/guard.ts","../src/server.ts","../src/cli.ts"],"sourcesContent":["// A client mistake is not a server fault, so the status rides with the error, not a blanket 500.\nexport class HttpError extends Error {\n readonly status: number;\n\n constructor(status: number, message: string) {\n super(message.startsWith('[verbaly]') ? message : `[verbaly] ${message}`);\n this.status = status;\n this.name = 'HttpError';\n }\n}\n\nexport const badRequest = (message: string) => new HttpError(400, message);\n\n// The absolute path of someone's project is not the browser's business.\nexport function scrub(message: string, root: string): string {\n return root ? message.split(root).join('.') : message;\n}\n","import type { Catalog } from '@verbaly/compiler';\n\nexport type TriageSignal = 'divergent' | 'collision' | 'echo' | 'digits' | 'code' | 'url';\n\nexport interface TriageReason {\n signal: TriageSignal;\n text: string;\n}\n\nexport type TriageResult = Record<string, TriageReason[]>;\n\n// check() already caught the structure, so these only point at the few worth reading first.\nexport interface TriageInput {\n source: Catalog;\n target: Catalog;\n reviewed: Catalog[];\n}\n\nconst DIGITS = /\\d[\\d.,]*/g;\nconst URLS = /https?:\\/\\/[^\\s<)\"']+/g;\nconst CODE = /<code>([^<]+)<\\/code>/g;\n\nfunction bag(text: string, re: RegExp, normalize?: (v: string) => string): string[] {\n const found = text.match(re) ?? [];\n return (normalize ? found.map(normalize) : found).sort();\n}\n\nfunction same(a: string[], b: string[]): boolean {\n return a.length === b.length && a.every((v, i) => v === b[i]);\n}\n\n// A number or a url at the end of a clause takes the punctuation with it, and that is not a signal.\nconst trimPunct = (v: string) => v.replace(/[.,;:!?)\\]]+$/, '');\n// A separator is idiomatic (3.09 is 3,09 in Spanish); canonizing keeps 1.5 apart from 15.\nconst bareDigits = (v: string) => trimPunct(v).replaceAll(/[.,]/g, '.');\n\nfunction push(out: TriageResult, key: string, reason: TriageReason): void {\n (out[key] ??= []).push(reason);\n}\n\nexport function triage({ source, target, reviewed }: TriageInput): TriageResult {\n // null-prototype: a source text of \"__proto__\" or \"toString\" is a string, not a member of Object\n const out: TriageResult = Object.create(null);\n const keys = Object.keys(source);\n\n // One source text that two humans rendered two ways is ambiguous, so the machine had no chance.\n const bySource: Record<string, string[]> = Object.create(null);\n for (const key of keys) {\n const text = source[key]?.trim();\n if (text) (bySource[text] ??= []).push(key);\n }\n for (const [text, group] of Object.entries(bySource)) {\n if (group.length < 2) continue;\n const divergent = reviewed.some(\n (catalog) => new Set(group.map((k) => catalog[k]?.trim() ?? '').filter(Boolean)).size > 1,\n );\n if (divergent) {\n for (const key of group) {\n push(out, key, { signal: 'divergent', text: `\"${text}\" was already translated two ways` });\n }\n }\n // The same ambiguity seen from the result instead of from the history.\n const targets = new Set(group.map((key) => target[key]?.trim() ?? '').filter(Boolean));\n if (!divergent && targets.size > 1) {\n for (const key of group) {\n push(out, key, { signal: 'collision', text: `\"${text}\" came back as two different translations` });\n }\n }\n }\n\n for (const key of keys) {\n const src = source[key]?.trim() ?? '';\n const trg = target[key]?.trim() ?? '';\n if (!src || !trg) continue;\n\n // Only the locales with an opinion on this key vote: alone it fires on every do-not-translate.\n const opinions = reviewed.map((c) => c[key]?.trim()).filter((v) => v !== undefined && v !== '');\n if (trg === src && opinions.length > 0 && opinions.every((v) => v !== src)) {\n push(out, key, { signal: 'echo', text: 'still identical to the source' });\n }\n\n // a translation that adds a number is idiomatic (\"refresh\" is \"F5\"); one that changes it is not\n const srcDigits = bag(src, DIGITS, bareDigits);\n if (srcDigits.length > 0 && !same(srcDigits, bag(trg, DIGITS, bareDigits))) {\n push(out, key, { signal: 'digits', text: 'the numbers do not match the source' });\n }\n if (!same(bag(src, URLS, trimPunct), bag(trg, URLS, trimPunct))) {\n push(out, key, { signal: 'url', text: 'a URL changed' });\n }\n if (!same(bag(src, CODE), bag(trg, CODE))) {\n push(out, key, { signal: 'code', text: 'the contents of a <code> span changed' });\n }\n }\n\n return out;\n}\n","import { existsSync } from 'node:fs';\nimport { relative } from 'node:path';\nimport {\n check,\n clearDrafts,\n collectOrigins,\n doctor,\n effectiveDrafts,\n extractProject,\n loadDrafts,\n markDrafts,\n readCatalog,\n saveDrafts,\n status,\n targetLocales,\n validateMessage,\n validatePair,\n writeCatalog,\n} from '@verbaly/compiler';\nimport { DRAFTS_FILE } from '@verbaly/compiler';\nimport type {\n Catalog,\n Catalogs,\n CheckResult,\n DoctorResult,\n Drafts,\n ResolvedConfig,\n StatusResult,\n} from '@verbaly/compiler';\nimport { badRequest } from './http';\nimport { triage, type TriageResult } from './triage';\n\nexport interface StudioProblem {\n scope: string;\n message: string;\n}\n\nexport interface StudioState {\n root: string;\n dir: string;\n sourceLocale: string;\n locales: string[];\n scanning: boolean;\n catalogs: Catalogs;\n origins: Record<string, string[]>;\n status: StatusResult;\n check: CheckResult;\n drafts: Drafts;\n triage: Record<string, TriageResult>;\n problems: StudioProblem[];\n}\n\n// A locale nobody can parse must not take the panel down: Studio is what you open to fix it.\nfunction readAll(cfg: ResolvedConfig): { catalogs: Catalogs; problems: StudioProblem[] } {\n const catalogs: Catalogs = {};\n const problems: StudioProblem[] = [];\n // an empty green panel and a wrong --root look identical, and only this line tells them apart\n if (!existsSync(cfg.dir)) {\n const dir = relative(cfg.root, cfg.dir).replaceAll('\\\\', '/');\n problems.push({ scope: dir, message: '[verbaly] no catalogs here, run npx verbaly init or check --root' });\n }\n for (const locale of cfg.locales) {\n try {\n catalogs[locale] = readCatalog(cfg, locale);\n } catch (error) {\n catalogs[locale] = {};\n problems.push({\n scope: locale,\n message: error instanceof Error ? error.message : String(error),\n });\n }\n }\n return { catalogs, problems };\n}\n\n// A sidecar nobody can parse is the other file Studio exists to fix, so it degrades like a catalog.\nfunction readDrafts(cfg: ResolvedConfig): { drafts: Drafts; problems: StudioProblem[] } {\n try {\n return { drafts: loadDrafts(cfg), problems: [] };\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return { drafts: {}, problems: [{ scope: DRAFTS_FILE, message }] };\n }\n}\n\n// check() reports the file a stray key was used in as an absolute path, origins already do not.\nfunction relativize(cfg: ResolvedConfig, result: CheckResult): CheckResult {\n return {\n ...result,\n unknown: result.unknown.map((entry) => ({\n ...entry,\n files: entry.files.map((file) => relative(cfg.root, file).replaceAll('\\\\', '/')),\n })),\n };\n}\n\n// One shot and unpaginated: the biggest catalog we know is 1587 messages, instant over localhost.\nexport async function buildState(cfg: ResolvedConfig): Promise<StudioState> {\n const { catalogs, problems } = readAll(cfg);\n const registry = await extractProject(cfg);\n const sidecar = readDrafts(cfg);\n problems.push(...sidecar.problems);\n const live = effectiveDrafts(sidecar.drafts, catalogs);\n const source = (catalogs[cfg.sourceLocale] ?? {}) as Catalog;\n const targets = targetLocales(cfg);\n\n for (const { file, message } of registry.parseErrors()) {\n problems.push({ scope: relative(cfg.root, file).replaceAll('\\\\', '/'), message });\n }\n\n const perLocale: Record<string, TriageResult> = {};\n for (const locale of targets) {\n perLocale[locale] = triage({\n source,\n target: (catalogs[locale] ?? {}) as Catalog,\n // a locale still carrying drafts has not been reviewed, so it cannot be the control\n reviewed: targets\n .filter((other) => other !== locale && !(live[other] ?? []).length)\n .map((other) => (catalogs[other] ?? {}) as Catalog),\n });\n }\n\n return {\n root: cfg.root,\n dir: cfg.dir,\n sourceLocale: cfg.sourceLocale,\n locales: cfg.locales,\n // with include: [] nothing is scanned, so an empty origins map is off, not \"none found\"\n scanning: cfg.include.length > 0,\n catalogs,\n origins: await collectOrigins(cfg, registry),\n status: status(cfg, catalogs, registry, live),\n check: relativize(cfg, check(cfg, catalogs, registry)),\n drafts: live,\n triage: perLocale,\n problems,\n };\n}\n\nexport function health(cfg: ResolvedConfig): Promise<DoctorResult> {\n return doctor(cfg);\n}\n\nexport interface WriteMessageResult {\n locale: string;\n key: string;\n clearedDraft: boolean;\n}\n\n// A human wrote it, so it is reviewed: the rule import already applies in run.ts:447.\nexport function writeMessage(\n cfg: ResolvedConfig,\n locale: string,\n key: string,\n text: string,\n): WriteMessageResult {\n if (locale === cfg.sourceLocale) {\n throw badRequest('the source text lives in your code, Studio never edits it');\n }\n if (!cfg.locales.includes(locale)) {\n throw badRequest(`${locale} is not one of this project's locales`);\n }\n const source = readCatalog(cfg, cfg.sourceLocale);\n // hasOwn, not `in`: `in` walks the prototype, so toString and __proto__ would pass this guard\n if (!Object.hasOwn(source, key)) {\n throw badRequest(`\"${key}\" is not in the source catalog, so Studio will not create it`);\n }\n // '' is untranslated everywhere else, so whitespace is that and not a translation to validate\n const value = text.trim() ? text : '';\n // the same two calls check() makes: validatePair alone misses a plural block with no other case\n const issues = value\n ? [...validateMessage(value, locale), ...validatePair(source[key] as string, value)]\n : [];\n const errors = issues.filter((issue) => issue.severity === 'error');\n if (errors.length) {\n throw badRequest(`${errors.map((issue) => issue.message).join('; ')}`);\n }\n\n const catalog = { ...readCatalog(cfg, locale), [key]: value };\n writeCatalog(cfg, locale, catalog);\n\n const drafts = loadDrafts(cfg);\n const clearedDraft = (drafts[locale] ?? []).includes(key);\n clearDrafts(drafts, locale, [key]);\n saveDrafts(cfg, drafts);\n return { locale, key, clearedDraft };\n}\n\nexport interface ApproveResult {\n locale: string;\n approved: number;\n keys: string[];\n}\n\n// The sidecar is re-read here: saveDrafts writes every locale, so a stale copy drops another's.\nexport function approve(cfg: ResolvedConfig, locale: string, keys?: string[]): ApproveResult {\n if (!cfg.locales.includes(locale)) {\n throw badRequest(`${locale} is not one of this project's locales`);\n }\n const drafts = loadDrafts(cfg);\n const had = drafts[locale] ?? [];\n // the keys it really cleared, because undo has to put back exactly those and nothing else\n const cleared = keys ? had.filter((key) => keys.includes(key)) : [...had];\n clearDrafts(drafts, locale, keys);\n saveDrafts(cfg, drafts);\n return { locale, approved: cleared.length, keys: cleared };\n}\n\n// Undo is not a nicety here: the flag records who wrote the text, so restoring it restores a fact.\nexport function unapprove(cfg: ResolvedConfig, locale: string, keys: string[]): ApproveResult {\n if (!cfg.locales.includes(locale)) {\n throw badRequest(`${locale} is not one of this project's locales`);\n }\n const drafts = loadDrafts(cfg);\n markDrafts(drafts, locale, keys);\n saveDrafts(cfg, drafts);\n return { locale, approved: keys.length, keys };\n}\n","import { badRequest, HttpError } from './http';\n\nexport type JobState = 'running' | 'done' | 'failed';\n\nexport interface Job {\n id: string;\n kind: 'translate';\n state: JobState;\n total: number;\n done: number;\n locale?: string;\n message?: string;\n result?: unknown;\n}\n\n// One at a time, because two runs over the same catalogs is a race over the same files.\nlet current: Job | null = null;\nlet counter = 0;\n\nexport function running(): Job | null {\n return current?.state === 'running' ? current : null;\n}\n\nexport function read(id: string): Job {\n if (!current || current.id !== id) throw new HttpError(404, `no job called \"${id}\"`);\n return current;\n}\n\nexport function start(kind: Job['kind'], total: number): Job {\n if (running()) throw badRequest('a job is already running, wait for it or read its progress');\n counter += 1;\n current = { id: `${kind}-${counter}`, kind, state: 'running', total, done: 0 };\n return current;\n}\n\n// A batch that failed still finishes: the bar reports work attempted, and message says what broke.\nexport function advance(id: string, keys: number, locale: string, error?: string): void {\n if (current?.id !== id) return;\n current.done = Math.min(current.done + keys, current.total);\n current.locale = locale;\n if (error) current.message = error;\n}\n\n// A run that writes part of its work and then fails is a success for what it wrote, so both land.\nexport function finish(id: string, state: 'done' | 'failed', patch: Partial<Job> = {}): void {\n if (current?.id === id) Object.assign(current, patch, { state });\n}\n","import {\n collectOrigins,\n extractProject,\n loadCatalogs,\n resolveProvider,\n syncCatalogs,\n translateCatalogs,\n writeCatalog,\n writeDts,\n markDrafts,\n loadDrafts,\n saveDrafts,\n} from '@verbaly/compiler';\nimport type { ResolvedConfig } from '@verbaly/compiler';\nimport { badRequest } from './http';\nimport { advance, finish, start, type Job } from './jobs';\n\nexport interface ExtractResult {\n added: Record<string, string[]>;\n // only the source locale counts as new text: a target gains a blank for every gap it had\n found: number;\n messages: number;\n}\n\n// Local, fast and free, so it answers in the same request instead of becoming a job.\nexport async function runExtract(cfg: ResolvedConfig): Promise<ExtractResult> {\n if (!cfg.include.length) {\n throw badRequest('source scanning is off (include: []), so there is nothing to extract');\n }\n const registry = await extractProject(cfg);\n const catalogs = loadCatalogs(cfg);\n const result = syncCatalogs(cfg, catalogs, registry);\n for (const locale of cfg.locales) writeCatalog(cfg, locale, catalogs[locale] ?? {});\n // the types come from the source catalog, and cfg.dts false means the project opted out\n if (cfg.dts !== false) writeDts(cfg, catalogs[cfg.sourceLocale] ?? {}, cfg.dts);\n const found = result.added[cfg.sourceLocale]?.length ?? 0;\n return { added: result.added, found, messages: registry.messages().size };\n}\n\nexport interface Plan {\n pending: Record<string, string[]>;\n total: number;\n}\n\n// translate is the one command that spends money and minutes, so the panel always shows the bill.\nexport async function planTranslate(cfg: ResolvedConfig, locales?: string[]): Promise<Plan> {\n const provider = await resolveProvider(cfg);\n const catalogs = loadCatalogs(cfg);\n const result = await translateCatalogs(cfg, catalogs, provider, { locales, dryRun: true });\n const total = Object.values(result.pending).reduce((n, keys) => n + keys.length, 0);\n return { pending: result.pending, total };\n}\n\nexport async function startTranslate(cfg: ResolvedConfig, locales?: string[]): Promise<Job> {\n const provider = await resolveProvider(cfg);\n // the job carries its own denominator, so progress reads the same whether or not you asked first\n const plan = await planTranslate(cfg, locales);\n const job = start('translate', plan.total);\n const id = job.id;\n\n // the run outlives this request on purpose: the client asks the job how it is going\n void (async () => {\n try {\n const catalogs = loadCatalogs(cfg);\n const result = await translateCatalogs(cfg, catalogs, provider, {\n locales,\n batchSize: cfg.translate.batchSize,\n concurrency: cfg.translate.concurrency,\n retries: cfg.translate.retries,\n origins: await collectOrigins(cfg),\n onProgress: (p) => advance(id, p.keys, p.locale, p.error),\n });\n for (const [locale, keys] of Object.entries(result.translated)) {\n if (keys.length) writeCatalog(cfg, locale, catalogs[locale] ?? {});\n }\n // what a machine wrote stays a draft: the panel does not get to change that rule\n const drafts = loadDrafts(cfg);\n for (const [locale, keys] of Object.entries(result.translated)) {\n markDrafts(drafts, locale, keys);\n }\n saveDrafts(cfg, drafts);\n finish(id, result.failed.length ? 'failed' : 'done', {\n result,\n message: result.failed.length ? `${result.failed.length} batches did not answer` : undefined,\n });\n } catch (error) {\n finish(id, 'failed', { message: error instanceof Error ? error.message : String(error) });\n }\n })();\n\n return job;\n}\n","import { existsSync, readFileSync, statSync } from 'node:fs';\nimport { join, normalize, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\n// the built panel sits beside the compiled server, so it is found relative to this file, never cwd\nconst UI = resolve(fileURLToPath(new URL('.', import.meta.url)), 'ui');\n\nconst TYPES: Record<string, string> = {\n '.html': 'text/html; charset=utf-8',\n '.js': 'text/javascript; charset=utf-8',\n '.css': 'text/css; charset=utf-8',\n '.svg': 'image/svg+xml',\n '.woff2': 'font/woff2',\n '.json': 'application/json; charset=utf-8',\n '.ico': 'image/x-icon',\n};\n\nexport interface Asset {\n body: Buffer;\n type: string;\n}\n\nexport const uiBuilt = (): boolean => existsSync(join(UI, 'index.html'));\n\n// A path is resolved and then checked to still be inside UI: ../ and an absolute one both land out.\nexport function readAsset(pathname: string): Asset | undefined {\n const wanted = pathname === '/' ? '/index.html' : pathname;\n const target = resolve(join(UI, normalize(wanted)));\n if (target !== UI && !target.startsWith(UI + (process.platform === 'win32' ? '\\\\' : '/'))) {\n return undefined;\n }\n if (!existsSync(target) || !statSync(target).isFile()) return undefined;\n const dot = target.lastIndexOf('.');\n return {\n body: readFileSync(target),\n type: TYPES[target.slice(dot).toLowerCase()] ?? 'application/octet-stream',\n };\n}\n","import { randomBytes, timingSafeEqual } from 'node:crypto';\n\nexport interface GuardOptions {\n port: number;\n token: string;\n}\n\n// A local server that writes files is reachable from any page the browser has open.\nexport function newToken(): string {\n return randomBytes(9).toString('base64url');\n}\n\n// Only the two names that resolve to this machine. A DNS rebind arrives with a foreign Host.\nexport function hostAllowed(host: string | undefined, port: number): boolean {\n // a browser omits the port when it is the scheme default, so :80 would 403 every real request\n const names = port === 80 ? ['127.0.0.1', 'localhost'] : [];\n return [`127.0.0.1:${port}`, `localhost:${port}`, ...names].includes(host ?? '');\n}\n\n// No Origin at all is a same-origin navigation or a curl, both fine; a foreign one never is.\nexport function originAllowed(origin: string | undefined, port: number): boolean {\n if (origin === undefined) return true;\n return origin === `http://127.0.0.1:${port}` || origin === `http://localhost:${port}`;\n}\n\n// Compared in constant time so the token cannot be guessed one character at a time.\nexport function tokenMatches(given: string | null, expected: string): boolean {\n if (given === null) return false;\n const a = Buffer.from(given);\n const b = Buffer.from(expected);\n return a.length === b.length && timingSafeEqual(a, b);\n}\n\nexport interface GuardVerdict {\n ok: boolean;\n status: number;\n reason: string;\n}\n\nconst PASS: GuardVerdict = { ok: true, status: 200, reason: '' };\n\nexport function guardRequest(\n headers: { host?: string; origin?: string },\n url: URL,\n { port, token }: GuardOptions,\n): GuardVerdict {\n if (!hostAllowed(headers.host, port)) {\n return { ok: false, status: 403, reason: '[verbaly] unexpected Host header' };\n }\n if (!originAllowed(headers.origin, port)) {\n return { ok: false, status: 403, reason: '[verbaly] cross-origin request' };\n }\n if (!tokenMatches(url.searchParams.get('t'), token)) {\n return { ok: false, status: 401, reason: '[verbaly] missing or wrong token, open the printed url' };\n }\n return PASS;\n}\n","import { createServer, type IncomingMessage, type Server, type ServerResponse } from 'node:http';\nimport type { AddressInfo } from 'node:net';\nimport type { ResolvedConfig } from '@verbaly/compiler';\nimport { approve, buildState, health, unapprove, writeMessage } from './api';\nimport { planTranslate, runExtract, startTranslate } from './actions';\nimport { read as readJob, running } from './jobs';\nimport { readAsset, uiBuilt } from './assets';\nimport { guardRequest, newToken } from './guard';\nimport { HttpError, badRequest, scrub } from './http';\n\nexport interface StudioServer {\n server: Server;\n port: number;\n token: string;\n url: string;\n close: () => Promise<void>;\n}\n\nexport interface StartOptions {\n port?: number;\n token?: string;\n}\n\nconst DEFAULT_PORT = 4747;\nconst MAX_BODY = 1_000_000;\n\nfunction send(res: ServerResponse, status: number, body: unknown): void {\n res.writeHead(status, {\n 'content-type': 'application/json; charset=utf-8',\n 'cache-control': 'no-store',\n });\n res.end(JSON.stringify(body));\n}\n\nasync function readJson(req: IncomingMessage): Promise<Record<string, unknown>> {\n const chunks: Buffer[] = [];\n let size = 0;\n for await (const chunk of req) {\n size += (chunk as Buffer).length;\n if (size > MAX_BODY) throw new HttpError(413, 'request body too large');\n chunks.push(chunk as Buffer);\n }\n if (!chunks.length) return {};\n let parsed: unknown;\n try {\n parsed = JSON.parse(Buffer.concat(chunks).toString('utf8'));\n } catch {\n throw badRequest('body is not valid JSON');\n }\n // null and an array both pass typeof 'object', and reading .text off them is a 500\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw badRequest('body must be a JSON object');\n }\n return parsed as Record<string, unknown>;\n}\n\nfunction segment(raw: string | undefined, what: string): string {\n let value: string;\n try {\n value = decodeURIComponent(raw ?? '');\n } catch {\n throw badRequest(`the ${what} in the url is not valid percent-encoding`);\n }\n if (!value) throw badRequest(`the url is missing a ${what}`);\n return value;\n}\n\n// One page, JSON on every route: a framework would only add a dependency to a local tool.\nexport function createStudioApp(cfg: ResolvedConfig, port: number, token: string) {\n return async function handle(req: IncomingMessage, res: ServerResponse): Promise<void> {\n // The url is built inside the try: `GET //` throws, and out here that would kill the process.\n try {\n let url: URL;\n try {\n url = new URL(req.url ?? '/', `http://127.0.0.1:${port}`);\n } catch {\n throw badRequest('the request target is not a valid url');\n }\n\n const api = url.pathname.startsWith('/api/');\n const verdict = guardRequest({ host: req.headers.host, origin: req.headers.origin }, url, {\n port,\n token,\n });\n // the panel files are the published npm package: only the project data needs a token\n if (!verdict.ok && (api || verdict.status !== 401)) {\n send(res, verdict.status, { error: verdict.reason });\n return;\n }\n\n if (!api && req.method === 'GET') {\n const asset = readAsset(url.pathname);\n if (asset) {\n res.writeHead(200, { 'content-type': asset.type, 'cache-control': 'no-store' });\n res.end(asset.body);\n return;\n }\n if (url.pathname === '/' && !uiBuilt()) {\n throw new HttpError(500, 'the panel is not in this install, so only /api/* answers here');\n }\n }\n\n if (req.method === 'GET' && url.pathname === '/api/state') {\n send(res, 200, await buildState(cfg));\n return;\n }\n if (req.method === 'GET' && url.pathname === '/api/health') {\n send(res, 200, await health(cfg));\n return;\n }\n if (req.method === 'PUT' && url.pathname.startsWith('/api/message/')) {\n const rest = url.pathname.slice('/api/message/'.length).split('/');\n const locale = segment(rest[0], 'locale');\n const key = segment(rest.slice(1).join('/'), 'key');\n const body = await readJson(req);\n if (typeof body.text !== 'string') throw badRequest('body needs a \"text\" string');\n send(res, 200, writeMessage(cfg, locale, key, body.text));\n return;\n }\n if (req.method === 'POST' && url.pathname === '/api/approve') {\n const body = await readJson(req);\n if (typeof body.locale !== 'string') throw badRequest('body needs a \"locale\" string');\n const keys = Array.isArray(body.keys)\n ? body.keys.filter((k): k is string => typeof k === 'string')\n : undefined;\n if (body.undo === true) {\n if (!keys?.length) throw badRequest('undoing needs the \"keys\" it should put back');\n send(res, 200, unapprove(cfg, body.locale, keys));\n return;\n }\n send(res, 200, approve(cfg, body.locale, keys));\n return;\n }\n if (req.method === 'POST' && url.pathname === '/api/extract') {\n send(res, 200, await runExtract(cfg));\n return;\n }\n if (req.method === 'GET' && url.pathname === '/api/translate') {\n const locales = url.searchParams.getAll('locale');\n send(res, 200, await planTranslate(cfg, locales.length ? locales : undefined));\n return;\n }\n if (req.method === 'POST' && url.pathname === '/api/translate') {\n const body = await readJson(req);\n const locales = Array.isArray(body.locales)\n ? body.locales.filter((l): l is string => typeof l === 'string')\n : undefined;\n send(res, 200, await startTranslate(cfg, locales?.length ? locales : undefined));\n return;\n }\n // a reload loses the id, so the panel asks what is running rather than losing the bar\n if (req.method === 'GET' && url.pathname === '/api/job') {\n send(res, 200, running());\n return;\n }\n if (req.method === 'GET' && url.pathname.startsWith('/api/job/')) {\n send(res, 200, readJob(segment(url.pathname.slice('/api/job/'.length), 'job id')));\n return;\n }\n\n send(res, 404, { error: `[verbaly] no route for ${req.method} ${url.pathname}` });\n } catch (error) {\n const status = error instanceof HttpError ? error.status : 500;\n const message = error instanceof Error ? error.message : String(error);\n send(res, status, { error: scrub(message, cfg.root) });\n }\n };\n}\n\n// A busy port climbs instead of failing: this is a local tool, not a service on a fixed address.\nfunction listen(server: Server, port: number, attemptsLeft: number): Promise<number> {\n return new Promise((resolve, reject) => {\n const onError = (error: NodeJS.ErrnoException) => {\n if (error.code === 'EADDRINUSE' && attemptsLeft > 0) {\n server.removeListener('error', onError);\n resolve(listen(server, port + 1, attemptsLeft - 1));\n return;\n }\n reject(error);\n };\n server.once('error', onError);\n server.listen(port, '127.0.0.1', () => {\n server.removeListener('error', onError);\n // port 0 asks the system to pick, so the bound address is the only place the answer is\n const bound = server.address() as AddressInfo | string | null;\n resolve(typeof bound === 'object' && bound !== null ? bound.port : port);\n });\n });\n}\n\nexport async function startStudio(\n cfg: ResolvedConfig,\n options: StartOptions = {},\n): Promise<StudioServer> {\n const token = options.token ?? newToken();\n const server = createServer();\n // The handler is attached before listen so no request can arrive without one.\n let port = options.port ?? DEFAULT_PORT;\n server.on('request', (req, res) => void createStudioApp(cfg, port, token)(req, res));\n port = await listen(server, port, 20);\n\n return {\n server,\n port,\n token,\n url: `http://127.0.0.1:${port}/?t=${token}`,\n close: () =>\n new Promise<void>((resolve, reject) =>\n server.close((error) => (error ? reject(error) : resolve())),\n ),\n };\n}\n","#!/usr/bin/env node\nimport { parseArgs } from 'node:util';\nimport { counted, formatCliError, loadConfig } from '@verbaly/compiler';\nimport { buildState } from './api';\nimport { startStudio } from './server';\n\ntry {\n const { values } = parseArgs({\n options: {\n root: { type: 'string' },\n port: { type: 'string' },\n json: { type: 'boolean' },\n },\n });\n const port = values.port === undefined ? undefined : Number(values.port);\n if (port !== undefined && !Number.isInteger(port)) {\n throw new Error(`[verbaly] --port needs a number, got \"${values.port}\"`);\n }\n const cfg = await loadConfig(values.root ?? process.cwd());\n\n // --json is the state the interface reads, on stdout, before any interface exists.\n if (values.json) {\n console.log(JSON.stringify(await buildState(cfg), null, 2));\n } else {\n const studio = await startStudio(cfg, { port });\n const state = await buildState(cfg);\n const missing = state.status.locales.reduce((n, l) => n + (l.total - l.translated), 0);\n const drafts = Object.values(state.drafts).reduce((n, keys) => n + keys.length, 0);\n console.log(`\\n Verbaly Studio ${studio.url}`);\n console.log(` Project ${cfg.root}`);\n console.log(\n ` Catalog ${counted(state.status.messages, 'message')} · ` +\n `${missing} untranslated · ${counted(drafts, 'draft')}`,\n );\n for (const problem of state.problems) console.log(` ! ${problem.scope}: ${problem.message}`);\n console.log('\\n Ctrl+C to stop\\n');\n }\n} catch (error) {\n console.error(formatCliError(error));\n process.exitCode = 1;\n}\n"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import { createServer } from "node:http";
2
- import { existsSync } from "node:fs";
3
- import { relative } from "node:path";
4
- import { DRAFTS_FILE, check, clearDrafts, collectOrigins, doctor, effectiveDrafts, extractProject, loadDrafts, readCatalog, saveDrafts, status, targetLocales, validateMessage, validatePair, writeCatalog } from "@verbaly/compiler";
2
+ import { existsSync, readFileSync, statSync } from "node:fs";
3
+ import { join, normalize, relative, resolve } from "node:path";
4
+ import { DRAFTS_FILE, check, clearDrafts, collectOrigins, doctor, effectiveDrafts, extractProject, loadCatalogs, loadDrafts, markDrafts, readCatalog, resolveProvider, saveDrafts, status, syncCatalogs, targetLocales, translateCatalogs, validateMessage, validatePair, writeCatalog, writeDts } from "@verbaly/compiler";
5
+ import { fileURLToPath } from "node:url";
5
6
  import { randomBytes, timingSafeEqual } from "node:crypto";
6
7
  //#region src/http.ts
7
8
  var HttpError = class extends Error {
@@ -193,13 +194,138 @@ function writeMessage(cfg, locale, key, text) {
193
194
  function approve(cfg, locale, keys) {
194
195
  if (!cfg.locales.includes(locale)) throw badRequest(`${locale} is not one of this project's locales`);
195
196
  const drafts = loadDrafts(cfg);
196
- const before = (drafts[locale] ?? []).length;
197
+ const had = drafts[locale] ?? [];
198
+ const cleared = keys ? had.filter((key) => keys.includes(key)) : [...had];
197
199
  clearDrafts(drafts, locale, keys);
198
- const after = (drafts[locale] ?? []).length;
199
200
  saveDrafts(cfg, drafts);
200
201
  return {
201
202
  locale,
202
- approved: before - after
203
+ approved: cleared.length,
204
+ keys: cleared
205
+ };
206
+ }
207
+ function unapprove(cfg, locale, keys) {
208
+ if (!cfg.locales.includes(locale)) throw badRequest(`${locale} is not one of this project's locales`);
209
+ const drafts = loadDrafts(cfg);
210
+ markDrafts(drafts, locale, keys);
211
+ saveDrafts(cfg, drafts);
212
+ return {
213
+ locale,
214
+ approved: keys.length,
215
+ keys
216
+ };
217
+ }
218
+ //#endregion
219
+ //#region src/jobs.ts
220
+ let current = null;
221
+ let counter = 0;
222
+ function running() {
223
+ return current?.state === "running" ? current : null;
224
+ }
225
+ function read(id) {
226
+ if (!current || current.id !== id) throw new HttpError(404, `no job called "${id}"`);
227
+ return current;
228
+ }
229
+ function start(kind, total) {
230
+ if (running()) throw badRequest("a job is already running, wait for it or read its progress");
231
+ counter += 1;
232
+ current = {
233
+ id: `${kind}-${counter}`,
234
+ kind,
235
+ state: "running",
236
+ total,
237
+ done: 0
238
+ };
239
+ return current;
240
+ }
241
+ function advance(id, keys, locale, error) {
242
+ if (current?.id !== id) return;
243
+ current.done = Math.min(current.done + keys, current.total);
244
+ current.locale = locale;
245
+ if (error) current.message = error;
246
+ }
247
+ function finish(id, state, patch = {}) {
248
+ if (current?.id === id) Object.assign(current, patch, { state });
249
+ }
250
+ //#endregion
251
+ //#region src/actions.ts
252
+ async function runExtract(cfg) {
253
+ if (!cfg.include.length) throw badRequest("source scanning is off (include: []), so there is nothing to extract");
254
+ const registry = await extractProject(cfg);
255
+ const catalogs = loadCatalogs(cfg);
256
+ const result = syncCatalogs(cfg, catalogs, registry);
257
+ for (const locale of cfg.locales) writeCatalog(cfg, locale, catalogs[locale] ?? {});
258
+ if (cfg.dts !== false) writeDts(cfg, catalogs[cfg.sourceLocale] ?? {}, cfg.dts);
259
+ const found = result.added[cfg.sourceLocale]?.length ?? 0;
260
+ return {
261
+ added: result.added,
262
+ found,
263
+ messages: registry.messages().size
264
+ };
265
+ }
266
+ async function planTranslate(cfg, locales) {
267
+ const provider = await resolveProvider(cfg);
268
+ const catalogs = loadCatalogs(cfg);
269
+ const result = await translateCatalogs(cfg, catalogs, provider, {
270
+ locales,
271
+ dryRun: true
272
+ });
273
+ const total = Object.values(result.pending).reduce((n, keys) => n + keys.length, 0);
274
+ return {
275
+ pending: result.pending,
276
+ total
277
+ };
278
+ }
279
+ async function startTranslate(cfg, locales) {
280
+ const provider = await resolveProvider(cfg);
281
+ const job = start("translate", (await planTranslate(cfg, locales)).total);
282
+ const id = job.id;
283
+ (async () => {
284
+ try {
285
+ const catalogs = loadCatalogs(cfg);
286
+ const result = await translateCatalogs(cfg, catalogs, provider, {
287
+ locales,
288
+ batchSize: cfg.translate.batchSize,
289
+ concurrency: cfg.translate.concurrency,
290
+ retries: cfg.translate.retries,
291
+ origins: await collectOrigins(cfg),
292
+ onProgress: (p) => advance(id, p.keys, p.locale, p.error)
293
+ });
294
+ for (const [locale, keys] of Object.entries(result.translated)) if (keys.length) writeCatalog(cfg, locale, catalogs[locale] ?? {});
295
+ const drafts = loadDrafts(cfg);
296
+ for (const [locale, keys] of Object.entries(result.translated)) markDrafts(drafts, locale, keys);
297
+ saveDrafts(cfg, drafts);
298
+ finish(id, result.failed.length ? "failed" : "done", {
299
+ result,
300
+ message: result.failed.length ? `${result.failed.length} batches did not answer` : void 0
301
+ });
302
+ } catch (error) {
303
+ finish(id, "failed", { message: error instanceof Error ? error.message : String(error) });
304
+ }
305
+ })();
306
+ return job;
307
+ }
308
+ //#endregion
309
+ //#region src/assets.ts
310
+ const UI = resolve(fileURLToPath(new URL(".", import.meta.url)), "ui");
311
+ const TYPES = {
312
+ ".html": "text/html; charset=utf-8",
313
+ ".js": "text/javascript; charset=utf-8",
314
+ ".css": "text/css; charset=utf-8",
315
+ ".svg": "image/svg+xml",
316
+ ".woff2": "font/woff2",
317
+ ".json": "application/json; charset=utf-8",
318
+ ".ico": "image/x-icon"
319
+ };
320
+ const uiBuilt = () => existsSync(join(UI, "index.html"));
321
+ function readAsset(pathname) {
322
+ const target = resolve(join(UI, normalize(pathname === "/" ? "/index.html" : pathname)));
323
+ if (target !== UI && !target.startsWith(UI + (process.platform === "win32" ? "\\" : "/"))) return;
324
+ if (!existsSync(target) || !statSync(target).isFile()) return void 0;
325
+ const dot = target.lastIndexOf(".");
326
+ return {
327
+ body: readFileSync(target),
328
+ type: TYPES[target.slice(dot).toLowerCase()] ?? "application/octet-stream"
203
329
  };
204
330
  }
205
331
  //#endregion
@@ -296,6 +422,7 @@ function createStudioApp(cfg, port, token) {
296
422
  } catch {
297
423
  throw badRequest("the request target is not a valid url");
298
424
  }
425
+ const api = url.pathname.startsWith("/api/");
299
426
  const verdict = guardRequest({
300
427
  host: req.headers.host,
301
428
  origin: req.headers.origin
@@ -303,10 +430,22 @@ function createStudioApp(cfg, port, token) {
303
430
  port,
304
431
  token
305
432
  });
306
- if (!verdict.ok) {
433
+ if (!verdict.ok && (api || verdict.status !== 401)) {
307
434
  send(res, verdict.status, { error: verdict.reason });
308
435
  return;
309
436
  }
437
+ if (!api && req.method === "GET") {
438
+ const asset = readAsset(url.pathname);
439
+ if (asset) {
440
+ res.writeHead(200, {
441
+ "content-type": asset.type,
442
+ "cache-control": "no-store"
443
+ });
444
+ res.end(asset.body);
445
+ return;
446
+ }
447
+ if (url.pathname === "/" && !uiBuilt()) throw new HttpError(500, "the panel is not in this install, so only /api/* answers here");
448
+ }
310
449
  if (req.method === "GET" && url.pathname === "/api/state") {
311
450
  send(res, 200, await buildState(cfg));
312
451
  return;
@@ -328,9 +467,37 @@ function createStudioApp(cfg, port, token) {
328
467
  const body = await readJson(req);
329
468
  if (typeof body.locale !== "string") throw badRequest("body needs a \"locale\" string");
330
469
  const keys = Array.isArray(body.keys) ? body.keys.filter((k) => typeof k === "string") : void 0;
470
+ if (body.undo === true) {
471
+ if (!keys?.length) throw badRequest("undoing needs the \"keys\" it should put back");
472
+ send(res, 200, unapprove(cfg, body.locale, keys));
473
+ return;
474
+ }
331
475
  send(res, 200, approve(cfg, body.locale, keys));
332
476
  return;
333
477
  }
478
+ if (req.method === "POST" && url.pathname === "/api/extract") {
479
+ send(res, 200, await runExtract(cfg));
480
+ return;
481
+ }
482
+ if (req.method === "GET" && url.pathname === "/api/translate") {
483
+ const locales = url.searchParams.getAll("locale");
484
+ send(res, 200, await planTranslate(cfg, locales.length ? locales : void 0));
485
+ return;
486
+ }
487
+ if (req.method === "POST" && url.pathname === "/api/translate") {
488
+ const body = await readJson(req);
489
+ const locales = Array.isArray(body.locales) ? body.locales.filter((l) => typeof l === "string") : void 0;
490
+ send(res, 200, await startTranslate(cfg, locales?.length ? locales : void 0));
491
+ return;
492
+ }
493
+ if (req.method === "GET" && url.pathname === "/api/job") {
494
+ send(res, 200, running());
495
+ return;
496
+ }
497
+ if (req.method === "GET" && url.pathname.startsWith("/api/job/")) {
498
+ send(res, 200, read(segment(url.pathname.slice(9), "job id")));
499
+ return;
500
+ }
334
501
  send(res, 404, { error: `[verbaly] no route for ${req.method} ${url.pathname}` });
335
502
  } catch (error) {
336
503
  send(res, error instanceof HttpError ? error.status : 500, { error: scrub(error instanceof Error ? error.message : String(error), cfg.root) });
@@ -365,7 +532,7 @@ async function startStudio(cfg, options = {}) {
365
532
  server,
366
533
  port,
367
534
  token,
368
- url: `http://127.0.0.1:${port}/api/state?t=${token}`,
535
+ url: `http://127.0.0.1:${port}/?t=${token}`,
369
536
  close: () => new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve()))
370
537
  };
371
538
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/http.ts","../src/triage.ts","../src/api.ts","../src/guard.ts","../src/server.ts"],"sourcesContent":["// A client mistake is not a server fault, so the status rides with the error, not a blanket 500.\nexport class HttpError extends Error {\n readonly status: number;\n\n constructor(status: number, message: string) {\n super(message.startsWith('[verbaly]') ? message : `[verbaly] ${message}`);\n this.status = status;\n this.name = 'HttpError';\n }\n}\n\nexport const badRequest = (message: string) => new HttpError(400, message);\n\n// The absolute path of someone's project is not the browser's business.\nexport function scrub(message: string, root: string): string {\n return root ? message.split(root).join('.') : message;\n}\n","import type { Catalog } from '@verbaly/compiler';\n\nexport type TriageSignal = 'divergent' | 'collision' | 'echo' | 'digits' | 'code' | 'url';\n\nexport interface TriageReason {\n signal: TriageSignal;\n text: string;\n}\n\nexport type TriageResult = Record<string, TriageReason[]>;\n\n// check() already caught the structure, so these only point at the few worth reading first.\nexport interface TriageInput {\n source: Catalog;\n target: Catalog;\n reviewed: Catalog[];\n}\n\nconst DIGITS = /\\d[\\d.,]*/g;\nconst URLS = /https?:\\/\\/[^\\s<)\"']+/g;\nconst CODE = /<code>([^<]+)<\\/code>/g;\n\nfunction bag(text: string, re: RegExp, normalize?: (v: string) => string): string[] {\n const found = text.match(re) ?? [];\n return (normalize ? found.map(normalize) : found).sort();\n}\n\nfunction same(a: string[], b: string[]): boolean {\n return a.length === b.length && a.every((v, i) => v === b[i]);\n}\n\n// A number or a url at the end of a clause takes the punctuation with it, and that is not a signal.\nconst trimPunct = (v: string) => v.replace(/[.,;:!?)\\]]+$/, '');\n// A separator is idiomatic (3.09 is 3,09 in Spanish); canonizing keeps 1.5 apart from 15.\nconst bareDigits = (v: string) => trimPunct(v).replaceAll(/[.,]/g, '.');\n\nfunction push(out: TriageResult, key: string, reason: TriageReason): void {\n (out[key] ??= []).push(reason);\n}\n\nexport function triage({ source, target, reviewed }: TriageInput): TriageResult {\n // null-prototype: a source text of \"__proto__\" or \"toString\" is a string, not a member of Object\n const out: TriageResult = Object.create(null);\n const keys = Object.keys(source);\n\n // One source text that two humans rendered two ways is ambiguous, so the machine had no chance.\n const bySource: Record<string, string[]> = Object.create(null);\n for (const key of keys) {\n const text = source[key]?.trim();\n if (text) (bySource[text] ??= []).push(key);\n }\n for (const [text, group] of Object.entries(bySource)) {\n if (group.length < 2) continue;\n const divergent = reviewed.some(\n (catalog) => new Set(group.map((k) => catalog[k]?.trim() ?? '').filter(Boolean)).size > 1,\n );\n if (divergent) {\n for (const key of group) {\n push(out, key, { signal: 'divergent', text: `\"${text}\" was already translated two ways` });\n }\n }\n // The same ambiguity seen from the result instead of from the history.\n const targets = new Set(group.map((key) => target[key]?.trim() ?? '').filter(Boolean));\n if (!divergent && targets.size > 1) {\n for (const key of group) {\n push(out, key, { signal: 'collision', text: `\"${text}\" came back as two different translations` });\n }\n }\n }\n\n for (const key of keys) {\n const src = source[key]?.trim() ?? '';\n const trg = target[key]?.trim() ?? '';\n if (!src || !trg) continue;\n\n // Only the locales with an opinion on this key vote: alone it fires on every do-not-translate.\n const opinions = reviewed.map((c) => c[key]?.trim()).filter((v) => v !== undefined && v !== '');\n if (trg === src && opinions.length > 0 && opinions.every((v) => v !== src)) {\n push(out, key, { signal: 'echo', text: 'still identical to the source' });\n }\n\n // a translation that adds a number is idiomatic (\"refresh\" is \"F5\"); one that changes it is not\n const srcDigits = bag(src, DIGITS, bareDigits);\n if (srcDigits.length > 0 && !same(srcDigits, bag(trg, DIGITS, bareDigits))) {\n push(out, key, { signal: 'digits', text: 'the numbers do not match the source' });\n }\n if (!same(bag(src, URLS, trimPunct), bag(trg, URLS, trimPunct))) {\n push(out, key, { signal: 'url', text: 'a URL changed' });\n }\n if (!same(bag(src, CODE), bag(trg, CODE))) {\n push(out, key, { signal: 'code', text: 'the contents of a <code> span changed' });\n }\n }\n\n return out;\n}\n","import { existsSync } from 'node:fs';\nimport { relative } from 'node:path';\nimport {\n check,\n clearDrafts,\n collectOrigins,\n doctor,\n effectiveDrafts,\n extractProject,\n loadDrafts,\n readCatalog,\n saveDrafts,\n status,\n targetLocales,\n validateMessage,\n validatePair,\n writeCatalog,\n} from '@verbaly/compiler';\nimport { DRAFTS_FILE } from '@verbaly/compiler';\nimport type {\n Catalog,\n Catalogs,\n CheckResult,\n DoctorResult,\n Drafts,\n ResolvedConfig,\n StatusResult,\n} from '@verbaly/compiler';\nimport { badRequest } from './http';\nimport { triage, type TriageResult } from './triage';\n\nexport interface StudioProblem {\n scope: string;\n message: string;\n}\n\nexport interface StudioState {\n root: string;\n dir: string;\n sourceLocale: string;\n locales: string[];\n scanning: boolean;\n catalogs: Catalogs;\n origins: Record<string, string[]>;\n status: StatusResult;\n check: CheckResult;\n drafts: Drafts;\n triage: Record<string, TriageResult>;\n problems: StudioProblem[];\n}\n\n// A locale nobody can parse must not take the panel down: Studio is what you open to fix it.\nfunction readAll(cfg: ResolvedConfig): { catalogs: Catalogs; problems: StudioProblem[] } {\n const catalogs: Catalogs = {};\n const problems: StudioProblem[] = [];\n // an empty green panel and a wrong --root look identical, and only this line tells them apart\n if (!existsSync(cfg.dir)) {\n const dir = relative(cfg.root, cfg.dir).replaceAll('\\\\', '/');\n problems.push({ scope: dir, message: '[verbaly] no catalogs here, run npx verbaly init or check --root' });\n }\n for (const locale of cfg.locales) {\n try {\n catalogs[locale] = readCatalog(cfg, locale);\n } catch (error) {\n catalogs[locale] = {};\n problems.push({\n scope: locale,\n message: error instanceof Error ? error.message : String(error),\n });\n }\n }\n return { catalogs, problems };\n}\n\n// A sidecar nobody can parse is the other file Studio exists to fix, so it degrades like a catalog.\nfunction readDrafts(cfg: ResolvedConfig): { drafts: Drafts; problems: StudioProblem[] } {\n try {\n return { drafts: loadDrafts(cfg), problems: [] };\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return { drafts: {}, problems: [{ scope: DRAFTS_FILE, message }] };\n }\n}\n\n// check() reports the file a stray key was used in as an absolute path, origins already do not.\nfunction relativize(cfg: ResolvedConfig, result: CheckResult): CheckResult {\n return {\n ...result,\n unknown: result.unknown.map((entry) => ({\n ...entry,\n files: entry.files.map((file) => relative(cfg.root, file).replaceAll('\\\\', '/')),\n })),\n };\n}\n\n// One shot and unpaginated: the biggest catalog we know is 1479 messages, instant over localhost.\nexport async function buildState(cfg: ResolvedConfig): Promise<StudioState> {\n const { catalogs, problems } = readAll(cfg);\n const registry = await extractProject(cfg);\n const sidecar = readDrafts(cfg);\n problems.push(...sidecar.problems);\n const live = effectiveDrafts(sidecar.drafts, catalogs);\n const source = (catalogs[cfg.sourceLocale] ?? {}) as Catalog;\n const targets = targetLocales(cfg);\n\n for (const { file, message } of registry.parseErrors()) {\n problems.push({ scope: relative(cfg.root, file).replaceAll('\\\\', '/'), message });\n }\n\n const perLocale: Record<string, TriageResult> = {};\n for (const locale of targets) {\n perLocale[locale] = triage({\n source,\n target: (catalogs[locale] ?? {}) as Catalog,\n // a locale still carrying drafts has not been reviewed, so it cannot be the control\n reviewed: targets\n .filter((other) => other !== locale && !(live[other] ?? []).length)\n .map((other) => (catalogs[other] ?? {}) as Catalog),\n });\n }\n\n return {\n root: cfg.root,\n dir: cfg.dir,\n sourceLocale: cfg.sourceLocale,\n locales: cfg.locales,\n // with include: [] nothing is scanned, so an empty origins map is off, not \"none found\"\n scanning: cfg.include.length > 0,\n catalogs,\n origins: await collectOrigins(cfg, registry),\n status: status(cfg, catalogs, registry, live),\n check: relativize(cfg, check(cfg, catalogs, registry)),\n drafts: live,\n triage: perLocale,\n problems,\n };\n}\n\nexport function health(cfg: ResolvedConfig): Promise<DoctorResult> {\n return doctor(cfg);\n}\n\nexport interface WriteMessageResult {\n locale: string;\n key: string;\n clearedDraft: boolean;\n}\n\n// A human wrote it, so it is reviewed: the rule import already applies in run.ts:447.\nexport function writeMessage(\n cfg: ResolvedConfig,\n locale: string,\n key: string,\n text: string,\n): WriteMessageResult {\n if (locale === cfg.sourceLocale) {\n throw badRequest('the source text lives in your code, Studio never edits it');\n }\n if (!cfg.locales.includes(locale)) {\n throw badRequest(`${locale} is not one of this project's locales`);\n }\n const source = readCatalog(cfg, cfg.sourceLocale);\n // hasOwn, not `in`: `in` walks the prototype, so toString and __proto__ would pass this guard\n if (!Object.hasOwn(source, key)) {\n throw badRequest(`\"${key}\" is not in the source catalog, so Studio will not create it`);\n }\n // '' is untranslated everywhere else, so whitespace is that and not a translation to validate\n const value = text.trim() ? text : '';\n // the same two calls check() makes: validatePair alone misses a plural block with no other case\n const issues = value\n ? [...validateMessage(value, locale), ...validatePair(source[key] as string, value)]\n : [];\n const errors = issues.filter((issue) => issue.severity === 'error');\n if (errors.length) {\n throw badRequest(`${errors.map((issue) => issue.message).join('; ')}`);\n }\n\n const catalog = { ...readCatalog(cfg, locale), [key]: value };\n writeCatalog(cfg, locale, catalog);\n\n const drafts = loadDrafts(cfg);\n const clearedDraft = (drafts[locale] ?? []).includes(key);\n clearDrafts(drafts, locale, [key]);\n saveDrafts(cfg, drafts);\n return { locale, key, clearedDraft };\n}\n\nexport interface ApproveResult {\n locale: string;\n approved: number;\n}\n\n// The sidecar is re-read here: saveDrafts writes every locale, so a stale copy drops another's.\nexport function approve(cfg: ResolvedConfig, locale: string, keys?: string[]): ApproveResult {\n if (!cfg.locales.includes(locale)) {\n throw badRequest(`${locale} is not one of this project's locales`);\n }\n const drafts = loadDrafts(cfg);\n const before = (drafts[locale] ?? []).length;\n clearDrafts(drafts, locale, keys);\n const after = (drafts[locale] ?? []).length;\n saveDrafts(cfg, drafts);\n return { locale, approved: before - after };\n}\n","import { randomBytes, timingSafeEqual } from 'node:crypto';\n\nexport interface GuardOptions {\n port: number;\n token: string;\n}\n\n// A local server that writes files is reachable from any page the browser has open.\nexport function newToken(): string {\n return randomBytes(9).toString('base64url');\n}\n\n// Only the two names that resolve to this machine. A DNS rebind arrives with a foreign Host.\nexport function hostAllowed(host: string | undefined, port: number): boolean {\n // a browser omits the port when it is the scheme default, so :80 would 403 every real request\n const names = port === 80 ? ['127.0.0.1', 'localhost'] : [];\n return [`127.0.0.1:${port}`, `localhost:${port}`, ...names].includes(host ?? '');\n}\n\n// No Origin at all is a same-origin navigation or a curl, both fine; a foreign one never is.\nexport function originAllowed(origin: string | undefined, port: number): boolean {\n if (origin === undefined) return true;\n return origin === `http://127.0.0.1:${port}` || origin === `http://localhost:${port}`;\n}\n\n// Compared in constant time so the token cannot be guessed one character at a time.\nexport function tokenMatches(given: string | null, expected: string): boolean {\n if (given === null) return false;\n const a = Buffer.from(given);\n const b = Buffer.from(expected);\n return a.length === b.length && timingSafeEqual(a, b);\n}\n\nexport interface GuardVerdict {\n ok: boolean;\n status: number;\n reason: string;\n}\n\nconst PASS: GuardVerdict = { ok: true, status: 200, reason: '' };\n\nexport function guardRequest(\n headers: { host?: string; origin?: string },\n url: URL,\n { port, token }: GuardOptions,\n): GuardVerdict {\n if (!hostAllowed(headers.host, port)) {\n return { ok: false, status: 403, reason: '[verbaly] unexpected Host header' };\n }\n if (!originAllowed(headers.origin, port)) {\n return { ok: false, status: 403, reason: '[verbaly] cross-origin request' };\n }\n if (!tokenMatches(url.searchParams.get('t'), token)) {\n return { ok: false, status: 401, reason: '[verbaly] missing or wrong token, open the printed url' };\n }\n return PASS;\n}\n","import { createServer, type IncomingMessage, type Server, type ServerResponse } from 'node:http';\nimport type { AddressInfo } from 'node:net';\nimport type { ResolvedConfig } from '@verbaly/compiler';\nimport { approve, buildState, health, writeMessage } from './api';\nimport { guardRequest, newToken } from './guard';\nimport { HttpError, badRequest, scrub } from './http';\n\nexport interface StudioServer {\n server: Server;\n port: number;\n token: string;\n url: string;\n close: () => Promise<void>;\n}\n\nexport interface StartOptions {\n port?: number;\n token?: string;\n}\n\nconst DEFAULT_PORT = 4747;\nconst MAX_BODY = 1_000_000;\n\nfunction send(res: ServerResponse, status: number, body: unknown): void {\n res.writeHead(status, {\n 'content-type': 'application/json; charset=utf-8',\n 'cache-control': 'no-store',\n });\n res.end(JSON.stringify(body));\n}\n\nasync function readJson(req: IncomingMessage): Promise<Record<string, unknown>> {\n const chunks: Buffer[] = [];\n let size = 0;\n for await (const chunk of req) {\n size += (chunk as Buffer).length;\n if (size > MAX_BODY) throw new HttpError(413, 'request body too large');\n chunks.push(chunk as Buffer);\n }\n if (!chunks.length) return {};\n let parsed: unknown;\n try {\n parsed = JSON.parse(Buffer.concat(chunks).toString('utf8'));\n } catch {\n throw badRequest('body is not valid JSON');\n }\n // null and an array both pass typeof 'object', and reading .text off them is a 500\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw badRequest('body must be a JSON object');\n }\n return parsed as Record<string, unknown>;\n}\n\nfunction segment(raw: string | undefined, what: string): string {\n let value: string;\n try {\n value = decodeURIComponent(raw ?? '');\n } catch {\n throw badRequest(`the ${what} in the url is not valid percent-encoding`);\n }\n if (!value) throw badRequest(`the url is missing a ${what}`);\n return value;\n}\n\n// One page, JSON on every route: a framework would only add a dependency to a local tool.\nexport function createStudioApp(cfg: ResolvedConfig, port: number, token: string) {\n return async function handle(req: IncomingMessage, res: ServerResponse): Promise<void> {\n // The url is built inside the try: `GET //` throws, and out here that would kill the process.\n try {\n let url: URL;\n try {\n url = new URL(req.url ?? '/', `http://127.0.0.1:${port}`);\n } catch {\n throw badRequest('the request target is not a valid url');\n }\n\n const verdict = guardRequest({ host: req.headers.host, origin: req.headers.origin }, url, {\n port,\n token,\n });\n if (!verdict.ok) {\n send(res, verdict.status, { error: verdict.reason });\n return;\n }\n\n if (req.method === 'GET' && url.pathname === '/api/state') {\n send(res, 200, await buildState(cfg));\n return;\n }\n if (req.method === 'GET' && url.pathname === '/api/health') {\n send(res, 200, await health(cfg));\n return;\n }\n if (req.method === 'PUT' && url.pathname.startsWith('/api/message/')) {\n const rest = url.pathname.slice('/api/message/'.length).split('/');\n const locale = segment(rest[0], 'locale');\n const key = segment(rest.slice(1).join('/'), 'key');\n const body = await readJson(req);\n if (typeof body.text !== 'string') throw badRequest('body needs a \"text\" string');\n send(res, 200, writeMessage(cfg, locale, key, body.text));\n return;\n }\n if (req.method === 'POST' && url.pathname === '/api/approve') {\n const body = await readJson(req);\n if (typeof body.locale !== 'string') throw badRequest('body needs a \"locale\" string');\n const keys = Array.isArray(body.keys)\n ? body.keys.filter((k): k is string => typeof k === 'string')\n : undefined;\n send(res, 200, approve(cfg, body.locale, keys));\n return;\n }\n send(res, 404, { error: `[verbaly] no route for ${req.method} ${url.pathname}` });\n } catch (error) {\n const status = error instanceof HttpError ? error.status : 500;\n const message = error instanceof Error ? error.message : String(error);\n send(res, status, { error: scrub(message, cfg.root) });\n }\n };\n}\n\n// A busy port climbs instead of failing: this is a local tool, not a service on a fixed address.\nfunction listen(server: Server, port: number, attemptsLeft: number): Promise<number> {\n return new Promise((resolve, reject) => {\n const onError = (error: NodeJS.ErrnoException) => {\n if (error.code === 'EADDRINUSE' && attemptsLeft > 0) {\n server.removeListener('error', onError);\n resolve(listen(server, port + 1, attemptsLeft - 1));\n return;\n }\n reject(error);\n };\n server.once('error', onError);\n server.listen(port, '127.0.0.1', () => {\n server.removeListener('error', onError);\n // port 0 asks the system to pick, so the bound address is the only place the answer is\n const bound = server.address() as AddressInfo | string | null;\n resolve(typeof bound === 'object' && bound !== null ? bound.port : port);\n });\n });\n}\n\nexport async function startStudio(\n cfg: ResolvedConfig,\n options: StartOptions = {},\n): Promise<StudioServer> {\n const token = options.token ?? newToken();\n const server = createServer();\n // The handler is attached before listen so no request can arrive without one.\n let port = options.port ?? DEFAULT_PORT;\n server.on('request', (req, res) => void createStudioApp(cfg, port, token)(req, res));\n port = await listen(server, port, 20);\n\n return {\n server,\n port,\n token,\n url: `http://127.0.0.1:${port}/api/state?t=${token}`,\n close: () =>\n new Promise<void>((resolve, reject) =>\n server.close((error) => (error ? reject(error) : resolve())),\n ),\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["readJob"],"sources":["../src/http.ts","../src/triage.ts","../src/api.ts","../src/jobs.ts","../src/actions.ts","../src/assets.ts","../src/guard.ts","../src/server.ts"],"sourcesContent":["// A client mistake is not a server fault, so the status rides with the error, not a blanket 500.\nexport class HttpError extends Error {\n readonly status: number;\n\n constructor(status: number, message: string) {\n super(message.startsWith('[verbaly]') ? message : `[verbaly] ${message}`);\n this.status = status;\n this.name = 'HttpError';\n }\n}\n\nexport const badRequest = (message: string) => new HttpError(400, message);\n\n// The absolute path of someone's project is not the browser's business.\nexport function scrub(message: string, root: string): string {\n return root ? message.split(root).join('.') : message;\n}\n","import type { Catalog } from '@verbaly/compiler';\n\nexport type TriageSignal = 'divergent' | 'collision' | 'echo' | 'digits' | 'code' | 'url';\n\nexport interface TriageReason {\n signal: TriageSignal;\n text: string;\n}\n\nexport type TriageResult = Record<string, TriageReason[]>;\n\n// check() already caught the structure, so these only point at the few worth reading first.\nexport interface TriageInput {\n source: Catalog;\n target: Catalog;\n reviewed: Catalog[];\n}\n\nconst DIGITS = /\\d[\\d.,]*/g;\nconst URLS = /https?:\\/\\/[^\\s<)\"']+/g;\nconst CODE = /<code>([^<]+)<\\/code>/g;\n\nfunction bag(text: string, re: RegExp, normalize?: (v: string) => string): string[] {\n const found = text.match(re) ?? [];\n return (normalize ? found.map(normalize) : found).sort();\n}\n\nfunction same(a: string[], b: string[]): boolean {\n return a.length === b.length && a.every((v, i) => v === b[i]);\n}\n\n// A number or a url at the end of a clause takes the punctuation with it, and that is not a signal.\nconst trimPunct = (v: string) => v.replace(/[.,;:!?)\\]]+$/, '');\n// A separator is idiomatic (3.09 is 3,09 in Spanish); canonizing keeps 1.5 apart from 15.\nconst bareDigits = (v: string) => trimPunct(v).replaceAll(/[.,]/g, '.');\n\nfunction push(out: TriageResult, key: string, reason: TriageReason): void {\n (out[key] ??= []).push(reason);\n}\n\nexport function triage({ source, target, reviewed }: TriageInput): TriageResult {\n // null-prototype: a source text of \"__proto__\" or \"toString\" is a string, not a member of Object\n const out: TriageResult = Object.create(null);\n const keys = Object.keys(source);\n\n // One source text that two humans rendered two ways is ambiguous, so the machine had no chance.\n const bySource: Record<string, string[]> = Object.create(null);\n for (const key of keys) {\n const text = source[key]?.trim();\n if (text) (bySource[text] ??= []).push(key);\n }\n for (const [text, group] of Object.entries(bySource)) {\n if (group.length < 2) continue;\n const divergent = reviewed.some(\n (catalog) => new Set(group.map((k) => catalog[k]?.trim() ?? '').filter(Boolean)).size > 1,\n );\n if (divergent) {\n for (const key of group) {\n push(out, key, { signal: 'divergent', text: `\"${text}\" was already translated two ways` });\n }\n }\n // The same ambiguity seen from the result instead of from the history.\n const targets = new Set(group.map((key) => target[key]?.trim() ?? '').filter(Boolean));\n if (!divergent && targets.size > 1) {\n for (const key of group) {\n push(out, key, { signal: 'collision', text: `\"${text}\" came back as two different translations` });\n }\n }\n }\n\n for (const key of keys) {\n const src = source[key]?.trim() ?? '';\n const trg = target[key]?.trim() ?? '';\n if (!src || !trg) continue;\n\n // Only the locales with an opinion on this key vote: alone it fires on every do-not-translate.\n const opinions = reviewed.map((c) => c[key]?.trim()).filter((v) => v !== undefined && v !== '');\n if (trg === src && opinions.length > 0 && opinions.every((v) => v !== src)) {\n push(out, key, { signal: 'echo', text: 'still identical to the source' });\n }\n\n // a translation that adds a number is idiomatic (\"refresh\" is \"F5\"); one that changes it is not\n const srcDigits = bag(src, DIGITS, bareDigits);\n if (srcDigits.length > 0 && !same(srcDigits, bag(trg, DIGITS, bareDigits))) {\n push(out, key, { signal: 'digits', text: 'the numbers do not match the source' });\n }\n if (!same(bag(src, URLS, trimPunct), bag(trg, URLS, trimPunct))) {\n push(out, key, { signal: 'url', text: 'a URL changed' });\n }\n if (!same(bag(src, CODE), bag(trg, CODE))) {\n push(out, key, { signal: 'code', text: 'the contents of a <code> span changed' });\n }\n }\n\n return out;\n}\n","import { existsSync } from 'node:fs';\nimport { relative } from 'node:path';\nimport {\n check,\n clearDrafts,\n collectOrigins,\n doctor,\n effectiveDrafts,\n extractProject,\n loadDrafts,\n markDrafts,\n readCatalog,\n saveDrafts,\n status,\n targetLocales,\n validateMessage,\n validatePair,\n writeCatalog,\n} from '@verbaly/compiler';\nimport { DRAFTS_FILE } from '@verbaly/compiler';\nimport type {\n Catalog,\n Catalogs,\n CheckResult,\n DoctorResult,\n Drafts,\n ResolvedConfig,\n StatusResult,\n} from '@verbaly/compiler';\nimport { badRequest } from './http';\nimport { triage, type TriageResult } from './triage';\n\nexport interface StudioProblem {\n scope: string;\n message: string;\n}\n\nexport interface StudioState {\n root: string;\n dir: string;\n sourceLocale: string;\n locales: string[];\n scanning: boolean;\n catalogs: Catalogs;\n origins: Record<string, string[]>;\n status: StatusResult;\n check: CheckResult;\n drafts: Drafts;\n triage: Record<string, TriageResult>;\n problems: StudioProblem[];\n}\n\n// A locale nobody can parse must not take the panel down: Studio is what you open to fix it.\nfunction readAll(cfg: ResolvedConfig): { catalogs: Catalogs; problems: StudioProblem[] } {\n const catalogs: Catalogs = {};\n const problems: StudioProblem[] = [];\n // an empty green panel and a wrong --root look identical, and only this line tells them apart\n if (!existsSync(cfg.dir)) {\n const dir = relative(cfg.root, cfg.dir).replaceAll('\\\\', '/');\n problems.push({ scope: dir, message: '[verbaly] no catalogs here, run npx verbaly init or check --root' });\n }\n for (const locale of cfg.locales) {\n try {\n catalogs[locale] = readCatalog(cfg, locale);\n } catch (error) {\n catalogs[locale] = {};\n problems.push({\n scope: locale,\n message: error instanceof Error ? error.message : String(error),\n });\n }\n }\n return { catalogs, problems };\n}\n\n// A sidecar nobody can parse is the other file Studio exists to fix, so it degrades like a catalog.\nfunction readDrafts(cfg: ResolvedConfig): { drafts: Drafts; problems: StudioProblem[] } {\n try {\n return { drafts: loadDrafts(cfg), problems: [] };\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return { drafts: {}, problems: [{ scope: DRAFTS_FILE, message }] };\n }\n}\n\n// check() reports the file a stray key was used in as an absolute path, origins already do not.\nfunction relativize(cfg: ResolvedConfig, result: CheckResult): CheckResult {\n return {\n ...result,\n unknown: result.unknown.map((entry) => ({\n ...entry,\n files: entry.files.map((file) => relative(cfg.root, file).replaceAll('\\\\', '/')),\n })),\n };\n}\n\n// One shot and unpaginated: the biggest catalog we know is 1587 messages, instant over localhost.\nexport async function buildState(cfg: ResolvedConfig): Promise<StudioState> {\n const { catalogs, problems } = readAll(cfg);\n const registry = await extractProject(cfg);\n const sidecar = readDrafts(cfg);\n problems.push(...sidecar.problems);\n const live = effectiveDrafts(sidecar.drafts, catalogs);\n const source = (catalogs[cfg.sourceLocale] ?? {}) as Catalog;\n const targets = targetLocales(cfg);\n\n for (const { file, message } of registry.parseErrors()) {\n problems.push({ scope: relative(cfg.root, file).replaceAll('\\\\', '/'), message });\n }\n\n const perLocale: Record<string, TriageResult> = {};\n for (const locale of targets) {\n perLocale[locale] = triage({\n source,\n target: (catalogs[locale] ?? {}) as Catalog,\n // a locale still carrying drafts has not been reviewed, so it cannot be the control\n reviewed: targets\n .filter((other) => other !== locale && !(live[other] ?? []).length)\n .map((other) => (catalogs[other] ?? {}) as Catalog),\n });\n }\n\n return {\n root: cfg.root,\n dir: cfg.dir,\n sourceLocale: cfg.sourceLocale,\n locales: cfg.locales,\n // with include: [] nothing is scanned, so an empty origins map is off, not \"none found\"\n scanning: cfg.include.length > 0,\n catalogs,\n origins: await collectOrigins(cfg, registry),\n status: status(cfg, catalogs, registry, live),\n check: relativize(cfg, check(cfg, catalogs, registry)),\n drafts: live,\n triage: perLocale,\n problems,\n };\n}\n\nexport function health(cfg: ResolvedConfig): Promise<DoctorResult> {\n return doctor(cfg);\n}\n\nexport interface WriteMessageResult {\n locale: string;\n key: string;\n clearedDraft: boolean;\n}\n\n// A human wrote it, so it is reviewed: the rule import already applies in run.ts:447.\nexport function writeMessage(\n cfg: ResolvedConfig,\n locale: string,\n key: string,\n text: string,\n): WriteMessageResult {\n if (locale === cfg.sourceLocale) {\n throw badRequest('the source text lives in your code, Studio never edits it');\n }\n if (!cfg.locales.includes(locale)) {\n throw badRequest(`${locale} is not one of this project's locales`);\n }\n const source = readCatalog(cfg, cfg.sourceLocale);\n // hasOwn, not `in`: `in` walks the prototype, so toString and __proto__ would pass this guard\n if (!Object.hasOwn(source, key)) {\n throw badRequest(`\"${key}\" is not in the source catalog, so Studio will not create it`);\n }\n // '' is untranslated everywhere else, so whitespace is that and not a translation to validate\n const value = text.trim() ? text : '';\n // the same two calls check() makes: validatePair alone misses a plural block with no other case\n const issues = value\n ? [...validateMessage(value, locale), ...validatePair(source[key] as string, value)]\n : [];\n const errors = issues.filter((issue) => issue.severity === 'error');\n if (errors.length) {\n throw badRequest(`${errors.map((issue) => issue.message).join('; ')}`);\n }\n\n const catalog = { ...readCatalog(cfg, locale), [key]: value };\n writeCatalog(cfg, locale, catalog);\n\n const drafts = loadDrafts(cfg);\n const clearedDraft = (drafts[locale] ?? []).includes(key);\n clearDrafts(drafts, locale, [key]);\n saveDrafts(cfg, drafts);\n return { locale, key, clearedDraft };\n}\n\nexport interface ApproveResult {\n locale: string;\n approved: number;\n keys: string[];\n}\n\n// The sidecar is re-read here: saveDrafts writes every locale, so a stale copy drops another's.\nexport function approve(cfg: ResolvedConfig, locale: string, keys?: string[]): ApproveResult {\n if (!cfg.locales.includes(locale)) {\n throw badRequest(`${locale} is not one of this project's locales`);\n }\n const drafts = loadDrafts(cfg);\n const had = drafts[locale] ?? [];\n // the keys it really cleared, because undo has to put back exactly those and nothing else\n const cleared = keys ? had.filter((key) => keys.includes(key)) : [...had];\n clearDrafts(drafts, locale, keys);\n saveDrafts(cfg, drafts);\n return { locale, approved: cleared.length, keys: cleared };\n}\n\n// Undo is not a nicety here: the flag records who wrote the text, so restoring it restores a fact.\nexport function unapprove(cfg: ResolvedConfig, locale: string, keys: string[]): ApproveResult {\n if (!cfg.locales.includes(locale)) {\n throw badRequest(`${locale} is not one of this project's locales`);\n }\n const drafts = loadDrafts(cfg);\n markDrafts(drafts, locale, keys);\n saveDrafts(cfg, drafts);\n return { locale, approved: keys.length, keys };\n}\n","import { badRequest, HttpError } from './http';\n\nexport type JobState = 'running' | 'done' | 'failed';\n\nexport interface Job {\n id: string;\n kind: 'translate';\n state: JobState;\n total: number;\n done: number;\n locale?: string;\n message?: string;\n result?: unknown;\n}\n\n// One at a time, because two runs over the same catalogs is a race over the same files.\nlet current: Job | null = null;\nlet counter = 0;\n\nexport function running(): Job | null {\n return current?.state === 'running' ? current : null;\n}\n\nexport function read(id: string): Job {\n if (!current || current.id !== id) throw new HttpError(404, `no job called \"${id}\"`);\n return current;\n}\n\nexport function start(kind: Job['kind'], total: number): Job {\n if (running()) throw badRequest('a job is already running, wait for it or read its progress');\n counter += 1;\n current = { id: `${kind}-${counter}`, kind, state: 'running', total, done: 0 };\n return current;\n}\n\n// A batch that failed still finishes: the bar reports work attempted, and message says what broke.\nexport function advance(id: string, keys: number, locale: string, error?: string): void {\n if (current?.id !== id) return;\n current.done = Math.min(current.done + keys, current.total);\n current.locale = locale;\n if (error) current.message = error;\n}\n\n// A run that writes part of its work and then fails is a success for what it wrote, so both land.\nexport function finish(id: string, state: 'done' | 'failed', patch: Partial<Job> = {}): void {\n if (current?.id === id) Object.assign(current, patch, { state });\n}\n","import {\n collectOrigins,\n extractProject,\n loadCatalogs,\n resolveProvider,\n syncCatalogs,\n translateCatalogs,\n writeCatalog,\n writeDts,\n markDrafts,\n loadDrafts,\n saveDrafts,\n} from '@verbaly/compiler';\nimport type { ResolvedConfig } from '@verbaly/compiler';\nimport { badRequest } from './http';\nimport { advance, finish, start, type Job } from './jobs';\n\nexport interface ExtractResult {\n added: Record<string, string[]>;\n // only the source locale counts as new text: a target gains a blank for every gap it had\n found: number;\n messages: number;\n}\n\n// Local, fast and free, so it answers in the same request instead of becoming a job.\nexport async function runExtract(cfg: ResolvedConfig): Promise<ExtractResult> {\n if (!cfg.include.length) {\n throw badRequest('source scanning is off (include: []), so there is nothing to extract');\n }\n const registry = await extractProject(cfg);\n const catalogs = loadCatalogs(cfg);\n const result = syncCatalogs(cfg, catalogs, registry);\n for (const locale of cfg.locales) writeCatalog(cfg, locale, catalogs[locale] ?? {});\n // the types come from the source catalog, and cfg.dts false means the project opted out\n if (cfg.dts !== false) writeDts(cfg, catalogs[cfg.sourceLocale] ?? {}, cfg.dts);\n const found = result.added[cfg.sourceLocale]?.length ?? 0;\n return { added: result.added, found, messages: registry.messages().size };\n}\n\nexport interface Plan {\n pending: Record<string, string[]>;\n total: number;\n}\n\n// translate is the one command that spends money and minutes, so the panel always shows the bill.\nexport async function planTranslate(cfg: ResolvedConfig, locales?: string[]): Promise<Plan> {\n const provider = await resolveProvider(cfg);\n const catalogs = loadCatalogs(cfg);\n const result = await translateCatalogs(cfg, catalogs, provider, { locales, dryRun: true });\n const total = Object.values(result.pending).reduce((n, keys) => n + keys.length, 0);\n return { pending: result.pending, total };\n}\n\nexport async function startTranslate(cfg: ResolvedConfig, locales?: string[]): Promise<Job> {\n const provider = await resolveProvider(cfg);\n // the job carries its own denominator, so progress reads the same whether or not you asked first\n const plan = await planTranslate(cfg, locales);\n const job = start('translate', plan.total);\n const id = job.id;\n\n // the run outlives this request on purpose: the client asks the job how it is going\n void (async () => {\n try {\n const catalogs = loadCatalogs(cfg);\n const result = await translateCatalogs(cfg, catalogs, provider, {\n locales,\n batchSize: cfg.translate.batchSize,\n concurrency: cfg.translate.concurrency,\n retries: cfg.translate.retries,\n origins: await collectOrigins(cfg),\n onProgress: (p) => advance(id, p.keys, p.locale, p.error),\n });\n for (const [locale, keys] of Object.entries(result.translated)) {\n if (keys.length) writeCatalog(cfg, locale, catalogs[locale] ?? {});\n }\n // what a machine wrote stays a draft: the panel does not get to change that rule\n const drafts = loadDrafts(cfg);\n for (const [locale, keys] of Object.entries(result.translated)) {\n markDrafts(drafts, locale, keys);\n }\n saveDrafts(cfg, drafts);\n finish(id, result.failed.length ? 'failed' : 'done', {\n result,\n message: result.failed.length ? `${result.failed.length} batches did not answer` : undefined,\n });\n } catch (error) {\n finish(id, 'failed', { message: error instanceof Error ? error.message : String(error) });\n }\n })();\n\n return job;\n}\n","import { existsSync, readFileSync, statSync } from 'node:fs';\nimport { join, normalize, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\n// the built panel sits beside the compiled server, so it is found relative to this file, never cwd\nconst UI = resolve(fileURLToPath(new URL('.', import.meta.url)), 'ui');\n\nconst TYPES: Record<string, string> = {\n '.html': 'text/html; charset=utf-8',\n '.js': 'text/javascript; charset=utf-8',\n '.css': 'text/css; charset=utf-8',\n '.svg': 'image/svg+xml',\n '.woff2': 'font/woff2',\n '.json': 'application/json; charset=utf-8',\n '.ico': 'image/x-icon',\n};\n\nexport interface Asset {\n body: Buffer;\n type: string;\n}\n\nexport const uiBuilt = (): boolean => existsSync(join(UI, 'index.html'));\n\n// A path is resolved and then checked to still be inside UI: ../ and an absolute one both land out.\nexport function readAsset(pathname: string): Asset | undefined {\n const wanted = pathname === '/' ? '/index.html' : pathname;\n const target = resolve(join(UI, normalize(wanted)));\n if (target !== UI && !target.startsWith(UI + (process.platform === 'win32' ? '\\\\' : '/'))) {\n return undefined;\n }\n if (!existsSync(target) || !statSync(target).isFile()) return undefined;\n const dot = target.lastIndexOf('.');\n return {\n body: readFileSync(target),\n type: TYPES[target.slice(dot).toLowerCase()] ?? 'application/octet-stream',\n };\n}\n","import { randomBytes, timingSafeEqual } from 'node:crypto';\n\nexport interface GuardOptions {\n port: number;\n token: string;\n}\n\n// A local server that writes files is reachable from any page the browser has open.\nexport function newToken(): string {\n return randomBytes(9).toString('base64url');\n}\n\n// Only the two names that resolve to this machine. A DNS rebind arrives with a foreign Host.\nexport function hostAllowed(host: string | undefined, port: number): boolean {\n // a browser omits the port when it is the scheme default, so :80 would 403 every real request\n const names = port === 80 ? ['127.0.0.1', 'localhost'] : [];\n return [`127.0.0.1:${port}`, `localhost:${port}`, ...names].includes(host ?? '');\n}\n\n// No Origin at all is a same-origin navigation or a curl, both fine; a foreign one never is.\nexport function originAllowed(origin: string | undefined, port: number): boolean {\n if (origin === undefined) return true;\n return origin === `http://127.0.0.1:${port}` || origin === `http://localhost:${port}`;\n}\n\n// Compared in constant time so the token cannot be guessed one character at a time.\nexport function tokenMatches(given: string | null, expected: string): boolean {\n if (given === null) return false;\n const a = Buffer.from(given);\n const b = Buffer.from(expected);\n return a.length === b.length && timingSafeEqual(a, b);\n}\n\nexport interface GuardVerdict {\n ok: boolean;\n status: number;\n reason: string;\n}\n\nconst PASS: GuardVerdict = { ok: true, status: 200, reason: '' };\n\nexport function guardRequest(\n headers: { host?: string; origin?: string },\n url: URL,\n { port, token }: GuardOptions,\n): GuardVerdict {\n if (!hostAllowed(headers.host, port)) {\n return { ok: false, status: 403, reason: '[verbaly] unexpected Host header' };\n }\n if (!originAllowed(headers.origin, port)) {\n return { ok: false, status: 403, reason: '[verbaly] cross-origin request' };\n }\n if (!tokenMatches(url.searchParams.get('t'), token)) {\n return { ok: false, status: 401, reason: '[verbaly] missing or wrong token, open the printed url' };\n }\n return PASS;\n}\n","import { createServer, type IncomingMessage, type Server, type ServerResponse } from 'node:http';\nimport type { AddressInfo } from 'node:net';\nimport type { ResolvedConfig } from '@verbaly/compiler';\nimport { approve, buildState, health, unapprove, writeMessage } from './api';\nimport { planTranslate, runExtract, startTranslate } from './actions';\nimport { read as readJob, running } from './jobs';\nimport { readAsset, uiBuilt } from './assets';\nimport { guardRequest, newToken } from './guard';\nimport { HttpError, badRequest, scrub } from './http';\n\nexport interface StudioServer {\n server: Server;\n port: number;\n token: string;\n url: string;\n close: () => Promise<void>;\n}\n\nexport interface StartOptions {\n port?: number;\n token?: string;\n}\n\nconst DEFAULT_PORT = 4747;\nconst MAX_BODY = 1_000_000;\n\nfunction send(res: ServerResponse, status: number, body: unknown): void {\n res.writeHead(status, {\n 'content-type': 'application/json; charset=utf-8',\n 'cache-control': 'no-store',\n });\n res.end(JSON.stringify(body));\n}\n\nasync function readJson(req: IncomingMessage): Promise<Record<string, unknown>> {\n const chunks: Buffer[] = [];\n let size = 0;\n for await (const chunk of req) {\n size += (chunk as Buffer).length;\n if (size > MAX_BODY) throw new HttpError(413, 'request body too large');\n chunks.push(chunk as Buffer);\n }\n if (!chunks.length) return {};\n let parsed: unknown;\n try {\n parsed = JSON.parse(Buffer.concat(chunks).toString('utf8'));\n } catch {\n throw badRequest('body is not valid JSON');\n }\n // null and an array both pass typeof 'object', and reading .text off them is a 500\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw badRequest('body must be a JSON object');\n }\n return parsed as Record<string, unknown>;\n}\n\nfunction segment(raw: string | undefined, what: string): string {\n let value: string;\n try {\n value = decodeURIComponent(raw ?? '');\n } catch {\n throw badRequest(`the ${what} in the url is not valid percent-encoding`);\n }\n if (!value) throw badRequest(`the url is missing a ${what}`);\n return value;\n}\n\n// One page, JSON on every route: a framework would only add a dependency to a local tool.\nexport function createStudioApp(cfg: ResolvedConfig, port: number, token: string) {\n return async function handle(req: IncomingMessage, res: ServerResponse): Promise<void> {\n // The url is built inside the try: `GET //` throws, and out here that would kill the process.\n try {\n let url: URL;\n try {\n url = new URL(req.url ?? '/', `http://127.0.0.1:${port}`);\n } catch {\n throw badRequest('the request target is not a valid url');\n }\n\n const api = url.pathname.startsWith('/api/');\n const verdict = guardRequest({ host: req.headers.host, origin: req.headers.origin }, url, {\n port,\n token,\n });\n // the panel files are the published npm package: only the project data needs a token\n if (!verdict.ok && (api || verdict.status !== 401)) {\n send(res, verdict.status, { error: verdict.reason });\n return;\n }\n\n if (!api && req.method === 'GET') {\n const asset = readAsset(url.pathname);\n if (asset) {\n res.writeHead(200, { 'content-type': asset.type, 'cache-control': 'no-store' });\n res.end(asset.body);\n return;\n }\n if (url.pathname === '/' && !uiBuilt()) {\n throw new HttpError(500, 'the panel is not in this install, so only /api/* answers here');\n }\n }\n\n if (req.method === 'GET' && url.pathname === '/api/state') {\n send(res, 200, await buildState(cfg));\n return;\n }\n if (req.method === 'GET' && url.pathname === '/api/health') {\n send(res, 200, await health(cfg));\n return;\n }\n if (req.method === 'PUT' && url.pathname.startsWith('/api/message/')) {\n const rest = url.pathname.slice('/api/message/'.length).split('/');\n const locale = segment(rest[0], 'locale');\n const key = segment(rest.slice(1).join('/'), 'key');\n const body = await readJson(req);\n if (typeof body.text !== 'string') throw badRequest('body needs a \"text\" string');\n send(res, 200, writeMessage(cfg, locale, key, body.text));\n return;\n }\n if (req.method === 'POST' && url.pathname === '/api/approve') {\n const body = await readJson(req);\n if (typeof body.locale !== 'string') throw badRequest('body needs a \"locale\" string');\n const keys = Array.isArray(body.keys)\n ? body.keys.filter((k): k is string => typeof k === 'string')\n : undefined;\n if (body.undo === true) {\n if (!keys?.length) throw badRequest('undoing needs the \"keys\" it should put back');\n send(res, 200, unapprove(cfg, body.locale, keys));\n return;\n }\n send(res, 200, approve(cfg, body.locale, keys));\n return;\n }\n if (req.method === 'POST' && url.pathname === '/api/extract') {\n send(res, 200, await runExtract(cfg));\n return;\n }\n if (req.method === 'GET' && url.pathname === '/api/translate') {\n const locales = url.searchParams.getAll('locale');\n send(res, 200, await planTranslate(cfg, locales.length ? locales : undefined));\n return;\n }\n if (req.method === 'POST' && url.pathname === '/api/translate') {\n const body = await readJson(req);\n const locales = Array.isArray(body.locales)\n ? body.locales.filter((l): l is string => typeof l === 'string')\n : undefined;\n send(res, 200, await startTranslate(cfg, locales?.length ? locales : undefined));\n return;\n }\n // a reload loses the id, so the panel asks what is running rather than losing the bar\n if (req.method === 'GET' && url.pathname === '/api/job') {\n send(res, 200, running());\n return;\n }\n if (req.method === 'GET' && url.pathname.startsWith('/api/job/')) {\n send(res, 200, readJob(segment(url.pathname.slice('/api/job/'.length), 'job id')));\n return;\n }\n\n send(res, 404, { error: `[verbaly] no route for ${req.method} ${url.pathname}` });\n } catch (error) {\n const status = error instanceof HttpError ? error.status : 500;\n const message = error instanceof Error ? error.message : String(error);\n send(res, status, { error: scrub(message, cfg.root) });\n }\n };\n}\n\n// A busy port climbs instead of failing: this is a local tool, not a service on a fixed address.\nfunction listen(server: Server, port: number, attemptsLeft: number): Promise<number> {\n return new Promise((resolve, reject) => {\n const onError = (error: NodeJS.ErrnoException) => {\n if (error.code === 'EADDRINUSE' && attemptsLeft > 0) {\n server.removeListener('error', onError);\n resolve(listen(server, port + 1, attemptsLeft - 1));\n return;\n }\n reject(error);\n };\n server.once('error', onError);\n server.listen(port, '127.0.0.1', () => {\n server.removeListener('error', onError);\n // port 0 asks the system to pick, so the bound address is the only place the answer is\n const bound = server.address() as AddressInfo | string | null;\n resolve(typeof bound === 'object' && bound !== null ? bound.port : port);\n });\n });\n}\n\nexport async function startStudio(\n cfg: ResolvedConfig,\n options: StartOptions = {},\n): Promise<StudioServer> {\n const token = options.token ?? newToken();\n const server = createServer();\n // The handler is attached before listen so no request can arrive without one.\n let port = options.port ?? DEFAULT_PORT;\n server.on('request', (req, res) => void createStudioApp(cfg, port, token)(req, res));\n port = await listen(server, port, 20);\n\n return {\n server,\n port,\n token,\n url: `http://127.0.0.1:${port}/?t=${token}`,\n close: () =>\n new Promise<void>((resolve, reject) =>\n server.close((error) => (error ? reject(error) : resolve())),\n ),\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ (function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),t.credentials=e.crossOrigin===`use-credentials`?`include`:e.crossOrigin===`anonymous`?`omit`:`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var e=Array.isArray,t=Array.prototype.indexOf,n=Array.prototype.includes,r=Array.from,i=Object.defineProperty,a=Object.getOwnPropertyDescriptor,o=Object.getOwnPropertyDescriptors,s=Object.prototype,c=Array.prototype,l=Object.getPrototypeOf,u=Object.isExtensible,d=()=>{};function f(e){for(var t=0;t<e.length;t++)e[t]()}function p(){var e,t;return{promise:new Promise((n,r)=>{e=n,t=r}),resolve:e,reject:t}}var m=1024,h=2048,g=4096,_=8192,v=16384,y=32768,ee=1<<25,b=65536,x=1<<19,te=1<<20,ne=1<<25,S=65536,re=1<<21,ie=1<<22,ae=1<<23,oe=Symbol(`$state`),se=Symbol(`component`),ce=Symbol(``),le=Symbol(`attributes`),ue=Symbol(`class`),de=Symbol(`style`),fe=Symbol(`text`),pe=Symbol(`form reset`),me=new class extends Error{name=`StaleReactionError`;message="The reaction that called `getAbortSignal()` was re-run or destroyed"},he=!!globalThis.document?.contentType&&globalThis.document.contentType.includes(`xml`),ge={},C=Symbol(`uninitialized`),_e=`http://www.w3.org/1999/xhtml`;function ve(){console.warn(`https://svelte.dev/e/derived_inert`)}function ye(e){console.warn(`https://svelte.dev/e/hydration_mismatch`)}function be(){console.warn(`https://svelte.dev/e/svelte_boundary_reset_noop`)}var w=!1;function xe(e){w=e}var T;function E(e){if(e===null)throw ye(),ge;return T=e}function Se(){return E(Zt(T))}function D(e){if(w){if(Zt(T)!==null)throw ye(),ge;T=e}}function Ce(e=1){if(w){for(var t=e,n=T;t--;)n=Zt(n);T=n}}function we(e=!0){for(var t=0,n=T;;){if(n.nodeType===8){var r=n.data;if(r===`]`){if(t===0)return n;--t}else(r===`[`||r===`[!`||r[0]===`[`&&!isNaN(Number(r.slice(1))))&&(t+=1)}var i=Zt(n);e&&n.remove(),n=i}}function Te(e){if(!e||e.nodeType!==8)throw ye(),ge;return e.data}function Ee(e){return e===this.v}function De(e,t){return e==e?e!==t||typeof e==`object`&&!!e||typeof e==`function`:t==t}function Oe(e){return!De(e,this.v)}function ke(e){throw Error(`https://svelte.dev/e/lifecycle_outside_component`)}function Ae(){throw Error(`https://svelte.dev/e/async_derived_orphan`)}function je(e,t,n){throw Error(`https://svelte.dev/e/each_key_duplicate`)}function Me(e){throw Error(`https://svelte.dev/e/effect_in_teardown`)}function Ne(){throw Error(`https://svelte.dev/e/effect_in_unowned_derived`)}function Pe(e){throw Error(`https://svelte.dev/e/effect_orphan`)}function Fe(){throw Error(`https://svelte.dev/e/effect_update_depth_exceeded`)}function Ie(){throw Error(`https://svelte.dev/e/state_descriptors_fixed`)}function Le(){throw Error(`https://svelte.dev/e/state_prototype_fixed`)}function Re(){throw Error(`https://svelte.dev/e/state_unsafe_mutation`)}function ze(){throw Error(`https://svelte.dev/e/svelte_boundary_reset_onerror`)}var O=null;function Be(e){O=e}function Ve(e,t=!1,n){O={p:O,i:!1,c:null,e:null,s:e,x:null,r:W,l:null}}function He(e){var t=O,n=t.e;if(n!==null){t.e=null;for(var r of n)fn(r)}return e!==void 0&&(t.x=e),t.i=!0,O=t.p,Ue(e)}function Ue(e={}){return i(e,se,{value:!0}),e}function We(){return!0}var Ge=[];function Ke(){var e=Ge;Ge=[],f(e)}function qe(e){if(Ge.length===0&&!St){var t=Ge;queueMicrotask(()=>{t===Ge&&Ke()})}Ge.push(e)}function Je(){for(;Ge.length>0;)Ke()}var Ye=~(h|g|m);function k(e,t){e.f=e.f&Ye|t}function Xe(e){e.f&512||e.deps===null?k(e,m):k(e,g)}function Ze(e){if(e!==null)for(let t of e)t.f&2&&t.f&65536&&(t.f^=S,Ze(t.deps))}function Qe(e,t,n){e.f&2048?t.add(e):e.f&4096&&n.add(e),Ze(e.deps),k(e,m)}function $e(e,t){if(t){let t=document.body;e.autofocus=!0,qe(()=>{document.activeElement===t&&e.focus()})}}function et(e){w&&F(e)!==null&&$t(e)}var tt=!1;function nt(){tt||(tt=!0,document.addEventListener(`reset`,e=>{Promise.resolve().then(()=>{if(!e.defaultPrevented)for(let t of e.target.elements)t[pe]?.()})},{capture:!0}))}function rt(e){var t=H,n=W;U(null),Mn(null);try{return e()}finally{U(t),Mn(n)}}function it(e,t,n,r=n){e.addEventListener(t,()=>rt(n));let i=e[pe];e[pe]=i?()=>{i(),r(!0)}:()=>r(!0),nt()}function at(e,t,n,r){let i=We()?lt:ft;var a=e.filter(e=>!e.settled),o=t.map(i);if(n.length===0&&a.length===0){r(o);return}var s=W,c=ot(),l=a.length===1?a[0].promise:a.length>1?Promise.all(a.map(e=>e.promise)):null;function u(e){if(!(s.f&16384)){c();try{r([...o,...e])}catch(e){an(e,s)}st()}}var d=ct();if(n.length===0){l.then(()=>u([])).finally(d);return}function f(){Promise.all(n.map(e=>dt(e))).then(u).catch(e=>an(e,s)).finally(d)}l?l.then(()=>{c(),f(),st()}):f()}function ot(){var e=W,t=H,n=O,r=j;return function(i=!0){Mn(e),U(t),Be(n),i&&!(e.f&16384)&&(r?.activate(),r?.apply())}}function st(e=!0){Mn(null),U(null),Be(null),e&&j?.deactivate()}function ct(){var e=W,t=e.b,n=j,r=!!t?.is_rendered();return t?.update_pending_count(1,n),n.increment(r,e),()=>{t?.update_pending_count(-1,n),n.decrement(r,e)}}function lt(e){var t=2|h;return W!==null&&(W.f|=x),{ctx:O,deps:null,effects:null,equals:Ee,f:t,fn:e,reactions:null,rv:0,v:C,wv:0,parent:W,ac:null}}var ut=Symbol(`obsolete`);function dt(e,t,n){let r=W;r===null&&Ae();var i=void 0,a=zt(C),o=!H,s=new Set;return mn(()=>{var t=W,n=p();i=n.promise;try{Promise.resolve(e()).then(n.resolve,e=>{e!==me&&n.reject(e)}).finally(st)}catch(e){n.reject(e),st()}var c=j;if(o){if(t.f&32768)var l=ct();if(r.b?.is_rendered())c.async_deriveds.get(t)?.reject(ut);else for(let e of s.values())e.reject(ut);s.add(n),c.async_deriveds.set(t,n)}let u=(e,t=void 0)=>{l?.(),s.delete(n),t!==ut&&(c.activate(),t?(a.f|=ae,Vt(a,t)):(a.f&8388608&&(a.f^=ae),Vt(a,e)),c.deactivate())};n.promise.then(u,e=>u(null,e||`unknown`))}),un(()=>{for(let e of s)e.reject(ut)}),new Promise(e=>{function t(n){function r(){n===i?e(a):t(i)}n.then(r,r)}t(i)})}function A(e){let t=lt(e);return Pn(t),t}function ft(e){let t=lt(e);return t.equals=Oe,t}function pt(e){var t=e.effects;if(t!==null){e.effects=null;for(var n=0;n<t.length;n+=1)V(t[n])}}function mt(e){var t,n=W,r=e.parent;if(!kn&&r!==null&&e.v!==C&&r.f&24576)return ve(),e.v;Mn(r);try{e.f&=~S,pt(e),t=Un(e)}finally{Mn(n)}return t}function ht(e){var t=mt(e);if(!e.equals(t)&&(e.wv=Bn(),(!j?.is_fork||e.deps===null)&&(j===null?e.v=t:(j.capture(e,t,!0),yt?.capture(e,t,!0)),e.deps===null))){k(e,m);return}kn||(bt===null?Xe(e):(ln()||j?.is_fork)&&bt.set(e,t))}function gt(e){if(e.effects!==null)for(let t of e.effects)(t.teardown||t.ac)&&(t.teardown?.(),t.ac!==null&&rt(()=>{t.ac.abort(me),t.ac=null}),t.fn!==null&&(t.teardown=d),Kn(t,0),vn(t))}function _t(e){if(e.effects!==null)for(let t of e.effects)t.teardown&&t.fn!==null&&qn(t)}var vt=null,j=null,yt=null,bt=null,xt=null,St=!1,Ct=!1,wt=null,Tt=null,Et=0,Dt=1,Ot=class e{id=Dt++;#e=!1;linked=!0;#t=null;#n=null;async_deriveds=new Map;current=new Map;previous=new Map;#r=new Set;#i=new Set;#a=0;#o=new Map;#s=null;#c=[];#l=[];#u=new Set;#d=new Set;#f=new Map;#p=new Set;is_fork=!1;#m=!1;constructor(){vt===null?vt=this:(vt.#n=this,this.#t=vt),vt=this}#h(){if(this.is_fork)return!0;for(let n of this.#o.keys()){for(var e=n,t=!1;e.parent!==null;){if(this.#f.has(e)){t=!0;break}e=e.parent}if(!t)return!0}return!1}skip_effect(e){this.#f.has(e)||this.#f.set(e,{d:[],m:[]}),this.#p.delete(e)}unskip_effect(e,t=e=>this.schedule(e)){var n=this.#f.get(e);if(n){this.#f.delete(e);for(var r of n.d)k(r,h),t(r);for(r of n.m)k(r,g),t(r)}this.#p.add(e)}#g(){this.#e=!0,Et++>1e3&&(this.#x(),At());for(let e of this.#u)this.#d.delete(e),k(e,h),this.schedule(e);for(let e of this.#d)k(e,g),this.schedule(e);let t=this.#c;this.#c=[],this.apply();var n=wt=[],r=[],i=Tt=[];for(let e of t)try{this.#_(e,n,r)}catch(t){throw Ft(e),this.#h()||this.discard(),t}if(j=null,i.length>0){var a=e.ensure();for(let e of i)a.schedule(e)}if(wt=null,Tt=null,this.#h()){this.#b(r),this.#b(n);for(let[e,t]of this.#f)Pt(e,t);i.length>0&&j.#g();return}let o=this.#v();if(o){this.#b(r),this.#b(n),o.#y(this);return}this.#u.clear(),this.#d.clear();for(let e of this.#r)e(this);this.#r.clear(),yt=this,Mt(r),Mt(n),yt=null,this.#s?.resolve();var s=j;if(this.#a===0&&(this.#c.length===0||s!==null)&&this.#x(),this.#c.length>0){if(s!==null){let e=s;e.#c.push(...this.#c.filter(t=>!e.#c.includes(t)))}else s=this}s!==null&&(Lt.clear(),s.#g())}#_(e,t,n){e.f^=m;for(var r=e.first;r!==null;){var i=r.f,a=!!(i&96);if(!(a&&i&1024||i&8192||this.#f.has(r))&&r.fn!==null){a?r.f^=m:i&4?t.push(r):Vn(r)&&(i&16&&this.#d.add(r),qn(r));var o=r.first;if(o!==null){r=o;continue}}for(;r!==null;){var s=r.next;if(s!==null){r=s;break}r=r.parent}}}#v(){for(var e=this.#t;e!==null;){if(!e.is_fork){for(let[t,[,n]]of this.current)if(e.current.has(t)&&!n)return e}e=e.#t}return null}#y(e){for(let[t,n]of e.current)!this.previous.has(t)&&e.previous.has(t)&&this.previous.set(t,e.previous.get(t)),this.current.set(t,n);for(let[t,n]of e.async_deriveds){let e=this.async_deriveds.get(t);e&&n.promise.then(e.resolve).catch(e.reject)}e.async_deriveds.clear(),this.transfer_effects(e.#u,e.#d);let t=e=>{var n=e.reactions;if(n!==null&&!(e.f&2&&!(e.f&6144)))for(let e of n){var r=e.f;if(r&2)t(e);else{var i=e;r&4194320&&!this.async_deriveds.has(i)&&(this.#d.delete(i),k(i,h),this.schedule(i))}}};for(let e of this.current.keys())t(e);this.oncommit(()=>e.discard()),e.#x(),j=this,this.#g()}#b(e){for(var t=0;t<e.length;t+=1)Qe(e[t],this.#u,this.#d)}capture(e,t,n=!1){e.v!==C&&!this.previous.has(e)&&this.previous.set(e,e.v),e.f&8388608||(this.current.set(e,[t,n]),bt?.set(e,t)),this.is_fork||(e.v=t)}activate(){j=this}deactivate(){j=null,bt=null}flush(){try{Ct=!0,j=this,this.#g()}finally{Et=0,xt=null,wt=null,Tt=null,Ct=!1,j=null,bt=null,Lt.clear()}}discard(){for(let e of this.#i)e(this);this.#i.clear();for(let e of this.async_deriveds.values())e.reject(ut);this.#x(),this.#s?.resolve()}register_created_effect(e){this.#l.push(e)}increment(e,t){if(this.#a+=1,e){let e=this.#o.get(t)??0;this.#o.set(t,e+1)}}decrement(e,t){if(--this.#a,e){let e=this.#o.get(t)??0;e===1?this.#o.delete(t):this.#o.set(t,e-1)}this.#m||(this.#m=!0,qe(()=>{this.#m=!1,this.linked&&this.flush()}))}transfer_effects(e,t){for(let t of e)this.#u.add(t);for(let e of t)this.#d.add(e);e.clear(),t.clear()}oncommit(e){this.#r.add(e)}ondiscard(e){this.#i.add(e)}settled(){return(this.#s??=p()).promise}static ensure(){if(j===null){let t=j=new e;!Ct&&!St&&qe(()=>{t.#e||t.flush()})}return j}apply(){bt=null}schedule(e){if(xt=e,e.b?.is_pending&&e.f&16777228&&!(e.f&32768)){e.b.defer_effect(e);return}for(var t=e;t.parent!==null;){t=t.parent;var n=t.f;if(wt!==null&&t===W&&(H===null||!(H.f&2)))return;if(n&96){if(!(n&1024))return;t.f^=m}}this.#c.push(t)}#x(){if(this.linked){var e=this.#t,t=this.#n;e===null||(e.#n=t),t===null?vt=e:t.#t=e,this.linked=!1}}};function kt(e){var t=St;St=!0;try{var n;for(e&&(j!==null&&!j.is_fork&&j.flush(),n=e());;){if(Je(),j===null)return n;j.flush()}}finally{St=t}}function At(){try{Fe()}catch(e){an(e,xt)}}var jt=null;function Mt(e){var t=e.length;if(t!==0){for(var n=0;n<t;){var r=e[n++];if(!(r.f&24576)&&Vn(r)&&(jt=new Set,qn(r),r.deps===null&&r.first===null&&r.nodes===null&&r.teardown===null&&r.ac===null&&xn(r),jt?.size>0)){Lt.clear();for(let e of jt){if(e.f&24576)continue;let t=[e],n=e.parent;for(;n!==null;)jt.has(n)&&(jt.delete(n),t.push(n)),n=n.parent;for(let e=t.length-1;e>=0;e--){let n=t[e];n.f&24576||qn(n)}}jt.clear()}}jt=null}}function Nt(e){j.schedule(e)}function Pt(e,t){if(!(e.f&32&&e.f&1024)){e.f&2048?t.d.push(e):e.f&4096&&t.m.push(e),k(e,m);for(var n=e.first;n!==null;)Pt(n,t),n=n.next}}function Ft(e){k(e,m);for(var t=e.first;t!==null;)Ft(t),t=t.next}var It=new Set,Lt=new Map,Rt=!1;function zt(e,t){return{f:0,v:e,reactions:null,equals:Ee,rv:0,wv:0}}function M(e,t){let n=zt(e,t);return Pn(n),n}function Bt(e,t=!1,n=!0){let r=zt(e);return t||(r.equals=Oe),r}function N(e,t,n=!1){return H!==null&&(!jn||H.f&131072)&&We()&&H.f&4325394&&(Nn===null||!Nn.has(e))&&Re(),Vt(e,n?Gt(t):t,Tt)}function Vt(e,t,n=null){if(!e.equals(t)){kn?Lt.set(e,t):Lt.has(e)||Lt.set(e,e.v);var r=Ot.ensure();if(r.capture(e,t),e.f&2){let t=e;e.f&2048&&mt(t),bt===null&&Xe(t)}e.wv=Bn(),Wt(e,h,n),We()&&W!==null&&W.f&1024&&!(W.f&96)&&(q===null?Fn([e]):q.push(e)),!r.is_fork&&It.size>0&&!Rt&&Ht()}return t}function Ht(){Rt=!1;for(let e of It){e.f&1024&&k(e,g);let t;try{t=Vn(e)}catch{t=!0}t&&qn(e)}It.clear()}function Ut(e){N(e,e.v+1)}function Wt(e,t,n){var r=e.reactions;if(r!==null)for(var i=We(),a=r.length,o=0;o<a;o++){var s=r[o],c=s.f;if(i||s!==W){var l=(c&h)===0;if(l&&k(s,t),c&131072)It.add(s);else if(c&2){var u=s;bt?.delete(u),c&65536||(c&512&&(W===null||!(W.f&2097152))&&(s.f|=S),Wt(u,g,n))}else if(l){var d=s;c&16&&jt!==null&&jt.add(d),n===null?Nt(d):n.push(d)}}}}function Gt(t){if(typeof t!=`object`||!t||oe in t||se in t)return t;let n=l(t);if(n!==s&&n!==c)return t;var r=new Map,i=e(t),o=M(0),u=null,d=Rn,f=e=>{if(Rn===d)return e();var t=H,n=Rn;U(null),zn(d);var r=e();return U(t),zn(n),r};return i&&r.set(`length`,M(t.length,u)),new Proxy(t,{defineProperty(e,t,n){(!(`value`in n)||n.configurable===!1||n.enumerable===!1||n.writable===!1)&&Ie();var i=r.get(t);return i===void 0?f(()=>{var e=M(n.value,u);return r.set(t,e),e}):N(i,n.value,!0),!0},deleteProperty(e,t){var n=r.get(t);if(n===void 0){if(t in e){let e=f(()=>M(C,u));r.set(t,e),Ut(o)}}else N(n,C),Ut(o);return!0},get(e,n,i){if(n===oe)return t;var o=r.get(n),s=n in e;if(o===void 0&&(!s||a(e,n)?.writable)&&(o=f(()=>M(Gt(s?e[n]:C),u)),r.set(n,o)),o!==void 0){var c=J(o);return c===C?void 0:c}return Reflect.get(e,n,i)},getOwnPropertyDescriptor(e,t){var n=Reflect.getOwnPropertyDescriptor(e,t);if(n&&`value`in n){var i=r.get(t);i&&(n.value=J(i))}else if(n===void 0){var a=r.get(t),o=a?.v;if(a!==void 0&&o!==C)return{enumerable:!0,configurable:!0,value:o,writable:!0}}return n},has(e,t){if(t===oe)return!0;var n=r.get(t),i=n!==void 0&&n.v!==C||Reflect.has(e,t);return(n!==void 0||W!==null&&(!i||a(e,t)?.writable))&&(n===void 0&&(n=f(()=>M(i?Gt(e[t]):C,u)),r.set(t,n)),J(n)===C)?!1:i},set(e,t,n,s){var c=r.get(t),l=t in e;if(i&&t===`length`)for(var d=n;d<c.v;d+=1){var p=r.get(d+``);p===void 0?d in e&&(p=f(()=>M(C,u)),r.set(d+``,p)):N(p,C)}if(c===void 0)(!l||a(e,t)?.writable)&&(c=f(()=>M(void 0,u)),N(c,Gt(n)),r.set(t,c));else{l=c.v!==C;var m=f(()=>Gt(n));N(c,m)}var h=Reflect.getOwnPropertyDescriptor(e,t);if(h?.set&&h.set.call(s,n),!l){if(i&&typeof t==`string`){var g=r.get(`length`),_=Number(t);Number.isInteger(_)&&_>=g.v&&N(g,_+1)}Ut(o)}return!0},ownKeys(e){J(o);var t=Reflect.ownKeys(e).filter(e=>{var t=r.get(e);return t===void 0||t.v!==C});for(var[n,i]of r)i.v!==C&&!(n in e)&&t.push(n);return t},setPrototypeOf(){Le()}})}var Kt,qt,Jt,Yt;function Xt(){if(Kt===void 0){Kt=window,qt=/Firefox/.test(navigator.userAgent);var e=Element.prototype,t=Node.prototype,n=Text.prototype;Jt=a(t,`firstChild`).get,Yt=a(t,`nextSibling`).get,u(e)&&(e[ue]=void 0,e[le]=null,e[de]=void 0,e.__e=void 0),u(n)&&(n[fe]=void 0)}}function P(e=``){return document.createTextNode(e)}function F(e){return Jt.call(e)}function Zt(e){return Yt.call(e)}function I(e,t){if(!w)return F(e);var n=F(T);if(n===null)n=T.appendChild(P());else if(t&&n.nodeType!==3){var r=P();return n?.before(r),E(r),r}return t&&nn(n),E(n),n}function Qt(e,t=!1){if(!w){var n=F(e);return n instanceof Comment&&n.data===``?Zt(n):n}if(t){if(T?.nodeType!==3){var r=P();return T?.before(r),E(r),r}nn(T)}return T}function L(e,t=!1){if(!w)return F(e);var n=I(e,t);return D(e),n}function R(e,t=1,n=!1){let r=w?T:e;for(var i;t--;)i=r,r=Zt(r);if(!w)return r;if(n){if(r?.nodeType!==3){var a=P();return r===null?i?.after(a):r.before(a),E(a),a}nn(r)}return E(r),r}function $t(e){e.textContent=``}function en(){return!1}function tn(e,t,n){return t==null||t===`http://www.w3.org/1999/xhtml`?n?document.createElement(e,{is:n}):document.createElement(e):n?document.createElementNS(t,e,{is:n}):document.createElementNS(t,e)}function nn(e){if(e.nodeValue.length<65536)return;let t=e.nextSibling;for(;t!==null&&t.nodeType===3;)t.remove(),e.nodeValue+=t.nodeValue,t=e.nextSibling}function rn(e){var t=W;if(t===null)return H.f|=ae,e;if(!(t.f&32768)&&!(t.f&4))throw e;an(e,t)}function an(e,t){if(!(t!==null&&t.f&16384)){for(;t!==null;){if(t.f&128&&!(t.f&33570816)){if(!(t.f&32768))throw e;try{t.b.error(e);return}catch(t){e=t}}t=t.parent}throw e}}function on(e){W===null&&(H===null&&Pe(e),Ne()),kn&&Me(e)}function sn(e,t){var n=t.last;n===null?t.last=t.first=e:(n.next=e,e.prev=n,t.last=e)}function cn(e,t){var n=W;n!==null&&n.f&8192&&(e|=_);var r={ctx:O,deps:null,nodes:null,f:e|h|512,first:null,fn:t,last:null,next:null,parent:n,b:n&&n.b,prev:null,teardown:null,wv:0,ac:null};j?.register_created_effect(r);var i=r;if(e&4)wt===null?Ot.ensure().schedule(r):wt.push(r);else if(t!==null){try{qn(r)}catch(e){throw V(r),e}i.deps===null&&i.teardown===null&&i.nodes===null&&i.first===i.last&&!(i.f&524288)&&(i=i.first,e&16&&e&65536&&i!==null&&(i.f|=b))}if(i!==null&&(i.parent=n,n!==null&&sn(i,n),H!==null&&H.f&2&&!(e&64))){var a=H;(a.effects??=[]).push(i)}return r}function ln(){return H!==null&&!jn}function un(e){let t=cn(8,null);return k(t,m),t.teardown=e,t}function dn(e){on(`$effect`);var t=W.f;if(!H&&t&32&&O!==null&&!O.i){var n=O;(n.e??=[]).push(e)}else return fn(e)}function fn(e){return cn(4|te,e)}function pn(e){Ot.ensure();let t=cn(64|x,e);return(e={})=>new Promise(n=>{e.outro?Sn(t,()=>{V(t),n(void 0)}):(V(t),n(void 0))})}function mn(e){return cn(ie|x,e)}function hn(e,t=0){return cn(8|t,e)}function z(e,t=[],n=[],r=[]){at(r,t,n,t=>{cn(8,()=>{e(...t.map(J))})})}function gn(e,t=0){return cn(16|t,e)}function B(e){return cn(32|x,e)}function _n(e){var t=e.teardown;if(t!==null){let n=kn,r=H;An(!0),U(null);try{t.call(null)}catch(t){an(t,e.parent)}finally{An(n),U(r)}}}function vn(e,t=!1){var n=e.first;for(e.first=e.last=null;n!==null;){let e=n.ac;e!==null&&rt(()=>{e.abort(me)});var r=n.next;n.f&64?n.parent=null:V(n,t),n=r}}function yn(e){for(var t=e.first;t!==null;){var n=t.next;t.f&32||V(t),t=n}}function V(e,t=!0){var n=!1;(t||e.f&262144)&&e.nodes!==null&&e.nodes.end!==null&&(bn(e.nodes.start,e.nodes.end),n=!0),e.f|=ee,vn(e,t&&!n),Kn(e,0);var r=e.nodes&&e.nodes.t;if(r!==null)for(let e of r)e.stop();_n(e),e.f^=ee,e.f|=v;var i=e.parent;i!==null&&i.first!==null&&xn(e),e.next=e.prev=e.teardown=e.ctx=e.deps=e.fn=e.nodes=e.ac=e.b=null}function bn(e,t){for(;e!==null;){var n=e===t?null:Zt(e);e.remove(),e=n}}function xn(e){var t=e.parent,n=e.prev,r=e.next;n!==null&&(n.next=r),r!==null&&(r.prev=n),t!==null&&(t.first===e&&(t.first=r),t.last===e&&(t.last=n))}function Sn(e,t,n=!0){var r=[];e.f|=256,Cn(e,r,!0);var i=()=>{n&&V(e),t&&t()},a=r.length;if(a>0){var o=()=>--a||i();for(var s of r)s.out(o)}else i()}function Cn(e,t,n){if(!(e.f&8192)){e.f^=_;var r=e.nodes&&e.nodes.t;if(r!==null)for(let e of r)(e.is_global||n)&&t.push(e);for(var i=e.first;i!==null;){var a=i.next;if(!(i.f&64)){var o=!!(i.f&65536)||!!(i.f&32)&&!!(e.f&16);Cn(i,t,o?n:!1)}i=a}}}function wn(e){e.f&=-257,Tn(e,!0)}function Tn(e,t){if(!(e.f&256)&&e.f&8192){e.f^=_,e.f&1024||(k(e,h),Ot.ensure().schedule(e));for(var n=e.first;n!==null;){var r=n.next,i=!!(n.f&65536)||!!(n.f&32);Tn(n,i?t:!1),n=r}var a=e.nodes&&e.nodes.t;if(a!==null)for(let e of a)(e.is_global||t)&&e.in()}}function En(e,t){if(e.nodes)for(var n=e.nodes.start,r=e.nodes.end;n!==null;){var i=n===r?null:Zt(n);t.append(n),n=i}}var Dn=null,On=!1,kn=!1;function An(e){kn=e}var H=null,jn=!1;function U(e){H=e}var W=null;function Mn(e){W=e}var Nn=null;function Pn(e){H!==null&&(Nn??=new Set).add(e)}var G=null,K=0,q=null;function Fn(e){q=e}var In=1,Ln=0,Rn=Ln;function zn(e){Rn=e}function Bn(){return++In}function Vn(e){var t=e.f;if(t&2048)return!0;if(t&2&&(e.f&=~S),t&4096){for(var n=e.deps,r=n.length,i=0;i<r;i++){var a=n[i];if(Vn(a)&&ht(a),a.wv>e.wv)return!0}t&512&&bt===null&&k(e,m)}return!1}function Hn(e,t,n=!0){var r=e.reactions;if(r!==null&&!(Nn!==null&&Nn.has(e)))for(var i=0;i<r.length;i++){var a=r[i];a.f&2?Hn(a,t,!1):t===a&&(n?k(a,h):a.f&1024&&k(a,g),Nt(a))}}function Un(e){var t=G,n=K,r=q,i=H,a=Nn,o=O,s=jn,c=Rn,l=e.f;G=null,K=0,q=null,H=l&96?null:e,Nn=null,Be(e.ctx),jn=!1,Rn=++Ln,e.ac!==null&&(rt(()=>{e.ac.abort(me)}),e.ac=null);try{e.f|=re;var u=e.fn,d=u();e.f|=y;var f=Wn(e);if(We()&&q!==null&&!jn&&f!==null&&!(e.f&6146))for(var p=0;p<q.length;p++)Hn(q[p],e);if(i!==null&&i!==e){if(Ln++,i.deps!==null)for(let e=0;e<n;e+=1)i.deps[e].rv=Ln;if(t!==null)for(let e of t)e.rv=Ln;q!==null&&(r===null?r=q:r.push(...q))}return e.f&8388608&&(e.f^=ae),d}catch(t){return Wn(e),rn(t)}finally{e.f^=re,G=t,K=n,q=r,H=i,Nn=a,Be(o),jn=s,Rn=c}}function Wn(e){var t=e.deps,n=j?.is_fork;if(G!==null){var r;if(n||Kn(e,K),t!==null&&K>0)for(t.length=K+G.length,r=0;r<G.length;r++)t[K+r]=G[r];else e.deps=t=G;if(ln()&&e.f&512)for(r=K;r<t.length;r++)(t[r].reactions??=[]).push(e)}else!n&&t!==null&&K<t.length&&(Kn(e,K),t.length=K);return t}function Gn(e,r){let i=r.reactions;if(i!==null){var a=t.call(i,e);if(a!==-1){var o=i.length-1;o===0?i=r.reactions=null:(i[a]=i[o],i.pop())}}if(i===null&&r.f&2&&(G===null||!n.call(G,r))){var s=r;s.f&512&&(s.f^=512,s.f&=~S),s.v!==C&&Xe(s),s.ac!==null&&rt(()=>{s.ac.abort(me),s.ac=null,k(s,h)}),gt(s),Kn(s,0)}}function Kn(e,t){var n=e.deps;if(n!==null)for(var r=t;r<n.length;r++)Gn(e,n[r])}function qn(e){var t=e.f;if(!(t&16384)){k(e,m);var n=W,r=On;W=e,On=!(t&96);try{t&16777232?yn(e):vn(e),_n(e);var i=Un(e);e.teardown=typeof i==`function`?i:null,e.wv=In}finally{On=r,W=n}}}async function Jn(){await Promise.resolve(),kt()}function J(e){var t=!!(e.f&2);if(Dn?.add(e),H!==null&&!jn&&!(W!==null&&W.f&16384)&&(Nn===null||!Nn.has(e))){var r=H.deps;if(H.f&2097152)e.rv<Ln&&(e.rv=Ln,G===null&&r!==null&&r[K]===e?K++:G===null?G=[e]:G.push(e));else{H.deps??=[],n.call(H.deps,e)||H.deps.push(e);var i=e.reactions;i===null?e.reactions=[H]:n.call(i,H)||i.push(H)}}if(kn&&Lt.has(e))return Lt.get(e);if(t){var a=e;if(kn){var o=a.v;return(!(a.f&1024)&&a.reactions!==null||Xn(a))&&(o=mt(a)),Lt.set(a,o),o}var s=!(a.f&512)&&!jn&&H!==null&&(On||!!(H.f&512)),c=(a.f&y)===0;Vn(a)&&(s&&(a.f|=512),ht(a)),s&&!c&&(_t(a),Yn(a))}if(bt?.has(e))return bt.get(e);if(e.f&8388608)throw e.v;return e.v}function Yn(e){if(e.f|=512,e.deps!==null)for(let t of e.deps)(t.reactions??=[]).push(e),t.f&2&&!(t.f&512)&&(_t(t),Yn(t))}function Xn(e){if(e.v===C)return!0;if(e.deps===null)return!1;for(let t of e.deps)if(Lt.has(t)||t.f&2&&Xn(t))return!0;return!1}function Zn(e){var t=jn;try{return jn=!0,e()}finally{jn=t}}[...`allowfullscreen.async.autofocus.autoplay.checked.controls.default.disabled.formnovalidate.indeterminate.inert.ismap.loop.multiple.muted.nomodule.novalidate.open.playsinline.readonly.required.reversed.seamless.selected.webkitdirectory.defer.disablepictureinpicture.disableremoteplayback`.split(`.`)];var Qn=[`touchstart`,`touchmove`];function $n(e){return Qn.includes(e)}var er=Symbol(`events`),tr=new Set,nr=new Set;function rr(e,t,n,r={}){function i(e){if(r.capture||cr.call(t,e),!e.cancelBubble)return rt(()=>n?.call(this,e))}return e.startsWith(`pointer`)||e.startsWith(`touch`)||e===`wheel`?qe(()=>{t.addEventListener(e,i,r)}):t.addEventListener(e,i,r),i}function ir(e,t,n,r,i){var a={capture:r,passive:i},o=rr(e,t,n,a);(t===document.body||t===window||t===document||t instanceof HTMLMediaElement)&&un(()=>{t.removeEventListener(e,o,a)})}function Y(e,t,n){(t[er]??={})[e]=n}function ar(e){for(var t=0;t<e.length;t++)tr.add(e[t]);for(var n of nr)n(e)}var or=null,sr=!1;function cr(e){var t=this,n=t.ownerDocument,r=e.type,a=e.composedPath?.()||[],o=a[0]||e.target;or=e,sr||(sr=!0,setTimeout(()=>{sr=!1,or=null}));var s=0,c=or===e&&e[er];if(c){var l=a.indexOf(c);if(l!==-1&&(t===document||t===window)){e[er]=t;return}var u=a.indexOf(t);if(u===-1)return;l<=u&&(s=l)}if(o=a[s]||e.target,o!==t){i(e,`currentTarget`,{configurable:!0,get(){return o||n}});var d=H,f=W;U(null),Mn(null);try{for(var p,m=[];o!==null&&o!==t;){try{var h=o[er]?.[r];h!=null&&(!o.disabled||e.target===o)&&h.call(o,e)}catch(e){p?m.push(e):p=e}if(e.cancelBubble)break;s++,o=s<a.length?a[s]:null}if(p){for(let e of m)queueMicrotask(()=>{throw e});throw p}}finally{e[er]=t,delete e.currentTarget,U(d),Mn(f)}}}var lr=globalThis?.window?.trustedTypes&&globalThis.window.trustedTypes.createPolicy(`svelte-trusted-html`,{createHTML:e=>e});function ur(e){return lr?.createHTML(e)??e}function dr(e){var t=tn(`template`);return t.innerHTML=ur(e.replaceAll(`<!>`,`<!---->`)),t.content}function fr(e,t){var n=W;n.nodes===null&&(n.nodes={start:e,end:t,a:null,t:null})}function X(e,t){var n=!!(t&1),r=!!(t&2),i,a=!e.startsWith(`<!>`);return()=>{if(w)return fr(T,null),T;i===void 0&&(i=dr(a?e:`<!>`+e),n||(i=F(i)));var t=r||qt?document.importNode(i,!0):i.cloneNode(!0);if(n){var o=F(t),s=t.lastChild;fr(o,s)}else fr(t,t);return t}}function pr(e,t,n=`svg`){var r=!e.startsWith(`<!>`),i=!!(t&1),a=`<${n}>${r?e:`<!>`+e}</${n}>`,o;return()=>{if(w)return fr(T,null),T;if(!o){var e=F(dr(a));if(i)for(o=document.createDocumentFragment();F(e);)o.appendChild(F(e));else o=F(e)}var t=o.cloneNode(!0);if(i){var n=F(t),r=t.lastChild;fr(n,r)}else fr(t,t);return t}}function mr(e,t){return pr(e,t,`svg`)}function hr(e=``){if(!w){var t=P(e+``);return fr(t,t),t}var n=T;return n.nodeType===3?nn(n):(n.before(n=P()),E(n)),fr(n,n),n}function gr(){if(w)return fr(T,null),T;var e=document.createDocumentFragment(),t=document.createComment(``),n=P();return e.append(t,n),fr(t,n),e}function Z(e,t){if(w){var n=W;(!(n.f&32768)||n.nodes.end===null)&&(n.nodes.end=T),Se();return}e!==null&&e.before(t)}function _r(e){let t=0,n=zt(0),r;return()=>{ln()&&(J(n),hn(()=>(t===0&&(r=Zn(()=>e(()=>Ut(n)))),t+=1,()=>{qe(()=>{--t,t===0&&(r?.(),r=void 0,Ut(n))})})))}}var vr=b|x;function yr(e,t,n,r){new br(e,t,n,r)}var br=class{parent;is_pending=!1;transform_error;#e;#t=w?T:null;#n;#r;#i;#a=null;#o=null;#s=null;#c=null;#l=0;#u=0;#d=!1;#f=new Set;#p=new Set;#m=null;#h=_r(()=>(this.#m=zt(this.#l),()=>{this.#m=null}));constructor(e,t,n,r){this.#e=e,this.#n=t,this.#r=e=>{var t=W;t.b=this,t.f|=128,n(e)},this.parent=W.b,this.transform_error=r??this.parent?.transform_error??(e=>e),this.#i=gn(()=>{if(w){let e=this.#t;Se();let t=e.data===`[!`;if(e.data.startsWith(`[?`)){let t=JSON.parse(e.data.slice(2));this.#_(t)}else t?this.#y():this.#g()}else this.#b()},vr),w&&(this.#e=T)}#g(){try{this.#a=B(()=>this.#r(this.#e))}catch(e){this.error(e)}}#_(e){let t=this.#n.failed,{reset:n,invoke_onerror:r}=this.#v(e);qe(r),t&&(this.#s=B(()=>{t(this.#e,()=>e,()=>n)}))}#v(e){var t=!1,n=!1;let r=()=>{if(t){be();return}t=!0,n&&ze(),this.#s!==null&&Sn(this.#s,()=>{this.#s=null}),this.#S(()=>{this.#b()})};return{reset:r,invoke_onerror:()=>{try{n=!0,this.#n.onerror?.(e,r),n=!1}catch(e){an(e,this.#i&&this.#i.parent)}}}}#y(){let e=this.#n.pending;e&&(this.is_pending=!0,this.#o=B(()=>e(this.#e)),qe(()=>{var e=this.#c=document.createDocumentFragment(),t=P(),n=!1;if(e.append(t),this.#a=this.#S(()=>{try{return B(()=>this.#r(t))}catch(e){try{this.error(e),n=!0}catch(e){an(e,this.#i.parent)}return null}}),this.#a===null){this.#c=null,n&&this.#x(j);return}this.#u===0&&(this.#e.before(e),this.#c=null,Sn(this.#o,()=>{this.#o=null}),this.#x(j))}))}#b(){try{if(this.is_pending=this.has_pending_snippet(),this.#u=0,this.#l=0,this.#a=B(()=>{this.#r(this.#e)}),this.#u>0){var e=this.#c=document.createDocumentFragment();En(this.#a,e);let t=this.#n.pending;this.#o=B(()=>t(this.#e))}else this.#x(j)}catch(e){this.error(e)}}#x(e){this.is_pending=!1,e.transfer_effects(this.#f,this.#p)}defer_effect(e){Qe(e,this.#f,this.#p)}is_rendered(){return!this.is_pending&&(!this.parent||this.parent.is_rendered())}has_pending_snippet(){return!!this.#n.pending}#S(e){var t=W,n=H,r=O;Mn(this.#i),U(this.#i),Be(this.#i.ctx);try{return Ot.ensure(),e()}finally{Mn(t),U(n),Be(r)}}#C(e,t){if(!this.has_pending_snippet()){this.parent&&this.parent.#C(e,t);return}this.#u+=e,this.#u===0&&(this.#x(t),this.#o&&Sn(this.#o,()=>{this.#o=null}),this.#c&&=(this.#e.before(this.#c),null))}update_pending_count(e,t){this.#C(e,t),this.#l+=e,!(!this.#m||this.#d)&&(this.#d=!0,qe(()=>{this.#d=!1,this.#m&&Vt(this.#m,this.#l)}))}get_effect_pending(){return this.#h(),J(this.#m)}error(e){if(!this.#n.onerror&&!this.#n.failed)throw e;j?.is_fork?(this.#a&&j.skip_effect(this.#a),this.#o&&j.skip_effect(this.#o),this.#s&&j.skip_effect(this.#s),j.oncommit(()=>{this.#w(e)})):this.#w(e)}#w(e){this.#a&&=(V(this.#a),null),this.#o&&=(V(this.#o),null),this.#s&&=(V(this.#s),null),w&&(E(this.#t),Ce(),E(we()));let t=this.#n.failed,n=e=>{let{reset:n,invoke_onerror:r}=this.#v(e);r(),t&&(this.#s=this.#S(()=>{try{return B(()=>{var r=W;r.b=this,r.f|=128,t(this.#e,()=>e,()=>n)})}catch(e){return an(e,this.#i.parent),null}}))};qe(()=>{var t;try{t=this.transform_error(e)}catch(e){an(e,this.#i&&this.#i.parent);return}typeof t==`object`&&t&&typeof t.then==`function`?t.then(n,e=>an(e,this.#i&&this.#i.parent)):n(t)})}};function Q(e,t){var n=t==null?``:typeof t==`object`?`${t}`:t;n!==(e[fe]??=e.nodeValue)&&(e[fe]=n,e.nodeValue=`${n}`)}function xr(e,t){return Cr(e,t)}var Sr=new Map;function Cr(e,{target:t,anchor:n,props:i={},events:a,context:o,intro:s=!0,transformError:c}){Xt();var l=void 0,u=pn(()=>{var s=n??t.appendChild(P());yr(s,{pending:()=>{}},t=>{Ve({});var n=O;if(o&&(n.c=o),a&&(i.$$events=a),w&&fr(t,null),l=e(t,i)||Ue(),w&&(W.nodes.end=T,T===null||T.nodeType!==8||T.data!==`]`))throw ye(),ge;He()},c);var u=new Set,d=e=>{for(var n=0;n<e.length;n++){var r=e[n];if(!u.has(r)){u.add(r);var i=$n(r);for(let e of[t,document]){var a=Sr.get(e);a===void 0&&(a=new Map,Sr.set(e,a));var o=a.get(r);o===void 0?(e.addEventListener(r,cr,{passive:i}),a.set(r,1)):a.set(r,o+1)}}}};return d(r(tr)),nr.add(d),()=>{for(var e of u)for(let n of[t,document]){var r=Sr.get(n),i=r.get(e);--i==0?(n.removeEventListener(e,cr),r.delete(e),r.size===0&&Sr.delete(n)):r.set(e,i)}nr.delete(d),s!==n&&s.parentNode?.removeChild(s)}});return wr.set(l,u),l}var wr=new WeakMap,Tr=class{anchor;#e=new Map;#t=new Map;#n=new Map;#r=new Set;#i=!0;constructor(e,t=!0){this.anchor=e,this.#i=t}#a=e=>{if(this.#e.has(e)){var t=this.#e.get(e),n=this.#t.get(t);if(n)wn(n),this.#r.delete(t);else{var r=this.#n.get(t);r&&(wn(r.effect),this.#t.set(t,r.effect),this.#n.delete(t),r.fragment.lastChild.remove(),this.anchor.before(r.fragment),n=r.effect)}for(let[t,n]of this.#e){if(this.#e.delete(t),t===e)break;let r=this.#n.get(n);r&&(V(r.effect),this.#n.delete(n))}for(let[e,r]of this.#t){if(e===t||this.#r.has(e))continue;let i=()=>{if(Array.from(this.#e.values()).includes(e)){var t=document.createDocumentFragment();En(r,t),t.append(P()),this.#n.set(e,{effect:r,fragment:t})}else V(r);this.#r.delete(e),this.#t.delete(e)};this.#i||!n?(this.#r.add(e),Sn(r,i,!1)):i()}}};#o=e=>{this.#e.delete(e);let t=Array.from(this.#e.values());for(let[e,n]of this.#n)t.includes(e)||(V(n.effect),this.#n.delete(e))};ensure(e,t){var n=j,r=en();if(t&&!this.#t.has(e)&&!this.#n.has(e)){if(r){var i=document.createDocumentFragment(),a=P();i.append(a),this.#n.set(e,{effect:B(()=>t(a)),fragment:i})}else this.#t.set(e,B(()=>t(this.anchor)))}if(this.#e.set(n,e),r){for(let[t,r]of this.#t)t===e?n.unskip_effect(r):n.skip_effect(r);for(let[t,r]of this.#n)t===e?n.unskip_effect(r.effect):n.skip_effect(r.effect);n.oncommit(this.#a),n.ondiscard(this.#o)}else w&&(this.anchor=T),this.#a(n)}};function $(e,t,n=!1){var r;w&&(r=T,Se());var i=new Tr(e),a=n?b:0;function o(e,t){if(w){var n=Te(r);if(e!==parseInt(n.substring(1))){var a=we();E(a),i.anchor=a,xe(!1),i.ensure(e,t),xe(!0);return}}i.ensure(e,t)}gn(()=>{var e=!1;t((t,n=0)=>{e=!0,o(n,t)}),e||o(-1,null)},a)}function Er(e,t){return t}function Dr(e,t,n){for(var i=[],a=t.length,o,s=t.length,c=0;c<a;c++){let n=t[c];Sn(n,()=>{if(o){if(o.pending.delete(n),o.done.add(n),o.pending.size===0){var t=e.outrogroups;Or(e,r(o.done)),t.delete(o),t.size===0&&(e.outrogroups=null)}}else--s},!1)}if(s===0){var l=i.length===0&&n!==null&&e.pending.size===0;if(l){var u=n,d=u.parentNode;$t(d),d.append(u),e.items.clear()}Or(e,t,!l)}else o={pending:new Set(t),done:new Set},(e.outrogroups??=new Set).add(o)}function Or(e,t,n=!0){var r;if(e.pending.size>0){r=new Set;for(let t of e.pending.values())for(let n of t)r.add(e.items.get(n).e)}for(var i=0;i<t.length;i++){var a=t[i];r?.has(a)?(a.f|=ne,En(a,document.createDocumentFragment())):V(t[i],n)}}var kr;function Ar(t,n,i,a,o,s=null){var c=t,l=new Map;if(n&4){var u=t;c=w?E(F(u)):u.appendChild(P())}w&&Se();var d=null,f=ft(()=>{var t=i();return e(t)?t:t==null?[]:r(t)}),p,m=new Map,h=!0;function g(e){v.effect.f&16384||(v.pending.delete(e),v.fallback=d,Mr(v,p,c,n,a),d!==null&&(p.length===0?d.f&33554432?(d.f^=ne,Pr(d,null,c)):wn(d):Sn(d,()=>{d=null})))}function _(e){v.pending.delete(e)}var v={effect:gn(()=>{p=J(f);var e=p.length;let t=!1;w&&Te(c)===`[!`!=(e===0)&&(c=we(),E(c),xe(!1),t=!0);for(var r=new Set,u=j,v=en(),y=0;y<e;y+=1){w&&T.nodeType===8&&T.data===`]`&&(c=T,t=!0,xe(!1));var ee=p[y],b=a(ee,y),x=h?null:l.get(b);x?(x.v&&Vt(x.v,ee),x.i&&Vt(x.i,y),v&&u.unskip_effect(x.e)):(x=Nr(l,h?c:kr??=P(),ee,b,y,o,n,i),h||(x.e.f|=ne),l.set(b,x)),r.add(b)}if(e===0&&s&&!d&&(h?d=B(()=>s(c)):(d=B(()=>s(kr??=P())),d.f|=ne)),e>r.size&&je(``,``,``),w&&e>0&&E(we()),!h){if(m.set(u,r),v){for(let[e,t]of l)r.has(e)||u.skip_effect(t.e);u.oncommit(g),u.ondiscard(_)}else g(u)}t&&xe(!0),J(f)}),flags:n,items:l,pending:m,outrogroups:null,fallback:d};h=!1,w&&(c=T)}function jr(e){for(;e!==null&&!(e.f&32);)e=e.next;return e}function Mr(e,t,n,i,a){var o=!!(i&8),s=t.length,c=e.items,l=jr(e.effect.first),u,d=null,f,p=[],m=[],h,g,_,v;if(o)for(v=0;v<s;v+=1)h=t[v],g=a(h,v),_=c.get(g).e,_.f&33554432||(_.nodes?.a?.measure(),(f??=new Set).add(_));for(v=0;v<s;v+=1){if(h=t[v],g=a(h,v),_=c.get(g).e,e.outrogroups!==null)for(let t of e.outrogroups)t.pending.delete(_),t.done.delete(_);if(_.f&8192&&(wn(_),o&&(_.nodes?.a?.unfix(),(f??=new Set).delete(_))),_.f&33554432){if(_.f^=ne,_===l)Pr(_,null,n);else{var y=d?d.next:l;_===e.effect.last&&(e.effect.last=_.prev),_.prev&&(_.prev.next=_.next),_.next&&(_.next.prev=_.prev),Fr(e,d,_),Fr(e,_,y),Pr(_,y,n),d=_,p=[],m=[],l=jr(d.next);continue}}if(_!==l){if(u!==void 0&&u.has(_)){if(p.length<m.length){var ee=m[0],b;d=ee.prev;var x=p[0],te=p[p.length-1];for(b=0;b<p.length;b+=1)Pr(p[b],ee,n);for(b=0;b<m.length;b+=1)u.delete(m[b]);Fr(e,x.prev,te.next),Fr(e,d,x),Fr(e,te,ee),l=ee,d=te,--v,p=[],m=[]}else u.delete(_),Pr(_,l,n),Fr(e,_.prev,_.next),Fr(e,_,d===null?e.effect.first:d.next),Fr(e,d,_),d=_;continue}for(p=[],m=[];l!==null&&l!==_;)(u??=new Set).add(l),m.push(l),l=jr(l.next);if(l===null)continue}_.f&33554432||p.push(_),d=_,l=jr(_.next)}if(e.outrogroups!==null){for(let t of e.outrogroups)t.pending.size===0&&(Or(e,r(t.done)),e.outrogroups?.delete(t));e.outrogroups.size===0&&(e.outrogroups=null)}if(l!==null||u!==void 0){var S=[];if(u!==void 0)for(_ of u)_.f&8192||S.push(_);for(;l!==null;)!(l.f&8192)&&l!==e.fallback&&S.push(l),l=jr(l.next);var re=S.length;if(re>0){var ie=i&4&&s===0?n:null;if(o){for(v=0;v<re;v+=1)S[v].nodes?.a?.measure();for(v=0;v<re;v+=1)S[v].nodes?.a?.fix()}Dr(e,S,ie)}}o&&qe(()=>{if(f!==void 0)for(_ of f)_.nodes?.a?.apply()})}function Nr(e,t,n,r,i,a,o,s){var c=o&1?o&16?zt(n):Bt(n,!1,!1):null,l=o&2?zt(i):null;return{v:c,i:l,e:B(()=>(a(t,c??n,l??i,s),()=>{e.delete(r)}))}}function Pr(e,t,n){if(e.nodes)for(var r=e.nodes.start,i=e.nodes.end,a=t&&!(t.f&33554432)?t.nodes.start:n;r!==null;){var o=Zt(r);if(a.before(r),r===i)return;r=o}}function Fr(e,t,n){t===null?e.effect.first=n:t.next=n,n===null?e.effect.last=t:n.prev=t}function Ir(e,t,...n){var r=new Tr(e);gn(()=>{let e=t()??null;r.ensure(e,e&&(t=>e(t,...n)))},b)}var Lr=[...`
2
+ \r\f\xA0\v`];function Rr(e,t,n){var r=e==null?``:``+e;if(t&&(r=r?r+` `+t:t),n){for(var i of Object.keys(n))if(n[i])r=r?r+` `+i:i;else if(r.length)for(var a=i.length,o=0;(o=r.indexOf(i,o))>=0;){var s=o+a;(o===0||Lr.includes(r[o-1]))&&(s===r.length||Lr.includes(r[s]))?r=(o===0?``:r.substring(0,o))+r.substring(s+1):o=s}}return r===``?null:r}function zr(e,t=!1){var n=t?` !important;`:`;`,r=``;for(var i of Object.keys(e)){var a=e[i];a!=null&&a!==``&&(r+=` `+i+`: `+a+n)}return r}function Br(e){return e[0]!==`-`||e[1]!==`-`?e.toLowerCase():e}function Vr(e,t){if(t){var n=``,r,i;if(Array.isArray(t)?(r=t[0],i=t[1]):r=t,e){e=String(e).replaceAll(/\/\*.*?\*\//g,``).trim();var a=!1,o=0,s=!1,c=[];r&&c.push(...Object.keys(r).map(Br)),i&&c.push(...Object.keys(i).map(Br));var l=0,u=-1;let t=e.length;for(var d=0;d<t;d++){var f=e[d];if(s?f===`/`&&e[d-1]===`*`&&(s=!1):a?a===f&&(a=!1):f===`/`&&e[d+1]===`*`?s=!0:f===`"`||f===`'`?a=f:f===`(`?o++:f===`)`&&o--,!s&&a===!1&&o===0){if(f===`:`&&u===-1)u=d;else if(f===`;`||d===t-1){if(u!==-1){var p=Br(e.substring(l,u).trim());if(!c.includes(p)){f!==`;`&&d++;var m=e.substring(l,d).trim();n+=` `+m+`;`}}l=d+1,u=-1}}}}return r&&(n+=zr(r)),i&&(n+=zr(i,!0)),n=n.trim(),n===``?null:n}return e==null?null:String(e)}function Hr(e,t,n,r,i,a){var o=e[ue];if(w||o!==n||o===void 0){var s=Rr(n,r,a);(!w||s!==e.getAttribute(`class`))&&(s==null?e.removeAttribute(`class`):t?e.className=s:e.setAttribute(`class`,s)),e[ue]=n}else if(a&&i!==a)for(var c in a){var l=!!a[c];(i==null||l!==!!i[c])&&e.classList.toggle(c,l)}return a}function Ur(e,t={},n,r){for(var i in n){var a=n[i];t[i]!==a&&(n[i]==null?e.style.removeProperty(i):e.style.setProperty(i,a,r))}}function Wr(e,t,n,r){var i=e[de];if(w||i!==t){var a=Vr(t,r);(!w||a!==e.getAttribute(`style`))&&(a==null?e.removeAttribute(`style`):e.style.cssText=a),e[de]=t}else r&&(Array.isArray(r)?(Ur(e,n?.[0],r[0]),Ur(e,n?.[1],r[1],`important`)):Ur(e,n,r));return r}var Gr=Symbol(`is custom element`),Kr=Symbol(`is html`),qr=he?`link`:`LINK`;function Jr(e){if(w){var t=!1,n=()=>{if(!t){if(t=!0,e.hasAttribute(`value`)){var n=e.value;Xr(e,`value`,null),e.value=n}if(e.hasAttribute(`checked`)){var r=e.checked;Xr(e,`checked`,null),e.checked=r}}};e[pe]=n,qe(n),nt()}}function Yr(e,t){var n=Zr(e);n.checked!==(n.checked=t??void 0)&&(e.checked=t)}function Xr(e,t,n,r){var i=Zr(e);w&&(i[t]=e.getAttribute(t),t===`src`||t===`srcset`||t===`href`&&e.nodeName===qr)||i[t]!==(i[t]=n)&&(t===`loading`&&(e[ce]=n),n==null?e.removeAttribute(t):typeof n!=`string`&&$r(e).has(t)?e[t]=n:e.setAttribute(t,n))}function Zr(e){return e[le]??={[Gr]:e.nodeName.includes(`-`),[Kr]:e.namespaceURI===_e}}var Qr=new Map;function $r(e){var t=e.getAttribute(`is`)||e.nodeName,n=Qr.get(t);if(n)return n;Qr.set(t,n=new Set);for(var r,i=e,a=Element.prototype;a!==i;){for(var s in r=o(i),r)r[s].set&&s!==`innerHTML`&&s!==`textContent`&&s!==`innerText`&&n.add(s);i=l(i)}return n}function ei(e,t,n=t){var r=new WeakSet;it(e,`input`,async i=>{var a=i?e.defaultValue:e.value;if(a=ti(e)?ni(a):a,n(a),j!==null&&r.add(j),await Jn(),a!==(a=t())){var o=e.selectionStart,s=e.selectionEnd,c=e.value.length;if(e.value=a??``,s!==null){var l=e.value.length;o===s&&s===c&&l>c?(e.selectionStart=l,e.selectionEnd=l):(e.selectionStart=o,e.selectionEnd=Math.min(s,l))}}}),(w&&e.defaultValue!==e.value||Zn(t)==null&&e.value)&&(n(ti(e)?ni(e.value):e.value),j!==null&&r.add(j)),hn(()=>{var n=t();if(e===document.activeElement){var i=j;if(r.has(i))return}ti(e)&&n===ni(e.value)||(e.type!==`date`||n||e.value)&&n!==e.value&&(e.value=n??``)})}function ti(e){var t=e.type;return t===`number`||t===`range`}function ni(e){return e===``?null:+e}function ri(e){O===null&&ke(`onMount`),dn(()=>{let t=Zn(e);if(typeof t==`function`)return t})}typeof window<`u`&&((window.__svelte??={}).v??=new Set).add(`5`);var ii=mr(`<svg viewBox="0 0 16 16" width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m3.4 8.4 3 3 6.2-6.8"></path></svg>`),ai=mr(`<svg viewBox="0 0 16 16" width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M8 4v5"></path><path d="M8 12h.01"></path></svg>`),oi=mr(`<svg viewBox="0 0 16 16" width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M4 4l8 8M12 4l-8 8"></path></svg>`),si=X(`<span class="fix svelte-hz6ou4"> </span>`),ci=X(`<li class="svelte-hz6ou4"><span class="mark svelte-hz6ou4" aria-hidden="true"><!></span> <span class="body svelte-hz6ou4"><b class="svelte-hz6ou4"> </b> <span class="msg svelte-hz6ou4"> </span> <!></span> <span class="sr svelte-hz6ou4"> </span></li>`),li=X(`<section class="health svelte-hz6ou4" aria-label="Health"><header class="svelte-hz6ou4"><h2 class="svelte-hz6ou4"> </h2> <p class="svelte-hz6ou4">This is the same check <code>npx verbaly doctor</code> runs, read out loud. It looks at your
3
+ settings, your catalogs and the way your build is wired, and it never changes anything.</p> <button class="close svelte-hz6ou4">Back to the messages</button></header> <ul class="svelte-hz6ou4"></ul></section>`);function ui(e,t){Ve(t,!0);let n={config:`Your settings`,cli:`The verbaly command`,catalogs:`Your catalogs`,routing:`Where the language lives in your urls`,plugin:`The piece that wires your build`,types:`The generated types`,sources:`Reading your code`,orphans:`Keys nothing in your code uses`,bundle:`What the browser downloads`,translations:`The translations themselves`},r=A(()=>t.entries.filter(e=>e.level!==`ok`).length);var i=li(),a=I(i),o=I(a),s=L(o,!0),c=R(o,4);D(a);var l=R(a,2);Ar(l,21,()=>t.entries,Er,(e,t)=>{var r=ci(),i=I(r),a=I(i),o=e=>{Z(e,ii())},s=e=>{Z(e,ai())},c=e=>{Z(e,oi())};$(a,e=>{J(t).level===`ok`?e(o):J(t).level===`warn`?e(s,1):e(c,-1)}),D(i);var l=R(i,2),u=I(l),d=L(u,!0),f=R(u,2),p=L(f,!0),m=R(f,2),h=e=>{var n=si(),r=L(n,!0);z(()=>Q(r,J(t).fix)),Z(e,n)};$(m,e=>{J(t).fix&&e(h)}),D(l);var g=L(R(l,2),!0);D(r),z(()=>{Xr(r,`data-level`,J(t).level),Q(d,n[J(t).check]??J(t).check),Q(p,J(t).message),Q(g,J(t).level)}),Z(e,r)}),D(l),D(i),z(()=>Q(s,t.ok?`Nothing is broken`:`${J(r)} of ${t.entries.length} need you`)),Y(`click`,c,function(...e){t.onClose?.apply(this,e)}),Z(e,i),He()}ar([`click`]);var di={done:{short:`ready`,long:`ready`},draft:{short:`unread`,long:`written by a machine, nobody has read it`},missing:{short:`missing`,long:`missing, so your visitor sees the source language`},broken:{short:`breaks`,long:`breaks your site`}},fi={all:`everything`,missing:`missing`,draft:`unread`,broken:`breaks your site`,look:`worth a look`},pi={divergent:`the source says this two ways`,collision:`came back as two translations`,echo:`still identical to the source`,digits:`the numbers moved`,url:`a link changed`,code:`a code span changed`},mi=`Undo`,hi={find:`Find new text`,findWhy:`Reads your code and adds what your catalogs do not have yet.`,translate:`Translate what is missing`,translateWhy:`Calls your provider, so it costs money and takes minutes.`,cancel:`Not now`,working:`Reading your code`,running:`Translating`,nothingNew:`Nothing new: your catalogs already have every message your code uses.`,nothingMissing:`Nothing missing in the languages you ticked.`,pickFirst:`Tick a language to translate it.`};function gi(e){return`${e} ${e===1?`message`:`messages`}`}var _i=X(`<span> </span>`),vi=X(`<button class="read svelte-1rwkwvu"> <span class="n svelte-1rwkwvu"> </span></button>`),yi=X(`<li><label><input type="checkbox" class="svelte-1rwkwvu"/> <span class="meta svelte-1rwkwvu"><span class="name svelte-1rwkwvu"> </span> <span class="state svelte-1rwkwvu"> </span></span></label> <!></li>`),bi=X(`<p class="undo svelte-1rwkwvu"><span> </span> <button class="svelte-1rwkwvu"> </button></p>`),xi=X(`<li><button type="button"><span> </span> <span class="count svelte-1rwkwvu"> </span></button></li>`),Si=X(`<nav class="rail svelte-1rwkwvu" aria-label="Languages and filters"><button class="nav svelte-1rwkwvu"><span>Health</span> <!></button> <!> <h2 class="cap svelte-1rwkwvu">Languages</h2> <ul class="langs svelte-1rwkwvu"></ul> <!> <h2 class="cap svelte-1rwkwvu">Show</h2> <ul class="filters svelte-1rwkwvu"></ul></nav>`);function Ci(e,t){Ve(t,!0);let n=[`all`,`look`,`missing`,`draft`,`broken`];function r(e){return e.source?`the one you write`:e.broken?`${e.broken} breaks your site`:e.missing?`${e.missing} missing`:e.draft?`${e.draft} nobody has read`:`all read`}function i(e){return e.source?`done`:e.broken?`broken`:e.missing?`missing`:e.draft?`draft`:`done`}var a=Si(),o=I(a),s=R(I(o),2),c=e=>{var n=_i();let r;var i=L(n,!0);z(()=>{r=Hr(n,1,`bad svelte-1rwkwvu`,null,r,{clean:!t.checks.bad}),Q(i,t.checks.bad||`ok`)}),Z(e,n)};$(s,e=>{t.checks&&e(c)}),D(o);var l=R(o,2);Ir(l,()=>t.actions);var u=R(l,4);Ar(u,21,()=>t.health,e=>e.locale,(e,n)=>{var a=yi(),o=I(a);let s;var c=I(o);Jr(c);var l=R(c,2),u=I(l),d=L(u,!0),f=R(u,2),p=L(f,!0);D(l),D(o);var m=R(o,2),h=e=>{var r=vi(),i=I(r),a=L(R(i),!0);D(r),z(()=>{Q(i,`Mark as read `),Q(a,J(n).draft)}),Y(`click`,r,()=>t.onRead(J(n).locale)),Z(e,r)};$(m,e=>{!J(n).source&&J(n).draft>0&&e(h)}),D(a),z((e,t,r)=>{s=Hr(o,1,`lang svelte-1rwkwvu`,null,s,{source:J(n).source}),Yr(c,e),c.disabled=J(n).source,Q(d,J(n).locale),Xr(f,`data-state`,t),Q(p,r)},[()=>t.shown.includes(J(n).locale),()=>i(J(n)),()=>r(J(n))]),Y(`change`,c,()=>t.onToggle(J(n).locale)),Z(e,a)}),D(u);var d=R(u,2),f=e=>{var n=bi(),r=I(n),i=L(r),a=R(r,2),o=L(a,!0);D(n),z(()=>{Q(i,`${t.pending.keys.length??``} in ${t.pending.locale??``} marked as read`),Q(o,mi)}),Y(`click`,a,function(...e){t.onUndo?.apply(this,e)}),Z(e,n)};$(d,e=>{t.pending&&e(f)});var p=R(d,4);Ar(p,20,()=>n,e=>e,(e,n)=>{var r=xi(),i=I(r);let a;var o=I(i),s=L(o,!0),c=L(R(o,2),!0);D(i),D(r),z(()=>{a=Hr(i,1,`svelte-1rwkwvu`,null,a,{on:t.state===n}),Q(s,fi[n]),Q(c,t.counts[n]??0)}),Y(`click`,i,()=>t.onState(n)),Z(e,r)}),D(p),D(a),Y(`click`,o,function(...e){t.onHealth?.apply(this,e)}),Z(e,a),He()}ar([`click`,`change`]);var wi=mr(`<svg viewBox="0 0 22 22" width="15" height="15" aria-hidden="true"><path d="M5 11h12" stroke="currentColor" stroke-width="2" stroke-linecap="round"></path></svg>`),Ti=mr(`<svg viewBox="0 0 22 22" width="15" height="15" aria-hidden="true" fill="none"><path d="M6 12.5 11 7l5 5.5" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"></path><path d="M11 8v7" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"></path></svg>`),Ei=mr(`<svg viewBox="0 0 22 22" width="15" height="15" aria-hidden="true" fill="none"><path d="M4.5 12.5c1.6-2.2 3.2-2.2 4.8 0s3.2 2.2 4.8 0 3.2-2.2 4.8 0" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"></path></svg>`),Di=mr(`<svg viewBox="0 0 22 22" width="15" height="15" aria-hidden="true"><path d="M11 6v6.4" stroke="currentColor" stroke-width="2" stroke-linecap="round"></path><circle cx="11" cy="16" r="1.2" fill="currentColor"></circle></svg>`),Oi=X(`<!> <span class="sr svelte-150apog"> </span>`,1);function ki(e,t){let n={done:`translated`,draft:`written by a machine`,missing:`not translated, shows in your source language`,broken:`breaks your site`};var r=Oi(),i=Qt(r),a=e=>{Z(e,wi())},o=e=>{Z(e,Ti())},s=e=>{Z(e,Ei())},c=e=>{Z(e,Di())};$(i,e=>{t.state===`done`?e(a):t.state===`missing`?e(o,1):t.state===`draft`?e(s,2):e(c,-1)});var l=L(R(i,2),!0);z(()=>Q(l,n[t.state])),Z(e,r)}var Ai=new Set;function ji(e){Ai.has(e)||(Ai.add(e),console.warn(`[verbaly] ${e}`))}var Mi=/\{\s*[a-zA-Z0-9_]+\s*,\s*(?:plural|selectordinal|select|number|date|time)\b/;function Ni(e){return Mi.test(e)}var Pi=new Map,Fi=5e3;function Ii(e,t){let n=Pi.get(e);if(!n){if(Ni(e)){if(!t)return Li(e);n=t(e)}else n=Ri(e,!1);Pi.size>=Fi&&Pi.delete(Pi.keys().next().value),Pi.set(e,n)}return n}function Li(e){return ji(`a message uses ICU syntax but no ICU parser is loaded: pass icu to createVerbaly (the compiler wires it when a catalog needs it, so this means it arrived after the build)`),[{kind:`text`,value:e}]}function Ri(e,t){let n=[],r=``,i=0,a=()=>{r&&n.push({kind:`text`,value:r}),r=``};for(;i<e.length;){let o=e[i];if(o===`{`){if(e[i+1]===`{`){r+=`{`,i+=2;continue}let t=zi(e,i);if(t<0){r+=e.slice(i);break}a(),n.push(Bi(e.slice(i+1,t))),i=t+1}else o===`}`&&e[i+1]===`}`?(r+=`}`,i+=2):t&&o===`#`?e[i+1]===`#`?(r+=`#`,i+=2):(a(),n.push({kind:`hash`}),i+=1):(r+=o,i+=1)}return a(),n}function zi(e,t){let n=0,r=t;for(;r<e.length;){let t=e[r];if(t===`{`){if(e[r+1]===`{`){r+=2;continue}n+=1}else if(t===`}`){if(e[r+1]===`}`){r+=2;continue}if(--n,n===0)return r}r+=1}return-1}function Bi(e){let t=Vi(e),n=(t[0]??``).trim();if(!n)return{kind:`text`,value:`{${e}}`};let r=n.indexOf(`:`),i=(r<0?n:n.slice(0,r)).trim();if(!i)return{kind:`text`,value:`{${e}}`};let a={kind:`param`,name:i};if(r>=0){let e=n.slice(r+1).trim(),t=e.indexOf(`/`);a.format=(t<0?e:e.slice(0,t)).trim(),t>=0&&(a.arg=e.slice(t+1).trim())}if(t.length>1){a.variants=[];for(let e of t.slice(1)){let t=e.indexOf(`:`);if(t<0)continue;let n=e.slice(0,t).trim(),r=e.slice(t+1).trim();n&&a.variants.push([n,Ri(r,!0)])}}return a}function Vi(e){let t=[],n=``,r=0,i=0;for(;i<e.length;){let a=e[i];if(a===`{`){if(e[i+1]===`{`){n+=`{{`,i+=2;continue}r+=1,n+=a}else if(a===`}`){if(e[i+1]===`}`){n+=`}}`,i+=2;continue}--r,n+=a}else if(a===`|`&&r===0){if(e[i+1]===`|`){n+=`|`,i+=2;continue}t.push(n),n=``}else n+=a;i+=1}return t.push(n),t}var Hi=/<(\/?)([a-zA-Z][\w-]*)(\/?)>/g,Ui=/&(?:(lt|gt|amp)|#(\d+)|#[xX]([0-9a-fA-F]+));/g,Wi={lt:`<`,gt:`>`,amp:`&`},Gi=e=>e.replace(Ui,(e,t,n,r)=>{if(t)return Wi[t];let i=n?parseInt(n,10):parseInt(r,16);return i<=1114111?String.fromCodePoint(i):e});function Ki(e){Hi.lastIndex=0;let t=[],n=[],r=()=>n.length?n[n.length-1].children:t,i=e=>{e&&r().push(Gi(e))},a=0,o;for(;(o=Hi.exec(e))!==null;){let[t,s,c,l]=o;if(i(e.slice(a,o.index)),a=o.index+t.length,l)r().push({name:c,children:[]});else if(s){if(n.length&&n[n.length-1].name===c){let e=n.pop();r().push(e)}else i(t)}else n.push({name:c,children:[]})}for(i(e.slice(a));n.length;){let e=n.pop();r().push(...e.children)}return t}function qi(e,t){for(let n of e)if(n.kind===`param`&&n.name){t.has(n.name)||t.set(n.name,new Set);for(let[e,r]of n.variants??[])t.get(n.name).add(e),qi(r,t)}}function Ji(e){let t=new Map;try{qi(Ii(e),t)}catch{}return t}function Yi(e,t){for(let n of e)typeof n!=`string`&&(t.push(n.name),Yi(n.children,t))}function Xi(e){let t=[];try{Yi(Ki(e),t)}catch{return t}return t}function Zi(e){let t=new Map;for(let n of e)t.set(n,(t.get(n)??0)+1);return t}var Qi=e=>e.length===1?e[0]:`${e.slice(0,-1).join(`, `)} and ${e.at(-1)}`;function $i(e,t){let n=Ji(e),r=Ji(t),i=[...new Set([...n.keys(),...r.keys()])].sort().map(e=>({name:e,inSource:n.has(e),inTarget:r.has(e)})),a=[],o=i.filter(e=>e.inSource&&!e.inTarget).map(e=>`{${e.name}}`),s=i.filter(e=>!e.inSource&&e.inTarget).map(e=>`{${e.name}}`);o.length&&a.push(`${Qi(o)} is missing, so its value never reaches the text`),s.length&&a.push(`${Qi(s)} is not in the source, so it renders as literal text`);for(let[e,t]of r)t.size&&!t.has(`other`)&&a.push(`the block for {${e}} has no "other" case, so some counts render empty`);let c=Zi(Xi(e)),l=Zi(Xi(t));for(let[e,t]of c)(l.get(e)??0)<t&&a.push(`<${e}> is missing from the translation`);for(let[e,t]of l)(c.get(e)??0)<t&&a.push(`<${e}> is not in the source message`);return{params:i,problems:a}}var ea=X(`<span> </span>`),ta=X(`<span class="vmsg svelte-1vuyl2e"> </span>`),na=X(`<span class="tl edit svelte-1vuyl2e"><span class="disc svelte-1vuyl2e"> </span> <textarea class="tf svelte-1vuyl2e" rows="2"></textarea></span> <span class="editbar svelte-1vuyl2e"><!> <!> <!> <button class="btn sm svelte-1vuyl2e"> </button> <button class="btn sm quiet svelte-1vuyl2e">Cancel</button> <span class="keys svelte-1vuyl2e">Enter saves · Esc closes</span></span>`,1),ra=X(`<button class="tv svelte-1vuyl2e"> </button>`),ia=X(`<button class="tv none svelte-1vuyl2e"> </button>`),aa=X(`<span class="sig svelte-1vuyl2e"> </span>`),oa=X(`<span class="tl svelte-1vuyl2e"><span class="disc svelte-1vuyl2e"> </span> <!> <!></span>`),sa=X(`<article><span class="tile svelte-1vuyl2e"><!></span> <span class="row-t svelte-1vuyl2e"><span class="src svelte-1vuyl2e"> </span> <!></span></article>`);function ca(e,t){Ve(t,!0);let n=A(()=>t.row.cells.filter(e=>t.shown.includes(e.locale))),r=A(()=>[`broken`,`missing`,`draft`,`done`].find(e=>J(n).some(t=>t.state===e))??`done`),i=M(``),a=M(!1),o=M(null),s=A(()=>$i(t.row.source,J(i))),c=e=>`${e.locale} ${t.row.key}`;function l(e){N(i,e.text,!0),N(o,null),t.onEdit(c(e))}async function u(e){J(a)||(N(a,!0),N(o,await t.onSave(e.locale,t.row.key,J(i)),!0),N(a,!1),J(o)||t.onEdit(null))}function d(e,n){e.key===`Escape`&&t.onEdit(null),e.key===`Enter`&&!e.shiftKey&&(e.preventDefault(),u(n))}var f=sa();let p;var m=I(f);ki(I(m),{get state(){return J(r)}}),D(m);var h=R(m,2),g=I(h),_=L(g,!0);Ar(R(g,2),17,()=>J(n),e=>e.locale,(e,n)=>{var r=gr(),f=Qt(r),p=e=>{var r=na(),c=Qt(r),l=I(c),f=L(l,!0),p=R(l,2);et(p),$e(p,!0),D(c);var m=R(c,2),h=I(m);Ar(h,17,()=>J(s).params,e=>e.name,(e,t)=>{var n=ea();let r;var i=L(n);z(()=>{r=Hr(n,1,`pm svelte-1vuyl2e`,null,r,{gone:J(t).inSource&&!J(t).inTarget}),Q(i,`${J(t).inSource&&!J(t).inTarget?`+`:``}{${J(t).name??``}}`)}),Z(e,n)});var g=R(h,2);Ar(g,16,()=>J(s).problems,e=>e,(e,t)=>{var n=ta(),r=L(n,!0);z(()=>Q(r,t)),Z(e,n)});var _=R(g,2),v=e=>{var t=ta(),n=L(t,!0);z(()=>Q(n,J(o))),Z(e,t)};$(_,e=>{J(o)&&!J(s).problems.length&&e(v)});var y=R(_,2),ee=L(y,!0),b=R(y,2);Ce(2),D(m),z(()=>{Xr(l,`data-state`,J(n).state),Q(f,J(n).locale),Xr(p,`aria-label`,`Translation for ${J(n).locale??``}`),y.disabled=J(a),Q(ee,J(a)?`Saving`:`Save`)}),Y(`keydown`,p,e=>d(e,J(n))),ei(p,()=>J(i),e=>N(i,e)),Y(`click`,y,()=>u(J(n))),Y(`click`,b,()=>t.onEdit(null)),Z(e,r)},m=A(()=>t.editing===c(J(n))),h=e=>{var t=oa(),r=I(t),i=L(r,!0),a=R(r,2),o=e=>{var t=ra(),r=L(t,!0);z(()=>{Xr(t,`title`,J(n).text),Q(r,J(n).text)}),Y(`click`,t,()=>l(J(n))),Z(e,t)},s=e=>{var t=ia(),r=L(t,!0);z(()=>Q(r,di.missing.long)),Y(`click`,t,()=>l(J(n))),Z(e,t)};$(a,e=>{J(n).text?e(o):e(s,-1)}),Ar(R(a,2),16,()=>J(n).signals,e=>e,(e,t)=>{var n=aa(),r=L(n,!0);z(()=>{Xr(n,`title`,pi[t]??t),Q(r,pi[t]??t)}),Z(e,n)}),D(t),z(()=>{Xr(r,`data-state`,J(n).state),Q(i,J(n).locale)}),Z(e,t)};$(f,e=>{J(m)?e(p):e(h,-1)}),Z(e,r)}),D(h),D(f),z(e=>{p=Hr(f,1,`row svelte-1vuyl2e`,null,p,{open:e}),Xr(f,`data-worst`,J(r)),Xr(m,`data-state`,J(r)),Q(_,t.row.source)},[()=>J(n).some(e=>t.editing===c(e))]),Z(e,f),He()}ar([`keydown`,`click`]);function la(e,t=``,n={}){for(let[r,i]of Object.entries(e)){let e=t?`${t}.${r}`:r;typeof i==`string`?n[e]=i:i&&typeof i==`object`&&!Array.isArray(i)&&la(i,e,n)}return n}function ua(e){let t={};for(let n of e.locales)t[n]=la(e.catalogs[n]??{});let n=new Map;for(let t of e.check.broken)t.severity===`error`&&n.set(`${t.locale}\u0000${t.key}`,t.issue);let r=new Map;for(let[t,n]of Object.entries(e.drafts))r.set(t,new Set(n));let i=t[e.sourceLocale]??{},a=e.locales.filter(t=>t!==e.sourceLocale),o=Object.keys(i).map(o=>({key:o,source:i[o]??``,origins:e.origins[o]??[],cells:a.map(i=>{let a=t[i]?.[o]??``,s=n.get(`${i}\u0000${o}`);return{locale:i,text:a,state:s?`broken`:a?r.get(i)?.has(o)?`draft`:`done`:`missing`,issue:s,signals:(e.triage?.[i]?.[o]??[]).map(e=>e.signal)}})}));return{root:e.root,sourceLocale:e.sourceLocale,locales:e.locales,rows:o,problems:e.problems}}function da(e){return e.locales.map(t=>{let n=e.rows.length;if(t===e.sourceLocale)return{locale:t,source:!0,total:n,done:n,draft:0,missing:0,broken:0};let r={done:0,draft:0,missing:0,broken:0};for(let n of e.rows){let e=n.cells.find(e=>e.locale===t);e&&r[e.state]++}return{locale:t,source:!1,total:n,...r}})}function fa(e,t){let n=t.text.trim().toLowerCase(),r=new Set(t.locales);return e.filter(e=>{let i=e.cells.filter(e=>r.has(e.locale));return t.state===`look`&&!i.some(e=>e.signals.length)||t.state!==`all`&&t.state!==`look`&&!i.some(e=>e.state===t.state)?!1:!n||e.source.toLowerCase().includes(n)?!0:i.some(e=>e.text.toLowerCase().includes(n))})}async function pa(e,t,n){let r=t.includes(`?`)?`&`:`?`,i;try{i=await fetch(`${t}${r}t=${encodeURIComponent(e)}`,{...n,headers:n?.body===void 0?void 0:{"content-type":`application/json`},body:n?.body===void 0?void 0:JSON.stringify(n.body)})}catch{return{error:`[verbaly] the studio server is not answering, is it still running?`}}let a=await i.json().catch(()=>({}));return i.ok?{value:a}:{error:a.error??`[verbaly] the server answered ${i.status}`}}function ma(e){return pa(e,`/api/extract`,{method:`POST`})}function ha(e,t){let n=t.map(e=>`locale=${encodeURIComponent(e)}`).join(`&`);return pa(e,`/api/translate${n?`?${n}`:``}`)}function ga(e,t){return pa(e,`/api/translate`,{method:`POST`,body:{locales:t}})}function _a(e){return pa(e,`/api/job`)}function va(e,t){return pa(e,`/api/job/${encodeURIComponent(t)}`)}function ya(e){return pa(e,`/api/state`)}var ba=X(`<div class="bill svelte-5qpy41"><p class="lede svelte-5qpy41"> </p> <p class="why svelte-5qpy41"> </p> <div class="pair svelte-5qpy41"><button class="go svelte-5qpy41"> </button> <button class="plain svelte-5qpy41"> </button></div></div>`),xa=X(`<div class="bill svelte-5qpy41"><p class="lede svelte-5qpy41"> </p> <div class="track svelte-5qpy41"><span class="fill svelte-5qpy41"></span></div> <p class="why svelte-5qpy41"> </p></div>`),Sa=X(`<button class="act svelte-5qpy41"><span class="name svelte-5qpy41"> </span> <span class="why svelte-5qpy41"> </span></button> <button class="act svelte-5qpy41"><span class="name svelte-5qpy41"> </span> <span class="why svelte-5qpy41"> </span></button>`,1),Ca=X(`<p> </p>`),wa=X(`<section class="acts svelte-5qpy41" aria-label="Commands"><h2 class="cap svelte-5qpy41">Commands</h2> <!> <!></section>`);function Ta(e,t){Ve(t,!0);let n=M(Gt({at:`idle`})),r=A(()=>J(n).at===`finding`||J(n).at===`planning`||J(n).at===`running`);function i(e,t=!1){N(n,{at:`said`,text:e,bad:t},!0)}async function a(){N(n,{at:`finding`},!0);let e=await ma(t.token);if(e.error||!e.value)return i(e.error??`no answer`,!0);let{found:r}=e.value;if(!r)return i(hi.nothingNew);let a=`${gi(r)} your code uses and your catalogs did not have`;await t.onDone(a),i(a)}async function o(){N(n,{at:`planning`},!0);let e=await ha(t.token,t.shown);if(e.error||!e.value)return i(e.error??`no answer`,!0);if(!e.value.total)return i(hi.nothingMissing);N(n,{at:`plan`,plan:e.value},!0)}async function s(e){let r=await va(t.token,e);if(r.error||!r.value)return i(r.error??`no answer`,!0);let a=r.value;if(a.state===`running`){N(n,{at:`running`,job:a},!0);return}let o=`${gi(a.done)} translated, unread until you read them`;await t.onDone(o),i(a.state===`failed`?a.message??`the run failed`:o,a.state===`failed`)}async function c(){let e=await ga(t.token,t.shown);if(e.error||!e.value)return i(e.error??`no answer`,!0);N(n,{at:`running`,job:e.value},!0)}ri(()=>{_a(t.token).then(e=>{e.value&&J(n).at===`idle`&&N(n,{at:`running`,job:e.value},!0)})}),dn(()=>{if(J(n).at!==`running`)return;let e=J(n).job.id,t=setInterval(()=>void s(e),1e3);return()=>clearInterval(t)});let l=A(()=>J(n).at===`running`&&J(n).job.total?J(n).job.done/J(n).job.total:0);var u=wa(),d=R(I(u),2),f=e=>{var t=ba(),r=I(t),i=L(r),a=R(r,2),o=L(a,!0),s=R(a,2),l=I(s),u=L(l,!0),d=R(l,2),f=L(d,!0);D(s),D(t),z((e,t)=>{Q(i,`${e??``} in ${t??``}`),Q(o,hi.translateWhy),Q(u,hi.translate),Q(f,hi.cancel)},[()=>gi(J(n).plan.total),()=>Object.keys(J(n).plan.pending).join(`, `)]),Y(`click`,l,c),Y(`click`,d,()=>N(n,{at:`idle`},!0)),Z(e,t)},p=e=>{var t=xa(),r=I(t),i=L(r),a=R(r,2),o=L(a),s=L(R(a,2));D(t),z(e=>{Q(i,`${hi.running??``} ${J(n).job.locale??``??``}`),Wr(o,`width: ${e??``}%`),Q(s,`${J(n).job.done??``} of ${J(n).job.total??``}`)},[()=>Math.round(J(l)*100)]),Z(e,t)},m=e=>{var i=Sa(),s=Qt(i),c=I(s),l=L(c,!0),u=L(R(c,2),!0);D(s);var d=R(s,2),f=I(d),p=L(f,!0),m=L(R(f,2),!0);D(d),z(()=>{s.disabled=J(r),Q(l,J(n).at===`finding`?hi.working:hi.find),Q(u,hi.findWhy),d.disabled=J(r)||!t.shown.length,Q(p,hi.translate),Q(m,t.shown.length?hi.translateWhy:hi.pickFirst)}),Y(`click`,s,a),Y(`click`,d,o),Z(e,i)};$(d,e=>{J(n).at===`plan`?e(f):J(n).at===`running`?e(p,1):e(m,-1)});var h=R(d,2),g=e=>{var t=Ca();let r;var i=L(t,!0);z(()=>{r=Hr(t,1,`said svelte-5qpy41`,null,r,{bad:J(n).bad}),Q(i,J(n).text)}),Z(e,t)};$(h,e=>{J(n).at===`said`&&e(g)}),D(u),Z(e,u),He()}ar([`click`]);async function Ea(e,t,n,r){let i=await pa(e,`/api/message/${encodeURIComponent(t)}/${encodeURIComponent(n)}`,{method:`PUT`,body:{text:r}});return i.error?{error:i.error}:{saved:i.value}}async function Da(e,t,n,r){let i=await pa(e,`/api/approve`,{method:`POST`,body:{locale:t,keys:n,undo:r}});return i.error?{error:i.error}:{done:i.value}}async function Oa(e){let t=await pa(e,`/api/health`);return t.error?{ok:!1,entries:[],error:t.error}:{ok:t.value?.ok??!1,entries:t.value?.entries??[]}}function ka(e){return Gt(ua(e))}function Aa(e,t){Object.assign(e,ua(t))}var ja=X(`<li><code> </code> </li>`),Ma=X(`<ul class="problems svelte-c7dw08"></ul>`),Na=X(`<p class="note svelte-c7dw08">Tick a language in the rail to see its translations.</p>`),Pa=X(`<p class="note svelte-c7dw08">Nothing matches. Every message here is in the state you asked for.</p>`),Fa=X(`<p class="note svelte-c7dw08"> </p>`),Ia=X(`<!> <!>`,1),La=X(`<main class="table svelte-c7dw08"><!></main>`),Ra=X(`Saved in <code> </code>`,1),za=X(`<p class="snack svelte-c7dw08" role="status"><svg viewBox="0 0 16 16" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="svelte-c7dw08"><path d="m3.4 8.4 3 3 6.2-6.8"></path></svg> <!> <button class="svelte-c7dw08">Dismiss</button></p>`),Ba=X(`<div class="shell svelte-c7dw08"><header class="bar svelte-c7dw08"><span class="brand svelte-c7dw08">verbaly <b class="svelte-c7dw08">studio</b></span> <input class="search svelte-c7dw08" type="search" placeholder="Search the text, not the key" aria-label="Search the text, not the key"/> <span class="root svelte-c7dw08"> </span></header> <!> <div class="body svelte-c7dw08"><!> <!></div> <!></div>`);function Va(e,t){Ve(t,!0);let n=M(null),r=M(null),i=M(null),a=M(`messages`),o=M(null);async function s(){N(a,`health`);let e=await Oa(t.token);if(e.error){N(r,{text:e.error,file:``},!0);return}N(o,{ok:e.ok,entries:e.entries},!0)}dn(()=>{Oa(t.token).then(e=>{e.error||N(o,{ok:e.ok,entries:e.entries},!0)})});async function c(e){let a=await ya(t.token);if(a.error||!a.value){N(r,{text:a.error??`[verbaly] the server sent no state`,file:``},!0);return}N(n,null),N(i,null),Aa(t.panel,a.value),N(r,{text:e,file:``},!0);let s=await Oa(t.token);s.error||N(o,{ok:s.ok,entries:s.entries},!0)}async function l(e,n,a){let o=await Da(t.token,e,n,a);if(o.error){N(r,{text:o.error,file:``},!0);return}let s=new Set(o.done?.keys??[]);for(let n of t.panel.rows){if(!s.has(n.key))continue;let t=n.cells.find(t=>t.locale===e);t?.text&&(t.state=a?`draft`:`done`)}N(i,a?null:{locale:e,keys:o.done?.keys??[]},!0)}async function u(e,n,i){let a=await Ea(t.token,e,n,i);if(a.error)return a.error;let o=t.panel.rows.find(e=>e.key===n)?.cells.find(t=>t.locale===e);return o&&(o.text=i,o.state=i?`done`:`missing`,o.issue=void 0),N(r,{text:i,file:`${e}.json`},!0),null}let d=M(`all`),f=M(``),p=M(null),m=A(()=>t.panel.locales.filter(e=>e!==t.panel.sourceLocale)),h=A(()=>J(p)??J(m).slice(0,1)),g=A(()=>da(t.panel)),_=A(()=>fa(t.panel.rows,{text:J(f),state:J(d),locales:J(h)})),v=A(()=>{let e={all:0,look:0,missing:0,draft:0,broken:0};for(let n of[`all`,`look`,`missing`,`draft`,`broken`])e[n]=fa(t.panel.rows,{text:J(f),state:n,locales:J(h)}).length;return e}),y=M(60);dn(()=>{J(f),J(d),J(h),N(y,60)});function ee(e){let t=e.currentTarget;t.scrollTop+t.clientHeight>t.scrollHeight-600&&J(y)<J(_).length&&N(y,J(y)+60)}function b(e){N(p,J(h).includes(e)?J(h).filter(t=>t!==e):[...J(h),e],!0)}var x=Ba(),te=I(x),ne=R(I(te),2);Jr(ne);var S=R(ne,2),re=L(S,!0);D(te);var ie=R(te,2),ae=e=>{var n=Ma();Ar(n,21,()=>t.panel.problems,e=>e.scope+e.message,(e,t)=>{var n=ja(),r=I(n),i=L(r,!0),a=R(r);D(n),z(()=>{Q(i,J(t).scope),Q(a,` ${J(t).message??``}`)}),Z(e,n)}),D(n),Z(e,n)};$(ie,e=>{t.panel.problems.length&&e(ae)});var oe=R(ie,2),se=I(oe);{let e=e=>{Ta(e,{get token(){return t.token},get shown(){return J(h)},onDone:c})},n=A(()=>J(o)?{bad:J(o).entries.filter(e=>e.level!==`ok`).length}:null);Ci(se,{get health(){return J(g)},get shown(){return J(h)},get state(){return J(d)},get counts(){return J(v)},get pending(){return J(i)},get checks(){return J(n)},onToggle:b,onState:e=>N(d,e,!0),onRead:e=>l(e,void 0,!1),onUndo:()=>J(i)&&l(J(i).locale,J(i).keys,!0),onHealth:s,actions:e,$$slots:{actions:!0}})}var ce=R(se,2),le=e=>{{let t=A(()=>J(o)?.entries??[]),n=A(()=>J(o)?.ok??!0);ui(e,{get entries(){return J(t)},get ok(){return J(n)},onClose:()=>N(a,`messages`)})}},ue=e=>{var t=La(),r=I(t),i=e=>{Z(e,Na())},a=e=>{Z(e,Pa())},o=e=>{var t=Ia(),r=Qt(t);Ar(r,17,()=>J(_).slice(0,J(y)),e=>e.key,(e,t)=>{ca(e,{get row(){return J(t)},get shown(){return J(h)},get editing(){return J(n)},onEdit:e=>N(n,e,!0),onSave:u})});var i=R(r,2),a=e=>{var t=Fa(),n=L(t);z(()=>Q(n,`${J(_).length-J(y)} more below`)),Z(e,t)};$(i,e=>{J(y)<J(_).length&&e(a)}),Z(e,t)};$(r,e=>{J(h).length?J(_).length?e(o,-1):e(a,1):e(i)}),D(t),ir(`scroll`,t,ee),Z(e,t)};$(ce,e=>{J(a)===`health`?e(le):e(ue,-1)}),D(oe);var de=R(oe,2),fe=e=>{var t=za(),n=R(I(t),2),i=e=>{var t=Ra(),n=L(R(Qt(t)),!0);z(()=>Q(n,J(r).file)),Z(e,t)},a=e=>{var t=hr();z(()=>Q(t,J(r).text)),Z(e,t)};$(n,e=>{J(r).file?e(i):e(a,-1)});var o=R(n,2);D(t),Y(`click`,o,()=>N(r,null)),Z(e,t)};$(de,e=>{J(r)&&e(fe)}),D(x),z(()=>{Xr(S,`title`,t.panel.root),Q(re,t.panel.root)}),ei(ne,()=>J(f),e=>N(f,e)),Z(e,x),He()}ar([`click`]);var Ha=new URLSearchParams(location.search).get(`t`)??``;async function Ua(){let e=document.getElementById(`app`),t=await fetch(`/api/state?t=${encodeURIComponent(Ha)}`);if(!t.ok){e.textContent=(await t.json().catch(()=>({}))).error??`[verbaly] the server answered ${t.status}`;return}xr(Va,{target:e,props:{panel:ka(await t.json()),token:Ha}})}Ua();
@@ -0,0 +1 @@
1
+ .health.svelte-hz6ou4{background:var(--paper);flex:1;min-width:0;overflow-y:auto}header.svelte-hz6ou4{border-bottom:1px solid var(--rule);padding:28px 24px 20px}h2.svelte-hz6ou4{letter-spacing:-.014em;color:var(--ink);margin:0;font-size:21px;font-weight:500}header.svelte-hz6ou4 p:where(.svelte-hz6ou4){max-width:64ch;color:var(--muted);margin:8px 0 0;font-size:13px}.close.svelte-hz6ou4{border:1px solid var(--rule-strong);border-radius:var(--radius);background:var(--paper);color:var(--body);font:inherit;cursor:pointer;margin-top:16px;padding:5px 12px;font-size:12.5px}.close.svelte-hz6ou4:hover{background:var(--ground)}ul.svelte-hz6ou4{margin:0;padding:0;list-style:none}li.svelte-hz6ou4{border-bottom:1px solid var(--rule);align-items:flex-start;gap:12px;padding:14px 24px;display:flex}.mark.svelte-hz6ou4{background:var(--done-tint);width:20px;height:20px;color:var(--done);border-radius:50%;flex:none;justify-content:center;align-items:center;margin-top:1px;display:inline-flex}[data-level=warn].svelte-hz6ou4 .mark:where(.svelte-hz6ou4){background:var(--missing-tint);color:var(--missing)}[data-level=error].svelte-hz6ou4 .mark:where(.svelte-hz6ou4){background:var(--broken-tint);color:var(--broken)}.body.svelte-hz6ou4{flex-direction:column;gap:2px;min-width:0;display:flex}b.svelte-hz6ou4{color:var(--ink);font-size:13.5px;font-weight:500}.msg.svelte-hz6ou4{color:var(--body);font-size:13px}.fix.svelte-hz6ou4{font-family:var(--mono);color:var(--draft);margin-top:4px;font-size:12px}.sr.svelte-hz6ou4{clip-path:inset(50%);width:1px;height:1px;position:absolute;overflow:hidden}.rail.svelte-1rwkwvu{width:var(--rail);border-right:1px solid var(--rule);background:var(--ground);flex:none;padding:18px 14px;overflow-y:auto}.nav.svelte-1rwkwvu{border-radius:var(--radius);width:100%;font:inherit;color:var(--body);cursor:pointer;text-align:left;background:0 0;border:0;align-items:center;gap:8px;margin-bottom:18px;padding:7px 8px;font-size:13px;display:flex}.nav.svelte-1rwkwvu:hover{background:var(--hollow)}.bad.svelte-1rwkwvu{background:var(--broken-tint);color:var(--broken);font-family:var(--mono);border-radius:999px;margin-left:auto;padding:0 7px;font-size:11px}.bad.clean.svelte-1rwkwvu{background:var(--done-tint);color:var(--done)}.cap.svelte-1rwkwvu{letter-spacing:.08em;text-transform:uppercase;color:var(--muted);margin:0 0 8px;padding:0 6px;font-size:11px;font-weight:600}ul.svelte-1rwkwvu{margin:0 0 22px;padding:0;list-style:none}.lang.svelte-1rwkwvu{border-radius:var(--radius);cursor:pointer;align-items:flex-start;gap:10px;padding:7px 6px;display:flex}.lang.svelte-1rwkwvu:hover{background:var(--hollow)}.lang.source.svelte-1rwkwvu{cursor:default}.lang.svelte-1rwkwvu input:where(.svelte-1rwkwvu){accent-color:var(--done);margin:4px 0 0}.meta.svelte-1rwkwvu{flex-direction:column;min-width:0;display:flex}.name.svelte-1rwkwvu{font-family:var(--mono);color:var(--ink);font-size:13px;font-weight:600}.state.svelte-1rwkwvu{color:var(--muted);font-size:11.5px}.state[data-state=broken].svelte-1rwkwvu{color:var(--broken)}.state[data-state=missing].svelte-1rwkwvu{color:var(--missing)}.state[data-state=draft].svelte-1rwkwvu{color:var(--draft)}.read.svelte-1rwkwvu{border:1px solid var(--draft);background:var(--draft-tint);color:var(--draft);font:inherit;cursor:pointer;border-radius:999px;align-items:center;gap:6px;margin:2px 0 6px 30px;padding:3px 9px;font-size:11.5px;display:flex}.read.svelte-1rwkwvu .n:where(.svelte-1rwkwvu){font-family:var(--mono);opacity:.8}.undo.svelte-1rwkwvu{border-radius:var(--radius);background:var(--hollow);color:var(--body);align-items:center;gap:8px;margin:-14px 0 22px;padding:8px 10px;font-size:11.5px;display:flex}.undo.svelte-1rwkwvu button:where(.svelte-1rwkwvu){border:1px solid var(--rule-strong);border-radius:var(--radius);background:var(--paper);color:var(--ink);font:inherit;cursor:pointer;margin-left:auto;padding:3px 10px;font-size:11.5px}.filters.svelte-1rwkwvu button:where(.svelte-1rwkwvu){border-radius:var(--radius);width:100%;font:inherit;color:var(--body);cursor:pointer;text-align:left;background:0 0;border:0;justify-content:space-between;align-items:center;gap:8px;padding:6px 8px;font-size:13px;display:flex}.filters.svelte-1rwkwvu button:where(.svelte-1rwkwvu):hover{background:var(--hollow)}.filters.svelte-1rwkwvu button.on:where(.svelte-1rwkwvu){background:var(--hollow);color:var(--ink);font-weight:600}.count.svelte-1rwkwvu{font-family:var(--mono);color:var(--muted);font-size:12px}.sr.svelte-150apog{clip-path:inset(50%);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}.row.svelte-1vuyl2e{border-bottom:1px solid var(--rule);align-items:flex-start;gap:16px;min-height:64px;padding:12px 20px;display:flex}.row.svelte-1vuyl2e:hover{background:var(--ground)}.row.open.svelte-1vuyl2e{background:var(--paper);box-shadow:inset 3px 0 0 var(--done);outline:1px solid var(--rule);outline-offset:-1px}.tile.svelte-1vuyl2e{border-radius:var(--radius);flex:none;justify-content:center;align-items:center;width:26px;height:26px;margin-top:2px;display:inline-flex}.tile[data-state=done].svelte-1vuyl2e{color:var(--rule-strong)}.tile[data-state=draft].svelte-1vuyl2e{background:var(--draft-tint);color:var(--draft)}.tile[data-state=missing].svelte-1vuyl2e{background:var(--missing-tint);color:var(--missing)}.tile[data-state=broken].svelte-1vuyl2e{background:var(--broken);color:var(--paper)}.row-t.svelte-1vuyl2e{flex-direction:column;flex:1;gap:3px;min-width:0;display:flex}.src.svelte-1vuyl2e{color:var(--muted);text-overflow:ellipsis;white-space:nowrap;font-size:13.5px;line-height:1.3;overflow:hidden}.tl.svelte-1vuyl2e{align-items:baseline;gap:8px;min-width:0;display:flex}.tl.edit.svelte-1vuyl2e{align-items:flex-start}.disc.svelte-1vuyl2e{background:var(--hollow);width:20px;height:20px;color:var(--body);font-family:var(--mono);letter-spacing:-.02em;border-radius:50%;flex:none;justify-content:center;align-items:center;font-size:9px;display:inline-flex;transform:translateY(3px)}.disc[data-state=done].svelte-1vuyl2e{background:var(--done-tint);color:var(--done)}.disc[data-state=draft].svelte-1vuyl2e{background:var(--draft-tint);color:var(--draft)}.disc[data-state=missing].svelte-1vuyl2e{background:var(--missing-tint);color:var(--missing)}.disc[data-state=broken].svelte-1vuyl2e{background:var(--broken-tint);color:var(--broken)}.tv.svelte-1vuyl2e{text-align:left;min-width:0;font-family:var(--serif);color:var(--ink);text-overflow:ellipsis;white-space:nowrap;cursor:text;background:0 0;border:0;flex:1;padding:0;font-size:17px;line-height:1.35;overflow:hidden}.sig.svelte-1vuyl2e{color:var(--draft);background:var(--draft-tint);white-space:nowrap;border-radius:4px;flex:none;padding:1px 6px;font-size:11px}.tv.none.svelte-1vuyl2e{font-family:var(--sans);color:var(--missing);font-size:13px;font-style:italic}.tf.svelte-1vuyl2e{min-width:0;font-family:var(--serif);color:var(--ink);background:var(--paper);border:1px solid var(--done);border-radius:var(--radius);resize:vertical;flex:1;padding:6px 10px;font-size:17px;line-height:1.45}.tf.svelte-1vuyl2e:focus-visible{outline:2px solid color-mix(in oklab, var(--done) 26%, transparent);outline-offset:1px}.editbar.svelte-1vuyl2e{flex-wrap:wrap;align-items:center;gap:8px;margin-top:10px;display:flex}.pm.svelte-1vuyl2e{font-family:var(--mono);color:var(--body);background:var(--hollow);border-radius:4px;padding:1px 5px;font-size:12.5px}.pm.gone.svelte-1vuyl2e{background:var(--broken-tint);border:1px dashed var(--broken);color:var(--broken)}.vmsg.svelte-1vuyl2e{color:var(--broken);background:var(--broken-tint);border-radius:var(--radius);align-items:center;gap:6px;padding:4px 9px;font-size:12.5px;display:inline-flex}.btn.svelte-1vuyl2e{border:1px solid var(--done);border-radius:var(--radius);background:var(--done);height:30px;color:var(--paper);font:inherit;cursor:pointer;padding:0 12px;font-size:12.5px;font-weight:500}.btn.quiet.svelte-1vuyl2e{background:var(--paper);color:var(--body);border-color:var(--rule-strong);font-weight:400}.btn.svelte-1vuyl2e:disabled{opacity:.6;cursor:default}.keys.svelte-1vuyl2e{font-family:var(--mono);color:var(--muted);font-size:11px}.acts.svelte-5qpy41{margin-bottom:22px}.cap.svelte-5qpy41{letter-spacing:.08em;text-transform:uppercase;color:var(--muted);margin:0 0 8px;padding:0 6px;font-size:11px;font-weight:600}.act.svelte-5qpy41{border-radius:var(--radius);width:100%;font:inherit;color:var(--body);cursor:pointer;text-align:left;background:0 0;border:0;flex-direction:column;gap:1px;margin-bottom:4px;padding:7px 8px;display:flex}.act.svelte-5qpy41:hover:not(:disabled){background:var(--hollow)}.act.svelte-5qpy41:disabled{cursor:default}.act.svelte-5qpy41:disabled .name:where(.svelte-5qpy41){opacity:.55}.name.svelte-5qpy41{color:var(--ink);font-size:13px}.why.svelte-5qpy41{color:var(--muted);margin:0;font-size:11.5px;line-height:1.35}.bill.svelte-5qpy41{border:1px solid var(--rule);border-radius:var(--radius);background:var(--paper);padding:10px}.lede.svelte-5qpy41{color:var(--ink);margin:0 0 4px;font-size:13px}.pair.svelte-5qpy41{gap:6px;margin-top:10px;display:flex}.go.svelte-5qpy41{border-radius:var(--radius);background:var(--done);color:var(--paper);font:inherit;cursor:pointer;border:0;flex:1;padding:5px 10px;font-size:12px}.plain.svelte-5qpy41{border:1px solid var(--rule-strong);border-radius:var(--radius);color:var(--body);font:inherit;cursor:pointer;background:0 0;padding:5px 10px;font-size:12px}.track.svelte-5qpy41{background:var(--hollow);border-radius:999px;height:4px;margin:8px 0 6px;overflow:hidden}.fill.svelte-5qpy41{background:var(--done);height:100%;transition:width .3s linear;display:block}.said.svelte-5qpy41{color:var(--muted);margin:6px 0 0;padding:0 8px;font-size:11.5px}.said.bad.svelte-5qpy41{color:var(--broken)}.shell.svelte-c7dw08{flex-direction:column;height:100vh;display:flex;position:relative}.bar.svelte-c7dw08{background:var(--bar);height:48px;color:var(--on-bar);flex:none;align-items:center;gap:16px;padding:0 16px;display:flex;box-shadow:inset 0 -1px #ffffff1f}.brand.svelte-c7dw08{letter-spacing:.02em;opacity:.75;white-space:nowrap;font-size:13px}.brand.svelte-c7dw08 b:where(.svelte-c7dw08){opacity:1;font-weight:600}.search.svelte-c7dw08{border-radius:var(--radius);max-width:520px;color:var(--on-bar);font:inherit;background:#ffffff0f;border:1px solid #ffffff29;flex:1;padding:6px 12px;font-size:13px}.search.svelte-c7dw08::placeholder{color:#ffffff73}.search.svelte-c7dw08:focus-visible{outline:2px solid var(--done);outline-offset:1px}.root.svelte-c7dw08{font-family:var(--mono);opacity:.55;text-overflow:ellipsis;white-space:nowrap;max-width:30ch;margin-left:auto;font-size:11.5px;overflow:hidden}.problems.svelte-c7dw08{background:var(--broken-tint);color:var(--broken);border-bottom:1px solid var(--rule);margin:0;padding:8px 16px;font-size:12.5px;list-style:none}.body.svelte-c7dw08{flex:1;min-height:0;display:flex}.table.svelte-c7dw08{background:var(--paper);flex:1;min-width:0;overflow-y:auto}.note.svelte-c7dw08{color:var(--muted);margin:0;padding:24px 20px;font-size:13px}.snack.svelte-c7dw08{background:var(--bar);height:44px;color:var(--on-bar);border-radius:var(--radius);white-space:nowrap;align-items:center;gap:12px;margin:0;padding:0 12px 0 20px;font-size:13px;display:flex;position:absolute;bottom:24px;left:50%;transform:translate(-50%);box-shadow:0 2px 6px #0610052e,0 34px 64px -28px #06100566}.snack.svelte-c7dw08 svg:where(.svelte-c7dw08){color:#8ed67f}.snack.svelte-c7dw08 button:where(.svelte-c7dw08){height:30px;color:inherit;font:inherit;cursor:pointer;background:#ffffff24;border:0;border-radius:4px;padding:0 12px;font-size:12.5px}:root{--paper:#f8fbf7;--ground:#eff4ee;--hollow:#e5ede4;--rule:#d7dfd5;--rule-strong:#828d80;--muted:#636c61;--body:#374036;--ink:#061005;--bar:#131e12;--on-bar:#f8fbf7;--broken:#b43931;--broken-tint:#eee0db;--missing:#8e5a04;--missing-tint:#e9e4d5;--draft:#2267ad;--draft-tint:#dae6ed;--done:#2e7428;--done-tint:#dce8da;--sans:"Schibsted Grotesk", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;--serif:"Newsreader", ui-serif, Georgia, "Times New Roman", serif;--mono:"JetBrains Mono", ui-monospace, "Cascadia Code", "SF Mono", monospace;--rail:232px;--radius:6px}:root[data-theme=dark]{--paper:#10170f;--ground:#161e15;--hollow:#1d271c;--rule:#2a352a;--rule-strong:#6f7a6d;--muted:#9aa598;--body:#d3dbd1;--ink:#f2f7f1;--broken:#f08a82;--broken-tint:#3a211f;--missing:#dcae4e;--missing-tint:#352c16;--draft:#7db6e8;--draft-tint:#172a3a;--done:#79c46f;--done-tint:#182c16}*{box-sizing:border-box}html,body{height:100%}body{background:var(--ground);color:var(--body);font-family:var(--sans);-webkit-font-smoothing:antialiased;margin:0;font-size:14px;line-height:1.5}code,kbd{font-family:var(--mono);font-size:.92em}@media (prefers-reduced-motion:reduce){*,:before,:after{transition-duration:.01ms!important;animation-duration:.01ms!important}}
@@ -0,0 +1,13 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Verbaly Studio</title>
7
+ <script type="module" crossorigin src="/assets/index-D8NKyN7c.js"></script>
8
+ <link rel="stylesheet" crossorigin href="/assets/index-XfXTmvne.css">
9
+ </head>
10
+ <body>
11
+ <div id="app"></div>
12
+ </body>
13
+ </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@verbaly/studio",
3
- "version": "0.54.0",
3
+ "version": "0.55.0",
4
4
  "description": "Local review server for Verbaly: read, edit and approve translations over the JSON files already in your repo. No account, no database.",
5
5
  "keywords": [
6
6
  "i18n",
@@ -16,7 +16,7 @@
16
16
  "url": "git+https://github.com/AronSoto/verbaly.git",
17
17
  "directory": "packages/studio"
18
18
  },
19
- "homepage": "https://verbaly-web.vercel.app",
19
+ "homepage": "https://verbaly-web.vercel.app/docs/guide/studio",
20
20
  "bugs": {
21
21
  "url": "https://github.com/AronSoto/verbaly/issues"
22
22
  },
@@ -43,15 +43,21 @@
43
43
  "access": "public"
44
44
  },
45
45
  "dependencies": {
46
- "@verbaly/compiler": "^0.54.0"
46
+ "@verbaly/compiler": "^0.55.0"
47
47
  },
48
48
  "devDependencies": {
49
- "@types/node": "^26.4.1"
49
+ "@sveltejs/vite-plugin-svelte": "^7.3.0",
50
+ "@types/node": "^26.4.1",
51
+ "svelte": "^5.57.0",
52
+ "svelte-check": "^4.7.6",
53
+ "typescript": "npm:@typescript/typescript6@^6.0.2",
54
+ "vite": "^8.3.0",
55
+ "verbaly": "^0.55.0"
50
56
  },
51
57
  "scripts": {
52
- "build": "tsdown",
53
- "dev": "tsdown --watch",
58
+ "build": "tsdown && vite build",
59
+ "dev": "vite",
54
60
  "test": "vitest run --passWithNoTests",
55
- "typecheck": "tsc --noEmit"
61
+ "typecheck": "tsc --noEmit && svelte-check --tsconfig ./tsconfig.json --fail-on-warnings"
56
62
  }
57
63
  }