@bud.sh/app 0.2.0 → 0.4.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 (77) hide show
  1. package/dist/client/assets/bud/design.js +1 -1
  2. package/dist/client/assets/canvas-C8hyPWoT.js +1 -0
  3. package/dist/client/assets/difference-worker-BfF4_N3e.js +8 -0
  4. package/dist/client/assets/files-D_Tm7gCK.js +1 -0
  5. package/dist/client/assets/i18n-BtfXum8P.js +1 -0
  6. package/dist/client/assets/index-Dpxjiok1.js +2 -0
  7. package/dist/client/assets/pages-B-_CKvDS.js +1 -0
  8. package/dist/client/assets/project-branches-DzXdaT9L.js +1 -0
  9. package/dist/client/assets/project-comments-CQkB2jbE.js +1 -0
  10. package/dist/client/assets/project-thread-links-BN7zL0AX.js +1 -0
  11. package/dist/client/assets/{project-threads-DezXiVK6.js → project-threads-CHGG-6fo.js} +104 -104
  12. package/dist/client/assets/{project-work-CB1JHqGW.js → project-work-L5fYMSOu.js} +1 -1
  13. package/dist/client/assets/task-design-DYqcqq1b.js +23 -0
  14. package/dist/client/assets/tasks-A2FKo3_h.js +1 -0
  15. package/dist/client/assets/tasks-request-DGigNdyK.js +1 -0
  16. package/dist/client/assets/workspace-CvWmU2RK.js +2 -0
  17. package/dist/client/index.html +2 -1
  18. package/dist/hosted/assets/{canvas-C69-96GR.js → canvas-MWtOwaWm.js} +69 -41
  19. package/dist/hosted/assets/{comment-data-CiDuvXi2.js → comment-data-DsgJWq4n.js} +7195 -1067
  20. package/dist/hosted/assets/{files-DQL5hzs3.js → files-Wvo11G2A.js} +92 -89
  21. package/dist/hosted/assets/{pages-DFs7jB6M.js → pages-BAmRFVtU.js} +31 -22
  22. package/dist/hosted/assets/{project-branches-CffV4IS1.js → project-branches-BNloVosX.js} +5 -5
  23. package/dist/hosted/assets/{project-thread-links-B1Y2bDUx.js → project-thread-links-BmY8HQXt.js} +12 -12
  24. package/dist/hosted/assets/{screen-BroBb1Z_.js → screen-QmqmI2Aq.js} +18 -18
  25. package/dist/hosted/assets/{tasks-mzmNhe2d.js → tasks-C8iWPrWg.js} +29 -29
  26. package/dist/hosted/assets/{tasks-request-Dcrpd2_8.js → tasks-request-C89fN1dK.js} +7 -7
  27. package/dist/hosted/hosted.js +159 -31
  28. package/dist/node/design.js +6235 -139
  29. package/dist/node/index.js +99 -2
  30. package/dist/node-host/assets/{canvas-BZhzizzt.js → canvas-D_F8SIlm.js} +70 -42
  31. package/dist/node-host/assets/{comment-data-nUlI0szN.js → comment-data-q_8INiZz.js} +7221 -1093
  32. package/dist/node-host/assets/{files-CTzcWGjb.js → files-xazOrNCX.js} +92 -89
  33. package/dist/node-host/assets/{pages-0aWH7z1j.js → pages-D6VkmdfQ.js} +31 -22
  34. package/dist/node-host/assets/{project-branches-1zs_0J_y.js → project-branches-C26hwmo3.js} +5 -5
  35. package/dist/node-host/assets/{project-thread-links-LpUAGcDr.js → project-thread-links-BOzjws5W.js} +12 -12
  36. package/dist/node-host/assets/{screen-DQoTOWYR.js → screen-Dog-YByu.js} +18 -18
  37. package/dist/node-host/assets/{tasks-C0WqnM7Q.js → tasks-A1_bpb3_.js} +30 -30
  38. package/dist/node-host/assets/{tasks-request-Bwky5ORf.js → tasks-request-Bie1PpQP.js} +7 -7
  39. package/dist/node-host/node-host.js +1765 -1630
  40. package/dist/server/assets/{branch-previews-C_TVt_HQ.js → branch-previews-ClkoJ0lM.js} +116 -5
  41. package/dist/server/assets/{canvas-D6tIkONI.js → canvas-Dn4RYoU2.js} +70 -42
  42. package/dist/server/assets/{comment-data-BuArfgcc.js → comment-data-B0OLHBGp.js} +7219 -1091
  43. package/dist/server/assets/{files-DkrEB48U.js → files-C5Vilfe4.js} +92 -89
  44. package/dist/server/assets/{listener-BVbFsnmX.js → listener-o1STuLju.js} +43 -29
  45. package/dist/server/assets/{pages-iEdmK_o2.js → pages-BNa0MHEc.js} +31 -22
  46. package/dist/server/assets/{project-branches-B7rtrvMl.js → project-branches-GtQYyOH5.js} +5 -5
  47. package/dist/server/assets/{project-thread-links-ue_ad9IX.js → project-thread-links-Nfz9NIny.js} +12 -12
  48. package/dist/server/assets/{project-work-6aOyja4v.js → project-work-Cy_BpHwR.js} +2 -2
  49. package/dist/server/assets/{screen-DMUB9a-K.js → screen-BtcJmZwj.js} +18 -18
  50. package/dist/server/assets/{server-DUBjmaBw.js → server-CYvuOSBA.js} +1 -1
  51. package/dist/server/assets/{services-Og5rcF50.js → services-DK4fqrlF.js} +2 -2
  52. package/dist/server/assets/{tasks-C2oa2wSC.js → tasks-B82tHwyQ.js} +31 -31
  53. package/dist/server/assets/{tasks-request-Bk5EoY_p.js → tasks-request-BtJBlkxV.js} +7 -6
  54. package/dist/server/capture.js +1 -1
  55. package/dist/server/migration.js +3 -3
  56. package/dist/server/runtime.js +4 -4
  57. package/dist/server/server.js +3 -3
  58. package/dist/types/assets/{config-BzzRLcL-.d.ts → config-DIC2cf_S.d.ts} +27 -1
  59. package/dist/types/assets/{contract-jA5pqK6h.d.ts → contract-CAT-XFNq.d.ts} +3 -1
  60. package/dist/types/companion.d.ts +1 -1
  61. package/dist/types/hosted.d.ts +2 -2
  62. package/dist/types/index.d.ts +1 -1
  63. package/dist/types/node-host.d.ts +7 -1
  64. package/dist/types/screen.d.ts +1 -1
  65. package/package.json +1 -1
  66. package/dist/client/assets/canvas-DxaIItAs.js +0 -1
  67. package/dist/client/assets/difference-worker-BpqPBs5g.js +0 -8
  68. package/dist/client/assets/files-C-7xoSt4.js +0 -1
  69. package/dist/client/assets/index-sj6mWw9-.js +0 -2
  70. package/dist/client/assets/pages-BVrTX_Eq.js +0 -1
  71. package/dist/client/assets/project-branches-DpxQx-T4.js +0 -1
  72. package/dist/client/assets/project-comments-D897aU5O.js +0 -1
  73. package/dist/client/assets/project-thread-links-Bp_TseD0.js +0 -1
  74. package/dist/client/assets/task-design-bktfn_Ok.js +0 -23
  75. package/dist/client/assets/tasks-Cqfj4fQN.js +0 -1
  76. package/dist/client/assets/tasks-request-mWMk9pBo.js +0 -1
  77. package/dist/client/assets/workspace-9KxFcfyV.js +0 -2
@@ -1,9 +1,9 @@
1
- import { Dn as try_, En as tryPromise, Et as callback, Gt as mapError, Ht as gen, It as flatMap, Kt as match, Mt as die, Nt as ensuring, Pt as fail$1, Wt as map, Xt as promise, _t as acquireRelease, g as resolveScreenRoutes, h as defaultScreens, hn as scoped, hr as TaggedError, jn as void_, s as Previews, t as BranchPreviews, u as Git, v as LOGO_TYPES, xn as sync, xt as andThen } from "./branch-previews-C_TVt_HQ.js";
2
- import { C as selected$1, I as WorkError, L as ScreenModules, O as AdapterRegistry, S as ready, T as LiveChannel, V as taskDesignModuleId, a as FileMutations, b as read, c as FileReview, f as Files, g as StoreActivity, h as ReviewActionError, m as FilesInputError, r as FileSaves, t as FileReviewActions, u as FileSearch, w as validate, x as readWorkspace, y as inspect } from "./server-DUBjmaBw.js";
1
+ import { Dn as tryPromise, Dt as callback, Ft as fail$1, Gt as map, Kt as mapError, Lt as flatMap, Mn as void_, Nt as die, On as try_, Pt as ensuring, Sn as sync, St as andThen, Ut as gen, Zt as promise, g as resolveScreenRoutes, gn as scoped, gr as TaggedError, h as defaultScreens, qt as match, s as Previews, t as BranchPreviews, u as Git, v as themeStyle, vt as acquireRelease, y as LOGO_TYPES } from "./branch-previews-ClkoJ0lM.js";
2
+ import { C as selected$1, I as WorkError, L as ScreenModules, O as AdapterRegistry, S as ready, T as LiveChannel, V as taskDesignModuleId, a as FileMutations, b as read, c as FileReview, f as Files, g as StoreActivity, h as ReviewActionError, m as FilesInputError, r as FileSaves, t as FileReviewActions, u as FileSearch, w as validate, x as readWorkspace, y as inspect } from "./server-CYvuOSBA.js";
3
3
  import { A as parseThreadReplyInput, D as parseThreadId, Dt as parseFileSearchQuery, E as parseThreadFilter, Ht as parseFilePath, N as parseThreadStatusInput, O as parseThreadInput, Rt as parseFileIndexQuery, St as parseReviewSourceQuery, X as parseReviewActionIntent, Xt as parseElementAnchor, Y as parseReviewActionApply, Yt as MAX_CANVAS_Y, _ as taskRoutes, _t as parseReviewHistoryQuery, an as parseWorkDetail, at as parseMutationIntent, cn as parseWorkMutationResult, en as parseSectionData, fn as parseWorkSync, g as pageRoutes, h as fileRoutes, ht as parseReviewDiffQuery, i as routes, in as parseWorkCreate, it as parseMutationApply, j as parseThreadSnapshot, k as parseThreadLinkInput, mn as parseWorkUpdate, pn as parseWorkSyncResult, pt as parseReviewComparison, rt as maxFileSaveBytes, sn as parseWorkItemRef, tn as parseWorkActionInput, tt as parseFileSaveRequest, un as parseWorkQuery, vn as parseMessageAuthor, w as parseThread } from "./events-6z5v7Uca.js";
4
- import { $ as openShellTab, B as parseTaskDesign, Bt as Button, Cn as Text, J as FieldCardContent, Q as initialShellTabs, Rt as Loader, Sn as writePreference, X as activeTab, Y as FieldCardHeader, Z as closeShellTab, _ as requestComments, _n as LiveStatus, _t as taskHref, at as tabsCookieValue, et as parseShellTabs, gt as selectedTask, ht as readConversationLocation, it as tabsCookieName, lt as conversationDestination, mn as ShellTabBar, n as unavailableComments, nt as stepShellTab, ot as visitShellTab, pt as legacyConversationHref, q as FieldCard, r as followThreads, rt as tabHref, t as commentsUnavailable, tt as readTabsCookie, v as projectLive, xn as readPreference, xt as legacyFileHref, yn as AppShell } from "./comment-data-BuArfgcc.js";
4
+ import { $ as openShellTab, B as parseTaskDesign, Bt as Button, Cn as isLocale, Dn as Text, En as useLocaleSource, J as FieldCardContent, Q as initialShellTabs, Rt as Loader, Sn as writePreference, Tn as t, X as activeTab, Y as FieldCardHeader, Z as closeShellTab, _ as requestComments, _n as LiveStatus, _t as taskHref, at as tabsCookieValue, et as parseShellTabs, gt as selectedTask, ht as readConversationLocation, it as tabsCookieName, lt as conversationDestination, mn as ShellTabBar, n as unavailableComments, nt as stepShellTab, ot as visitShellTab, pt as legacyConversationHref, q as FieldCard, r as followThreads, rt as tabHref, t as commentsUnavailable, tt as readTabsCookie, v as projectLive, wn as locale, xn as readPreference, xt as legacyFileHref, yn as AppShell } from "./comment-data-B0OLHBGp.js";
5
5
  import { d as parsePreviewHash, f as parsePreviewId, l as parsePreviewBranch, m as parsePreviewViewport } from "./parsers-CsiSkwTT.js";
6
- import { t as projectWork } from "./project-work-6aOyja4v.js";
6
+ import { t as projectWork } from "./project-work-Cy_BpHwR.js";
7
7
  import { n as showWorkspaceTab } from "./workspace-tab-DAUjkJzZ.js";
8
8
  import { createHash } from "node:crypto";
9
9
  import { extname, isAbsolute, relative, resolve } from "node:path";
@@ -17,6 +17,7 @@ import { renderToString } from "remix/component/server";
17
17
  import { createRedirectResponse } from "remix/response/redirect";
18
18
  import { readFile, realpath, stat } from "node:fs/promises";
19
19
  import * as form from "remix/data-schema/form-data";
20
+ import { AsyncLocalStorage } from "node:async_hooks";
20
21
  import { createServer } from "node:http";
21
22
  import { createRequestListener } from "remix/node-fetch-server";
22
23
  //#region app/screen-routes.ts
@@ -85,10 +86,10 @@ function ProjectLiveStatus(handle) {
85
86
  //#endregion
86
87
  //#region app/client/screens.ts
87
88
  var builtIns = {
88
- "bud:pages": () => import("./pages-iEdmK_o2.js"),
89
- "bud:canvas": () => import("./canvas-D6tIkONI.js"),
90
- "bud:tasks": () => import("./tasks-C2oa2wSC.js"),
91
- "bud:files": () => import("./files-DkrEB48U.js")
89
+ "bud:pages": () => import("./pages-BNa0MHEc.js"),
90
+ "bud:canvas": () => import("./canvas-Dn4RYoU2.js"),
91
+ "bud:tasks": () => import("./tasks-B82tHwyQ.js"),
92
+ "bud:files": () => import("./files-C5Vilfe4.js")
92
93
  };
93
94
  /**
94
95
  * Imports a screen module: a built-in by its key, any other by the URL the server gave it.
@@ -174,7 +175,7 @@ function ScreenHost(handle) {
174
175
  } catch (cause) {
175
176
  if (handle.signal.aborted) return;
176
177
  console.error(cause);
177
- error = cause instanceof Error && cause.message ? cause.message : `Could not open ${label}.`;
178
+ error = cause instanceof Error && cause.message ? cause.message : t("app.client.screenHost.couldNotOpen", { label });
178
179
  }
179
180
  handle.update();
180
181
  }
@@ -186,7 +187,7 @@ function ScreenHost(handle) {
186
187
  let context = opened?.context;
187
188
  let routeError = "";
188
189
  if (opened && context) try {
189
- if (location.routes && !matchScreenRoutes(location.routes, location.href)) throw new Error(`No screen route matches ${location.href}.`);
190
+ if (location.routes && !matchScreenRoutes(location.routes, location.href)) throw new Error(t("app.client.screenHost.noRouteMatches", { href: location.href }));
190
191
  context = matchScreenContext(opened.screen.routes, {
191
192
  ...context,
192
193
  active: handle.props.active,
@@ -194,7 +195,7 @@ function ScreenHost(handle) {
194
195
  url: new URL(location.href, context.url.origin)
195
196
  });
196
197
  } catch (cause) {
197
- routeError = cause instanceof Error ? cause.message : "This screen route is invalid.";
198
+ routeError = cause instanceof Error ? cause.message : t("app.client.screenHost.invalidRoute");
198
199
  }
199
200
  return /* @__PURE__ */ jsx("div", {
200
201
  class: "h-full min-h-0",
@@ -211,7 +212,7 @@ function ScreenHost(handle) {
211
212
  class: "max-w-2xl",
212
213
  role: "alert",
213
214
  children: [/* @__PURE__ */ jsx(FieldCardHeader, {
214
- title: `${label} didn't open`,
215
+ title: t("app.client.screenHost.didntOpen", { label }),
215
216
  subtitle: error
216
217
  }), /* @__PURE__ */ jsxs(FieldCardContent, {
217
218
  class: "flex-col items-start gap-3",
@@ -219,17 +220,17 @@ function ScreenHost(handle) {
219
220
  as: "p",
220
221
  variant: "ui",
221
222
  tone: "muted",
222
- children: "Fix the screen and retry. Details are in the browser console."
223
+ children: t("app.client.screenHost.fixAndRetry")
223
224
  }), /* @__PURE__ */ jsx(Button, {
224
225
  mix: on("click", () => {
225
226
  if ("error" in module || handle.props.taskDesign && "error" in handle.props.taskDesign) window.location.reload();
226
227
  else open();
227
228
  }),
228
- children: "Retry"
229
+ children: t("app.client.screenHost.retry")
229
230
  })]
230
231
  })]
231
232
  })
232
- }) : /* @__PURE__ */ jsx(Loader, { label: `Loading ${label}…` })
233
+ }) : /* @__PURE__ */ jsx(Loader, { label: t("app.client.screenHost.loading", { label }) })
233
234
  });
234
235
  };
235
236
  }
@@ -248,11 +249,11 @@ async function resolveConversation(id, targets, token, signal, fetcher = fetch)
248
249
  id = parseThreadId(id);
249
250
  let thread = await requestComments(routes.comments.get.href({ id }), token, signal, parseThread, fetcher);
250
251
  signal.throwIfAborted();
251
- if (thread.id !== id) throw new Error("The comments service returned another discussion.");
252
+ if (thread.id !== id) throw new Error(t("app.client.conversationRoute.anotherDiscussion"));
252
253
  let destination = conversationDestination(thread);
253
254
  let target = targets.find((target) => target.module === destination.module);
254
- if (!target) throw new Error("Enable the screen that owns this discussion to open it.");
255
- if (target.routes && !matchScreenRoutes(target.routes, destination.href)) throw new Error("The discussion location is not available in this screen.");
255
+ if (!target) throw new Error(t("app.client.conversationRoute.enableOwningScreen"));
256
+ if (target.routes && !matchScreenRoutes(target.routes, destination.href)) throw new Error(t("app.client.conversationRoute.locationUnavailable"));
256
257
  return {
257
258
  active: target.id,
258
259
  module: { url: target.module },
@@ -298,14 +299,14 @@ function Workspace(handle) {
298
299
  changed();
299
300
  try {
300
301
  let project = handle.props.project;
301
- if (!project) throw new Error("Connect a project to open this discussion.");
302
+ if (!project) throw new Error(t("app.client.workspace.connectProjectToOpenDiscussion"));
302
303
  let next = await resolveConversation(id, handle.props.conversationTargets ?? [], project.token, signal);
303
304
  if (!signal.aborted && conversation === id) {
304
305
  presented = next;
305
306
  hrefs.set(next.active, window.location.pathname);
306
307
  }
307
308
  } catch (cause) {
308
- if (!signal.aborted) error = cause instanceof Error ? cause.message : "Could not open this discussion.";
309
+ if (!signal.aborted) error = cause instanceof Error ? cause.message : t("app.client.workspace.couldNotOpenDiscussion");
309
310
  } finally {
310
311
  if (request === controller && !signal.aborted) {
311
312
  loading = false;
@@ -348,7 +349,7 @@ function Workspace(handle) {
348
349
  let { pathname } = new URL(href, "http://bud.local");
349
350
  if (readConversationLocation(href)) return {
350
351
  href,
351
- title: "Discussion",
352
+ title: t("app.client.workspace.discussion"),
352
353
  icon: "message"
353
354
  };
354
355
  let match = handle.props.screens.map((item) => ({
@@ -510,19 +511,19 @@ function Workspace(handle) {
510
511
  children: project ? /* @__PURE__ */ jsxs("div", {
511
512
  class: "h-full min-h-0",
512
513
  mix: mount,
513
- children: [loading ? /* @__PURE__ */ jsx(Loader, { label: "Opening discussion…" }) : error ? /* @__PURE__ */ jsx("div", {
514
+ children: [loading ? /* @__PURE__ */ jsx(Loader, { label: t("app.client.workspace.openingDiscussion") }) : error ? /* @__PURE__ */ jsx("div", {
514
515
  class: "p-3 md:p-4",
515
516
  children: /* @__PURE__ */ jsxs(FieldCard, {
516
517
  class: "max-w-2xl",
517
518
  role: "alert",
518
519
  children: [/* @__PURE__ */ jsx(FieldCardHeader, {
519
- title: "Discussion didn't open",
520
+ title: t("app.client.workspace.discussionDidntOpen"),
520
521
  subtitle: error
521
522
  }), /* @__PURE__ */ jsx(FieldCardContent, { children: /* @__PURE__ */ jsx(Button, {
522
523
  mix: on("click", () => {
523
524
  if (conversation) resolve(conversation);
524
525
  }),
525
- children: "Retry"
526
+ children: t("app.client.workspace.retry")
526
527
  }) })]
527
528
  })
528
529
  }) : null, [...opened].map(([id, { module, taskDesign, location }]) => /* @__PURE__ */ jsx("div", {
@@ -540,12 +541,12 @@ function Workspace(handle) {
540
541
  }) : /* @__PURE__ */ jsxs(FieldCard, {
541
542
  class: "max-w-2xl",
542
543
  children: [/* @__PURE__ */ jsx(FieldCardHeader, {
543
- title: "Your project, side by side.",
544
- subtitle: "Open Bud beside a local project to review its pages, files and tasks."
544
+ title: t("app.client.workspace.yourProjectSideBySide"),
545
+ subtitle: t("app.client.workspace.openBudBesideLocalProject")
545
546
  }), /* @__PURE__ */ jsx(FieldCardContent, { children: /* @__PURE__ */ jsx(Text, {
546
547
  as: "p",
547
548
  variant: "ui",
548
- children: "Start your project with the Bud Vite plugin, or run Bud from its directory. Its screens appear here when the companion connects."
549
+ children: t("app.client.workspace.startProjectWithBudVitePlugin")
549
550
  }) })]
550
551
  })
551
552
  });
@@ -682,7 +683,7 @@ function createAppController(template, project, runtime) {
682
683
  let title = (conversation ? "Discussion" : screen.label).replace(/[&<>]/g, (character) => `&#${character.charCodeAt(0)};`);
683
684
  let logo = project?.config.logo;
684
685
  let shell = logo ? withLogoIcons(template, logo, (theme) => routes.logo.href({ theme })) : template;
685
- return new Response(shell.replace("<title>bud.sh</title>", () => `<title>${title} · bud.sh</title>`).replace("<!--app-html-->", () => content), { headers: { "Content-Type": "text/html; charset=utf-8" } });
686
+ return new Response(shell.replace("<html lang=\"en\"", () => `<html lang="${locale()}"`).replace("<title>bud.sh</title>", () => `<title>${title} · bud.sh</title>`).replace("</head>", () => project ? `${themeStyle(project.config.theme)}</head>` : "</head>").replace("<!--app-html-->", () => content), { headers: { "Content-Type": "text/html; charset=utf-8" } });
686
687
  }
687
688
  return createController(routes, { actions: {
688
689
  conversation({ params, request }) {
@@ -1885,6 +1886,19 @@ function createBranchesController(project, runtime) {
1885
1886
  } });
1886
1887
  }
1887
1888
  //#endregion
1889
+ //#region app/server/locale.ts
1890
+ var requestLocale = new AsyncLocalStorage();
1891
+ useLocaleSource(() => requestLocale.getStore());
1892
+ /**
1893
+ * Runs a request's handling in its language, so what it renders speaks it.
1894
+ * @param value - The request's locale; English when it isn't one of Bud's.
1895
+ * @param handle - The request's handling.
1896
+ * @returns What the handling returns.
1897
+ */
1898
+ function inLocale(value, handle) {
1899
+ return requestLocale.run(isLocale(value) ? value : "en", handle);
1900
+ }
1901
+ //#endregion
1888
1902
  //#region app/router.ts
1889
1903
  /**
1890
1904
  * Creates the app's HTTP handler for development, production, and tests.
@@ -1895,7 +1909,7 @@ function createBranchesController(project, runtime) {
1895
1909
  * @returns A Remix router with the application's routes and middleware.
1896
1910
  */
1897
1911
  function createApp(template, publicDirectory, project, runtime) {
1898
- let middleware = [];
1912
+ let middleware = [(_context, next) => inLocale(project?.config.locale, next)];
1899
1913
  if (project) middleware.push(async ({ request }, next) => {
1900
1914
  if (!isLoopbackHost(new URL(request.url).hostname)) return new Response("Bud is available on loopback only.", { status: 403 });
1901
1915
  let response = await next();
@@ -1,8 +1,8 @@
1
1
  import { Yt as MAX_CANVAS_Y, g as pageRoutes } from "./events-6z5v7Uca.js";
2
- import { Cn as Text, Rt as Loader, Sn as writePreference, gn as IconButton, hn as Select, l as projectStore, st as PagesWorkspace, xn as readPreference, zt as Spinner } from "./comment-data-BuArfgcc.js";
3
- import { n as createProjectComments, t as defineScreen } from "./screen-DMUB9a-K.js";
2
+ import { Dn as Text, Rt as Loader, Sn as writePreference, Tn as t, gn as IconButton, hn as Select, l as projectStore, st as PagesWorkspace, xn as readPreference, zt as Spinner } from "./comment-data-B0OLHBGp.js";
3
+ import { n as createProjectComments, t as defineScreen } from "./screen-BtcJmZwj.js";
4
4
  import { r as workspaceTab, t as followWorkspaceTab } from "./workspace-tab-DAUjkJzZ.js";
5
- import { n as createProjectPreviews, r as locatePreview, t as createProjectBranches } from "./project-branches-B7rtrvMl.js";
5
+ import { n as createProjectPreviews, r as locatePreview, t as createProjectBranches } from "./project-branches-GtQYyOH5.js";
6
6
  import { array, number, object, optional, parseSafe, string } from "remix/data-schema";
7
7
  import { on, ref } from "remix/component";
8
8
  import { Fragment, jsx, jsxs } from "remix/component/jsx-runtime";
@@ -44,7 +44,10 @@ function PagePicture(handle) {
44
44
  return /* @__PURE__ */ jsx("div", {
45
45
  class: inline ? "relative w-full" : "absolute inset-0 overflow-auto bg-board",
46
46
  role: "img",
47
- "aria-label": `${route.title} picture at ${size.width} pixels`,
47
+ "aria-label": t("app.client.pagePicture.pictureAt", {
48
+ title: route.title,
49
+ width: size.width
50
+ }),
48
51
  children: current ? /* @__PURE__ */ jsx("div", {
49
52
  class: `relative mx-auto ${picking ? "cursor-crosshair" : ""}`,
50
53
  style: { width: `min(100%, ${current.capture.width}px)` },
@@ -80,7 +83,7 @@ function PagePicture(handle) {
80
83
  variant: "ui",
81
84
  tone: "muted",
82
85
  class: "max-w-md text-center",
83
- children: "This page has no picture at this viewport yet. It appears once its branch is captured."
86
+ children: t("app.client.pagePicture.noPictureYet")
84
87
  }) : /* @__PURE__ */ jsx(Spinner, {
85
88
  size: 18,
86
89
  class: "text-muted"
@@ -192,9 +195,12 @@ function ProjectPages(handle) {
192
195
  }] : []);
193
196
  return pictureMode ? pictured : [{
194
197
  name: workingTree,
195
- caption: "live"
198
+ caption: t("app.client.projectPages.live")
196
199
  }, ...pictured];
197
200
  }
201
+ function branchLabel(name) {
202
+ return name === workingTree ? t("app.client.projectPages.workingTree") : name;
203
+ }
198
204
  let comparing = null;
199
205
  let pickedFrom;
200
206
  let viewport = project.previews.viewports[0];
@@ -329,26 +335,26 @@ function ProjectPages(handle) {
329
335
  if (!pictureMode && options.length < 2 && !project.routes.some((route) => route.flow)) return void 0;
330
336
  let compared = comparing;
331
337
  return {
332
- source: compared ? `Comparing ${compared.join(", ")}` : `Pictures of ${activeTab().branch ?? home}`,
338
+ source: compared ? t("app.client.projectPages.comparing", { branches: compared.join(", ") }) : t("app.client.projectPages.picturesOf", { branch: branchLabel(activeTab().branch ?? home) }),
333
339
  shows: (tab) => pictureMode || Boolean(tab.branch) || Boolean(compared) || Boolean(pageRoute(project.routes, tab.path)?.flow),
334
340
  actions: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(IconButton, {
335
341
  tooltip: true,
336
342
  icon: "compare",
337
- label: compared ? "Stop comparing" : "Compare branches side by side",
343
+ label: compared ? t("app.client.projectPages.stopComparing") : t("app.client.projectPages.compareBranches"),
338
344
  active: Boolean(compared),
339
345
  disabled: !compared && options.length < 2,
340
346
  mix: on("click", toggleComparing)
341
347
  }), compared ? /* @__PURE__ */ jsx("div", {
342
348
  role: "group",
343
- "aria-label": "Branches compared",
349
+ "aria-label": t("app.client.projectPages.branchesCompared"),
344
350
  class: "flex min-w-0 items-center gap-1.5",
345
351
  children: compared.map((name, index) => /* @__PURE__ */ jsx(Select, {
346
- label: `Compared branch ${index + 1}`,
352
+ label: t("app.client.projectPages.comparedBranch", { number: index + 1 }),
347
353
  value: name,
348
354
  class: "max-w-52",
349
355
  options: options.map((option) => ({
350
356
  value: option.name,
351
- label: option.name,
357
+ label: branchLabel(option.name),
352
358
  description: option.caption
353
359
  })),
354
360
  onChange: (value) => {
@@ -364,7 +370,10 @@ function ProjectPages(handle) {
364
370
  children: viewports.length > 1 ? viewports.map((option) => /* @__PURE__ */ jsx(IconButton, {
365
371
  tooltip: true,
366
372
  icon: option.mobile ? "mobile" : "desktop",
367
- label: `Show ${option.name} (${option.width})`,
373
+ label: t("app.client.projectPages.showViewport", {
374
+ name: option.name,
375
+ width: option.width
376
+ }),
368
377
  active: option.id === viewport.id,
369
378
  mix: on("click", () => {
370
379
  viewport = option;
@@ -382,7 +391,7 @@ function ProjectPages(handle) {
382
391
  variant: "ui",
383
392
  tone: "muted",
384
393
  class: "max-w-md text-center",
385
- children: "Only the pages in your routes have pictures."
394
+ children: t("app.client.projectPages.routePicturesOnly")
386
395
  })
387
396
  });
388
397
  if (compared) return /* @__PURE__ */ jsx("div", {
@@ -390,14 +399,14 @@ function ProjectPages(handle) {
390
399
  children: /* @__PURE__ */ jsx("div", {
391
400
  class: "flex w-full items-start justify-center gap-4 p-4",
392
401
  children: compared.map((branch) => /* @__PURE__ */ jsxs("section", {
393
- "aria-label": `${branch} picture`,
402
+ "aria-label": t("app.client.projectPages.branchPicture", { branch: branchLabel(branch) }),
394
403
  class: "flex min-w-0 flex-1 flex-col gap-2",
395
404
  style: { maxWidth: `${viewport.width}px` },
396
405
  children: [/* @__PURE__ */ jsx(Text, {
397
406
  variant: "ui-caption",
398
407
  tone: "muted",
399
408
  class: "truncate",
400
- children: branch
409
+ children: branchLabel(branch)
401
410
  }), /* @__PURE__ */ jsx(PagePicture, {
402
411
  previews: previewsOf(branch),
403
412
  route,
@@ -451,9 +460,9 @@ function ProjectPages(handle) {
451
460
  handle.update();
452
461
  }
453
462
  function unavailable(path) {
454
- if (!comments.loaded) return "Loading comments…";
455
- if (!comments.snapshot.capabilities.create) return "Comments here are read-only";
456
- if (!pageRoute(project.routes, path)) return "Only pages in your routes take comments. Add this page to bud.config.";
463
+ if (!comments.loaded) return t("app.client.projectPages.loadingComments");
464
+ if (!comments.snapshot.capabilities.create) return t("app.client.projectPages.commentsReadOnly");
465
+ if (!pageRoute(project.routes, path)) return t("app.client.projectPages.routeCommentsOnly");
457
466
  return null;
458
467
  }
459
468
  tabId = workspaceTab();
@@ -481,7 +490,7 @@ function ProjectPages(handle) {
481
490
  if (!branches.loaded && !branches.error) return /* @__PURE__ */ jsx("div", {
482
491
  class: "h-full min-h-0",
483
492
  mix: mount,
484
- children: /* @__PURE__ */ jsx(Loader, { label: "Loading branches…" })
493
+ children: /* @__PURE__ */ jsx(Loader, { label: t("app.client.projectPages.loadingBranches") })
485
494
  });
486
495
  let current = activeTab();
487
496
  if (!pictureMode && typeof window !== "undefined" && new URL(origin).origin === window.location.origin) return /* @__PURE__ */ jsx("div", {
@@ -492,7 +501,7 @@ function ProjectPages(handle) {
492
501
  variant: "ui",
493
502
  tone: "muted",
494
503
  class: "max-w-md text-center",
495
- children: "Your site is served from Bud's own address, so Pages can't show it. Serve it on its own port to browse it here."
504
+ children: t("app.client.projectPages.siteOnBudOrigin")
496
505
  })
497
506
  });
498
507
  return /* @__PURE__ */ jsx("div", {
@@ -529,7 +538,7 @@ function ProjectPages(handle) {
529
538
  unavailable: unavailable(current.path),
530
539
  draft: draft ? {
531
540
  path: draft.pick.path,
532
- label: draft.pick.element?.label ?? "A spot on the page",
541
+ label: draft.pick.element?.label ?? t("app.client.projectPages.spotOnPage"),
533
542
  source: draft.pick.element?.source ?? null
534
543
  } : null,
535
544
  busy: comments.busy,
@@ -548,7 +557,7 @@ function ProjectPages(handle) {
548
557
  design: project.designs.pages,
549
558
  sections: pictureMode || captured().length > 1 ? captured().map((branch) => ({
550
559
  id: branch.name,
551
- label: branch.name,
560
+ label: branchLabel(branch.name),
552
561
  caption: branch.caption
553
562
  })) : void 0,
554
563
  section: current.branch ?? home,
@@ -1,5 +1,5 @@
1
1
  import { Xt as parseElementAnchor, i as routes } from "./events-6z5v7Uca.js";
2
- import { c as previewKey, l as projectStore, v as projectLive, x as writeRows, y as plainRow } from "./comment-data-BuArfgcc.js";
2
+ import { Tn as t, c as previewKey, l as projectStore, v as projectLive, x as writeRows, y as plainRow } from "./comment-data-B0OLHBGp.js";
3
3
  import { c as parsePreview, l as parsePreviewBranch, p as parsePreviewPins } from "./parsers-CsiSkwTT.js";
4
4
  import { array, boolean, enum_, nullable, number, object, parseSafe, string } from "remix/data-schema";
5
5
  //#region app/client/project-previews.ts
@@ -165,12 +165,12 @@ async function request(url, token, signal, method = "GET") {
165
165
  });
166
166
  if (!response.ok) {
167
167
  let body = await response.json().catch(() => null);
168
- throw new Error(typeof body?.error === "string" ? body.error : `Preview request failed (${response.status}).`);
168
+ throw new Error(typeof body?.error === "string" ? body.error : t("app.client.projectPreviews.requestFailed", { status: response.status }));
169
169
  }
170
170
  return response;
171
171
  }
172
172
  function message(cause) {
173
- return cause instanceof Error ? cause.message : "Bud could not load this preview.";
173
+ return cause instanceof Error ? cause.message : t("app.client.projectPreviews.loadFailed");
174
174
  }
175
175
  /**
176
176
  * Shows server-captured route screenshots for one canvas from the project's local store,
@@ -504,7 +504,7 @@ var BranchListSchema = object({
504
504
  */
505
505
  function parseBranchList(value) {
506
506
  let result = parseSafe(BranchListSchema, value);
507
- if (!result.success) throw new TypeError("The companion returned an invalid branch list.");
507
+ if (!result.success) throw new TypeError(t("app.client.projectBranches.invalidBranchList"));
508
508
  return result.value;
509
509
  }
510
510
  /**
@@ -535,7 +535,7 @@ function createProjectBranches(options) {
535
535
  signal
536
536
  });
537
537
  let body = await response.json().catch(() => null);
538
- if (!response.ok) throw new Error(typeof body?.error === "string" ? body.error : "Bud could not load branches.");
538
+ if (!response.ok) throw new Error(typeof body?.error === "string" ? body.error : t("app.client.projectBranches.loadError"));
539
539
  return parseBranchList(body);
540
540
  }
541
541
  async function load() {
@@ -1,6 +1,6 @@
1
1
  import { Zt as parseWorkWorkspace, gn as valueByRole, i as routes, k as parseThreadLinkInput, sn as parseWorkItemRef, w as parseThread } from "./events-6z5v7Uca.js";
2
- import { C as ThreadTaskPicker, S as ThreadTaskLinks, _ as requestComments, _t as taskHref, gn as IconButton } from "./comment-data-BuArfgcc.js";
3
- import { n as requestTasks } from "./tasks-request-Bk5EoY_p.js";
2
+ import { C as ThreadTaskPicker, S as ThreadTaskLinks, Tn as t, _ as requestComments, _t as taskHref, gn as IconButton } from "./comment-data-B0OLHBGp.js";
3
+ import { n as requestTasks } from "./tasks-request-BtJBlkxV.js";
4
4
  import { on, ref } from "remix/component";
5
5
  import { Fragment, jsx, jsxs } from "remix/component/jsx-runtime";
6
6
  //#region app/client/project-task-picker.tsx
@@ -37,7 +37,7 @@ function ProjectTaskPicker(handle) {
37
37
  });
38
38
  let { schema, snapshot } = await requestTasks(routes.tasksApi.workspace.href() + "?" + search, token, signal, parseWorkWorkspace);
39
39
  signal.throwIfAborted();
40
- if (snapshot.connection.state !== "ready") throw new Error(snapshot.connection.message ?? "The task adapter is unavailable.");
40
+ if (snapshot.connection.state !== "ready") throw new Error(snapshot.connection.message ?? t("app.client.projectTaskPicker.adapterUnavailable"));
41
41
  items = snapshot.items.map((item) => {
42
42
  let title = valueByRole(item, schema, "title");
43
43
  return {
@@ -52,7 +52,7 @@ function ProjectTaskPicker(handle) {
52
52
  }
53
53
  truncated = snapshot.truncated;
54
54
  } catch (cause) {
55
- if (!signal.aborted) error = cause instanceof Error ? cause.message : "Could not load tasks.";
55
+ if (!signal.aborted) error = cause instanceof Error ? cause.message : t("app.client.projectTaskPicker.loadFailed");
56
56
  } finally {
57
57
  if (request === controller) {
58
58
  loading = false;
@@ -107,9 +107,9 @@ function ProjectTaskPicker(handle) {
107
107
  * @returns Bounded mutations, each applied against the previous acknowledged revision.
108
108
  */
109
109
  function planThreadLinks(thread, selected) {
110
- if (selected.length > 100 || new Set(selected).size !== selected.length) throw new Error(`Select up to 100 different tasks.`);
110
+ if (selected.length > 100 || new Set(selected).size !== selected.length) throw new Error(t("app.client.threadLinks.selectUpToTasks", { count: 100 }));
111
111
  for (let id of selected) parseWorkItemRef({ id });
112
- if (!thread.anchor && !selected.length) throw new Error("A task discussion needs at least one linked task.");
112
+ if (!thread.anchor && !selected.length) throw new Error(t("app.client.threadLinks.taskDiscussionNeedsLinkedTask"));
113
113
  let current = new Set(thread.itemIds);
114
114
  let desired = new Set(selected);
115
115
  let changes = [];
@@ -130,7 +130,7 @@ function planThreadLinks(thread, selected) {
130
130
  });
131
131
  for (let id of added) current.add(id);
132
132
  }
133
- if (!removed.length && !added.length) throw new Error("Could not prepare task links.");
133
+ if (!removed.length && !added.length) throw new Error(t("app.client.threadLinks.couldNotPrepareTaskLinks"));
134
134
  }
135
135
  return changes;
136
136
  }
@@ -147,7 +147,7 @@ async function saveThreadLinks(thread, selected, mutate, signal) {
147
147
  let current = thread;
148
148
  for (let change of changes) {
149
149
  signal.throwIfAborted();
150
- if (!current.revision) throw new Error("Review the latest discussion before changing links.");
150
+ if (!current.revision) throw new Error(t("app.client.threadLinks.reviewLatestDiscussionBeforeChangingLinks"));
151
151
  let input = parseThreadLinkInput({
152
152
  itemIds: change.itemIds,
153
153
  expectedRevision: current.revision
@@ -157,7 +157,7 @@ async function saveThreadLinks(thread, selected, mutate, signal) {
157
157
  let expected = new Set(current.itemIds);
158
158
  for (let id of change.itemIds) if (change.action === "link") expected.add(id);
159
159
  else expected.delete(id);
160
- if (next.id !== current.id || !next.revision || next.itemIds.length !== expected.size || next.itemIds.some((id) => !expected.has(id)) || JSON.stringify(next.anchor) !== JSON.stringify(current.anchor)) throw new Error("The task link change was not confirmed. Review the latest links before saving again.");
160
+ if (next.id !== current.id || !next.revision || next.itemIds.length !== expected.size || next.itemIds.some((id) => !expected.has(id)) || JSON.stringify(next.anchor) !== JSON.stringify(current.anchor)) throw new Error(t("app.client.threadLinks.taskLinkChangeNotConfirmed"));
161
161
  current = next;
162
162
  }
163
163
  return current;
@@ -180,7 +180,7 @@ function ProjectThreadLinks(handle) {
180
180
  async function review() {
181
181
  if (!editing) return;
182
182
  let next = await requestComments(routes.comments.get.href({ id: editing.id }), handle.props.project.token, handle.signal, parseThread);
183
- if (next.id !== editing.id) throw new Error("The service returned another discussion.");
183
+ if (next.id !== editing.id) throw new Error(t("app.client.projectThreadLinks.anotherDiscussion"));
184
184
  editing = next;
185
185
  needsReview = false;
186
186
  reviewed = true;
@@ -203,7 +203,7 @@ function ProjectThreadLinks(handle) {
203
203
  }
204
204
  return () => /* @__PURE__ */ jsxs(Fragment, { children: [handle.props.part === "action" ? handle.props.canLink ? /* @__PURE__ */ jsx(IconButton, {
205
205
  icon: "tasks",
206
- label: "Link tasks",
206
+ label: t("app.client.projectThreadLinks.linkTasks"),
207
207
  size: "small",
208
208
  tooltip: true,
209
209
  disabled: handle.props.busy,
@@ -221,7 +221,7 @@ function ProjectThreadLinks(handle) {
221
221
  onClose: close,
222
222
  onReview: needsReview ? review : void 0,
223
223
  onSave: async (selected) => {
224
- if (!editing || needsReview) throw new Error("Review the latest links before saving.");
224
+ if (!editing || needsReview) throw new Error(t("app.client.projectThreadLinks.reviewLinksBeforeSaving"));
225
225
  try {
226
226
  let initial = editing;
227
227
  editing = await saveThreadLinks(initial, selected, (action, input) => {
@@ -1,6 +1,6 @@
1
1
  import { Zt as parseWorkWorkspace, i as routes, y as parseWorkChange } from "./events-6z5v7Uca.js";
2
- import { d as saveWorkspace, l as projectStore, v as projectLive, x as writeRows, y as plainRow } from "./comment-data-BuArfgcc.js";
3
- import { n as requestTasks } from "./tasks-request-Bk5EoY_p.js";
2
+ import { d as saveWorkspace, l as projectStore, v as projectLive, x as writeRows, y as plainRow } from "./comment-data-B0OLHBGp.js";
3
+ import { n as requestTasks } from "./tasks-request-BtJBlkxV.js";
4
4
  //#region app/client/project-work.ts
5
5
  var syncs = /* @__PURE__ */ new Map();
6
6
  /**
@@ -1,5 +1,5 @@
1
1
  import { D as parseThreadId, E as parseThreadFilter, i as routes, j as parseThreadSnapshot, w as parseThread } from "./events-6z5v7Uca.js";
2
- import { _ as requestComments, a as saveThreads, b as writeOptimistically, bn as toast, g as CommentsRequestError, i as saveThread, l as projectStore, n as unavailableComments, o as threadMatches, y as plainRow } from "./comment-data-BuArfgcc.js";
2
+ import { Tn as t, _ as requestComments, a as saveThreads, b as writeOptimistically, bn as toast, g as CommentsRequestError, i as saveThread, l as projectStore, n as unavailableComments, o as threadMatches, y as plainRow } from "./comment-data-B0OLHBGp.js";
3
3
  //#region ../work/src/lib/predict.ts
4
4
  /**
5
5
  * How a new task shows until its adapter confirms it: the requested values, and the creation
@@ -162,7 +162,7 @@ async function loadItemComments(token, itemId, signal, conversationId) {
162
162
  signal.throwIfAborted();
163
163
  return {
164
164
  snapshot: null,
165
- error: cause instanceof Error ? cause.message : "Could not load the discussion."
165
+ error: cause instanceof Error ? cause.message : t("app.client.projectComments.couldNotLoadDiscussion")
166
166
  };
167
167
  }
168
168
  }
@@ -221,7 +221,7 @@ async function loadComments(project, signal, fetcher = fetch) {
221
221
  signal.throwIfAborted();
222
222
  return {
223
223
  snapshot: null,
224
- error: cause instanceof Error ? cause.message : "Could not load comments."
224
+ error: cause instanceof Error ? cause.message : t("app.client.projectComments.couldNotLoadComments")
225
225
  };
226
226
  }
227
227
  }
@@ -308,7 +308,7 @@ function createProjectComments(options) {
308
308
  error = "";
309
309
  }
310
310
  } catch (cause) {
311
- if (!signal.aborted && turn === generation) error = cause instanceof Error ? cause.message : "Could not load comments.";
311
+ if (!signal.aborted && turn === generation) error = cause instanceof Error ? cause.message : t("app.client.projectComments.couldNotLoadComments");
312
312
  } finally {
313
313
  if (turn === generation) {
314
314
  loading = false;
@@ -328,16 +328,16 @@ function createProjectComments(options) {
328
328
  changed();
329
329
  try {
330
330
  let thread = await request(routes.comments.get.href({ id }), signal, parseThread);
331
- if (thread.id !== id) throw new CommentsRequestError("The comments service returned another discussion.");
331
+ if (thread.id !== id) throw new CommentsRequestError(t("app.client.projectComments.serviceReturnedAnotherDiscussion"));
332
332
  if (!signal.aborted && turn === generation) {
333
333
  await merge(thread);
334
- if (!includes(thread)) throw new CommentsRequestError("This discussion is no longer part of this view.");
334
+ if (!includes(thread)) throw new CommentsRequestError(t("app.client.projectComments.discussionNoLongerInView"));
335
335
  if (!detailFailures.size) error = "";
336
336
  }
337
337
  } catch (cause) {
338
338
  if (!signal.aborted && turn === generation) {
339
339
  detailFailures.add(id);
340
- error = cause instanceof Error ? cause.message : "Could not load the discussion.";
340
+ error = cause instanceof Error ? cause.message : t("app.client.projectComments.couldNotLoadDiscussion");
341
341
  }
342
342
  } finally {
343
343
  if (details.get(id) === controller) {
@@ -347,10 +347,10 @@ function createProjectComments(options) {
347
347
  }
348
348
  }
349
349
  function requireCapability(capability) {
350
- if (busy) throw new Error("Wait for the current comment operation to finish.");
351
- if (!loaded) throw new Error("Load comments before saving.");
352
- if (snapshot.connection.state !== "ready") throw new Error(snapshot.connection.message ?? "The comment adapter is unavailable.");
353
- if (!snapshot.capabilities[capability]) throw new Error("The comment adapter does not support this operation.");
350
+ if (busy) throw new Error(t("app.client.projectComments.waitForCurrentOperation"));
351
+ if (!loaded) throw new Error(t("app.client.projectComments.loadCommentsBeforeSaving"));
352
+ if (snapshot.connection.state !== "ready") throw new Error(snapshot.connection.message ?? t("app.client.projectComments.adapterUnavailable"));
353
+ if (!snapshot.capabilities[capability]) throw new Error(t("app.client.projectComments.adapterDoesNotSupportOperation"));
354
354
  }
355
355
  async function operate(url, body, parse, apply, success, threadId, predict) {
356
356
  cancelReads();
@@ -372,13 +372,13 @@ function createProjectComments(options) {
372
372
  return snapshot;
373
373
  } catch (cause) {
374
374
  if (!options.signal.aborted) {
375
- error = cause instanceof Error ? cause.message : "The comment write was not confirmed.";
376
- if (predict) toast.error("Bud undid a comment change", { description: error });
375
+ error = cause instanceof Error ? cause.message : t("app.client.projectComments.commentWriteNotConfirmed");
376
+ if (predict) toast.error(t("app.client.projectComments.budUndidCommentChange"), { description: error });
377
377
  if (cause instanceof CommentsRequestError && cause.code === "conflict") try {
378
378
  await adopt(await request(listHref(filter), options.signal, parseThreadSnapshot));
379
379
  if (threadId) {
380
380
  let detail = await request(routes.comments.get.href({ id: threadId }), options.signal, parseThread);
381
- if (detail.id !== threadId) throw new CommentsRequestError("The comments service returned another discussion.");
381
+ if (detail.id !== threadId) throw new CommentsRequestError(t("app.client.projectComments.serviceReturnedAnotherDiscussion"));
382
382
  await merge(detail);
383
383
  }
384
384
  } catch {}
@@ -423,7 +423,7 @@ function createProjectComments(options) {
423
423
  let pending = `pending-${crypto.randomUUID()}`;
424
424
  if (change.id) {
425
425
  let id = parseThreadId(change.id), thread = snapshot.threads.find((item) => item.id === id);
426
- if (!thread?.revision) throw new Error("Reload this discussion before saving.");
426
+ if (!thread?.revision) throw new Error(t("app.client.projectComments.reloadDiscussionBeforeSaving"));
427
427
  let next;
428
428
  if (change.status !== void 0) {
429
429
  url = routes.comments.status.href({ id });
@@ -468,16 +468,16 @@ function createProjectComments(options) {
468
468
  let saved;
469
469
  return {
470
470
  ...await operate(url, body, parseThread, (thread) => {
471
- if (change.id && thread.id !== change.id) throw new CommentsRequestError("The comment write could not be confirmed. Refresh before trying again.", 409, "uncertain");
471
+ if (change.id && thread.id !== change.id) throw new CommentsRequestError(t("app.client.projectComments.refreshBeforeTryingAgain"), 409, "uncertain");
472
472
  saved = thread;
473
473
  return merge(thread);
474
- }, "Comment saved", change.id, predict),
474
+ }, t("app.client.projectComments.commentSaved"), change.id, predict),
475
475
  thread: saved
476
476
  };
477
477
  },
478
478
  async sync() {
479
479
  requireCapability("sync");
480
- return operate(routes.comments.sync.href(), { direction: "both" }, parseThreadSnapshot, adopt, "Comments synced");
480
+ return operate(routes.comments.sync.href(), { direction: "both" }, parseThreadSnapshot, adopt, t("app.client.projectComments.commentsSynced"));
481
481
  }
482
482
  };
483
483
  }