@tolkbot/cli 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +877 -205
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -559,7 +559,7 @@ var require_to_regex_range = __commonJS({
559
559
  let zipped = zip(start, stop);
560
560
  let digits = zipped.length;
561
561
  let pattern = "";
562
- let count = 0;
562
+ let count2 = 0;
563
563
  for (let i = 0; i < digits; i++) {
564
564
  let [startDigit, stopDigit] = zipped[i];
565
565
  if (startDigit === stopDigit) {
@@ -567,13 +567,13 @@ var require_to_regex_range = __commonJS({
567
567
  } else if (startDigit !== "0" || stopDigit !== "9") {
568
568
  pattern += toCharacterClass(startDigit, stopDigit, options);
569
569
  } else {
570
- count++;
570
+ count2++;
571
571
  }
572
572
  }
573
- if (count) {
573
+ if (count2) {
574
574
  pattern += options.shorthand === true ? "\\d" : "[0-9]";
575
575
  }
576
- return { pattern, count: [count], digits };
576
+ return { pattern, count: [count2], digits };
577
577
  }
578
578
  function splitToPatterns(min, max, tok, options) {
579
579
  let ranges = splitToRanges(min, max);
@@ -2320,13 +2320,13 @@ var require_parse2 = __commonJS({
2320
2320
  consume(token.value);
2321
2321
  };
2322
2322
  const negate = () => {
2323
- let count = 1;
2323
+ let count2 = 1;
2324
2324
  while (peek() === "!" && (peek(2) !== "(" || peek(3) === "?")) {
2325
2325
  advance();
2326
2326
  state.start++;
2327
- count++;
2327
+ count2++;
2328
2328
  }
2329
- if (count % 2 === 0) {
2329
+ if (count2 % 2 === 0) {
2330
2330
  return false;
2331
2331
  }
2332
2332
  state.negated = true;
@@ -5784,10 +5784,10 @@ var require_out4 = __commonJS({
5784
5784
  });
5785
5785
 
5786
5786
  // src/main.ts
5787
- import { Command as Command7, CommanderError, Option } from "commander";
5787
+ import { Command as Command9, CommanderError, Option as Option2 } from "commander";
5788
5788
 
5789
5789
  // package.json
5790
- var version = "0.4.0";
5790
+ var version = "0.6.0";
5791
5791
 
5792
5792
  // src/commands/auth/index.ts
5793
5793
  import { Command } from "commander";
@@ -6315,7 +6315,7 @@ import { ConvexHttpClient } from "convex/browser";
6315
6315
  var BACKEND_METHODS = /* @__PURE__ */ new Set(["query", "mutation", "action"]);
6316
6316
  var client = null;
6317
6317
  function getConvexClient() {
6318
- client ??= new ConvexHttpClient(requireConvexUrl());
6318
+ client ??= new ConvexHttpClient(requireConvexUrl(), { logger: false });
6319
6319
  return client;
6320
6320
  }
6321
6321
  function translateAuthError(error62, resolved) {
@@ -8068,14 +8068,14 @@ function codePointLength(str) {
8068
8068
  const units = str.length;
8069
8069
  if (!highSurrogate.test(str))
8070
8070
  return units;
8071
- let count = units;
8071
+ let count2 = units;
8072
8072
  for (let i = 0; i < units - 1; i++) {
8073
8073
  if ((str.charCodeAt(i) & 64512) === 55296 && (str.charCodeAt(i + 1) & 64512) === 56320) {
8074
- count--;
8074
+ count2--;
8075
8075
  i++;
8076
8076
  }
8077
8077
  }
8078
- return count;
8078
+ return count2;
8079
8079
  }
8080
8080
  function getLengthableOrigin(input2) {
8081
8081
  if (Array.isArray(input2))
@@ -12432,8 +12432,8 @@ function az_default() {
12432
12432
  localeError: error2()
12433
12433
  };
12434
12434
  }
12435
- function getBelarusianPlural(count, one, few, many) {
12436
- const absCount = Math.abs(count);
12435
+ function getBelarusianPlural(count2, one, few, many) {
12436
+ const absCount = Math.abs(count2);
12437
12437
  const lastDigit = absCount % 10;
12438
12438
  const lastTwoDigits = absCount % 100;
12439
12439
  if (lastTwoDigits >= 11 && lastTwoDigits <= 19) {
@@ -15038,8 +15038,8 @@ function hu_default() {
15038
15038
  localeError: error23()
15039
15039
  };
15040
15040
  }
15041
- function getArmenianPlural(count, one, many) {
15042
- return Math.abs(count) === 1 ? one : many;
15041
+ function getArmenianPlural(count2, one, many) {
15042
+ return Math.abs(count2) === 1 ? one : many;
15043
15043
  }
15044
15044
  function withDefiniteArticle(word) {
15045
15045
  if (!word)
@@ -17731,8 +17731,8 @@ function ro_default() {
17731
17731
  localeError: error45()
17732
17732
  };
17733
17733
  }
17734
- function getRussianPlural(count, one, few, many) {
17735
- const absCount = Math.abs(count);
17734
+ function getRussianPlural(count2, one, few, many) {
17735
+ const absCount = Math.abs(count2);
17736
17736
  const lastDigit = absCount % 10;
17737
17737
  const lastTwoDigits = absCount % 100;
17738
17738
  if (lastTwoDigits >= 11 && lastTwoDigits <= 19) {
@@ -26823,6 +26823,75 @@ var SUPPORTED_LOCALES = [
26823
26823
  "eu",
26824
26824
  "gl"
26825
26825
  ];
26826
+ var LOCALE_NAMES = {
26827
+ en: "English",
26828
+ "en-us": "English (US)",
26829
+ "en-gb": "English (UK)",
26830
+ "en-au": "English (Australia)",
26831
+ "en-ca": "English (Canada)",
26832
+ es: "Spanish",
26833
+ "es-es": "Spanish (Spain)",
26834
+ "es-mx": "Spanish (Mexico)",
26835
+ "es-ar": "Spanish (Argentina)",
26836
+ pt: "Portuguese",
26837
+ "pt-br": "Portuguese (Brazil)",
26838
+ "pt-pt": "Portuguese (Portugal)",
26839
+ zh: "Chinese",
26840
+ "zh-cn": "Chinese (Simplified)",
26841
+ "zh-tw": "Chinese (Traditional)",
26842
+ "zh-hk": "Chinese (Hong Kong)",
26843
+ fr: "French",
26844
+ "fr-fr": "French (France)",
26845
+ "fr-ca": "French (Canada)",
26846
+ de: "German",
26847
+ "de-de": "German (Germany)",
26848
+ "de-at": "German (Austria)",
26849
+ "de-ch": "German (Switzerland)",
26850
+ no: "Norwegian",
26851
+ nb: "Norwegian Bokm\xE5l",
26852
+ nn: "Norwegian Nynorsk",
26853
+ sv: "Swedish",
26854
+ da: "Danish",
26855
+ fi: "Finnish",
26856
+ is: "Icelandic",
26857
+ it: "Italian",
26858
+ nl: "Dutch",
26859
+ "nl-be": "Dutch (Belgium)",
26860
+ pl: "Polish",
26861
+ cs: "Czech",
26862
+ sk: "Slovak",
26863
+ hu: "Hungarian",
26864
+ ro: "Romanian",
26865
+ bg: "Bulgarian",
26866
+ hr: "Croatian",
26867
+ sl: "Slovenian",
26868
+ sr: "Serbian",
26869
+ uk: "Ukrainian",
26870
+ el: "Greek",
26871
+ tr: "Turkish",
26872
+ ru: "Russian",
26873
+ ja: "Japanese",
26874
+ ko: "Korean",
26875
+ th: "Thai",
26876
+ vi: "Vietnamese",
26877
+ id: "Indonesian",
26878
+ ms: "Malay",
26879
+ tl: "Tagalog",
26880
+ hi: "Hindi",
26881
+ bn: "Bengali",
26882
+ ta: "Tamil",
26883
+ ar: "Arabic",
26884
+ he: "Hebrew",
26885
+ fa: "Persian",
26886
+ af: "Afrikaans",
26887
+ sw: "Swahili",
26888
+ et: "Estonian",
26889
+ lv: "Latvian",
26890
+ lt: "Lithuanian",
26891
+ ca: "Catalan",
26892
+ eu: "Basque",
26893
+ gl: "Galician"
26894
+ };
26826
26895
  var DEFAULT_BEHAVIORAL_SETTINGS = {
26827
26896
  tonePreset: "formal",
26828
26897
  toneInstructions: "Use a formal, professional tone. Avoid slang.",
@@ -27354,6 +27423,7 @@ var analyticsEventNames = [
27354
27423
  "glossary_imported",
27355
27424
  "translation_job_viewed",
27356
27425
  "translation_logs_viewed",
27426
+ "activity_timeline_viewed",
27357
27427
  "cli_login_completed",
27358
27428
  "cli_command_completed",
27359
27429
  "cli_init_completed",
@@ -28316,6 +28386,16 @@ async function loadPlanContext(cwd) {
28316
28386
  framework: config3.config.framework
28317
28387
  };
28318
28388
  }
28389
+ async function readCreditBalance(ctx) {
28390
+ try {
28391
+ const balance = await ctx.client.query(api.usage.getCreditBalance, {
28392
+ organizationId: ctx.project.organizationId
28393
+ });
28394
+ return balance?.total ?? null;
28395
+ } catch {
28396
+ return null;
28397
+ }
28398
+ }
28319
28399
  function describePlan(plan) {
28320
28400
  const { summary } = plan;
28321
28401
  const lines = [
@@ -28331,9 +28411,9 @@ function describeLeftOut(plan) {
28331
28411
  const lines = [];
28332
28412
  const protectedCount = plan.protectedKeys.length;
28333
28413
  if (protectedCount > 0) {
28334
- const reasons = Object.entries(summary.protected).map(([reason, n]) => `${n} ${reason}`).join(", ");
28414
+ const reasons2 = Object.entries(summary.protected).map(([reason, n]) => `${n} ${reason}`).join(", ");
28335
28415
  lines.push(
28336
- `${protectedCount} changed but kept for a person (${reasons})`
28416
+ `${protectedCount} changed but kept for a person (${reasons2})`
28337
28417
  );
28338
28418
  }
28339
28419
  if (summary.held > 0)
@@ -28551,11 +28631,11 @@ function flattenValues(data) {
28551
28631
  const values = /* @__PURE__ */ new Map();
28552
28632
  function walk(obj, prefix) {
28553
28633
  for (const [key, value] of Object.entries(obj)) {
28554
- const fullKey5 = prefix ? `${prefix}.${key}` : key;
28634
+ const fullKey6 = prefix ? `${prefix}.${key}` : key;
28555
28635
  if (typeof value === "string") {
28556
- values.set(fullKey5, value);
28636
+ values.set(fullKey6, value);
28557
28637
  } else if (typeof value === "object" && value !== null) {
28558
- walk(value, fullKey5);
28638
+ walk(value, fullKey6);
28559
28639
  }
28560
28640
  }
28561
28641
  }
@@ -31310,6 +31390,13 @@ var BILLING_BLOCKED = "BILLING_BLOCKED";
31310
31390
 
31311
31391
  // ../shared/src/projects/localeSetup.ts
31312
31392
  var LOCALE_SETUP_STALE_MS = 15 * 60 * 1e3;
31393
+ function localeSetupState(row, now) {
31394
+ if (!row) return "not-set-up";
31395
+ if (row.status === "onboarded") return "set-up";
31396
+ if (row.onboardingError !== void 0) return "failed";
31397
+ if (row.onboardingRequestedAt === void 0) return "not-set-up";
31398
+ return now - row.onboardingRequestedAt > LOCALE_SETUP_STALE_MS ? "failed" : "running";
31399
+ }
31313
31400
 
31314
31401
  // ../shared/src/projects/index.ts
31315
31402
  var PROJECT_ARCHIVED = "PROJECT_ARCHIVED";
@@ -31343,6 +31430,7 @@ var ERROR_CODES = {
31343
31430
  login_failed: EXIT_CODES.failure,
31344
31431
  // 2: usage
31345
31432
  usage: EXIT_CODES.usage,
31433
+ unsupported_locale: EXIT_CODES.usage,
31346
31434
  // 3: not authenticated, or not allowed
31347
31435
  not_authenticated: EXIT_CODES.auth,
31348
31436
  token_expired: EXIT_CODES.auth,
@@ -31665,24 +31753,377 @@ jobsCommand.command("cancel").description("Cancel a running translation job").ar
31665
31753
  await runJobsCancel(validateOrExit(schema, { ...opts, jobId }));
31666
31754
  });
31667
31755
 
31668
- // src/commands/learnings/index.ts
31669
- import { Command as Command4 } from "commander";
31756
+ // src/commands/languages/index.ts
31757
+ import { Command as Command4, Option } from "commander";
31670
31758
  import { z as z6 } from "zod/v4";
31671
31759
 
31672
- // src/commands/learnings/archive.tsx
31760
+ // src/commands/languages/add.tsx
31673
31761
  import { Spinner as Spinner9 } from "@inkjs/ui";
31674
- import { Box as Box12, render as render12, Text as Text12 } from "ink";
31762
+ import { Box as Box12, Text as Text12 } from "ink";
31763
+
31764
+ // src/commands/languages/languages-core.ts
31765
+ var DEFAULT_EXISTING_MODE = "trust";
31766
+ var MAX_SUGGESTIONS = 6;
31767
+ function editDistance(a, b) {
31768
+ let previous = Array.from({ length: b.length + 1 }, (_, i) => i);
31769
+ for (let i = 1; i <= a.length; i++) {
31770
+ const current = [i];
31771
+ for (let j = 1; j <= b.length; j++) {
31772
+ current[j] = Math.min(
31773
+ (previous[j] ?? 0) + 1,
31774
+ (current[j - 1] ?? 0) + 1,
31775
+ (previous[j - 1] ?? 0) + (a[i - 1] === b[j - 1] ? 0 : 1)
31776
+ );
31777
+ }
31778
+ previous = current;
31779
+ }
31780
+ return previous[b.length] ?? 0;
31781
+ }
31782
+ function closestLocales(input2) {
31783
+ const typed = input2.trim().toLowerCase().replace(/_/g, "-");
31784
+ const language = typed.split("-")[0] ?? "";
31785
+ const found = /* @__PURE__ */ new Set();
31786
+ for (const locale of SUPPORTED_LOCALES) {
31787
+ if (locale.split("-")[0] === language) found.add(locale);
31788
+ }
31789
+ if (typed.length >= 3) {
31790
+ for (const locale of SUPPORTED_LOCALES) {
31791
+ if (LOCALE_NAMES[locale].toLowerCase().includes(typed))
31792
+ found.add(locale);
31793
+ }
31794
+ }
31795
+ for (const locale of SUPPORTED_LOCALES) {
31796
+ if (editDistance(locale, typed) === 1) found.add(locale);
31797
+ }
31798
+ return [...found].slice(0, MAX_SUGGESTIONS);
31799
+ }
31800
+ function resolveLocales(inputs) {
31801
+ const resolved = [];
31802
+ const unsupported = [];
31803
+ for (const input2 of inputs) {
31804
+ const locale = canonicalGlossaryLocale(input2.trim());
31805
+ if (!locale) {
31806
+ unsupported.push(input2);
31807
+ continue;
31808
+ }
31809
+ if (!resolved.includes(locale)) resolved.push(locale);
31810
+ }
31811
+ if (unsupported.length === 0) return resolved;
31812
+ const lines = unsupported.map((input2) => {
31813
+ const close = closestLocales(input2);
31814
+ return close.length > 0 ? `For ${input2}, the closest supported codes are ${close.join(", ")}.` : `Nothing supported is close to ${input2}.`;
31815
+ });
31816
+ const anyWithout = unsupported.some((i) => closestLocales(i).length === 0);
31817
+ if (anyWithout) {
31818
+ lines.push(`Supported codes: ${SUPPORTED_LOCALES.join(", ")}.`);
31819
+ }
31820
+ throw new CliError(
31821
+ `TolkBot does not support ${unsupported.join(", ")}.`,
31822
+ lines.join(" "),
31823
+ "unsupported_locale"
31824
+ );
31825
+ }
31826
+ function planAdd(project, rows, locales, now) {
31827
+ const base = locales.find((l) => sameLocale(l, project.baseLanguage));
31828
+ if (base) {
31829
+ throw new UsageError(
31830
+ `${base} is the project's base language, so it cannot be added as a target.`,
31831
+ "Name a language to translate into."
31832
+ );
31833
+ }
31834
+ return locales.map((locale) => {
31835
+ const onProject = project.targetLanguages.some(
31836
+ (t) => sameLocale(t, locale)
31837
+ );
31838
+ if (!onProject)
31839
+ return { locale, action: "add", setup: "not-on-project" };
31840
+ const row = rows.find((r) => sameLocale(r.locale, locale));
31841
+ const setup = localeSetupState(row, now);
31842
+ const action = setup === "set-up" || setup === "running" ? "none" : "set_up";
31843
+ return { locale, action, setup };
31844
+ });
31845
+ }
31846
+ function targetsAfter(project, plan) {
31847
+ return [
31848
+ ...project.targetLanguages,
31849
+ ...plan.filter((p) => p.action === "add").map((p) => p.locale)
31850
+ ];
31851
+ }
31852
+ function localeName(locale) {
31853
+ return LOCALE_NAMES[locale] ?? locale;
31854
+ }
31855
+ function describeOutcome(outcome) {
31856
+ const name = `${outcome.locale} (${localeName(outcome.locale)})`;
31857
+ switch (outcome.outcome) {
31858
+ case "added":
31859
+ return `\u2713 ${name} added and set up.`;
31860
+ case "set_up":
31861
+ return `\u2713 ${name} was on the project; now set up.`;
31862
+ case "unchanged":
31863
+ return outcome.setup === "running" ? `- ${name} is already on the project and being set up.` : `- ${name} is already on the project.`;
31864
+ case "failed":
31865
+ return `\u2717 ${name} is on the project, but setting it up failed: ${outcome.error}`;
31866
+ }
31867
+ }
31868
+ function describeNextSteps(ready, autoCreatePR) {
31869
+ if (ready.length === 0) return [];
31870
+ const lines = [
31871
+ "Next, fill the files now:",
31872
+ ...ready.map((l) => ` ${translateCommand(l)}`)
31873
+ ];
31874
+ if (autoCreatePR) {
31875
+ lines.push(
31876
+ "Or wait: pull requests are on for this project, so the next push that changes the base file opens one per language, with the keys that push added or changed."
31877
+ );
31878
+ }
31879
+ return lines;
31880
+ }
31881
+ function translateCommand(locale) {
31882
+ return `tolkbot translate --dst ${locale}`;
31883
+ }
31884
+
31885
+ // src/commands/languages/add.tsx
31675
31886
  import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
31887
+ async function runLanguagesAdd(opts) {
31888
+ const locales = resolveLocales(opts.locales);
31889
+ const ctx = await loadPlanContext(process.cwd());
31890
+ const { project } = ctx;
31891
+ const ids = {
31892
+ organizationId: project.organizationId,
31893
+ projectId: project._id
31894
+ };
31895
+ const rows = await ctx.client.query(api.projectLocales.list, ids);
31896
+ const plan = planAdd(project, rows, locales, Date.now());
31897
+ const outcomes = [];
31898
+ const progress = renderTransient(
31899
+ /* @__PURE__ */ jsxs12(Box12, { children: [
31900
+ /* @__PURE__ */ jsx12(Spinner9, {}),
31901
+ /* @__PURE__ */ jsxs12(Text12, { children: [
31902
+ " ",
31903
+ "Adding ",
31904
+ locales.join(", "),
31905
+ " to ",
31906
+ project.name,
31907
+ "\u2026"
31908
+ ] })
31909
+ ] })
31910
+ );
31911
+ try {
31912
+ if (plan.some((p) => p.action === "add")) {
31913
+ await ctx.client.mutation(api.projects.update, {
31914
+ ...ids,
31915
+ patch: {
31916
+ targetLanguages: targetsAfter(project, plan)
31917
+ }
31918
+ });
31919
+ }
31920
+ const client3 = await getAuthenticatedConvexClient();
31921
+ for (const { locale, action, setup } of plan) {
31922
+ if (action === "none") {
31923
+ outcomes.push({
31924
+ locale,
31925
+ outcome: "unchanged",
31926
+ setup: setup === "running" ? "running" : "set-up"
31927
+ });
31928
+ continue;
31929
+ }
31930
+ try {
31931
+ await client3.action(api.actions.onboardLocale.onboardLocale, {
31932
+ ...ids,
31933
+ locale,
31934
+ mode: opts.existing
31935
+ });
31936
+ outcomes.push({
31937
+ locale,
31938
+ outcome: action === "add" ? "added" : "set_up"
31939
+ });
31940
+ } catch (error62) {
31941
+ outcomes.push({
31942
+ locale,
31943
+ outcome: "failed",
31944
+ error: toCliError(error62).baseMessage
31945
+ });
31946
+ }
31947
+ }
31948
+ } finally {
31949
+ progress.unmount();
31950
+ }
31951
+ const ready = outcomes.filter((o) => o.outcome === "added" || o.outcome === "set_up").map((o) => o.locale);
31952
+ const autoCreatePR = project.workflow?.autoCreatePR ?? DEFAULT_BEHAVIORAL_SETTINGS.workflow.autoCreatePR;
31953
+ const failed = outcomes.filter((o) => o.outcome === "failed");
31954
+ const payload = {
31955
+ project: { name: project.name, slug: project.slug },
31956
+ existing: opts.existing,
31957
+ changed: ready.length > 0 || plan.some((p) => p.action === "add"),
31958
+ languages: outcomes,
31959
+ targetLanguages: targetsAfter(project, plan),
31960
+ next: {
31961
+ translate: ready.map(translateCommand),
31962
+ pullRequestOnPush: autoCreatePR
31963
+ }
31964
+ };
31965
+ const failure2 = failed.length > 0 ? new CliError(
31966
+ `Setting up ${failed.map((o) => o.locale).join(", ")} failed.`,
31967
+ "Run the same command again to retry, or set it up from the project's Translations page."
31968
+ ) : null;
31969
+ if (opts.json) {
31970
+ if (!failure2) jsonSuccess(payload);
31971
+ jsonResult(payload, 1);
31972
+ fail(failure2);
31973
+ }
31974
+ for (const outcome of outcomes) console.log(describeOutcome(outcome));
31975
+ if (!payload.changed) console.log("Nothing changed.");
31976
+ const next = describeNextSteps(ready, autoCreatePR);
31977
+ if (next.length > 0) {
31978
+ console.log("");
31979
+ for (const line of next) console.log(line);
31980
+ }
31981
+ if (failure2) fail(failure2);
31982
+ }
31983
+
31984
+ // src/commands/languages/list.ts
31985
+ var SETUP_WORDS = {
31986
+ "set-up": "set up",
31987
+ running: "being set up",
31988
+ failed: "setup failed",
31989
+ "not-set-up": "not set up"
31990
+ };
31991
+ async function runLanguagesList(opts) {
31992
+ const ctx = await loadPlanContext(process.cwd());
31993
+ const { project } = ctx;
31994
+ const rows = await ctx.client.query(api.projectLocales.list, {
31995
+ organizationId: project.organizationId,
31996
+ projectId: project._id
31997
+ });
31998
+ const scope = scopeOf(project);
31999
+ const files = await listLocaleFiles(ctx.root, scope);
32000
+ const sourceFiles = filesForLocale(files, project.baseLanguage);
32001
+ const found = (file2) => ({
32002
+ path: file2.relativePath,
32003
+ exists: true
32004
+ });
32005
+ const fromPattern = (locale) => {
32006
+ const existing = filesForLocale(files, locale);
32007
+ if (existing.length > 0) return existing.map(found);
32008
+ return [
32009
+ {
32010
+ path: replaceLocalePlaceholder(fullPattern(scope), locale),
32011
+ exists: false
32012
+ }
32013
+ ];
32014
+ };
32015
+ const base = {
32016
+ locale: project.baseLanguage,
32017
+ name: localeName(project.baseLanguage),
32018
+ files: fromPattern(project.baseLanguage)
32019
+ };
32020
+ const now = Date.now();
32021
+ const targets = project.targetLanguages.map((locale) => ({
32022
+ locale,
32023
+ name: localeName(locale),
32024
+ setup: localeSetupState(
32025
+ rows.find((r) => sameLocale(r.locale, locale)),
32026
+ now
32027
+ ),
32028
+ files: sourceFiles.length === 0 ? fromPattern(locale) : sourceFiles.map((source) => {
32029
+ const target = targetFileFor(
32030
+ source,
32031
+ locale,
32032
+ files,
32033
+ scope
32034
+ );
32035
+ return {
32036
+ path: target.relativePath,
32037
+ exists: target.existing !== null
32038
+ };
32039
+ })
32040
+ }));
32041
+ if (opts.json) {
32042
+ jsonSuccess({
32043
+ project: { name: project.name, slug: project.slug },
32044
+ pattern: fullPattern(scope),
32045
+ base,
32046
+ targets
32047
+ });
32048
+ }
32049
+ console.log(
32050
+ `${project.name} (${project.slug}), files ${fullPattern(scope)}`
32051
+ );
32052
+ console.log("");
32053
+ const printFiles = (language) => {
32054
+ for (const file2 of language.files) {
32055
+ console.log(
32056
+ ` ${file2.path}${file2.exists ? "" : " (no file yet)"}`
32057
+ );
32058
+ }
32059
+ };
32060
+ console.log(`${base.locale} (${base.name}), base language`);
32061
+ printFiles(base);
32062
+ for (const target of targets) {
32063
+ console.log(
32064
+ `${target.locale} (${target.name}), ${SETUP_WORDS[target.setup]}`
32065
+ );
32066
+ printFiles(target);
32067
+ }
32068
+ if (targets.length === 0) {
32069
+ console.log("");
32070
+ console.log(
32071
+ "No target languages yet. Add one with 'tolkbot languages add <code>'."
32072
+ );
32073
+ }
32074
+ }
32075
+
32076
+ // src/commands/languages/index.ts
32077
+ var languagesCommand = new Command4("languages").description(
32078
+ "List the project's languages and add target languages"
32079
+ );
32080
+ var JSON_HELP2 = "Output as JSON (machine-readable)";
32081
+ languagesCommand.command("list").description(
32082
+ "The base language and the target languages, with the files each one maps to and whether they exist"
32083
+ ).option("--json", JSON_HELP2).action(async (opts) => {
32084
+ const schema = z6.object({ json: z6.boolean().optional() });
32085
+ await runLanguagesList(validateOrExit(schema, opts));
32086
+ });
32087
+ languagesCommand.command("add").description(
32088
+ "Add target languages to the project and set them up, as the dashboard's Add language dialog does. Translating is 'tolkbot translate'"
32089
+ ).argument("<locales...>", "Locale codes, e.g. nb de pt-BR").addOption(
32090
+ new Option(
32091
+ "--existing <mode>",
32092
+ "What to do with translations the repository already has: keep them as written by a person (trust), or flag them for review"
32093
+ ).choices(["trust", "review"]).default(DEFAULT_EXISTING_MODE)
32094
+ ).option("--json", JSON_HELP2).addHelpText(
32095
+ "after",
32096
+ `
32097
+ Examples:
32098
+ $ tolkbot languages add nb
32099
+ $ tolkbot languages add de fr --existing review`
32100
+ ).action(async (locales, opts) => {
32101
+ const schema = z6.object({
32102
+ locales: z6.array(z6.string().min(1)).min(1),
32103
+ existing: z6.enum(["trust", "review"]),
32104
+ json: z6.boolean().optional()
32105
+ });
32106
+ await runLanguagesAdd(validateOrExit(schema, { ...opts, locales }));
32107
+ });
32108
+
32109
+ // src/commands/learnings/index.ts
32110
+ import { Command as Command5 } from "commander";
32111
+ import { z as z7 } from "zod/v4";
32112
+
32113
+ // src/commands/learnings/archive.tsx
32114
+ import { Spinner as Spinner10 } from "@inkjs/ui";
32115
+ import { Box as Box13, render as render12, Text as Text13 } from "ink";
32116
+ import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
31676
32117
  function ArchiveUI({ state }) {
31677
32118
  if (state.type === "archiving") {
31678
- return /* @__PURE__ */ jsxs12(Box12, { children: [
31679
- /* @__PURE__ */ jsx12(Spinner9, {}),
31680
- /* @__PURE__ */ jsx12(Text12, { children: " Archiving learning\u2026" })
32119
+ return /* @__PURE__ */ jsxs13(Box13, { children: [
32120
+ /* @__PURE__ */ jsx13(Spinner10, {}),
32121
+ /* @__PURE__ */ jsx13(Text13, { children: " Archiving learning\u2026" })
31681
32122
  ] });
31682
32123
  }
31683
- return /* @__PURE__ */ jsxs12(Box12, { children: [
31684
- /* @__PURE__ */ jsx12(Text12, { color: "green", children: "\u2713" }),
31685
- /* @__PURE__ */ jsx12(Text12, { children: " Learning archived" })
32124
+ return /* @__PURE__ */ jsxs13(Box13, { children: [
32125
+ /* @__PURE__ */ jsx13(Text13, { color: "green", children: "\u2713" }),
32126
+ /* @__PURE__ */ jsx13(Text13, { children: " Learning archived" })
31686
32127
  ] });
31687
32128
  }
31688
32129
  async function runLearningsArchive(opts) {
@@ -31699,20 +32140,20 @@ async function runLearningsArchive(opts) {
31699
32140
  jsonSuccess({ id: opts.id, ok: true });
31700
32141
  }
31701
32142
  const { unmount } = renderTransient(
31702
- /* @__PURE__ */ jsx12(ArchiveUI, { state: { type: "archiving" } })
32143
+ /* @__PURE__ */ jsx13(ArchiveUI, { state: { type: "archiving" } })
31703
32144
  );
31704
32145
  try {
31705
32146
  await run();
31706
32147
  } finally {
31707
32148
  unmount();
31708
32149
  }
31709
- render12(/* @__PURE__ */ jsx12(ArchiveUI, { state: { type: "success" } })).unmount();
32150
+ render12(/* @__PURE__ */ jsx13(ArchiveUI, { state: { type: "success" } })).unmount();
31710
32151
  }
31711
32152
 
31712
32153
  // src/commands/learnings/list.tsx
31713
- import { Spinner as Spinner10 } from "@inkjs/ui";
31714
- import { Box as Box13, render as render13, Text as Text13 } from "ink";
31715
- import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
32154
+ import { Spinner as Spinner11 } from "@inkjs/ui";
32155
+ import { Box as Box14, render as render13, Text as Text14 } from "ink";
32156
+ import { jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
31716
32157
  var feedbackLabels = {
31717
32158
  "incorrect-term": "Incorrect term",
31718
32159
  "wrong-tone": "Wrong tone",
@@ -31722,47 +32163,47 @@ var feedbackLabels = {
31722
32163
  };
31723
32164
  function LearningsListUI({ state }) {
31724
32165
  if (state.type === "loading") {
31725
- return /* @__PURE__ */ jsxs13(Box13, { children: [
31726
- /* @__PURE__ */ jsx13(Spinner10, {}),
31727
- /* @__PURE__ */ jsx13(Text13, { children: " Loading learnings\u2026" })
32166
+ return /* @__PURE__ */ jsxs14(Box14, { children: [
32167
+ /* @__PURE__ */ jsx14(Spinner11, {}),
32168
+ /* @__PURE__ */ jsx14(Text14, { children: " Loading learnings\u2026" })
31728
32169
  ] });
31729
32170
  }
31730
32171
  const { learnings } = state;
31731
32172
  if (learnings.length === 0) {
31732
- return /* @__PURE__ */ jsx13(Box13, { children: /* @__PURE__ */ jsx13(Text13, { dimColor: true, children: "No learnings found." }) });
32173
+ return /* @__PURE__ */ jsx14(Box14, { children: /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: "No learnings found." }) });
31733
32174
  }
31734
- return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", children: [
31735
- /* @__PURE__ */ jsx13(Box13, { marginBottom: 1, children: /* @__PURE__ */ jsxs13(Text13, { bold: true, children: [
32175
+ return /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", children: [
32176
+ /* @__PURE__ */ jsx14(Box14, { marginBottom: 1, children: /* @__PURE__ */ jsxs14(Text14, { bold: true, children: [
31736
32177
  learnings.length,
31737
32178
  " learning",
31738
32179
  learnings.length === 1 ? "" : "s"
31739
32180
  ] }) }),
31740
- learnings.map((l) => /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", marginBottom: 1, children: [
31741
- /* @__PURE__ */ jsxs13(Box13, { gap: 1, children: [
31742
- /* @__PURE__ */ jsxs13(Text13, { bold: true, children: [
32181
+ learnings.map((l) => /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", marginBottom: 1, children: [
32182
+ /* @__PURE__ */ jsxs14(Box14, { gap: 1, children: [
32183
+ /* @__PURE__ */ jsxs14(Text14, { bold: true, children: [
31743
32184
  l.srcLocale,
31744
32185
  " \u2192 ",
31745
32186
  l.dstLocale
31746
32187
  ] }),
31747
- /* @__PURE__ */ jsxs13(Text13, { dimColor: true, children: [
32188
+ /* @__PURE__ */ jsxs14(Text14, { dimColor: true, children: [
31748
32189
  "[",
31749
32190
  feedbackLabels[l.feedbackType] ?? l.feedbackType,
31750
32191
  "]"
31751
32192
  ] }),
31752
- l.status === "archived" && /* @__PURE__ */ jsx13(Text13, { color: "yellow", children: "[archived]" })
32193
+ l.status === "archived" && /* @__PURE__ */ jsx14(Text14, { color: "yellow", children: "[archived]" })
31753
32194
  ] }),
31754
- /* @__PURE__ */ jsx13(Box13, { paddingLeft: 2, children: /* @__PURE__ */ jsxs13(Text13, { children: [
32195
+ /* @__PURE__ */ jsx14(Box14, { paddingLeft: 2, children: /* @__PURE__ */ jsxs14(Text14, { children: [
31755
32196
  '"',
31756
32197
  l.sourceText,
31757
32198
  '" \u2192 "',
31758
32199
  l.suggestedTranslation,
31759
32200
  '"'
31760
32201
  ] }) }),
31761
- l.reasoning && /* @__PURE__ */ jsx13(Box13, { paddingLeft: 2, children: /* @__PURE__ */ jsxs13(Text13, { dimColor: true, children: [
32202
+ l.reasoning && /* @__PURE__ */ jsx14(Box14, { paddingLeft: 2, children: /* @__PURE__ */ jsxs14(Text14, { dimColor: true, children: [
31762
32203
  "Reason: ",
31763
32204
  l.reasoning
31764
32205
  ] }) }),
31765
- /* @__PURE__ */ jsx13(Box13, { paddingLeft: 2, children: /* @__PURE__ */ jsxs13(Text13, { dimColor: true, children: [
32206
+ /* @__PURE__ */ jsx14(Box14, { paddingLeft: 2, children: /* @__PURE__ */ jsxs14(Text14, { dimColor: true, children: [
31766
32207
  "ID: ",
31767
32208
  l._id
31768
32209
  ] }) })
@@ -31797,7 +32238,7 @@ async function runLearningsList(opts) {
31797
32238
  });
31798
32239
  }
31799
32240
  const { unmount } = renderTransient(
31800
- /* @__PURE__ */ jsx13(LearningsListUI, { state: { type: "loading" } })
32241
+ /* @__PURE__ */ jsx14(LearningsListUI, { state: { type: "loading" } })
31801
32242
  );
31802
32243
  let learnings;
31803
32244
  try {
@@ -31806,24 +32247,24 @@ async function runLearningsList(opts) {
31806
32247
  unmount();
31807
32248
  }
31808
32249
  render13(
31809
- /* @__PURE__ */ jsx13(LearningsListUI, { state: { type: "success", learnings } })
32250
+ /* @__PURE__ */ jsx14(LearningsListUI, { state: { type: "success", learnings } })
31810
32251
  ).unmount();
31811
32252
  }
31812
32253
 
31813
32254
  // src/commands/learnings/remove.tsx
31814
- import { Spinner as Spinner11 } from "@inkjs/ui";
31815
- import { Box as Box14, render as render14, Text as Text14 } from "ink";
31816
- import { jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
32255
+ import { Spinner as Spinner12 } from "@inkjs/ui";
32256
+ import { Box as Box15, render as render14, Text as Text15 } from "ink";
32257
+ import { jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
31817
32258
  function RemoveUI2({ state }) {
31818
32259
  if (state.type === "removing") {
31819
- return /* @__PURE__ */ jsxs14(Box14, { children: [
31820
- /* @__PURE__ */ jsx14(Spinner11, {}),
31821
- /* @__PURE__ */ jsx14(Text14, { children: " Removing learning\u2026" })
32260
+ return /* @__PURE__ */ jsxs15(Box15, { children: [
32261
+ /* @__PURE__ */ jsx15(Spinner12, {}),
32262
+ /* @__PURE__ */ jsx15(Text15, { children: " Removing learning\u2026" })
31822
32263
  ] });
31823
32264
  }
31824
- return /* @__PURE__ */ jsxs14(Box14, { children: [
31825
- /* @__PURE__ */ jsx14(Text14, { color: "green", children: "\u2713" }),
31826
- /* @__PURE__ */ jsx14(Text14, { children: " Learning removed" })
32265
+ return /* @__PURE__ */ jsxs15(Box15, { children: [
32266
+ /* @__PURE__ */ jsx15(Text15, { color: "green", children: "\u2713" }),
32267
+ /* @__PURE__ */ jsx15(Text15, { children: " Learning removed" })
31827
32268
  ] });
31828
32269
  }
31829
32270
  async function runLearningsRemove(opts) {
@@ -31840,62 +32281,62 @@ async function runLearningsRemove(opts) {
31840
32281
  jsonSuccess({ id: opts.id, ok: true });
31841
32282
  }
31842
32283
  const { unmount } = renderTransient(
31843
- /* @__PURE__ */ jsx14(RemoveUI2, { state: { type: "removing" } })
32284
+ /* @__PURE__ */ jsx15(RemoveUI2, { state: { type: "removing" } })
31844
32285
  );
31845
32286
  try {
31846
32287
  await run();
31847
32288
  } finally {
31848
32289
  unmount();
31849
32290
  }
31850
- render14(/* @__PURE__ */ jsx14(RemoveUI2, { state: { type: "success" } })).unmount();
32291
+ render14(/* @__PURE__ */ jsx15(RemoveUI2, { state: { type: "success" } })).unmount();
31851
32292
  }
31852
32293
 
31853
32294
  // src/commands/learnings/show.tsx
31854
- import { Spinner as Spinner12 } from "@inkjs/ui";
31855
- import { Box as Box15, render as render15, Text as Text15 } from "ink";
31856
- import { jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
32295
+ import { Spinner as Spinner13 } from "@inkjs/ui";
32296
+ import { Box as Box16, render as render15, Text as Text16 } from "ink";
32297
+ import { jsx as jsx16, jsxs as jsxs16 } from "react/jsx-runtime";
31857
32298
  function LearningShowUI({ state }) {
31858
32299
  if (state.type === "loading") {
31859
- return /* @__PURE__ */ jsxs15(Box15, { children: [
31860
- /* @__PURE__ */ jsx15(Spinner12, {}),
31861
- /* @__PURE__ */ jsx15(Text15, { children: " Loading learning\u2026" })
32300
+ return /* @__PURE__ */ jsxs16(Box16, { children: [
32301
+ /* @__PURE__ */ jsx16(Spinner13, {}),
32302
+ /* @__PURE__ */ jsx16(Text16, { children: " Loading learning\u2026" })
31862
32303
  ] });
31863
32304
  }
31864
32305
  const { learning } = state;
31865
- return /* @__PURE__ */ jsxs15(Box15, { flexDirection: "column", children: [
31866
- /* @__PURE__ */ jsxs15(Box15, { gap: 1, children: [
31867
- /* @__PURE__ */ jsx15(Text15, { bold: true, children: "Source text:" }),
31868
- /* @__PURE__ */ jsx15(Text15, { children: learning.sourceText })
32306
+ return /* @__PURE__ */ jsxs16(Box16, { flexDirection: "column", children: [
32307
+ /* @__PURE__ */ jsxs16(Box16, { gap: 1, children: [
32308
+ /* @__PURE__ */ jsx16(Text16, { bold: true, children: "Source text:" }),
32309
+ /* @__PURE__ */ jsx16(Text16, { children: learning.sourceText })
31869
32310
  ] }),
31870
- /* @__PURE__ */ jsxs15(Box15, { gap: 1, children: [
31871
- /* @__PURE__ */ jsx15(Text15, { bold: true, children: "Suggested translation:" }),
31872
- /* @__PURE__ */ jsx15(Text15, { children: learning.suggestedTranslation })
32311
+ /* @__PURE__ */ jsxs16(Box16, { gap: 1, children: [
32312
+ /* @__PURE__ */ jsx16(Text16, { bold: true, children: "Suggested translation:" }),
32313
+ /* @__PURE__ */ jsx16(Text16, { children: learning.suggestedTranslation })
31873
32314
  ] }),
31874
- /* @__PURE__ */ jsxs15(Box15, { gap: 1, children: [
31875
- /* @__PURE__ */ jsx15(Text15, { bold: true, children: "Locales:" }),
31876
- /* @__PURE__ */ jsxs15(Text15, { children: [
32315
+ /* @__PURE__ */ jsxs16(Box16, { gap: 1, children: [
32316
+ /* @__PURE__ */ jsx16(Text16, { bold: true, children: "Locales:" }),
32317
+ /* @__PURE__ */ jsxs16(Text16, { children: [
31877
32318
  learning.srcLocale,
31878
32319
  " \u2192 ",
31879
32320
  learning.dstLocale
31880
32321
  ] })
31881
32322
  ] }),
31882
- /* @__PURE__ */ jsxs15(Box15, { gap: 1, children: [
31883
- /* @__PURE__ */ jsx15(Text15, { bold: true, children: "Feedback type:" }),
31884
- /* @__PURE__ */ jsx15(Text15, { children: learning.feedbackType })
32323
+ /* @__PURE__ */ jsxs16(Box16, { gap: 1, children: [
32324
+ /* @__PURE__ */ jsx16(Text16, { bold: true, children: "Feedback type:" }),
32325
+ /* @__PURE__ */ jsx16(Text16, { children: learning.feedbackType })
31885
32326
  ] }),
31886
- /* @__PURE__ */ jsxs15(Box15, { gap: 1, children: [
31887
- /* @__PURE__ */ jsx15(Text15, { bold: true, children: "Reasoning:" }),
31888
- /* @__PURE__ */ jsx15(Text15, { children: learning.reasoning })
32327
+ /* @__PURE__ */ jsxs16(Box16, { gap: 1, children: [
32328
+ /* @__PURE__ */ jsx16(Text16, { bold: true, children: "Reasoning:" }),
32329
+ /* @__PURE__ */ jsx16(Text16, { children: learning.reasoning })
31889
32330
  ] }),
31890
- /* @__PURE__ */ jsxs15(Box15, { gap: 1, children: [
31891
- /* @__PURE__ */ jsx15(Text15, { bold: true, children: "Status:" }),
31892
- /* @__PURE__ */ jsx15(Text15, { color: learning.status === "active" ? "green" : "yellow", children: learning.status })
32331
+ /* @__PURE__ */ jsxs16(Box16, { gap: 1, children: [
32332
+ /* @__PURE__ */ jsx16(Text16, { bold: true, children: "Status:" }),
32333
+ /* @__PURE__ */ jsx16(Text16, { color: learning.status === "active" ? "green" : "yellow", children: learning.status })
31893
32334
  ] }),
31894
- /* @__PURE__ */ jsxs15(Box15, { gap: 1, children: [
31895
- /* @__PURE__ */ jsx15(Text15, { bold: true, children: "Source:" }),
31896
- /* @__PURE__ */ jsx15(Text15, { children: learning.source })
32335
+ /* @__PURE__ */ jsxs16(Box16, { gap: 1, children: [
32336
+ /* @__PURE__ */ jsx16(Text16, { bold: true, children: "Source:" }),
32337
+ /* @__PURE__ */ jsx16(Text16, { children: learning.source })
31897
32338
  ] }),
31898
- /* @__PURE__ */ jsx15(Box15, { marginTop: 1, children: /* @__PURE__ */ jsxs15(Text15, { dimColor: true, children: [
32339
+ /* @__PURE__ */ jsx16(Box16, { marginTop: 1, children: /* @__PURE__ */ jsxs16(Text16, { dimColor: true, children: [
31899
32340
  "ID: ",
31900
32341
  learning._id
31901
32342
  ] }) })
@@ -31933,7 +32374,7 @@ async function runLearningsShow(opts) {
31933
32374
  });
31934
32375
  }
31935
32376
  const { unmount } = renderTransient(
31936
- /* @__PURE__ */ jsx15(LearningShowUI, { state: { type: "loading" } })
32377
+ /* @__PURE__ */ jsx16(LearningShowUI, { state: { type: "loading" } })
31937
32378
  );
31938
32379
  let learning;
31939
32380
  try {
@@ -31941,50 +32382,287 @@ async function runLearningsShow(opts) {
31941
32382
  } finally {
31942
32383
  unmount();
31943
32384
  }
31944
- render15(/* @__PURE__ */ jsx15(LearningShowUI, { state: { type: "success", learning } })).unmount();
32385
+ render15(/* @__PURE__ */ jsx16(LearningShowUI, { state: { type: "success", learning } })).unmount();
31945
32386
  }
31946
32387
 
31947
32388
  // src/commands/learnings/index.ts
31948
- var learningsCommand = new Command4("learnings").description(
32389
+ var learningsCommand = new Command5("learnings").description(
31949
32390
  "View and manage translation learnings"
31950
32391
  );
31951
32392
  learningsCommand.command("list").description("List translation learnings for the current project").option("--status <status>", "Filter by status (active or archived)").option("--limit <n>", "Max number of results", (v) => Number(v), 100).option("--json", "Output as JSON (machine-readable)").action(async (opts) => {
31952
- const schema = z6.object({
31953
- status: z6.enum(["active", "archived"]).optional(),
31954
- limit: z6.coerce.number().int().positive().default(100),
31955
- json: z6.boolean().optional()
32393
+ const schema = z7.object({
32394
+ status: z7.enum(["active", "archived"]).optional(),
32395
+ limit: z7.coerce.number().int().positive().default(100),
32396
+ json: z7.boolean().optional()
31956
32397
  });
31957
32398
  const parsed = validateOrExit(schema, opts);
31958
32399
  await runLearningsList(parsed);
31959
32400
  });
31960
32401
  learningsCommand.command("show").description("Show details of a learning").requiredOption("--id <learningId>", "Learning ID").option("--json", "Output as JSON (machine-readable)").action(async (opts) => {
31961
- const schema = z6.object({
31962
- id: z6.string().min(1),
31963
- json: z6.boolean().optional()
32402
+ const schema = z7.object({
32403
+ id: z7.string().min(1),
32404
+ json: z7.boolean().optional()
31964
32405
  });
31965
32406
  const parsed = validateOrExit(schema, opts);
31966
32407
  await runLearningsShow(parsed);
31967
32408
  });
31968
32409
  learningsCommand.command("archive").description("Archive a learning (keeps it but excludes from translations)").requiredOption("--id <learningId>", "Learning ID").option("--json", "Output as JSON (machine-readable)").action(async (opts) => {
31969
- const schema = z6.object({
31970
- id: z6.string().min(1),
31971
- json: z6.boolean().optional()
32410
+ const schema = z7.object({
32411
+ id: z7.string().min(1),
32412
+ json: z7.boolean().optional()
31972
32413
  });
31973
32414
  const parsed = validateOrExit(schema, opts);
31974
32415
  await runLearningsArchive(parsed);
31975
32416
  });
31976
32417
  learningsCommand.command("remove").description("Permanently remove a learning").requiredOption("--id <learningId>", "Learning ID").option("--json", "Output as JSON (machine-readable)").action(async (opts) => {
31977
- const schema = z6.object({
31978
- id: z6.string().min(1),
31979
- json: z6.boolean().optional()
32418
+ const schema = z7.object({
32419
+ id: z7.string().min(1),
32420
+ json: z7.boolean().optional()
31980
32421
  });
31981
32422
  const parsed = validateOrExit(schema, opts);
31982
32423
  await runLearningsRemove(parsed);
31983
32424
  });
31984
32425
 
32426
+ // src/commands/status/index.ts
32427
+ import { Command as Command6 } from "commander";
32428
+ import { z as z8 } from "zod/v4";
32429
+
32430
+ // src/commands/status/status.tsx
32431
+ import { Spinner as Spinner14 } from "@inkjs/ui";
32432
+ import { Box as Box17, Text as Text17 } from "ink";
32433
+
32434
+ // src/commands/status/status-core.ts
32435
+ function fullKey4(namespace, key) {
32436
+ return namespace ? `${namespace}.${key}` : key;
32437
+ }
32438
+ function isPersonHold(reason) {
32439
+ return reason === "locked" || reason === "pending-draft";
32440
+ }
32441
+ function count(reasons2) {
32442
+ const byReason = {};
32443
+ for (const reason of reasons2)
32444
+ byReason[reason] = (byReason[reason] ?? 0) + 1;
32445
+ return { total: reasons2.length, byReason };
32446
+ }
32447
+ function openPullRequest(rows, locale) {
32448
+ const row = rows.find(
32449
+ (r) => r.prState === "open" && r.prUrl !== void 0 && sameLocale(r.targetLanguage, locale)
32450
+ );
32451
+ if (!row?.prUrl) return null;
32452
+ return { number: row.prNumber ?? null, url: row.prUrl };
32453
+ }
32454
+ function languageStatus(locale, plan, pullRequests, opts = {}) {
32455
+ const { summary } = plan;
32456
+ const held = [
32457
+ ...plan.heldKeys,
32458
+ ...plan.protectedKeys.flatMap(
32459
+ ({ key, reason }) => isPersonHold(reason) ? [{ key, reason }] : []
32460
+ )
32461
+ ];
32462
+ const kept = plan.protectedKeys.flatMap(
32463
+ ({ key, reason }) => isPersonHold(reason) ? [] : [{ key, reason }]
32464
+ );
32465
+ const tasks = (reason) => plan.tasks.filter((t) => t.reason === reason).map((t) => fullKey4(t.namespace, t.key));
32466
+ return {
32467
+ locale,
32468
+ expected: summary.totalKeys,
32469
+ upToDate: summary.upToDate,
32470
+ missing: summary.missing,
32471
+ changed: summary.changed,
32472
+ heldByPerson: count(held.map((h) => h.reason)),
32473
+ keptForPerson: count(kept.map((k) => k.reason)),
32474
+ untracked: summary.untracked,
32475
+ pullRequest: openPullRequest(pullRequests, locale),
32476
+ ...opts.keys ? {
32477
+ keys: {
32478
+ missing: tasks("missing"),
32479
+ changed: tasks("changed"),
32480
+ heldByPerson: held,
32481
+ keptForPerson: kept,
32482
+ untracked: plan.untrackedKeys.map((k) => k.key)
32483
+ }
32484
+ } : {}
32485
+ };
32486
+ }
32487
+ function needsTranslation(language) {
32488
+ return language.missing > 0 || language.changed > 0;
32489
+ }
32490
+ var REASON_WORDS = {
32491
+ locked: "locked",
32492
+ "pending-draft": "pending draft",
32493
+ manual: "written by a person",
32494
+ "target-edited": "edited since TolkBot wrote it",
32495
+ unknown: "no record of who wrote it"
32496
+ };
32497
+ function reasons(counts) {
32498
+ return Object.entries(counts.byReason).map(
32499
+ ([reason, n]) => `${n} ${REASON_WORDS[reason]}`
32500
+ ).join(", ");
32501
+ }
32502
+ function describeLanguage(language) {
32503
+ const counts = [
32504
+ `${language.expected} expected`,
32505
+ `${language.upToDate} up to date`,
32506
+ `${language.missing} missing`,
32507
+ `${language.changed} changed since translated`
32508
+ ].join(", ");
32509
+ const mark = needsTranslation(language) ? "\u2022" : "\u2713";
32510
+ const lines = [`${mark} ${language.locale}: ${counts}`];
32511
+ if (language.heldByPerson.total > 0) {
32512
+ lines.push(
32513
+ ` ${language.heldByPerson.total} held by a person (${reasons(language.heldByPerson)})`
32514
+ );
32515
+ }
32516
+ if (language.keptForPerson.total > 0) {
32517
+ lines.push(
32518
+ ` ${language.keptForPerson.total} changed but kept for a person (${reasons(language.keptForPerson)})`
32519
+ );
32520
+ }
32521
+ if (language.untracked > 0) {
32522
+ lines.push(
32523
+ ` ${language.untracked} untracked: TolkBot has no record of them`
32524
+ );
32525
+ }
32526
+ if (language.pullRequest) {
32527
+ lines.push(` Pull request: ${language.pullRequest.url}`);
32528
+ }
32529
+ const { keys } = language;
32530
+ if (keys) {
32531
+ const list = (label, names) => {
32532
+ if (names.length === 0) return;
32533
+ lines.push(` ${label}:`);
32534
+ for (const name of names) lines.push(` ${name}`);
32535
+ };
32536
+ list("Missing", keys.missing);
32537
+ list("Changed", keys.changed);
32538
+ list(
32539
+ "Held by a person",
32540
+ keys.heldByPerson.map(
32541
+ (k) => `${k.key} (${REASON_WORDS[k.reason]})`
32542
+ )
32543
+ );
32544
+ list(
32545
+ "Kept for a person",
32546
+ keys.keptForPerson.map(
32547
+ (k) => `${k.key} (${REASON_WORDS[k.reason]})`
32548
+ )
32549
+ );
32550
+ list("Untracked", keys.untracked);
32551
+ }
32552
+ return lines;
32553
+ }
32554
+
32555
+ // src/commands/status/status.tsx
32556
+ import { jsx as jsx17, jsxs as jsxs17 } from "react/jsx-runtime";
32557
+ var OFFLINE_HINT = "Without a login, 'tolkbot validate' checks the files on this machine.";
32558
+ async function runStatus(opts) {
32559
+ let ctx;
32560
+ try {
32561
+ ctx = await loadPlanContext(process.cwd());
32562
+ } catch (error62) {
32563
+ if (error62 instanceof AuthError) {
32564
+ throw new AuthError(
32565
+ error62.baseMessage,
32566
+ [error62.hint, OFFLINE_HINT].filter(Boolean).join(" "),
32567
+ error62.code
32568
+ );
32569
+ }
32570
+ throw error62;
32571
+ }
32572
+ const { project } = ctx;
32573
+ const srcLocale = project.baseLanguage;
32574
+ const locales = project.targetLanguages;
32575
+ const progress = renderTransient(
32576
+ /* @__PURE__ */ jsxs17(Box17, { children: [
32577
+ /* @__PURE__ */ jsx17(Spinner14, {}),
32578
+ /* @__PURE__ */ jsxs17(Text17, { children: [
32579
+ " Reading the status of ",
32580
+ project.name,
32581
+ "\u2026"
32582
+ ] })
32583
+ ] })
32584
+ );
32585
+ const languages = [];
32586
+ let credits;
32587
+ try {
32588
+ const pullRequests = locales.length === 0 ? [] : await ctx.client.query(api.prJobs.listByProject, {
32589
+ organizationId: project.organizationId,
32590
+ projectId: project._id
32591
+ });
32592
+ for (const locale of locales) {
32593
+ const plan = await planForLocale(ctx, srcLocale, locale);
32594
+ languages.push(
32595
+ languageStatus(locale, plan, pullRequests, {
32596
+ keys: opts.keys
32597
+ })
32598
+ );
32599
+ }
32600
+ credits = await readCreditBalance(ctx);
32601
+ } finally {
32602
+ progress.unmount();
32603
+ }
32604
+ const payload = {
32605
+ project: {
32606
+ name: project.name,
32607
+ slug: project.slug,
32608
+ url: `${env.TOLKBOT_WEB_URL}/projects/${project.slug}`,
32609
+ baseLanguage: srcLocale,
32610
+ pattern: fullPattern(scopeOf(project)),
32611
+ autoCreatePR: project.workflow?.autoCreatePR ?? DEFAULT_BEHAVIORAL_SETTINGS.workflow.autoCreatePR
32612
+ },
32613
+ credits: { balance: credits },
32614
+ languages
32615
+ };
32616
+ const outOfDate = languages.some(needsTranslation);
32617
+ const exitCode = opts.exitCode && outOfDate ? 1 : 0;
32618
+ if (opts.json) {
32619
+ if (exitCode === 0) jsonSuccess(payload);
32620
+ jsonResult(payload, exitCode);
32621
+ return;
32622
+ }
32623
+ const { project: p } = payload;
32624
+ console.log(`${p.name} (${p.slug}) ${p.url}`);
32625
+ console.log(
32626
+ `Base language ${p.baseLanguage}, files ${p.pattern}, auto-create pull requests ${p.autoCreatePR ? "on" : "off"}`
32627
+ );
32628
+ console.log(
32629
+ credits === null ? "Credits left: could not be read" : `Credits left: ${credits}`
32630
+ );
32631
+ console.log("");
32632
+ if (languages.length === 0) {
32633
+ console.log(
32634
+ `No target languages configured. Add them in the project settings at ${p.url}/settings.`
32635
+ );
32636
+ }
32637
+ for (const language of languages) {
32638
+ for (const line of describeLanguage(language)) console.log(line);
32639
+ }
32640
+ if (outOfDate) {
32641
+ console.log(
32642
+ "\nRun 'tolkbot translate --all' to translate what is missing or changed."
32643
+ );
32644
+ }
32645
+ process.exitCode = exitCode;
32646
+ }
32647
+
32648
+ // src/commands/status/index.ts
32649
+ var statusCommand = new Command6("status").description(
32650
+ "Show what each language is missing, what waits for a person, the open pull requests and the credits left"
32651
+ ).option("--keys", "List the keys by name under each language").option(
32652
+ "--exit-code",
32653
+ "Exit 1 when any language has missing or changed keys"
32654
+ ).option("--json", "Output as JSON (machine-readable)").action(async (opts) => {
32655
+ const schema = z8.object({
32656
+ keys: z8.boolean().optional(),
32657
+ exitCode: z8.boolean().optional(),
32658
+ json: z8.boolean().optional()
32659
+ });
32660
+ await runStatus(validateOrExit(schema, opts));
32661
+ });
32662
+
31985
32663
  // src/commands/telemetry.ts
31986
- import { Command as Command5 } from "commander";
31987
- import { z as z7 } from "zod/v4";
32664
+ import { Command as Command7 } from "commander";
32665
+ import { z as z9 } from "zod/v4";
31988
32666
  var REASONS = {
31989
32667
  env: `disabled by $${TELEMETRY_ENV_VAR}`,
31990
32668
  "do-not-track": `disabled by $${DO_NOT_TRACK_ENV_VAR}`,
@@ -32005,12 +32683,12 @@ async function printStatus(json2) {
32005
32683
  `Turn it off with \`tolkbot telemetry off\`, $${TELEMETRY_ENV_VAR}=0 or $${DO_NOT_TRACK_ENV_VAR}=1.`
32006
32684
  );
32007
32685
  }
32008
- var telemetryCommand = new Command5("telemetry").description(
32686
+ var telemetryCommand = new Command7("telemetry").description(
32009
32687
  "Control anonymous usage reporting"
32010
32688
  );
32011
32689
  telemetryCommand.command("status", { isDefault: true }).description("Show whether telemetry is on and why").option("--json", "Output as JSON (machine-readable)").action(async (opts) => {
32012
32690
  const parsed = validateOrExit(
32013
- z7.object({ json: z7.boolean().optional() }),
32691
+ z9.object({ json: z9.boolean().optional() }),
32014
32692
  opts
32015
32693
  );
32016
32694
  await printStatus(parsed.json);
@@ -32027,8 +32705,8 @@ telemetryCommand.command("on").description("Resume reporting usage from this mac
32027
32705
  });
32028
32706
 
32029
32707
  // src/commands/translate/index.ts
32030
- import { Command as Command6 } from "commander";
32031
- import { z as z8 } from "zod/v4";
32708
+ import { Command as Command8 } from "commander";
32709
+ import { z as z10 } from "zod/v4";
32032
32710
 
32033
32711
  // src/commands/translate/adopt.tsx
32034
32712
  var STAMP_CHUNK2 = 200;
@@ -32078,7 +32756,7 @@ async function runAdopt(opts) {
32078
32756
  if (locales.length === 0) {
32079
32757
  throw opts.all ? new CliError(
32080
32758
  "No target languages configured for this project.",
32081
- void 0,
32759
+ "Add one with 'tolkbot languages add <code>'.",
32082
32760
  "no_target_languages"
32083
32761
  ) : new UsageError(
32084
32762
  "Either --dst <locale> or --all must be provided."
@@ -32109,8 +32787,8 @@ async function runAdopt(opts) {
32109
32787
 
32110
32788
  // src/commands/translate/edit.tsx
32111
32789
  import path10 from "path";
32112
- import { Spinner as Spinner13 } from "@inkjs/ui";
32113
- import { Box as Box16, render as render16, Text as Text16 } from "ink";
32790
+ import { Spinner as Spinner15 } from "@inkjs/ui";
32791
+ import { Box as Box18, render as render16, Text as Text18 } from "ink";
32114
32792
 
32115
32793
  // src/utils/locale-files.ts
32116
32794
  import path9 from "path";
@@ -32148,25 +32826,25 @@ function resolveKeyInFile(key, targetFile, fileCount, namespace) {
32148
32826
  }
32149
32827
 
32150
32828
  // src/commands/translate/edit.tsx
32151
- import { jsx as jsx16, jsxs as jsxs16 } from "react/jsx-runtime";
32829
+ import { jsx as jsx18, jsxs as jsxs18 } from "react/jsx-runtime";
32152
32830
  function EditUI({ state }) {
32153
32831
  if (state.type === "editing") {
32154
- return /* @__PURE__ */ jsxs16(Box16, { children: [
32155
- /* @__PURE__ */ jsx16(Spinner13, {}),
32156
- /* @__PURE__ */ jsx16(Text16, { children: " Editing translation\u2026" })
32832
+ return /* @__PURE__ */ jsxs18(Box18, { children: [
32833
+ /* @__PURE__ */ jsx18(Spinner15, {}),
32834
+ /* @__PURE__ */ jsx18(Text18, { children: " Editing translation\u2026" })
32157
32835
  ] });
32158
32836
  }
32159
- return /* @__PURE__ */ jsxs16(Box16, { flexDirection: "column", children: [
32160
- /* @__PURE__ */ jsxs16(Box16, { children: [
32161
- /* @__PURE__ */ jsx16(Text16, { color: "green", children: "\u2713" }),
32162
- /* @__PURE__ */ jsxs16(Text16, { children: [
32837
+ return /* @__PURE__ */ jsxs18(Box18, { flexDirection: "column", children: [
32838
+ /* @__PURE__ */ jsxs18(Box18, { children: [
32839
+ /* @__PURE__ */ jsx18(Text18, { color: "green", children: "\u2713" }),
32840
+ /* @__PURE__ */ jsxs18(Text18, { children: [
32163
32841
  " ",
32164
32842
  "Updated ",
32165
- /* @__PURE__ */ jsx16(Text16, { bold: true, children: state.key })
32843
+ /* @__PURE__ */ jsx18(Text18, { bold: true, children: state.key })
32166
32844
  ] })
32167
32845
  ] }),
32168
- /* @__PURE__ */ jsx16(Box16, { paddingLeft: 2, children: /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: state.value }) }),
32169
- /* @__PURE__ */ jsx16(Box16, { marginTop: 1, children: /* @__PURE__ */ jsxs16(Text16, { dimColor: true, children: [
32846
+ /* @__PURE__ */ jsx18(Box18, { paddingLeft: 2, children: /* @__PURE__ */ jsx18(Text18, { dimColor: true, children: state.value }) }),
32847
+ /* @__PURE__ */ jsx18(Box18, { marginTop: 1, children: /* @__PURE__ */ jsxs18(Text18, { dimColor: true, children: [
32170
32848
  "File: ",
32171
32849
  state.filePath
32172
32850
  ] }) })
@@ -32223,20 +32901,20 @@ async function runEdit(opts) {
32223
32901
  value: result2.value
32224
32902
  });
32225
32903
  }
32226
- const { unmount } = renderTransient(/* @__PURE__ */ jsx16(EditUI, { state: { type: "editing" } }));
32904
+ const { unmount } = renderTransient(/* @__PURE__ */ jsx18(EditUI, { state: { type: "editing" } }));
32227
32905
  let result;
32228
32906
  try {
32229
32907
  result = await performEdit(opts);
32230
32908
  } finally {
32231
32909
  unmount();
32232
32910
  }
32233
- render16(/* @__PURE__ */ jsx16(EditUI, { state: { type: "success", ...result } })).unmount();
32911
+ render16(/* @__PURE__ */ jsx18(EditUI, { state: { type: "success", ...result } })).unmount();
32234
32912
  }
32235
32913
 
32236
32914
  // src/commands/translate/translate.tsx
32237
- import { Spinner as Spinner14 } from "@inkjs/ui";
32238
- import { Box as Box17, Text as Text17 } from "ink";
32239
- import { jsx as jsx17, jsxs as jsxs17 } from "react/jsx-runtime";
32915
+ import { Spinner as Spinner16 } from "@inkjs/ui";
32916
+ import { Box as Box19, Text as Text19 } from "ink";
32917
+ import { jsx as jsx19, jsxs as jsxs19 } from "react/jsx-runtime";
32240
32918
  function planOutput(plan) {
32241
32919
  return {
32242
32920
  ...plan.summary,
@@ -32245,7 +32923,7 @@ function planOutput(plan) {
32245
32923
  untrackedKeys: plan.untrackedKeys.map((k) => k.key)
32246
32924
  };
32247
32925
  }
32248
- function fullKey4(namespace, key) {
32926
+ function fullKey5(namespace, key) {
32249
32927
  return namespace ? `${namespace}.${key}` : key;
32250
32928
  }
32251
32929
  function characters(text) {
@@ -32257,7 +32935,7 @@ function targetLocales(ctx, opts) {
32257
32935
  if (locales.length === 0) {
32258
32936
  throw new CliError(
32259
32937
  "No target languages configured.",
32260
- `Configure them in the project settings at ${env.TOLKBOT_WEB_URL}/projects/${ctx.project.slug}/settings.`,
32938
+ "Add one with 'tolkbot languages add <code>'.",
32261
32939
  "no_target_languages"
32262
32940
  );
32263
32941
  }
@@ -32273,21 +32951,11 @@ async function runTranslate(opts) {
32273
32951
  }
32274
32952
  await runLanguages(ctx, srcLocale, dstLocales, opts);
32275
32953
  }
32276
- async function creditBalance(ctx) {
32277
- try {
32278
- const balance = await ctx.client.query(api.usage.getCreditBalance, {
32279
- organizationId: ctx.project.organizationId
32280
- });
32281
- return balance?.total ?? null;
32282
- } catch {
32283
- return null;
32284
- }
32285
- }
32286
32954
  var NO_ESTIMATE = "No credit estimate exists for this project yet. Keys and characters are what a run would send.";
32287
32955
  async function runDryRun(ctx, srcLocale, dstLocales, opts) {
32288
32956
  const languages = [];
32289
32957
  const progress = renderTransient(
32290
- /* @__PURE__ */ jsx17(Working, { text: `Planning ${dstLocales.join(", ")}\u2026` })
32958
+ /* @__PURE__ */ jsx19(Working, { text: `Planning ${dstLocales.join(", ")}\u2026` })
32291
32959
  );
32292
32960
  try {
32293
32961
  for (const dstLocale of dstLocales) {
@@ -32302,7 +32970,7 @@ async function runDryRun(ctx, srcLocale, dstLocales, opts) {
32302
32970
  0
32303
32971
  ),
32304
32972
  tasks: plan.tasks.map((task) => ({
32305
- key: fullKey4(task.namespace, task.key),
32973
+ key: fullKey5(task.namespace, task.key),
32306
32974
  path: task.path,
32307
32975
  reason: task.reason,
32308
32976
  ...opts.verbose ? { src: task.src } : {}
@@ -32317,7 +32985,7 @@ async function runDryRun(ctx, srcLocale, dstLocales, opts) {
32317
32985
  keys: languages.reduce((n, l) => n + l.keys, 0),
32318
32986
  characters: languages.reduce((n, l) => n + l.characters, 0)
32319
32987
  };
32320
- const balance = await creditBalance(ctx);
32988
+ const balance = await readCreditBalance(ctx);
32321
32989
  track("cli_translate_completed", {
32322
32990
  mode: "plan",
32323
32991
  key_count: total.keys,
@@ -32365,9 +33033,9 @@ In all: ${total.keys} key(s), ${total.characters} characters.` + (balance === nu
32365
33033
  console.log("Nothing was started. Run without --dry-run to translate.");
32366
33034
  }
32367
33035
  function Working({ text }) {
32368
- return /* @__PURE__ */ jsxs17(Box17, { children: [
32369
- /* @__PURE__ */ jsx17(Spinner14, {}),
32370
- /* @__PURE__ */ jsxs17(Text17, { children: [
33036
+ return /* @__PURE__ */ jsxs19(Box19, { children: [
33037
+ /* @__PURE__ */ jsx19(Spinner16, {}),
33038
+ /* @__PURE__ */ jsxs19(Text19, { children: [
32371
33039
  " ",
32372
33040
  text
32373
33041
  ] })
@@ -32378,7 +33046,7 @@ function progressText(dstLocale, summary) {
32378
33046
  const done = summary.completed + summary.failed;
32379
33047
  return `${dstLocale}: translating ${done}/${summary.total}\u2026`;
32380
33048
  }
32381
- function describeOutcome(outcome) {
33049
+ function describeOutcome2(outcome) {
32382
33050
  const { dstLocale, status, keys, written } = outcome;
32383
33051
  const wrote = written ? `, ${written.keys} written to ${written.files} file(s)` : "";
32384
33052
  const notApplied = outcome.notApplied.length > 0 ? `, ${outcome.notApplied.length} not applied` : "";
@@ -32413,14 +33081,14 @@ async function runLanguages(ctx, srcLocale, dstLocales, opts) {
32413
33081
  const report = (outcome) => {
32414
33082
  outcomes.push(outcome);
32415
33083
  if (json2) return;
32416
- console.log(describeOutcome(outcome));
33084
+ console.log(describeOutcome2(outcome));
32417
33085
  for (const line of describeNotApplied(outcome.notApplied)) {
32418
33086
  console.log(` ${line}`);
32419
33087
  }
32420
33088
  };
32421
33089
  for (const dstLocale of dstLocales) {
32422
33090
  const progress = renderTransient(
32423
- /* @__PURE__ */ jsx17(Working, { text: `${dstLocale}: planning\u2026` })
33091
+ /* @__PURE__ */ jsx19(Working, { text: `${dstLocale}: planning\u2026` })
32424
33092
  );
32425
33093
  try {
32426
33094
  const { tasks, plan } = await generateTasks(
@@ -32453,7 +33121,7 @@ async function runLanguages(ctx, srcLocale, dstLocales, opts) {
32453
33121
  printNotes(json2, leftOut);
32454
33122
  continue;
32455
33123
  }
32456
- progress.rerender(/* @__PURE__ */ jsx17(Working, { text: progressText(dstLocale, null) }));
33124
+ progress.rerender(/* @__PURE__ */ jsx19(Working, { text: progressText(dstLocale, null) }));
32457
33125
  let jobId;
32458
33126
  try {
32459
33127
  jobId = await startTranslationJob({
@@ -32490,7 +33158,7 @@ async function runLanguages(ctx, srcLocale, dstLocales, opts) {
32490
33158
  project.organizationId,
32491
33159
  jobId,
32492
33160
  (latest) => progress.rerender(
32493
- /* @__PURE__ */ jsx17(Working, { text: progressText(dstLocale, latest) })
33161
+ /* @__PURE__ */ jsx19(Working, { text: progressText(dstLocale, latest) })
32494
33162
  )
32495
33163
  );
32496
33164
  } catch (error62) {
@@ -32515,7 +33183,7 @@ async function runLanguages(ctx, srcLocale, dstLocales, opts) {
32515
33183
  };
32516
33184
  if (summary.status !== "cancelled") {
32517
33185
  progress.rerender(
32518
- /* @__PURE__ */ jsx17(Working, { text: `${dstLocale}: writing the files\u2026` })
33186
+ /* @__PURE__ */ jsx19(Working, { text: `${dstLocale}: writing the files\u2026` })
32519
33187
  );
32520
33188
  const applied = await applyJob(ctx, jobId);
32521
33189
  finished.written = { files: applied.files, keys: applied.keys };
@@ -32579,7 +33247,7 @@ function checkTranslateOptions(opts) {
32579
33247
 
32580
33248
  // src/commands/translate/index.ts
32581
33249
  var RETRANSLATE_UNTRACKED_HELP = "Also retranslate translations TolkBot has no record of (kept by default)";
32582
- var translateCommand = new Command6("translate").description(
33250
+ var translateCommand2 = new Command8("translate").description(
32583
33251
  "Translate what is missing or changed, and write the results into your locale files"
32584
33252
  ).option(
32585
33253
  "--src <locale>",
@@ -32595,31 +33263,31 @@ var translateCommand = new Command6("translate").description(
32595
33263
  "Run 'tolkbot translate --help'. Jobs are under 'tolkbot jobs'."
32596
33264
  );
32597
33265
  }
32598
- const schema = z8.object({
32599
- src: z8.string().min(1).optional(),
32600
- dst: z8.string().min(1).optional(),
32601
- all: z8.boolean().optional(),
32602
- dryRun: z8.boolean().optional(),
32603
- wait: z8.boolean(),
32604
- retranslateUntracked: z8.boolean().optional(),
32605
- verbose: z8.boolean().optional(),
32606
- json: z8.boolean().optional()
33266
+ const schema = z10.object({
33267
+ src: z10.string().min(1).optional(),
33268
+ dst: z10.string().min(1).optional(),
33269
+ all: z10.boolean().optional(),
33270
+ dryRun: z10.boolean().optional(),
33271
+ wait: z10.boolean(),
33272
+ retranslateUntracked: z10.boolean().optional(),
33273
+ verbose: z10.boolean().optional(),
33274
+ json: z10.boolean().optional()
32607
33275
  });
32608
33276
  const parsed = validateOrExit(schema, opts);
32609
33277
  checkTranslateOptions(parsed);
32610
33278
  await runTranslate(parsed);
32611
33279
  });
32612
- translateCommand.command("adopt").description(
33280
+ translateCommand2.command("adopt").description(
32613
33281
  "Record existing translations TolkBot has no baseline for as reviewed, so they are kept until their source changes"
32614
33282
  ).option(
32615
33283
  "--src <locale>",
32616
33284
  "Source locale (defaults to the project's base language)"
32617
33285
  ).option("--dst <locale>", "Destination locale").option("--all", "Every target language").option("--json", "Output as JSON (machine-readable)").action(async (opts) => {
32618
- const schema = z8.object({
32619
- src: z8.string().min(1).optional(),
32620
- dst: z8.string().min(1).optional(),
32621
- all: z8.boolean().optional(),
32622
- json: z8.boolean().optional()
33286
+ const schema = z10.object({
33287
+ src: z10.string().min(1).optional(),
33288
+ dst: z10.string().min(1).optional(),
33289
+ all: z10.boolean().optional(),
33290
+ json: z10.boolean().optional()
32623
33291
  });
32624
33292
  const parsed = validateOrExit(schema, opts);
32625
33293
  if (parsed.dst && parsed.all) {
@@ -32627,20 +33295,20 @@ translateCommand.command("adopt").description(
32627
33295
  }
32628
33296
  await runAdopt(parsed);
32629
33297
  });
32630
- translateCommand.command("edit").description("Edit a translation value in a locale file").requiredOption("--key <key>", "Translation key (e.g. common.hello)").requiredOption("--locale <locale>", "Target locale (e.g. fr, de)").requiredOption("--value <text>", "New translation value").option("--namespace <ns>", "Namespace to resolve file (if ambiguous)").option("--json", "Output as JSON (machine-readable)").action(async (opts) => {
32631
- const schema = z8.object({
32632
- key: z8.string().min(1),
32633
- locale: z8.string().min(1),
32634
- value: z8.string().min(1),
32635
- namespace: z8.string().optional(),
32636
- json: z8.boolean().optional()
33298
+ translateCommand2.command("edit").description("Edit a translation value in a locale file").requiredOption("--key <key>", "Translation key (e.g. common.hello)").requiredOption("--locale <locale>", "Target locale (e.g. fr, de)").requiredOption("--value <text>", "New translation value").option("--namespace <ns>", "Namespace to resolve file (if ambiguous)").option("--json", "Output as JSON (machine-readable)").action(async (opts) => {
33299
+ const schema = z10.object({
33300
+ key: z10.string().min(1),
33301
+ locale: z10.string().min(1),
33302
+ value: z10.string().min(1),
33303
+ namespace: z10.string().optional(),
33304
+ json: z10.boolean().optional()
32637
33305
  });
32638
33306
  const parsed = validateOrExit(schema, opts);
32639
33307
  await runEdit(parsed);
32640
33308
  });
32641
33309
 
32642
33310
  // src/commands/validate.tsx
32643
- import { Box as Box18, render as render17, Text as Text18 } from "ink";
33311
+ import { Box as Box20, render as render17, Text as Text20 } from "ink";
32644
33312
 
32645
33313
  // ../ai/src/validators/placeholders.ts
32646
33314
  var IDENTIFIER = /^[A-Za-z0-9_$][A-Za-z0-9_$.[\]-]*$/;
@@ -33733,7 +34401,7 @@ function finalize2(issues, stats, strict) {
33733
34401
  }
33734
34402
 
33735
34403
  // src/commands/validate.tsx
33736
- import { jsx as jsx18, jsxs as jsxs18 } from "react/jsx-runtime";
34404
+ import { jsx as jsx20, jsxs as jsxs20 } from "react/jsx-runtime";
33737
34405
  var LEVEL_COLOR = {
33738
34406
  error: "red",
33739
34407
  warning: "yellow",
@@ -33742,12 +34410,12 @@ var LEVEL_COLOR = {
33742
34410
  var GUTTER = 9;
33743
34411
  function IssueLine({ issue: issue2 }) {
33744
34412
  const location = [issue2.file, issue2.key].filter(Boolean).join(" \xB7 ");
33745
- return /* @__PURE__ */ jsxs18(Box18, { flexDirection: "column", children: [
33746
- /* @__PURE__ */ jsxs18(Box18, { children: [
33747
- /* @__PURE__ */ jsx18(Box18, { width: GUTTER, flexShrink: 0, children: /* @__PURE__ */ jsx18(Text18, { color: LEVEL_COLOR[issue2.level], children: issue2.level }) }),
33748
- /* @__PURE__ */ jsx18(Box18, { flexGrow: 1, children: /* @__PURE__ */ jsx18(Text18, { children: issue2.message }) })
34413
+ return /* @__PURE__ */ jsxs20(Box20, { flexDirection: "column", children: [
34414
+ /* @__PURE__ */ jsxs20(Box20, { children: [
34415
+ /* @__PURE__ */ jsx20(Box20, { width: GUTTER, flexShrink: 0, children: /* @__PURE__ */ jsx20(Text20, { color: LEVEL_COLOR[issue2.level], children: issue2.level }) }),
34416
+ /* @__PURE__ */ jsx20(Box20, { flexGrow: 1, children: /* @__PURE__ */ jsx20(Text20, { children: issue2.message }) })
33749
34417
  ] }),
33750
- /* @__PURE__ */ jsx18(Box18, { marginLeft: GUTTER, children: /* @__PURE__ */ jsxs18(Text18, { dimColor: true, children: [
34418
+ /* @__PURE__ */ jsx20(Box20, { marginLeft: GUTTER, children: /* @__PURE__ */ jsxs20(Text20, { dimColor: true, children: [
33751
34419
  issue2.rule,
33752
34420
  location ? ` \u2014 ${location}` : ""
33753
34421
  ] }) })
@@ -33756,20 +34424,20 @@ function IssueLine({ issue: issue2 }) {
33756
34424
  function ReportUI({ report }) {
33757
34425
  const { counts, stats } = report;
33758
34426
  const summary = ["error", "warning", "info"].filter((level) => counts[level] > 0).map((level) => `${counts[level]} ${level}(s)`).join(", ");
33759
- return /* @__PURE__ */ jsxs18(Box18, { flexDirection: "column", children: [
33760
- /* @__PURE__ */ jsxs18(Box18, { children: [
33761
- /* @__PURE__ */ jsx18(Text18, { color: report.valid ? "green" : "red", children: report.valid ? "\u2713" : "\u2717" }),
33762
- /* @__PURE__ */ jsxs18(Text18, { children: [
34427
+ return /* @__PURE__ */ jsxs20(Box20, { flexDirection: "column", children: [
34428
+ /* @__PURE__ */ jsxs20(Box20, { children: [
34429
+ /* @__PURE__ */ jsx20(Text20, { color: report.valid ? "green" : "red", children: report.valid ? "\u2713" : "\u2717" }),
34430
+ /* @__PURE__ */ jsxs20(Text20, { children: [
33763
34431
  " Validation ",
33764
34432
  report.valid ? "passed" : "failed"
33765
34433
  ] }),
33766
- summary ? /* @__PURE__ */ jsxs18(Text18, { dimColor: true, children: [
34434
+ summary ? /* @__PURE__ */ jsxs20(Text20, { dimColor: true, children: [
33767
34435
  " \u2014 ",
33768
34436
  summary
33769
34437
  ] }) : null
33770
34438
  ] }),
33771
- stats.locales.length > 0 && /* @__PURE__ */ jsxs18(Box18, { marginTop: 1, flexDirection: "column", children: [
33772
- /* @__PURE__ */ jsxs18(Text18, { dimColor: true, children: [
34439
+ stats.locales.length > 0 && /* @__PURE__ */ jsxs20(Box20, { marginTop: 1, flexDirection: "column", children: [
34440
+ /* @__PURE__ */ jsxs20(Text20, { dimColor: true, children: [
33773
34441
  stats.filesChecked,
33774
34442
  " ",
33775
34443
  stats.format,
@@ -33778,18 +34446,18 @@ function ReportUI({ report }) {
33778
34446
  stats.totalKeys,
33779
34447
  " keys"
33780
34448
  ] }),
33781
- /* @__PURE__ */ jsxs18(Text18, { dimColor: true, children: [
34449
+ /* @__PURE__ */ jsxs20(Text20, { dimColor: true, children: [
33782
34450
  "Locales: ",
33783
34451
  stats.locales.join(", "),
33784
34452
  stats.baseLocale ? ` (base: ${stats.baseLocale})` : ""
33785
34453
  ] }),
33786
- stats.pattern && /* @__PURE__ */ jsxs18(Text18, { dimColor: true, children: [
34454
+ stats.pattern && /* @__PURE__ */ jsxs20(Text20, { dimColor: true, children: [
33787
34455
  "Pattern: ",
33788
34456
  stats.pattern
33789
34457
  ] })
33790
34458
  ] }),
33791
- report.issues.length > 0 && /* @__PURE__ */ jsx18(Box18, { marginTop: 1, flexDirection: "column", gap: 1, children: report.issues.map((issue2, idx) => /* @__PURE__ */ jsx18(IssueLine, { issue: issue2 }, idx)) }),
33792
- !report.strict && counts.warning > 0 && report.valid && /* @__PURE__ */ jsx18(Box18, { marginTop: 1, children: /* @__PURE__ */ jsx18(Text18, { dimColor: true, children: "Warnings did not fail this run. Use --strict to treat them as errors." }) })
34459
+ report.issues.length > 0 && /* @__PURE__ */ jsx20(Box20, { marginTop: 1, flexDirection: "column", gap: 1, children: report.issues.map((issue2, idx) => /* @__PURE__ */ jsx20(IssueLine, { issue: issue2 }, idx)) }),
34460
+ !report.strict && counts.warning > 0 && report.valid && /* @__PURE__ */ jsx20(Box20, { marginTop: 1, children: /* @__PURE__ */ jsx20(Text20, { dimColor: true, children: "Warnings did not fail this run. Use --strict to treat them as errors." }) })
33793
34461
  ] });
33794
34462
  }
33795
34463
  function emit(report, json2) {
@@ -33809,7 +34477,7 @@ function emit(report, json2) {
33809
34477
  if (!report.valid) fail(validationFailure(report));
33810
34478
  return;
33811
34479
  }
33812
- render17(/* @__PURE__ */ jsx18(ReportUI, { report })).unmount();
34480
+ render17(/* @__PURE__ */ jsx20(ReportUI, { report })).unmount();
33813
34481
  process.exitCode = exitCode;
33814
34482
  }
33815
34483
  function validationFailure(report) {
@@ -33868,7 +34536,7 @@ function commandPath(command) {
33868
34536
  return names.join(" ");
33869
34537
  }
33870
34538
  function buildProgram() {
33871
- const program = new Command7().name("tolkbot").description("Localization workflow for your repo").version(version, "-v, --version", "Output the version number").helpOption("-h, --help", "Show help").helpCommand("help [command]", "Show help for a command").option("--token <token>", TOKEN_HELP).enablePositionalOptions();
34539
+ const program = new Command9().name("tolkbot").description("Localization workflow for your repo").version(version, "-v, --version", "Output the version number").helpOption("-h, --help", "Show help").helpCommand("help [command]", "Show help for a command").option("--token <token>", TOKEN_HELP).enablePositionalOptions();
33872
34540
  program.hook("preAction", (_thisCommand, actionCommand) => {
33873
34541
  const { token } = actionCommand.optsWithGlobals();
33874
34542
  setTokenOverride(token);
@@ -33897,9 +34565,11 @@ function buildProgram() {
33897
34565
  program.addCommand(authCommand);
33898
34566
  program.addCommand(glossaryCommand);
33899
34567
  program.addCommand(jobsCommand);
34568
+ program.addCommand(languagesCommand);
33900
34569
  program.addCommand(learningsCommand);
34570
+ program.addCommand(statusCommand);
33901
34571
  program.addCommand(telemetryCommand);
33902
- program.addCommand(translateCommand);
34572
+ program.addCommand(translateCommand2);
33903
34573
  program.addHelpText("after", PROGRAM_HELP_FOOTER);
33904
34574
  forEachCommand(program, (command) => {
33905
34575
  if (command !== program) addTokenOption(command);
@@ -33915,7 +34585,7 @@ function forEachCommand(command, visit2) {
33915
34585
  var TOKEN_HELP = `Auth token to use for this invocation (overrides $${TOKEN_ENV_VAR} and the stored token)`;
33916
34586
  function addTokenOption(command) {
33917
34587
  if (command.options.some((o) => o.long === "--token")) return;
33918
- command.addOption(new Option("--token <token>", TOKEN_HELP).hideHelp());
34588
+ command.addOption(new Option2("--token <token>", TOKEN_HELP).hideHelp());
33919
34589
  }
33920
34590
  function jsonOptionOf(command) {
33921
34591
  return command.options.find((option) => option.long === "--json");
@@ -33955,6 +34625,8 @@ var PROGRAM_HELP_FOOTER = `
33955
34625
  Examples:
33956
34626
  $ tolkbot auth login
33957
34627
  $ tolkbot init
34628
+ $ tolkbot status
34629
+ $ tolkbot languages add nb
33958
34630
  $ tolkbot translate --all
33959
34631
  $ tolkbot translate --all --dry-run
33960
34632
  $ tolkbot jobs list