@tolkbot/cli 0.5.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 +616 -184
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -5784,10 +5784,10 @@ var require_out4 = __commonJS({
5784
5784
  });
5785
5785
 
5786
5786
  // src/main.ts
5787
- import { Command as Command8, 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.5.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) {
@@ -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.",
@@ -31321,6 +31390,13 @@ var BILLING_BLOCKED = "BILLING_BLOCKED";
31321
31390
 
31322
31391
  // ../shared/src/projects/localeSetup.ts
31323
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
+ }
31324
31400
 
31325
31401
  // ../shared/src/projects/index.ts
31326
31402
  var PROJECT_ARCHIVED = "PROJECT_ARCHIVED";
@@ -31354,6 +31430,7 @@ var ERROR_CODES = {
31354
31430
  login_failed: EXIT_CODES.failure,
31355
31431
  // 2: usage
31356
31432
  usage: EXIT_CODES.usage,
31433
+ unsupported_locale: EXIT_CODES.usage,
31357
31434
  // 3: not authenticated, or not allowed
31358
31435
  not_authenticated: EXIT_CODES.auth,
31359
31436
  token_expired: EXIT_CODES.auth,
@@ -31676,24 +31753,377 @@ jobsCommand.command("cancel").description("Cancel a running translation job").ar
31676
31753
  await runJobsCancel(validateOrExit(schema, { ...opts, jobId }));
31677
31754
  });
31678
31755
 
31679
- // src/commands/learnings/index.ts
31680
- import { Command as Command4 } from "commander";
31756
+ // src/commands/languages/index.ts
31757
+ import { Command as Command4, Option } from "commander";
31681
31758
  import { z as z6 } from "zod/v4";
31682
31759
 
31683
- // src/commands/learnings/archive.tsx
31760
+ // src/commands/languages/add.tsx
31684
31761
  import { Spinner as Spinner9 } from "@inkjs/ui";
31685
- 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
31686
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";
31687
32117
  function ArchiveUI({ state }) {
31688
32118
  if (state.type === "archiving") {
31689
- return /* @__PURE__ */ jsxs12(Box12, { children: [
31690
- /* @__PURE__ */ jsx12(Spinner9, {}),
31691
- /* @__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" })
31692
32122
  ] });
31693
32123
  }
31694
- return /* @__PURE__ */ jsxs12(Box12, { children: [
31695
- /* @__PURE__ */ jsx12(Text12, { color: "green", children: "\u2713" }),
31696
- /* @__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" })
31697
32127
  ] });
31698
32128
  }
31699
32129
  async function runLearningsArchive(opts) {
@@ -31710,20 +32140,20 @@ async function runLearningsArchive(opts) {
31710
32140
  jsonSuccess({ id: opts.id, ok: true });
31711
32141
  }
31712
32142
  const { unmount } = renderTransient(
31713
- /* @__PURE__ */ jsx12(ArchiveUI, { state: { type: "archiving" } })
32143
+ /* @__PURE__ */ jsx13(ArchiveUI, { state: { type: "archiving" } })
31714
32144
  );
31715
32145
  try {
31716
32146
  await run();
31717
32147
  } finally {
31718
32148
  unmount();
31719
32149
  }
31720
- render12(/* @__PURE__ */ jsx12(ArchiveUI, { state: { type: "success" } })).unmount();
32150
+ render12(/* @__PURE__ */ jsx13(ArchiveUI, { state: { type: "success" } })).unmount();
31721
32151
  }
31722
32152
 
31723
32153
  // src/commands/learnings/list.tsx
31724
- import { Spinner as Spinner10 } from "@inkjs/ui";
31725
- import { Box as Box13, render as render13, Text as Text13 } from "ink";
31726
- 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";
31727
32157
  var feedbackLabels = {
31728
32158
  "incorrect-term": "Incorrect term",
31729
32159
  "wrong-tone": "Wrong tone",
@@ -31733,47 +32163,47 @@ var feedbackLabels = {
31733
32163
  };
31734
32164
  function LearningsListUI({ state }) {
31735
32165
  if (state.type === "loading") {
31736
- return /* @__PURE__ */ jsxs13(Box13, { children: [
31737
- /* @__PURE__ */ jsx13(Spinner10, {}),
31738
- /* @__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" })
31739
32169
  ] });
31740
32170
  }
31741
32171
  const { learnings } = state;
31742
32172
  if (learnings.length === 0) {
31743
- 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." }) });
31744
32174
  }
31745
- return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", children: [
31746
- /* @__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: [
31747
32177
  learnings.length,
31748
32178
  " learning",
31749
32179
  learnings.length === 1 ? "" : "s"
31750
32180
  ] }) }),
31751
- learnings.map((l) => /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", marginBottom: 1, children: [
31752
- /* @__PURE__ */ jsxs13(Box13, { gap: 1, children: [
31753
- /* @__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: [
31754
32184
  l.srcLocale,
31755
32185
  " \u2192 ",
31756
32186
  l.dstLocale
31757
32187
  ] }),
31758
- /* @__PURE__ */ jsxs13(Text13, { dimColor: true, children: [
32188
+ /* @__PURE__ */ jsxs14(Text14, { dimColor: true, children: [
31759
32189
  "[",
31760
32190
  feedbackLabels[l.feedbackType] ?? l.feedbackType,
31761
32191
  "]"
31762
32192
  ] }),
31763
- l.status === "archived" && /* @__PURE__ */ jsx13(Text13, { color: "yellow", children: "[archived]" })
32193
+ l.status === "archived" && /* @__PURE__ */ jsx14(Text14, { color: "yellow", children: "[archived]" })
31764
32194
  ] }),
31765
- /* @__PURE__ */ jsx13(Box13, { paddingLeft: 2, children: /* @__PURE__ */ jsxs13(Text13, { children: [
32195
+ /* @__PURE__ */ jsx14(Box14, { paddingLeft: 2, children: /* @__PURE__ */ jsxs14(Text14, { children: [
31766
32196
  '"',
31767
32197
  l.sourceText,
31768
32198
  '" \u2192 "',
31769
32199
  l.suggestedTranslation,
31770
32200
  '"'
31771
32201
  ] }) }),
31772
- 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: [
31773
32203
  "Reason: ",
31774
32204
  l.reasoning
31775
32205
  ] }) }),
31776
- /* @__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: [
31777
32207
  "ID: ",
31778
32208
  l._id
31779
32209
  ] }) })
@@ -31808,7 +32238,7 @@ async function runLearningsList(opts) {
31808
32238
  });
31809
32239
  }
31810
32240
  const { unmount } = renderTransient(
31811
- /* @__PURE__ */ jsx13(LearningsListUI, { state: { type: "loading" } })
32241
+ /* @__PURE__ */ jsx14(LearningsListUI, { state: { type: "loading" } })
31812
32242
  );
31813
32243
  let learnings;
31814
32244
  try {
@@ -31817,24 +32247,24 @@ async function runLearningsList(opts) {
31817
32247
  unmount();
31818
32248
  }
31819
32249
  render13(
31820
- /* @__PURE__ */ jsx13(LearningsListUI, { state: { type: "success", learnings } })
32250
+ /* @__PURE__ */ jsx14(LearningsListUI, { state: { type: "success", learnings } })
31821
32251
  ).unmount();
31822
32252
  }
31823
32253
 
31824
32254
  // src/commands/learnings/remove.tsx
31825
- import { Spinner as Spinner11 } from "@inkjs/ui";
31826
- import { Box as Box14, render as render14, Text as Text14 } from "ink";
31827
- 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";
31828
32258
  function RemoveUI2({ state }) {
31829
32259
  if (state.type === "removing") {
31830
- return /* @__PURE__ */ jsxs14(Box14, { children: [
31831
- /* @__PURE__ */ jsx14(Spinner11, {}),
31832
- /* @__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" })
31833
32263
  ] });
31834
32264
  }
31835
- return /* @__PURE__ */ jsxs14(Box14, { children: [
31836
- /* @__PURE__ */ jsx14(Text14, { color: "green", children: "\u2713" }),
31837
- /* @__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" })
31838
32268
  ] });
31839
32269
  }
31840
32270
  async function runLearningsRemove(opts) {
@@ -31851,62 +32281,62 @@ async function runLearningsRemove(opts) {
31851
32281
  jsonSuccess({ id: opts.id, ok: true });
31852
32282
  }
31853
32283
  const { unmount } = renderTransient(
31854
- /* @__PURE__ */ jsx14(RemoveUI2, { state: { type: "removing" } })
32284
+ /* @__PURE__ */ jsx15(RemoveUI2, { state: { type: "removing" } })
31855
32285
  );
31856
32286
  try {
31857
32287
  await run();
31858
32288
  } finally {
31859
32289
  unmount();
31860
32290
  }
31861
- render14(/* @__PURE__ */ jsx14(RemoveUI2, { state: { type: "success" } })).unmount();
32291
+ render14(/* @__PURE__ */ jsx15(RemoveUI2, { state: { type: "success" } })).unmount();
31862
32292
  }
31863
32293
 
31864
32294
  // src/commands/learnings/show.tsx
31865
- import { Spinner as Spinner12 } from "@inkjs/ui";
31866
- import { Box as Box15, render as render15, Text as Text15 } from "ink";
31867
- 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";
31868
32298
  function LearningShowUI({ state }) {
31869
32299
  if (state.type === "loading") {
31870
- return /* @__PURE__ */ jsxs15(Box15, { children: [
31871
- /* @__PURE__ */ jsx15(Spinner12, {}),
31872
- /* @__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" })
31873
32303
  ] });
31874
32304
  }
31875
32305
  const { learning } = state;
31876
- return /* @__PURE__ */ jsxs15(Box15, { flexDirection: "column", children: [
31877
- /* @__PURE__ */ jsxs15(Box15, { gap: 1, children: [
31878
- /* @__PURE__ */ jsx15(Text15, { bold: true, children: "Source text:" }),
31879
- /* @__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 })
31880
32310
  ] }),
31881
- /* @__PURE__ */ jsxs15(Box15, { gap: 1, children: [
31882
- /* @__PURE__ */ jsx15(Text15, { bold: true, children: "Suggested translation:" }),
31883
- /* @__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 })
31884
32314
  ] }),
31885
- /* @__PURE__ */ jsxs15(Box15, { gap: 1, children: [
31886
- /* @__PURE__ */ jsx15(Text15, { bold: true, children: "Locales:" }),
31887
- /* @__PURE__ */ jsxs15(Text15, { children: [
32315
+ /* @__PURE__ */ jsxs16(Box16, { gap: 1, children: [
32316
+ /* @__PURE__ */ jsx16(Text16, { bold: true, children: "Locales:" }),
32317
+ /* @__PURE__ */ jsxs16(Text16, { children: [
31888
32318
  learning.srcLocale,
31889
32319
  " \u2192 ",
31890
32320
  learning.dstLocale
31891
32321
  ] })
31892
32322
  ] }),
31893
- /* @__PURE__ */ jsxs15(Box15, { gap: 1, children: [
31894
- /* @__PURE__ */ jsx15(Text15, { bold: true, children: "Feedback type:" }),
31895
- /* @__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 })
31896
32326
  ] }),
31897
- /* @__PURE__ */ jsxs15(Box15, { gap: 1, children: [
31898
- /* @__PURE__ */ jsx15(Text15, { bold: true, children: "Reasoning:" }),
31899
- /* @__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 })
31900
32330
  ] }),
31901
- /* @__PURE__ */ jsxs15(Box15, { gap: 1, children: [
31902
- /* @__PURE__ */ jsx15(Text15, { bold: true, children: "Status:" }),
31903
- /* @__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 })
31904
32334
  ] }),
31905
- /* @__PURE__ */ jsxs15(Box15, { gap: 1, children: [
31906
- /* @__PURE__ */ jsx15(Text15, { bold: true, children: "Source:" }),
31907
- /* @__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 })
31908
32338
  ] }),
31909
- /* @__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: [
31910
32340
  "ID: ",
31911
32341
  learning._id
31912
32342
  ] }) })
@@ -31944,7 +32374,7 @@ async function runLearningsShow(opts) {
31944
32374
  });
31945
32375
  }
31946
32376
  const { unmount } = renderTransient(
31947
- /* @__PURE__ */ jsx15(LearningShowUI, { state: { type: "loading" } })
32377
+ /* @__PURE__ */ jsx16(LearningShowUI, { state: { type: "loading" } })
31948
32378
  );
31949
32379
  let learning;
31950
32380
  try {
@@ -31952,54 +32382,54 @@ async function runLearningsShow(opts) {
31952
32382
  } finally {
31953
32383
  unmount();
31954
32384
  }
31955
- render15(/* @__PURE__ */ jsx15(LearningShowUI, { state: { type: "success", learning } })).unmount();
32385
+ render15(/* @__PURE__ */ jsx16(LearningShowUI, { state: { type: "success", learning } })).unmount();
31956
32386
  }
31957
32387
 
31958
32388
  // src/commands/learnings/index.ts
31959
- var learningsCommand = new Command4("learnings").description(
32389
+ var learningsCommand = new Command5("learnings").description(
31960
32390
  "View and manage translation learnings"
31961
32391
  );
31962
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) => {
31963
- const schema = z6.object({
31964
- status: z6.enum(["active", "archived"]).optional(),
31965
- limit: z6.coerce.number().int().positive().default(100),
31966
- 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()
31967
32397
  });
31968
32398
  const parsed = validateOrExit(schema, opts);
31969
32399
  await runLearningsList(parsed);
31970
32400
  });
31971
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) => {
31972
- const schema = z6.object({
31973
- id: z6.string().min(1),
31974
- json: z6.boolean().optional()
32402
+ const schema = z7.object({
32403
+ id: z7.string().min(1),
32404
+ json: z7.boolean().optional()
31975
32405
  });
31976
32406
  const parsed = validateOrExit(schema, opts);
31977
32407
  await runLearningsShow(parsed);
31978
32408
  });
31979
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) => {
31980
- const schema = z6.object({
31981
- id: z6.string().min(1),
31982
- json: z6.boolean().optional()
32410
+ const schema = z7.object({
32411
+ id: z7.string().min(1),
32412
+ json: z7.boolean().optional()
31983
32413
  });
31984
32414
  const parsed = validateOrExit(schema, opts);
31985
32415
  await runLearningsArchive(parsed);
31986
32416
  });
31987
32417
  learningsCommand.command("remove").description("Permanently remove a learning").requiredOption("--id <learningId>", "Learning ID").option("--json", "Output as JSON (machine-readable)").action(async (opts) => {
31988
- const schema = z6.object({
31989
- id: z6.string().min(1),
31990
- json: z6.boolean().optional()
32418
+ const schema = z7.object({
32419
+ id: z7.string().min(1),
32420
+ json: z7.boolean().optional()
31991
32421
  });
31992
32422
  const parsed = validateOrExit(schema, opts);
31993
32423
  await runLearningsRemove(parsed);
31994
32424
  });
31995
32425
 
31996
32426
  // src/commands/status/index.ts
31997
- import { Command as Command5 } from "commander";
31998
- import { z as z7 } from "zod/v4";
32427
+ import { Command as Command6 } from "commander";
32428
+ import { z as z8 } from "zod/v4";
31999
32429
 
32000
32430
  // src/commands/status/status.tsx
32001
- import { Spinner as Spinner13 } from "@inkjs/ui";
32002
- import { Box as Box16, Text as Text16 } from "ink";
32431
+ import { Spinner as Spinner14 } from "@inkjs/ui";
32432
+ import { Box as Box17, Text as Text17 } from "ink";
32003
32433
 
32004
32434
  // src/commands/status/status-core.ts
32005
32435
  function fullKey4(namespace, key) {
@@ -32123,7 +32553,7 @@ function describeLanguage(language) {
32123
32553
  }
32124
32554
 
32125
32555
  // src/commands/status/status.tsx
32126
- import { jsx as jsx16, jsxs as jsxs16 } from "react/jsx-runtime";
32556
+ import { jsx as jsx17, jsxs as jsxs17 } from "react/jsx-runtime";
32127
32557
  var OFFLINE_HINT = "Without a login, 'tolkbot validate' checks the files on this machine.";
32128
32558
  async function runStatus(opts) {
32129
32559
  let ctx;
@@ -32143,9 +32573,9 @@ async function runStatus(opts) {
32143
32573
  const srcLocale = project.baseLanguage;
32144
32574
  const locales = project.targetLanguages;
32145
32575
  const progress = renderTransient(
32146
- /* @__PURE__ */ jsxs16(Box16, { children: [
32147
- /* @__PURE__ */ jsx16(Spinner13, {}),
32148
- /* @__PURE__ */ jsxs16(Text16, { children: [
32576
+ /* @__PURE__ */ jsxs17(Box17, { children: [
32577
+ /* @__PURE__ */ jsx17(Spinner14, {}),
32578
+ /* @__PURE__ */ jsxs17(Text17, { children: [
32149
32579
  " Reading the status of ",
32150
32580
  project.name,
32151
32581
  "\u2026"
@@ -32216,23 +32646,23 @@ async function runStatus(opts) {
32216
32646
  }
32217
32647
 
32218
32648
  // src/commands/status/index.ts
32219
- var statusCommand = new Command5("status").description(
32649
+ var statusCommand = new Command6("status").description(
32220
32650
  "Show what each language is missing, what waits for a person, the open pull requests and the credits left"
32221
32651
  ).option("--keys", "List the keys by name under each language").option(
32222
32652
  "--exit-code",
32223
32653
  "Exit 1 when any language has missing or changed keys"
32224
32654
  ).option("--json", "Output as JSON (machine-readable)").action(async (opts) => {
32225
- const schema = z7.object({
32226
- keys: z7.boolean().optional(),
32227
- exitCode: z7.boolean().optional(),
32228
- json: z7.boolean().optional()
32655
+ const schema = z8.object({
32656
+ keys: z8.boolean().optional(),
32657
+ exitCode: z8.boolean().optional(),
32658
+ json: z8.boolean().optional()
32229
32659
  });
32230
32660
  await runStatus(validateOrExit(schema, opts));
32231
32661
  });
32232
32662
 
32233
32663
  // src/commands/telemetry.ts
32234
- import { Command as Command6 } from "commander";
32235
- import { z as z8 } from "zod/v4";
32664
+ import { Command as Command7 } from "commander";
32665
+ import { z as z9 } from "zod/v4";
32236
32666
  var REASONS = {
32237
32667
  env: `disabled by $${TELEMETRY_ENV_VAR}`,
32238
32668
  "do-not-track": `disabled by $${DO_NOT_TRACK_ENV_VAR}`,
@@ -32253,12 +32683,12 @@ async function printStatus(json2) {
32253
32683
  `Turn it off with \`tolkbot telemetry off\`, $${TELEMETRY_ENV_VAR}=0 or $${DO_NOT_TRACK_ENV_VAR}=1.`
32254
32684
  );
32255
32685
  }
32256
- var telemetryCommand = new Command6("telemetry").description(
32686
+ var telemetryCommand = new Command7("telemetry").description(
32257
32687
  "Control anonymous usage reporting"
32258
32688
  );
32259
32689
  telemetryCommand.command("status", { isDefault: true }).description("Show whether telemetry is on and why").option("--json", "Output as JSON (machine-readable)").action(async (opts) => {
32260
32690
  const parsed = validateOrExit(
32261
- z8.object({ json: z8.boolean().optional() }),
32691
+ z9.object({ json: z9.boolean().optional() }),
32262
32692
  opts
32263
32693
  );
32264
32694
  await printStatus(parsed.json);
@@ -32275,8 +32705,8 @@ telemetryCommand.command("on").description("Resume reporting usage from this mac
32275
32705
  });
32276
32706
 
32277
32707
  // src/commands/translate/index.ts
32278
- import { Command as Command7 } from "commander";
32279
- import { z as z9 } from "zod/v4";
32708
+ import { Command as Command8 } from "commander";
32709
+ import { z as z10 } from "zod/v4";
32280
32710
 
32281
32711
  // src/commands/translate/adopt.tsx
32282
32712
  var STAMP_CHUNK2 = 200;
@@ -32326,7 +32756,7 @@ async function runAdopt(opts) {
32326
32756
  if (locales.length === 0) {
32327
32757
  throw opts.all ? new CliError(
32328
32758
  "No target languages configured for this project.",
32329
- void 0,
32759
+ "Add one with 'tolkbot languages add <code>'.",
32330
32760
  "no_target_languages"
32331
32761
  ) : new UsageError(
32332
32762
  "Either --dst <locale> or --all must be provided."
@@ -32357,8 +32787,8 @@ async function runAdopt(opts) {
32357
32787
 
32358
32788
  // src/commands/translate/edit.tsx
32359
32789
  import path10 from "path";
32360
- import { Spinner as Spinner14 } from "@inkjs/ui";
32361
- import { Box as Box17, render as render16, Text as Text17 } from "ink";
32790
+ import { Spinner as Spinner15 } from "@inkjs/ui";
32791
+ import { Box as Box18, render as render16, Text as Text18 } from "ink";
32362
32792
 
32363
32793
  // src/utils/locale-files.ts
32364
32794
  import path9 from "path";
@@ -32396,25 +32826,25 @@ function resolveKeyInFile(key, targetFile, fileCount, namespace) {
32396
32826
  }
32397
32827
 
32398
32828
  // src/commands/translate/edit.tsx
32399
- import { jsx as jsx17, jsxs as jsxs17 } from "react/jsx-runtime";
32829
+ import { jsx as jsx18, jsxs as jsxs18 } from "react/jsx-runtime";
32400
32830
  function EditUI({ state }) {
32401
32831
  if (state.type === "editing") {
32402
- return /* @__PURE__ */ jsxs17(Box17, { children: [
32403
- /* @__PURE__ */ jsx17(Spinner14, {}),
32404
- /* @__PURE__ */ jsx17(Text17, { children: " Editing translation\u2026" })
32832
+ return /* @__PURE__ */ jsxs18(Box18, { children: [
32833
+ /* @__PURE__ */ jsx18(Spinner15, {}),
32834
+ /* @__PURE__ */ jsx18(Text18, { children: " Editing translation\u2026" })
32405
32835
  ] });
32406
32836
  }
32407
- return /* @__PURE__ */ jsxs17(Box17, { flexDirection: "column", children: [
32408
- /* @__PURE__ */ jsxs17(Box17, { children: [
32409
- /* @__PURE__ */ jsx17(Text17, { color: "green", children: "\u2713" }),
32410
- /* @__PURE__ */ jsxs17(Text17, { 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: [
32411
32841
  " ",
32412
32842
  "Updated ",
32413
- /* @__PURE__ */ jsx17(Text17, { bold: true, children: state.key })
32843
+ /* @__PURE__ */ jsx18(Text18, { bold: true, children: state.key })
32414
32844
  ] })
32415
32845
  ] }),
32416
- /* @__PURE__ */ jsx17(Box17, { paddingLeft: 2, children: /* @__PURE__ */ jsx17(Text17, { dimColor: true, children: state.value }) }),
32417
- /* @__PURE__ */ jsx17(Box17, { marginTop: 1, children: /* @__PURE__ */ jsxs17(Text17, { 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: [
32418
32848
  "File: ",
32419
32849
  state.filePath
32420
32850
  ] }) })
@@ -32471,20 +32901,20 @@ async function runEdit(opts) {
32471
32901
  value: result2.value
32472
32902
  });
32473
32903
  }
32474
- const { unmount } = renderTransient(/* @__PURE__ */ jsx17(EditUI, { state: { type: "editing" } }));
32904
+ const { unmount } = renderTransient(/* @__PURE__ */ jsx18(EditUI, { state: { type: "editing" } }));
32475
32905
  let result;
32476
32906
  try {
32477
32907
  result = await performEdit(opts);
32478
32908
  } finally {
32479
32909
  unmount();
32480
32910
  }
32481
- render16(/* @__PURE__ */ jsx17(EditUI, { state: { type: "success", ...result } })).unmount();
32911
+ render16(/* @__PURE__ */ jsx18(EditUI, { state: { type: "success", ...result } })).unmount();
32482
32912
  }
32483
32913
 
32484
32914
  // src/commands/translate/translate.tsx
32485
- import { Spinner as Spinner15 } from "@inkjs/ui";
32486
- import { Box as Box18, Text as Text18 } from "ink";
32487
- import { jsx as jsx18, jsxs as jsxs18 } 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";
32488
32918
  function planOutput(plan) {
32489
32919
  return {
32490
32920
  ...plan.summary,
@@ -32505,7 +32935,7 @@ function targetLocales(ctx, opts) {
32505
32935
  if (locales.length === 0) {
32506
32936
  throw new CliError(
32507
32937
  "No target languages configured.",
32508
- `Configure them in the project settings at ${env.TOLKBOT_WEB_URL}/projects/${ctx.project.slug}/settings.`,
32938
+ "Add one with 'tolkbot languages add <code>'.",
32509
32939
  "no_target_languages"
32510
32940
  );
32511
32941
  }
@@ -32525,7 +32955,7 @@ var NO_ESTIMATE = "No credit estimate exists for this project yet. Keys and char
32525
32955
  async function runDryRun(ctx, srcLocale, dstLocales, opts) {
32526
32956
  const languages = [];
32527
32957
  const progress = renderTransient(
32528
- /* @__PURE__ */ jsx18(Working, { text: `Planning ${dstLocales.join(", ")}\u2026` })
32958
+ /* @__PURE__ */ jsx19(Working, { text: `Planning ${dstLocales.join(", ")}\u2026` })
32529
32959
  );
32530
32960
  try {
32531
32961
  for (const dstLocale of dstLocales) {
@@ -32603,9 +33033,9 @@ In all: ${total.keys} key(s), ${total.characters} characters.` + (balance === nu
32603
33033
  console.log("Nothing was started. Run without --dry-run to translate.");
32604
33034
  }
32605
33035
  function Working({ text }) {
32606
- return /* @__PURE__ */ jsxs18(Box18, { children: [
32607
- /* @__PURE__ */ jsx18(Spinner15, {}),
32608
- /* @__PURE__ */ jsxs18(Text18, { children: [
33036
+ return /* @__PURE__ */ jsxs19(Box19, { children: [
33037
+ /* @__PURE__ */ jsx19(Spinner16, {}),
33038
+ /* @__PURE__ */ jsxs19(Text19, { children: [
32609
33039
  " ",
32610
33040
  text
32611
33041
  ] })
@@ -32616,7 +33046,7 @@ function progressText(dstLocale, summary) {
32616
33046
  const done = summary.completed + summary.failed;
32617
33047
  return `${dstLocale}: translating ${done}/${summary.total}\u2026`;
32618
33048
  }
32619
- function describeOutcome(outcome) {
33049
+ function describeOutcome2(outcome) {
32620
33050
  const { dstLocale, status, keys, written } = outcome;
32621
33051
  const wrote = written ? `, ${written.keys} written to ${written.files} file(s)` : "";
32622
33052
  const notApplied = outcome.notApplied.length > 0 ? `, ${outcome.notApplied.length} not applied` : "";
@@ -32651,14 +33081,14 @@ async function runLanguages(ctx, srcLocale, dstLocales, opts) {
32651
33081
  const report = (outcome) => {
32652
33082
  outcomes.push(outcome);
32653
33083
  if (json2) return;
32654
- console.log(describeOutcome(outcome));
33084
+ console.log(describeOutcome2(outcome));
32655
33085
  for (const line of describeNotApplied(outcome.notApplied)) {
32656
33086
  console.log(` ${line}`);
32657
33087
  }
32658
33088
  };
32659
33089
  for (const dstLocale of dstLocales) {
32660
33090
  const progress = renderTransient(
32661
- /* @__PURE__ */ jsx18(Working, { text: `${dstLocale}: planning\u2026` })
33091
+ /* @__PURE__ */ jsx19(Working, { text: `${dstLocale}: planning\u2026` })
32662
33092
  );
32663
33093
  try {
32664
33094
  const { tasks, plan } = await generateTasks(
@@ -32691,7 +33121,7 @@ async function runLanguages(ctx, srcLocale, dstLocales, opts) {
32691
33121
  printNotes(json2, leftOut);
32692
33122
  continue;
32693
33123
  }
32694
- progress.rerender(/* @__PURE__ */ jsx18(Working, { text: progressText(dstLocale, null) }));
33124
+ progress.rerender(/* @__PURE__ */ jsx19(Working, { text: progressText(dstLocale, null) }));
32695
33125
  let jobId;
32696
33126
  try {
32697
33127
  jobId = await startTranslationJob({
@@ -32728,7 +33158,7 @@ async function runLanguages(ctx, srcLocale, dstLocales, opts) {
32728
33158
  project.organizationId,
32729
33159
  jobId,
32730
33160
  (latest) => progress.rerender(
32731
- /* @__PURE__ */ jsx18(Working, { text: progressText(dstLocale, latest) })
33161
+ /* @__PURE__ */ jsx19(Working, { text: progressText(dstLocale, latest) })
32732
33162
  )
32733
33163
  );
32734
33164
  } catch (error62) {
@@ -32753,7 +33183,7 @@ async function runLanguages(ctx, srcLocale, dstLocales, opts) {
32753
33183
  };
32754
33184
  if (summary.status !== "cancelled") {
32755
33185
  progress.rerender(
32756
- /* @__PURE__ */ jsx18(Working, { text: `${dstLocale}: writing the files\u2026` })
33186
+ /* @__PURE__ */ jsx19(Working, { text: `${dstLocale}: writing the files\u2026` })
32757
33187
  );
32758
33188
  const applied = await applyJob(ctx, jobId);
32759
33189
  finished.written = { files: applied.files, keys: applied.keys };
@@ -32817,7 +33247,7 @@ function checkTranslateOptions(opts) {
32817
33247
 
32818
33248
  // src/commands/translate/index.ts
32819
33249
  var RETRANSLATE_UNTRACKED_HELP = "Also retranslate translations TolkBot has no record of (kept by default)";
32820
- var translateCommand = new Command7("translate").description(
33250
+ var translateCommand2 = new Command8("translate").description(
32821
33251
  "Translate what is missing or changed, and write the results into your locale files"
32822
33252
  ).option(
32823
33253
  "--src <locale>",
@@ -32833,31 +33263,31 @@ var translateCommand = new Command7("translate").description(
32833
33263
  "Run 'tolkbot translate --help'. Jobs are under 'tolkbot jobs'."
32834
33264
  );
32835
33265
  }
32836
- const schema = z9.object({
32837
- src: z9.string().min(1).optional(),
32838
- dst: z9.string().min(1).optional(),
32839
- all: z9.boolean().optional(),
32840
- dryRun: z9.boolean().optional(),
32841
- wait: z9.boolean(),
32842
- retranslateUntracked: z9.boolean().optional(),
32843
- verbose: z9.boolean().optional(),
32844
- json: z9.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()
32845
33275
  });
32846
33276
  const parsed = validateOrExit(schema, opts);
32847
33277
  checkTranslateOptions(parsed);
32848
33278
  await runTranslate(parsed);
32849
33279
  });
32850
- translateCommand.command("adopt").description(
33280
+ translateCommand2.command("adopt").description(
32851
33281
  "Record existing translations TolkBot has no baseline for as reviewed, so they are kept until their source changes"
32852
33282
  ).option(
32853
33283
  "--src <locale>",
32854
33284
  "Source locale (defaults to the project's base language)"
32855
33285
  ).option("--dst <locale>", "Destination locale").option("--all", "Every target language").option("--json", "Output as JSON (machine-readable)").action(async (opts) => {
32856
- const schema = z9.object({
32857
- src: z9.string().min(1).optional(),
32858
- dst: z9.string().min(1).optional(),
32859
- all: z9.boolean().optional(),
32860
- json: z9.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()
32861
33291
  });
32862
33292
  const parsed = validateOrExit(schema, opts);
32863
33293
  if (parsed.dst && parsed.all) {
@@ -32865,20 +33295,20 @@ translateCommand.command("adopt").description(
32865
33295
  }
32866
33296
  await runAdopt(parsed);
32867
33297
  });
32868
- 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) => {
32869
- const schema = z9.object({
32870
- key: z9.string().min(1),
32871
- locale: z9.string().min(1),
32872
- value: z9.string().min(1),
32873
- namespace: z9.string().optional(),
32874
- json: z9.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()
32875
33305
  });
32876
33306
  const parsed = validateOrExit(schema, opts);
32877
33307
  await runEdit(parsed);
32878
33308
  });
32879
33309
 
32880
33310
  // src/commands/validate.tsx
32881
- import { Box as Box19, render as render17, Text as Text19 } from "ink";
33311
+ import { Box as Box20, render as render17, Text as Text20 } from "ink";
32882
33312
 
32883
33313
  // ../ai/src/validators/placeholders.ts
32884
33314
  var IDENTIFIER = /^[A-Za-z0-9_$][A-Za-z0-9_$.[\]-]*$/;
@@ -33971,7 +34401,7 @@ function finalize2(issues, stats, strict) {
33971
34401
  }
33972
34402
 
33973
34403
  // src/commands/validate.tsx
33974
- import { jsx as jsx19, jsxs as jsxs19 } from "react/jsx-runtime";
34404
+ import { jsx as jsx20, jsxs as jsxs20 } from "react/jsx-runtime";
33975
34405
  var LEVEL_COLOR = {
33976
34406
  error: "red",
33977
34407
  warning: "yellow",
@@ -33980,12 +34410,12 @@ var LEVEL_COLOR = {
33980
34410
  var GUTTER = 9;
33981
34411
  function IssueLine({ issue: issue2 }) {
33982
34412
  const location = [issue2.file, issue2.key].filter(Boolean).join(" \xB7 ");
33983
- return /* @__PURE__ */ jsxs19(Box19, { flexDirection: "column", children: [
33984
- /* @__PURE__ */ jsxs19(Box19, { children: [
33985
- /* @__PURE__ */ jsx19(Box19, { width: GUTTER, flexShrink: 0, children: /* @__PURE__ */ jsx19(Text19, { color: LEVEL_COLOR[issue2.level], children: issue2.level }) }),
33986
- /* @__PURE__ */ jsx19(Box19, { flexGrow: 1, children: /* @__PURE__ */ jsx19(Text19, { 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 }) })
33987
34417
  ] }),
33988
- /* @__PURE__ */ jsx19(Box19, { marginLeft: GUTTER, children: /* @__PURE__ */ jsxs19(Text19, { dimColor: true, children: [
34418
+ /* @__PURE__ */ jsx20(Box20, { marginLeft: GUTTER, children: /* @__PURE__ */ jsxs20(Text20, { dimColor: true, children: [
33989
34419
  issue2.rule,
33990
34420
  location ? ` \u2014 ${location}` : ""
33991
34421
  ] }) })
@@ -33994,20 +34424,20 @@ function IssueLine({ issue: issue2 }) {
33994
34424
  function ReportUI({ report }) {
33995
34425
  const { counts, stats } = report;
33996
34426
  const summary = ["error", "warning", "info"].filter((level) => counts[level] > 0).map((level) => `${counts[level]} ${level}(s)`).join(", ");
33997
- return /* @__PURE__ */ jsxs19(Box19, { flexDirection: "column", children: [
33998
- /* @__PURE__ */ jsxs19(Box19, { children: [
33999
- /* @__PURE__ */ jsx19(Text19, { color: report.valid ? "green" : "red", children: report.valid ? "\u2713" : "\u2717" }),
34000
- /* @__PURE__ */ jsxs19(Text19, { 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: [
34001
34431
  " Validation ",
34002
34432
  report.valid ? "passed" : "failed"
34003
34433
  ] }),
34004
- summary ? /* @__PURE__ */ jsxs19(Text19, { dimColor: true, children: [
34434
+ summary ? /* @__PURE__ */ jsxs20(Text20, { dimColor: true, children: [
34005
34435
  " \u2014 ",
34006
34436
  summary
34007
34437
  ] }) : null
34008
34438
  ] }),
34009
- stats.locales.length > 0 && /* @__PURE__ */ jsxs19(Box19, { marginTop: 1, flexDirection: "column", children: [
34010
- /* @__PURE__ */ jsxs19(Text19, { dimColor: true, children: [
34439
+ stats.locales.length > 0 && /* @__PURE__ */ jsxs20(Box20, { marginTop: 1, flexDirection: "column", children: [
34440
+ /* @__PURE__ */ jsxs20(Text20, { dimColor: true, children: [
34011
34441
  stats.filesChecked,
34012
34442
  " ",
34013
34443
  stats.format,
@@ -34016,18 +34446,18 @@ function ReportUI({ report }) {
34016
34446
  stats.totalKeys,
34017
34447
  " keys"
34018
34448
  ] }),
34019
- /* @__PURE__ */ jsxs19(Text19, { dimColor: true, children: [
34449
+ /* @__PURE__ */ jsxs20(Text20, { dimColor: true, children: [
34020
34450
  "Locales: ",
34021
34451
  stats.locales.join(", "),
34022
34452
  stats.baseLocale ? ` (base: ${stats.baseLocale})` : ""
34023
34453
  ] }),
34024
- stats.pattern && /* @__PURE__ */ jsxs19(Text19, { dimColor: true, children: [
34454
+ stats.pattern && /* @__PURE__ */ jsxs20(Text20, { dimColor: true, children: [
34025
34455
  "Pattern: ",
34026
34456
  stats.pattern
34027
34457
  ] })
34028
34458
  ] }),
34029
- report.issues.length > 0 && /* @__PURE__ */ jsx19(Box19, { marginTop: 1, flexDirection: "column", gap: 1, children: report.issues.map((issue2, idx) => /* @__PURE__ */ jsx19(IssueLine, { issue: issue2 }, idx)) }),
34030
- !report.strict && counts.warning > 0 && report.valid && /* @__PURE__ */ jsx19(Box19, { marginTop: 1, children: /* @__PURE__ */ jsx19(Text19, { 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." }) })
34031
34461
  ] });
34032
34462
  }
34033
34463
  function emit(report, json2) {
@@ -34047,7 +34477,7 @@ function emit(report, json2) {
34047
34477
  if (!report.valid) fail(validationFailure(report));
34048
34478
  return;
34049
34479
  }
34050
- render17(/* @__PURE__ */ jsx19(ReportUI, { report })).unmount();
34480
+ render17(/* @__PURE__ */ jsx20(ReportUI, { report })).unmount();
34051
34481
  process.exitCode = exitCode;
34052
34482
  }
34053
34483
  function validationFailure(report) {
@@ -34106,7 +34536,7 @@ function commandPath(command) {
34106
34536
  return names.join(" ");
34107
34537
  }
34108
34538
  function buildProgram() {
34109
- const program = new Command8().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();
34110
34540
  program.hook("preAction", (_thisCommand, actionCommand) => {
34111
34541
  const { token } = actionCommand.optsWithGlobals();
34112
34542
  setTokenOverride(token);
@@ -34135,10 +34565,11 @@ function buildProgram() {
34135
34565
  program.addCommand(authCommand);
34136
34566
  program.addCommand(glossaryCommand);
34137
34567
  program.addCommand(jobsCommand);
34568
+ program.addCommand(languagesCommand);
34138
34569
  program.addCommand(learningsCommand);
34139
34570
  program.addCommand(statusCommand);
34140
34571
  program.addCommand(telemetryCommand);
34141
- program.addCommand(translateCommand);
34572
+ program.addCommand(translateCommand2);
34142
34573
  program.addHelpText("after", PROGRAM_HELP_FOOTER);
34143
34574
  forEachCommand(program, (command) => {
34144
34575
  if (command !== program) addTokenOption(command);
@@ -34154,7 +34585,7 @@ function forEachCommand(command, visit2) {
34154
34585
  var TOKEN_HELP = `Auth token to use for this invocation (overrides $${TOKEN_ENV_VAR} and the stored token)`;
34155
34586
  function addTokenOption(command) {
34156
34587
  if (command.options.some((o) => o.long === "--token")) return;
34157
- command.addOption(new Option("--token <token>", TOKEN_HELP).hideHelp());
34588
+ command.addOption(new Option2("--token <token>", TOKEN_HELP).hideHelp());
34158
34589
  }
34159
34590
  function jsonOptionOf(command) {
34160
34591
  return command.options.find((option) => option.long === "--json");
@@ -34195,6 +34626,7 @@ Examples:
34195
34626
  $ tolkbot auth login
34196
34627
  $ tolkbot init
34197
34628
  $ tolkbot status
34629
+ $ tolkbot languages add nb
34198
34630
  $ tolkbot translate --all
34199
34631
  $ tolkbot translate --all --dry-run
34200
34632
  $ tolkbot jobs list
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tolkbot/cli",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Command-line interface for TolkBot: AI translation for the i18n files in a GitHub repository.",
5
5
  "license": "UNLICENSED",
6
6
  "homepage": "https://tolkbot.ai",