wizards 0.5.0 → 0.5.1

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 (158) hide show
  1. package/README.md +45 -42
  2. package/apps/runtime/dist/agents/studio-assistant.js +3 -3
  3. package/apps/runtime/dist/app.js +7 -4
  4. package/apps/runtime/dist/auth/index.js +2 -2
  5. package/apps/runtime/dist/authoring/guide.js +1 -1
  6. package/apps/runtime/dist/cli/account.js +31 -28
  7. package/apps/runtime/dist/cli/autostart.js +5 -3
  8. package/apps/runtime/dist/cli/clients.js +1 -1
  9. package/apps/runtime/dist/cli/connect.js +24 -24
  10. package/apps/runtime/dist/cli/features.js +6 -6
  11. package/apps/runtime/dist/cli/main.js +51 -39
  12. package/apps/runtime/dist/cli/mcp.js +5 -4
  13. package/apps/runtime/dist/cli/menu.js +32 -30
  14. package/apps/runtime/dist/cli/report.js +7 -9
  15. package/apps/runtime/dist/cli/setup.js +42 -38
  16. package/apps/runtime/dist/cli/start.js +16 -15
  17. package/apps/runtime/dist/cli/status.js +20 -20
  18. package/apps/runtime/dist/cli/ui.js +9 -2
  19. package/apps/runtime/dist/cli/update.js +6 -6
  20. package/apps/runtime/dist/cli/validate.js +1 -1
  21. package/apps/runtime/dist/connectors/external.js +3 -2
  22. package/apps/runtime/dist/connectors/index.js +5 -5
  23. package/apps/runtime/dist/connectors/plugin-connections.js +2 -2
  24. package/apps/runtime/dist/connectors/via-account.js +5 -5
  25. package/apps/runtime/dist/db/control-schema.js +14 -0
  26. package/apps/runtime/dist/db/migrations/control/0009_plugin_claim.sql +9 -0
  27. package/apps/runtime/dist/db/migrations/control/meta/0009_snapshot.json +561 -0
  28. package/apps/runtime/dist/db/migrations/control/meta/_journal.json +7 -0
  29. package/apps/runtime/dist/docs/dev/index.md +16 -14
  30. package/apps/runtime/dist/docs/dev/plugins/connections.md +27 -24
  31. package/apps/runtime/dist/docs/dev/plugins/events.md +19 -17
  32. package/apps/runtime/dist/docs/dev/plugins/examples.md +13 -12
  33. package/apps/runtime/dist/docs/dev/plugins/index.md +29 -24
  34. package/apps/runtime/dist/docs/dev/plugins/layout.md +20 -19
  35. package/apps/runtime/dist/docs/dev/plugins/quick-start.md +16 -15
  36. package/apps/runtime/dist/docs/dev/plugins/reference.md +52 -40
  37. package/apps/runtime/dist/docs/dev/plugins/reloading.md +24 -23
  38. package/apps/runtime/dist/docs/dev/plugins/routes.md +26 -22
  39. package/apps/runtime/dist/docs/dev/plugins/runners.md +49 -44
  40. package/apps/runtime/dist/docs/dev/plugins/server.md +95 -43
  41. package/apps/runtime/dist/docs/dev/plugins/shipping.md +19 -16
  42. package/apps/runtime/dist/docs/dev/plugins/space.md +75 -69
  43. package/apps/runtime/dist/docs/dev/plugins/starters.md +22 -17
  44. package/apps/runtime/dist/docs/dev/plugins/studio.md +80 -37
  45. package/apps/runtime/dist/docs/dev/plugins/styling.md +44 -41
  46. package/apps/runtime/dist/docs/dev/plugins/tables.md +11 -11
  47. package/apps/runtime/dist/docs/dev/plugins/tools.md +26 -25
  48. package/apps/runtime/dist/docs/dev/wizard-format.md +35 -23
  49. package/apps/runtime/dist/docs/user/ai-apps.md +46 -43
  50. package/apps/runtime/dist/docs/user/assistant.md +41 -36
  51. package/apps/runtime/dist/docs/user/build-a-wizard.md +64 -39
  52. package/apps/runtime/dist/docs/user/command-line.md +47 -44
  53. package/apps/runtime/dist/docs/user/connectors.md +40 -35
  54. package/apps/runtime/dist/docs/user/create-account.md +27 -22
  55. package/apps/runtime/dist/docs/user/data.md +29 -29
  56. package/apps/runtime/dist/docs/user/first-start.md +26 -28
  57. package/apps/runtime/dist/docs/user/index.md +16 -17
  58. package/apps/runtime/dist/docs/user/install.md +28 -27
  59. package/apps/runtime/dist/docs/user/local-only.md +21 -17
  60. package/apps/runtime/dist/docs/user/models.md +92 -84
  61. package/apps/runtime/dist/docs/user/plugins.md +28 -23
  62. package/apps/runtime/dist/docs/user/run.md +51 -50
  63. package/apps/runtime/dist/docs/user/server.md +9 -9
  64. package/apps/runtime/dist/docs/user/space.md +87 -65
  65. package/apps/runtime/dist/docs/user/steps.md +73 -69
  66. package/apps/runtime/dist/docs/user/test-and-share.md +127 -105
  67. package/apps/runtime/dist/docs/user/troubleshooting.md +40 -38
  68. package/apps/runtime/dist/docs/user/wordpress.md +62 -54
  69. package/apps/runtime/dist/documents/parse.js +2 -2
  70. package/apps/runtime/dist/engenty/shims/web-ingest.js +17 -1
  71. package/apps/runtime/dist/engine/input.js +4 -4
  72. package/apps/runtime/dist/engine/push.js +2 -2
  73. package/apps/runtime/dist/engine/runner.js +8 -8
  74. package/apps/runtime/dist/engine/steps.js +2 -2
  75. package/apps/runtime/dist/engine/surface.js +1 -1
  76. package/apps/runtime/dist/film/step.js +3 -3
  77. package/apps/runtime/dist/harness/claude.js +1 -1
  78. package/apps/runtime/dist/harness/codex.js +1 -1
  79. package/apps/runtime/dist/harness/model.js +2 -2
  80. package/apps/runtime/dist/harness/terminal.js +3 -3
  81. package/apps/runtime/dist/mcp/handler.js +2 -2
  82. package/apps/runtime/dist/mcp/tools.js +9 -9
  83. package/apps/runtime/dist/models.js +15 -15
  84. package/apps/runtime/dist/plugins/claims.js +56 -0
  85. package/apps/runtime/dist/plugins/loader.js +2 -0
  86. package/apps/runtime/dist/plugins/public.js +2 -3
  87. package/apps/runtime/dist/plugins/runs.js +13 -8
  88. package/apps/runtime/dist/render/chromium.js +24 -11
  89. package/apps/runtime/dist/routes/api.js +15 -9
  90. package/apps/runtime/dist/routes/debug.js +1 -1
  91. package/apps/runtime/dist/routes/delivery.js +46 -0
  92. package/apps/runtime/dist/routes/lang.js +7 -0
  93. package/apps/runtime/dist/routes/marketplace.js +2 -1
  94. package/apps/runtime/dist/routes/plugin-public.js +2 -1
  95. package/apps/runtime/dist/routes/plugins.js +13 -14
  96. package/apps/runtime/dist/routes/runs.js +59 -49
  97. package/apps/runtime/dist/routes/studio.js +53 -23
  98. package/apps/runtime/dist/services/access.js +3 -3
  99. package/apps/runtime/dist/services/assistant-chats.js +1 -1
  100. package/apps/runtime/dist/services/brand.js +2 -2
  101. package/apps/runtime/dist/services/cloud.js +5 -4
  102. package/apps/runtime/dist/services/errors.js +2 -1
  103. package/apps/runtime/dist/services/knowledge-model.js +2 -2
  104. package/apps/runtime/dist/services/marketplace.js +2 -2
  105. package/apps/runtime/dist/services/package-file.js +12 -9
  106. package/apps/runtime/dist/services/package.js +2 -2
  107. package/apps/runtime/dist/services/project-files.js +10 -12
  108. package/apps/runtime/dist/services/projects.js +13 -8
  109. package/apps/runtime/dist/services/runners.js +4 -3
  110. package/apps/runtime/dist/services/runs.js +3 -3
  111. package/apps/runtime/dist/services/site.js +2 -2
  112. package/apps/runtime/dist/services/space-categories.js +4 -4
  113. package/apps/runtime/dist/services/space-data.js +13 -13
  114. package/apps/runtime/dist/services/space-origin.js +1 -1
  115. package/apps/runtime/dist/services/spaces.js +7 -7
  116. package/apps/runtime/dist/services/wizards.js +5 -5
  117. package/apps/runtime/dist/tools/net-guard.js +135 -24
  118. package/apps/runtime/dist/tools/plugin.js +1 -1
  119. package/apps/runtime/dist/tools/store.js +1 -1
  120. package/apps/runtime/dist/widgets/step.js +1 -1
  121. package/apps/web/dist/assets/AboutDialog-DaYwk84X.js +1 -0
  122. package/apps/web/dist/assets/{ChangelogDialog-BXpkgatF.js → ChangelogDialog-CsYbEayb.js} +1 -1
  123. package/apps/web/dist/assets/{ChatView-t7z44uFN.js → ChatView-BRD3fKZU.js} +1 -1
  124. package/apps/web/dist/assets/{EditorPage-4qxNJgKr.js → EditorPage-ocPlZD3E.js} +3 -3
  125. package/apps/web/dist/assets/{FlowPreview-WpS8DCPl.js → FlowPreview-Bm7sv746.js} +1 -1
  126. package/apps/web/dist/assets/{Harness-DL7Bse40.js → Harness-DKE-YNA5.js} +1 -1
  127. package/apps/web/dist/assets/{NewWizardChannels-Crt6PzH2.js → NewWizardChannels-CnWckqjO.js} +1 -1
  128. package/apps/web/dist/assets/{NewWizardPage-COiVmNsl.js → NewWizardPage-BXTpbHVM.js} +1 -1
  129. package/apps/web/dist/assets/{OnboardingPage-BwluEqXq.js → OnboardingPage-3Rz5uOsa.js} +1 -1
  130. package/apps/web/dist/assets/{ProjectPage-BIRU_AlN.js → ProjectPage-QdSJwm3k.js} +1 -1
  131. package/apps/web/dist/assets/{PublicRunner-BxMb4gEt.js → PublicRunner-DEpBer5g.js} +1 -1
  132. package/apps/web/dist/assets/{ReadOnly-D9r5ze5S.js → ReadOnly-rr_RJD-a.js} +1 -1
  133. package/apps/web/dist/assets/{RunDrawer-DmJl2QOJ.js → RunDrawer-BsZt0tL9.js} +1 -1
  134. package/apps/web/dist/assets/{RunnerView-CF_1rC-7.js → RunnerView-BbRukEdE.js} +1 -1
  135. package/apps/web/dist/assets/SettingsPage-qkWsQIMN.js +1 -0
  136. package/apps/web/dist/assets/{SetupPage-BGKtbS7j.js → SetupPage-PzvPPLfM.js} +1 -1
  137. package/apps/web/dist/assets/{SharePage-C4MqXKDA.js → SharePage-7vdDWG1n.js} +1 -1
  138. package/apps/web/dist/assets/changelog-BFdAYD28.js +1 -0
  139. package/apps/web/dist/assets/{esm-CsY8TkyE.js → esm-g0kaGp8t.js} +1 -1
  140. package/apps/web/dist/assets/{index-BjQvGQp_.js → index-BzPid1oQ.js} +4 -4
  141. package/apps/web/dist/assets/{lock-CI6TpWL-.js → lock-BK9RYIft.js} +1 -1
  142. package/apps/web/dist/assets/oss-credits-BJrR7BR1.js +1 -0
  143. package/apps/web/dist/assets/{phone-BrtQz-fG.js → phone-DtkY0NfU.js} +1 -1
  144. package/apps/web/dist/assets/{settings-menu-BtEKL72n.js → settings-menu-7EJQRFOi.js} +1 -1
  145. package/apps/web/dist/assets/{ui-4IaTD7LB.js → ui-B6S5hYPd.js} +3 -3
  146. package/apps/web/dist/assets/{upload-CWJAtPRo.js → upload-rYCAy7ox.js} +1 -1
  147. package/apps/web/dist/index.html +2 -2
  148. package/apps/web/dist/mcp/flow.html +3 -3
  149. package/bin/wizards.mjs +1 -1
  150. package/node_modules/@engenty-wizards/plugin-sdk/dist/index.d.ts +21 -2
  151. package/node_modules/@engenty-wizards/shared/dist/projects.js +1 -1
  152. package/node_modules/@engenty-wizards/shared/dist/run.d.ts +48 -48
  153. package/node_modules/@engenty-wizards/shared/dist/run.js +48 -48
  154. package/package.json +5 -4
  155. package/apps/web/dist/assets/AboutDialog-A_ILyeue.js +0 -1
  156. package/apps/web/dist/assets/SettingsPage-3xSInE-A.js +0 -1
  157. package/apps/web/dist/assets/changelog-CM_GdIVI.js +0 -1
  158. package/apps/web/dist/assets/oss-credits-BQQX0kvk.js +0 -1
@@ -1,4 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
+ import { bilingual } from "@engenty-wizards/shared/run";
2
3
  import { and, asc, eq, inArray, isNotNull, isNull, like } from "drizzle-orm";
3
4
  import { accountToken, linkedAccount } from "../auth/account.js";
4
5
  import { control, controlDb, db, schema } from "../db/client.js";
@@ -25,7 +26,7 @@ export async function cloudState(wizardId) {
25
26
  async function call(method, path, body, timeoutMs = 30_000) {
26
27
  const token = await accountToken();
27
28
  if (!token) {
28
- throw new ServiceError("refused", "The account is signed out. Please sign in again.", {
29
+ throw new ServiceError("refused", bilingual("Das Konto ist abgemeldet. Melde dich neu an.", "The account is signed out. Sign in again."), {
29
30
  reason: "signed_out",
30
31
  });
31
32
  }
@@ -39,7 +40,7 @@ async function call(method, path, body, timeoutMs = 30_000) {
39
40
  signal: AbortSignal.timeout(timeoutMs),
40
41
  }).catch(() => null);
41
42
  if (!res) {
42
- throw new ServiceError("refused", "The cloud is not reachable right now.", {
43
+ throw new ServiceError("refused", bilingual("Die Cloud ist gerade nicht erreichbar.", "The cloud can’t be reached right now."), {
43
44
  reason: "unreachable",
44
45
  });
45
46
  }
@@ -49,7 +50,7 @@ async function call(method, path, body, timeoutMs = 30_000) {
49
50
  function refusal(answer, fallback) {
50
51
  const { error, reason, ...rest } = answer.body;
51
52
  const message = answer.status === 401
52
- ? "The account is signed out. Please sign in again."
53
+ ? bilingual("Das Konto ist abgemeldet. Melde dich neu an.", "The account is signed out. Sign in again.")
53
54
  : typeof error === "string" && error
54
55
  ? error
55
56
  : fallback;
@@ -213,7 +214,7 @@ export async function syncToCloud(userId, wizardId) {
213
214
  const w = await ownedWizard(userId, wizardId);
214
215
  const version = await publishedVersion(w);
215
216
  if (!version) {
216
- throw new ServiceError("refused", "Publish the wizard first.");
217
+ throw new ServiceError("refused", bilingual("Veröffentliche den Wizard zuerst.", "Publish the wizard first."));
217
218
  }
218
219
  const before = await cloudState(w.id);
219
220
  try {
@@ -1,3 +1,4 @@
1
+ import { bilingual } from "@engenty-wizards/shared/run";
1
2
  import { errorText, NotedError } from "../model-errors.js";
2
3
  const STATUS = {
3
4
  not_found: 404,
@@ -25,4 +26,4 @@ export class ServiceError extends NotedError {
25
26
  return { error: errorText(this, lang), code: this.code, ...this.data };
26
27
  }
27
28
  }
28
- export const notFound = () => new ServiceError("not_found", "not found");
29
+ export const notFound = () => new ServiceError("not_found", bilingual("Das gibt es nicht oder nicht mehr.", "This doesn’t exist or was deleted."));
@@ -504,12 +504,12 @@ export async function writeSummary(valueId) {
504
504
  where: eq(schema.spaceCategoryValue.id, valueId),
505
505
  });
506
506
  if (!value) {
507
- throw new ServiceError("not_found", bilingual("Diesen Wert gibt es nicht.", "There is no such value."));
507
+ throw new ServiceError("not_found", bilingual("Diesen Wert gibt es nicht.", "This value doesn’t exist."));
508
508
  }
509
509
  const category = await categoryRow(value.categoryId);
510
510
  const { items, total } = await itemsOfValue(value.id);
511
511
  if (!items.length) {
512
- throw new ServiceError("invalid", "Noch steht nichts unter diesem Wert.");
512
+ throw new ServiceError("invalid", bilingual("Unter diesem Wert steht noch nichts.", "Nothing is filed under this value yet."));
513
513
  }
514
514
  const model = await textModel("standard");
515
515
  const result = await generateText({
@@ -404,13 +404,13 @@ export async function marketplaceWizard(id, lang) {
404
404
  return null;
405
405
  }
406
406
  if (!readable(found.definition)) {
407
- throw new ServiceError("refused", bilingual("Diese Vorlage braucht eine neuere Version der App. Bitte aktualisiere die App.", "This template needs a newer version of the app. Please update the app."));
407
+ throw new ServiceError("refused", bilingual("Diese Vorlage braucht eine neuere Version von engenty wizards. Aktualisiere zuerst engenty wizards.", "This template needs a newer version of engenty wizards. Update engenty wizards first."));
408
408
  }
409
409
  // A wizard whose plugins are not here would stop at its first step that uses them.
410
410
  const have = await pluginIdsOf(currentTenant());
411
411
  const needs = pluginsOf(wizardSchema.parse(found.definition)).filter((p) => !have.has(p));
412
412
  if (needs.length) {
413
- throw new ServiceError("refused", bilingual(`Diese Vorlage braucht ${needs.length === 1 ? "das Plugin" : "die Plugins"} ${needs.join(", ")}: mit Pro auf engenty.ai dabei.`, `This template needs the ${needs.length === 1 ? "plugin" : "plugins"} ${needs.join(", ")}: included with Pro on engenty.ai.`));
413
+ throw new ServiceError("refused", bilingual(`Diese Vorlage braucht ${needs.length === 1 ? "das Plugin" : "die Plugins"} ${needs.join(", ")}: bei Pro auf engenty.ai dabei.`, `This template needs the ${needs.length === 1 ? "plugin" : "plugins"} ${needs.join(", ")}: included with Pro on engenty.ai.`));
414
414
  }
415
415
  return {
416
416
  id: found.id,
@@ -1,3 +1,4 @@
1
+ import { bilingual } from "@engenty-wizards/shared/run";
1
2
  import { manifestSchema, PACKAGE_FILES, PACKAGE_FORMAT, PACKAGE_MANIFEST, PACKAGE_MAX_BYTES, } from "@engenty-wizards/shared/wizard-package";
2
3
  import { cleanPath, WORKSPACE_LIMITS } from "@engenty-wizards/shared/workspace";
3
4
  import { strFromU8, unzipSync } from "fflate";
@@ -6,13 +7,13 @@ import { ServiceError } from "./errors.js";
6
7
  * Reading a `.wizard` package, with nothing of a project or a database: the studio's import and
7
8
  * `wizards validate` both start here.
8
9
  */
9
- const invalid = (message) => new ServiceError("invalid", message);
10
+ const invalid = (said) => new ServiceError("invalid", said);
10
11
  export function json(data) {
11
12
  try {
12
13
  return JSON.parse(strFromU8(data));
13
14
  }
14
15
  catch {
15
- throw invalid("This is not a wizard package: wizard.json is not valid JSON.");
16
+ throw invalid(bilingual("Das ist keine Wizard-Datei: Die wizard.json darin lässt sich nicht lesen.", "This isn’t a wizard file: its wizard.json can’t be read."));
16
17
  }
17
18
  }
18
19
  /** A manifest, or a bare definition: a wizard without workspace is fully told by its JSON. */
@@ -22,7 +23,7 @@ export function readManifest(raw) {
22
23
  }
23
24
  const parsed = manifestSchema.safeParse(raw);
24
25
  if (!parsed.success) {
25
- throw invalid("This wizard package comes from a newer version, or it is damaged.");
26
+ throw invalid(bilingual("Diese Wizard-Datei stammt aus einer neueren Version oder ist beschädigt. Aktualisiere engenty wizards oder exportiere den Wizard neu.", "This wizard file comes from a newer version, or it’s damaged. Update engenty wizards or export the wizard again."));
26
27
  }
27
28
  return parsed.data;
28
29
  }
@@ -45,24 +46,26 @@ export function readPackage(data) {
45
46
  total += file.originalSize;
46
47
  count += 1;
47
48
  if (file.originalSize > WORKSPACE_LIMITS.fileBytes) {
48
- throw invalid(`${file.name} is larger than 5 MB.`);
49
+ throw invalid(bilingual(`${file.name} ist größer als 5 MB.`, `${file.name} is larger than 5 MB.`));
49
50
  }
50
51
  if (count > WORKSPACE_LIMITS.files + 1 || total > PACKAGE_MAX_BYTES) {
51
- throw invalid("The package is larger than a workspace may be (200 files, 25 MB).");
52
+ throw invalid(bilingual("Die Wizard-Datei ist zu groß: Ein Arbeitsbereich fasst höchstens 200 Dateien und 25 MB.", "The wizard file is too large: a workspace holds at most 200 files and 25 MB."));
52
53
  }
53
54
  return true;
54
55
  },
55
56
  });
56
57
  }
57
58
  catch (err) {
58
- throw err instanceof ServiceError ? err : invalid("The package cannot be unpacked.");
59
+ throw err instanceof ServiceError
60
+ ? err
61
+ : invalid(bilingual("Die Wizard-Datei lässt sich nicht öffnen. Exportiere den Wizard neu und versuche es noch einmal.", "The wizard file can’t be opened. Export the wizard again and try once more."));
59
62
  }
60
63
  // A package unpacked and zipped again often sits in a folder of its own.
61
64
  const manifestPath = Object.keys(entries)
62
65
  .filter((name) => name === PACKAGE_MANIFEST || name.endsWith(`/${PACKAGE_MANIFEST}`))
63
66
  .sort((a, b) => a.length - b.length)[0];
64
67
  if (manifestPath === undefined) {
65
- throw invalid("This is not a wizard package: wizard.json is missing.");
68
+ throw invalid(bilingual("Das ist keine Wizard-Datei: Die wizard.json fehlt.", "This isn’t a wizard file: wizard.json is missing."));
66
69
  }
67
70
  const root = manifestPath.slice(0, -PACKAGE_MANIFEST.length);
68
71
  const files = Object.entries(entries)
@@ -70,12 +73,12 @@ export function readPackage(data) {
70
73
  .map(([name, content]) => {
71
74
  const path = cleanPath(name.slice(root.length + PACKAGE_FILES.length));
72
75
  if (!path) {
73
- throw invalid(`"${name}" is not a valid file path.`);
76
+ throw invalid(bilingual(`„${name}“ ist kein gültiger Dateipfad.`, `“${name}” isn’t a valid file path.`));
74
77
  }
75
78
  return { path, data: content };
76
79
  });
77
80
  if (files.reduce((n, f) => n + f.data.byteLength, 0) > WORKSPACE_LIMITS.totalBytes) {
78
- throw invalid("The workspace in the package is larger than 25 MB.");
81
+ throw invalid(bilingual("Der Arbeitsbereich in der Wizard-Datei ist größer als 25 MB.", "The workspace in the wizard file is larger than 25 MB."));
79
82
  }
80
83
  return { manifest: readManifest(json(entries[manifestPath])), files };
81
84
  }
@@ -151,8 +151,8 @@ lang = "en") {
151
151
  : `Imported${name ? ` from “${name}”` : ""}: ${draft.steps.length} steps, ${files.length} ${files.length === 1 ? "file" : "files"}.`,
152
152
  lacks.length
153
153
  ? de
154
- ? `Diesem Projekt fehlt noch: ${lacks.join(", ")} – in den Einstellungen unter „Connectors“ hinzufügen.`
155
- : `This project still lacks: ${lacks.join(", ")} – add it under “Connectors” in the settings.`
154
+ ? `Diesem Space fehlt noch: ${lacks.join(", ")}. Füge es unter Einstellungen → Connectors hinzu.`
155
+ : `This space still needs: ${lacks.join(", ")}. Add it under Settings → Connectors.`
156
156
  : "",
157
157
  ]
158
158
  .filter(Boolean)
@@ -28,7 +28,7 @@ export function fileView(row) {
28
28
  description: row.description,
29
29
  source: row.source,
30
30
  status: stale ? "failed" : row.status,
31
- error: stale ? "Interrupted. Please read it in again." : row.error,
31
+ error: stale ? "Reading was interrupted. Read it in again." : row.error,
32
32
  indexed: row.indexed,
33
33
  pages: row.pages,
34
34
  chars: row.chars,
@@ -82,11 +82,11 @@ export async function addProjectFile(projectId, input) {
82
82
  ? bilingual("Ein Logo ist ein Bild (PNG, SVG, JPEG, WebP).", "A logo is an image (PNG, SVG, JPEG, WebP).")
83
83
  : input.kind === "pattern"
84
84
  ? bilingual("Ein Muster ist ein Bild (SVG, PNG, JPEG, WebP).", "A pattern is an image (SVG, PNG, JPEG, WebP).")
85
- : bilingual("Hierher gehören Bilder, Videos, Audio, PDFs, Schriften, Lottie und 3D-Modelle. Texte zum Nachschlagen bitte zu den Dokumenten.", "Images, videos, audio, PDFs, fonts, Lottie and 3D models belong here. Texts to look things up in go to the documents."));
85
+ : bilingual("Hierher gehören Bilder, Videos, Audio, PDFs, Schriften, Lottie und 3D-Modelle. Texte zum Nachschlagen gehören ins Wissen.", "Images, videos, audio, PDFs, fonts, Lottie and 3D models go here. Texts to look things up in go under knowledge."));
86
86
  }
87
87
  const limit = projectFileLimit(input.kind, mime);
88
88
  if (!input.data.byteLength || input.data.byteLength > limit) {
89
- throw new ServiceError("invalid", bilingual(`Bitte eine Datei bis ${Math.round(limit / 1_000_000)} MB wählen.`, `Please choose a file of up to ${Math.round(limit / 1_000_000)} MB.`));
89
+ throw new ServiceError("invalid", bilingual(`Wähl eine Datei bis ${Math.round(limit / 1_000_000)} MB.`, `Choose a file of ${Math.round(limit / 1_000_000)} MB or less.`));
90
90
  }
91
91
  const scope = and(eq(schema.projectFile.projectId, projectId), eq(schema.projectFile.kind, input.kind));
92
92
  const [{ n, last }] = await db
@@ -94,7 +94,7 @@ export async function addProjectFile(projectId, input) {
94
94
  .from(schema.projectFile)
95
95
  .where(scope);
96
96
  if (n >= PROJECT_LIMITS.files[input.kind]) {
97
- throw new ServiceError("refused", bilingual(`Höchstens ${PROJECT_LIMITS.files[input.kind]} Dateien.`, `At most ${PROJECT_LIMITS.files[input.kind]} files.`));
97
+ throw new ServiceError("refused", bilingual(`Hier haben höchstens ${PROJECT_LIMITS.files[input.kind]} Dateien Platz.`, `There’s room for at most ${PROJECT_LIMITS.files[input.kind]} files here.`));
98
98
  }
99
99
  const name = input.name.slice(0, 120) || "datei";
100
100
  const ref = await saveAsset({
@@ -150,15 +150,15 @@ export async function addProjectFileFromUrl(projectId, input) {
150
150
  headers: { "user-agent": "Mozilla/5.0 (compatible; engenty-wizards/0.1)" },
151
151
  signal: AbortSignal.any([AbortSignal.timeout(60_000), ...(signal ? [signal] : [])]),
152
152
  }).catch(() => {
153
- throw new ServiceError("invalid", bilingual("Die Adresse ist nicht erreichbar.", "The address cannot be reached."));
153
+ throw new ServiceError("invalid", bilingual("Die Adresse ist nicht erreichbar.", "The address can’t be reached."));
154
154
  });
155
155
  if (!res.ok) {
156
- throw new ServiceError("invalid", bilingual(`Die Adresse antwortete ${res.status}.`, `The address answered ${res.status}.`));
156
+ throw new ServiceError("invalid", bilingual(`Die Adresse antwortete mit ${res.status}.`, `The address responded with ${res.status}.`));
157
157
  }
158
158
  const mime = (res.headers.get("content-type") ?? "").split(";")[0].trim().toLowerCase();
159
159
  if (mime === "text/html" && kind !== "document") {
160
160
  await res.body?.cancel();
161
- throw new ServiceError("invalid", bilingual("Unter der Adresse steht eine Webseite, kein Bild.", "The address holds a web page, not an image."), {
161
+ throw new ServiceError("invalid", bilingual("Unter der Adresse steht eine Webseite, kein Bild.", "The address leads to a web page, not an image."), {
162
162
  reason: "page",
163
163
  url: res.url,
164
164
  });
@@ -296,9 +296,7 @@ async function prepare(row) {
296
296
  console.error("[project-file]", row.name, err);
297
297
  await set({
298
298
  status: "failed",
299
- error: err instanceof UnreadableDocument
300
- ? err.message
301
- : "Reading did not work. Please try again.",
299
+ error: err instanceof UnreadableDocument ? err.message : "Reading didn’t work. Try again.",
302
300
  });
303
301
  }
304
302
  });
@@ -382,7 +380,7 @@ export async function putOriginFile(origin, input) {
382
380
  });
383
381
  const mime = documentMime(input.name, input.mime) || "application/octet-stream";
384
382
  if (!input.data.byteLength || input.data.byteLength > projectFileLimit("document", mime)) {
385
- throw new ServiceError("invalid", `"${input.name}" is empty or larger than a document may be.`);
383
+ throw new ServiceError("invalid", bilingual(`„${input.name}“ ist leer oder zu groß für ein Dokument.`, `“${input.name}” is empty or too large for a document.`));
386
384
  }
387
385
  const name = input.name.slice(0, 120) || "datei";
388
386
  const values = {
@@ -409,7 +407,7 @@ export async function putOriginFile(origin, input) {
409
407
  .from(schema.projectFile)
410
408
  .where(and(eq(schema.projectFile.projectId, input.space), eq(schema.projectFile.kind, "document")));
411
409
  if (n >= PROJECT_LIMITS.files.document) {
412
- throw new ServiceError("refused", `A space keeps at most ${PROJECT_LIMITS.files.document} documents.`);
410
+ throw new ServiceError("refused", bilingual(`Ein Space hat höchstens ${PROJECT_LIMITS.files.document} Dokumente.`, `A space can have at most ${PROJECT_LIMITS.files.document} documents.`));
413
411
  }
414
412
  const ref = await saveAsset({ kind: "project", mime, name, data: input.data });
415
413
  id = ref.id;
@@ -147,18 +147,23 @@ export async function ownedProject(_userId, projectId) {
147
147
  const madeHere = () => and(eq(schema.project.tenantId, currentTenant()), isNull(schema.project.origin));
148
148
  /** The tenant's projects a local install synced. */
149
149
  const synced = () => and(eq(schema.project.tenantId, currentTenant()), eq(schema.project.origin, "local"));
150
+ /** What the first space is called, in the language of whoever first opens the studio. */
151
+ const FIRST_SPACE = {
152
+ cloud: { de: "Meine Wizards", en: "My wizards" },
153
+ local: { de: "Mein Computer", en: "My computer" },
154
+ };
150
155
  /**
151
156
  * A tenant that builds here always has a project of its own: the first one is made when its
152
- * studio is first opened.
157
+ * studio is first opened, named in that request's language.
153
158
  */
154
- async function ensureProject() {
159
+ async function ensureProject(lang = "en") {
155
160
  const any = await db.query.project.findFirst({ where: madeHere() });
156
161
  if (!any) {
157
162
  const id = nanoid(12);
158
163
  await db.insert(schema.project).values({
159
164
  id,
160
165
  tenantId: currentTenant(),
161
- name: env.manage.url ? "Meine Wizards" : "Local Space",
166
+ name: FIRST_SPACE[env.manage.url ? "cloud" : "local"][lang],
162
167
  });
163
168
  spaceChanged("space.created", id);
164
169
  }
@@ -185,10 +190,10 @@ export async function defaultProject(_userId) {
185
190
  * The projects the studio shows. A tenant that builds here: its own and the synced ones, up to
186
191
  * its number. One that does not: only what its local install synced — possibly nothing yet.
187
192
  */
188
- export async function listProjects(_userId) {
193
+ export async function listProjects(_userId, lang) {
189
194
  const build = await mayBuild();
190
195
  if (build) {
191
- await ensureProject();
196
+ await ensureProject(lang);
192
197
  }
193
198
  // The oldest come first: with a limit of one, that is the project everything lands in.
194
199
  const limit = await projectLimit();
@@ -226,8 +231,8 @@ export async function createProject(_userId, name) {
226
231
  const limit = await projectLimit();
227
232
  if (n >= limit) {
228
233
  throw new ServiceError("refused", limit === 1
229
- ? bilingual("Hier gibt es ein Projekt.", "There is one project here.")
230
- : bilingual(`Höchstens ${limit} Projekte.`, `At most ${limit} projects.`));
234
+ ? bilingual("Hier ist nur ein Space möglich.", "Only one space is allowed here.")
235
+ : bilingual(`Hier sind höchstens ${limit} Spaces möglich.`, `You can have at most ${limit} spaces here.`));
231
236
  }
232
237
  const id = nanoid(12);
233
238
  await db
@@ -365,7 +370,7 @@ export async function deleteProject(userId, projectId) {
365
370
  await requireAdmin();
366
371
  const all = await db.query.project.findMany({ where: madeHere() });
367
372
  if (all.length <= 1) {
368
- throw new ServiceError("refused", bilingual("Das letzte Projekt bleibt.", "The last project stays."));
373
+ throw new ServiceError("refused", bilingual("Der letzte Space lässt sich nicht löschen.", "You can’t delete the last space."));
369
374
  }
370
375
  await removeProject(projectId);
371
376
  }
@@ -1,4 +1,5 @@
1
1
  import { wizardLang } from "@engenty-wizards/shared/definition";
2
+ import { bilingual } from "@engenty-wizards/shared/run";
2
3
  import { BUILT_IN_RUNNERS, CONVERSATION_RUNNERS, DEFAULT_RUNNER, DEFAULT_RUNNER_SETTINGS, runnerFit, } from "@engenty-wizards/shared/runners";
3
4
  import { env } from "../env.js";
4
5
  import { conversationAccess, conversationAvailable } from "../models.js";
@@ -71,14 +72,14 @@ export async function checkRunnerSettings(settings) {
71
72
  const ids = new Set(available.map((r) => r.id));
72
73
  const unknown = settings.enabled.find((id) => !ids.has(id));
73
74
  if (unknown) {
74
- throw new ServiceError("invalid", `There is no runner "${unknown}" here.`);
75
+ throw new ServiceError("invalid", bilingual(`Einen Runner „${unknown}“ gibt es hier nicht.`, `There’s no runner “${unknown}” here.`));
75
76
  }
76
77
  const enabled = [...new Set(settings.enabled)];
77
78
  if (!enabled.length) {
78
- throw new ServiceError("invalid", "At least one runner stays on.");
79
+ throw new ServiceError("invalid", bilingual("Mindestens ein Runner muss eingeschaltet bleiben.", "At least one runner has to stay on."));
79
80
  }
80
81
  if (!enabled.includes(settings.default)) {
81
- throw new ServiceError("invalid", "The link opens a runner that is switched on.");
82
+ throw new ServiceError("invalid", bilingual("Der Link muss einen eingeschalteten Runner öffnen.", "The link has to open a runner that’s turned on."));
82
83
  }
83
84
  return { default: settings.default, enabled };
84
85
  }
@@ -86,7 +86,7 @@ function fly(runId, answers, acceptReviews) {
86
86
  .join("; ")
87
87
  : "";
88
88
  const message = err instanceof RunInputError
89
- ? bilingual(`Antworten fehlen oder passen nicht: ${answers("de")}`, `Answers are missing or do not fit: ${answers("en")}`)
89
+ ? bilingual(`Antworten fehlen oder passen nicht: ${answers("de")}`, `Answers are missing or don’t fit: ${answers("en")}`)
90
90
  : saidOf(err);
91
91
  await emitEvent(runId, null, "error", message).catch(() => undefined);
92
92
  land();
@@ -368,7 +368,7 @@ export function refusedInput(err) {
368
368
  export async function startRun(userId, wizardId, answers = {}) {
369
369
  const w = await ownedWizard(userId, wizardId);
370
370
  if (w.publishedVersion === null) {
371
- throw new ServiceError("refused", "This wizard is not published yet. Publish it, or try the draft with start_test_run.");
371
+ throw new ServiceError("refused", "This wizard isn’t published yet. Publish it, or try the draft with start_test_run.");
372
372
  }
373
373
  const version = await db.query.wizardVersion.findFirst({
374
374
  where: and(eq(schema.wizardVersion.wizardId, w.id), eq(schema.wizardVersion.version, w.publishedVersion)),
@@ -429,7 +429,7 @@ export async function controlRun(userId, runId, action) {
429
429
  export async function answerRunAsk(userId, runId, askId, answer) {
430
430
  await ownedRun(userId, runId);
431
431
  if (!(await answerAsk(runId, askId, answer === "allow" ? { type: "done" } : { type: "skip" }))) {
432
- throw new ServiceError("refused", "This question is no longer open.");
432
+ throw new ServiceError("refused", { code: "askClosed" });
433
433
  }
434
434
  }
435
435
  export async function listRuns(userId, wizardId) {
@@ -150,7 +150,7 @@ function assistMessage(input) {
150
150
  .slice(0, 6)
151
151
  .map((p) => `## ${p.title}${p.url ? ` (${p.url})` : ""}${p.role ? ` [${p.role}]` : ""}\n\n${p.markdown.slice(0, 6000)}`);
152
152
  return [
153
- `This is the WordPress website ${input.url}. Fill the space from what it says about itself: a title, a description (about), facts (address, phone, email, VAT id, company register, opening hours, who runs it) and the colours, if there are none yet. Its pages are already in Knowledge; do not copy them. Write in the website's own language.`,
153
+ `This is the WordPress website ${input.url}. Fill the space from what it says about itself: a title, a description (about), facts (address, phone, email, VAT ID, company register, opening hours, who runs it) and the colours, if there are none yet. Its pages are already in Knowledge; do not copy them. Write in the website's own language.`,
154
154
  "The details below come straight from WordPress. Read the website with read_website only when something important is missing. Invent nothing.",
155
155
  `Name: ${input.name || "—"}\nTagline: ${input.tagline || "—"}\nLanguage: ${input.language ?? "—"}`,
156
156
  input.facts.length
@@ -267,7 +267,7 @@ function siteJobView(job) {
267
267
  export function siteJob(jobId) {
268
268
  const job = jobs.get(jobId);
269
269
  if (!job || job.tenantId !== currentTenant()) {
270
- throw new ServiceError("not_found", bilingual("Diesen Vorgang gibt es nicht (mehr).", "There is no such job (any more)."));
270
+ throw new ServiceError("not_found", bilingual("Diesen Vorgang gibt es nicht, oder er ist schon beendet.", "This job doesn’t exist or has already finished."));
271
271
  }
272
272
  return siteJobView(job);
273
273
  }
@@ -330,7 +330,7 @@ export async function putCategory(projectId, draft, opts = {}) {
330
330
  return known;
331
331
  }
332
332
  if (rows.length >= MAX_CATEGORIES) {
333
- throw new ServiceError("refused", bilingual(`Ein Space hat höchstens ${MAX_CATEGORIES} Kategorien.`, `A space has at most ${MAX_CATEGORIES} categories.`));
333
+ throw new ServiceError("refused", bilingual(`Ein Space hat höchstens ${MAX_CATEGORIES} Kategorien.`, `A space can have at most ${MAX_CATEGORIES} categories.`));
334
334
  }
335
335
  const id = nanoid(12);
336
336
  const [row] = await db
@@ -459,7 +459,7 @@ export async function addCategoryValue(categoryId, value) {
459
459
  const category = await categoryRow(categoryId);
460
460
  await requireWritableProject(category.projectId);
461
461
  if (category.type !== "choice") {
462
- throw new ServiceError("invalid", bilingual("Nur eine Auswahl hat Werte.", "Only a choice has values."));
462
+ throw new ServiceError("invalid", bilingual("Werte gibt es nur bei Kategorien vom Typ Auswahl.", "Only choice categories have values."));
463
463
  }
464
464
  const ids = await addValues(category.id, [value]);
465
465
  return { id: [...ids.values()].at(-1) };
@@ -731,7 +731,7 @@ export async function syncTableRowCategories(tableId, rowIds) {
731
731
  function condition(category, w, valueIds) {
732
732
  const t = schema.spaceItemCategory;
733
733
  const list = Array.isArray(w) ? w : null;
734
- const bad = () => new ServiceError("invalid", `„${category.name}“ is a ${category.type}: ${{
734
+ const bad = () => new ServiceError("invalid", `“${category.name}” is a ${category.type}: ${{
735
735
  choice: "give a value, a list of values or { atLeast }",
736
736
  text: "give a text, a list or { contains }",
737
737
  number: "give a number, a list or { gte, lte }",
@@ -827,7 +827,7 @@ export async function filterItems(projectId, where) {
827
827
  for (const [name, w] of entries) {
828
828
  const category = byName(rows, name);
829
829
  if (!category) {
830
- throw new ServiceError("invalid", `No category "${name}". There are: ${rows.map((c) => `${c.name} (${c.type})`).join(", ") || "none"}.`);
830
+ throw new ServiceError("invalid", `No category “${name}”. There are: ${rows.map((c) => `${c.name} (${c.type})`).join(", ") || "none"}.`);
831
831
  }
832
832
  const valueIds = new Map((await db.query.spaceCategoryValue.findMany({
833
833
  where: eq(schema.spaceCategoryValue.categoryId, category.id),
@@ -23,7 +23,7 @@ async function ownerOf(projectId, wizardId) {
23
23
  columns: { id: true },
24
24
  });
25
25
  if (!w) {
26
- throw new ServiceError("invalid", bilingual("Diesen Wizard gibt es im Space nicht.", "There is no such wizard in the space."));
26
+ throw new ServiceError("invalid", bilingual("Diesen Wizard gibt es im Space nicht.", "This wizard isn’t in the space."));
27
27
  }
28
28
  return w.id;
29
29
  }
@@ -104,7 +104,7 @@ async function tableCount(projectId) {
104
104
  .from(schema.spaceTable)
105
105
  .where(eq(schema.spaceTable.projectId, projectId));
106
106
  if (n >= SPACE_DATA_LIMITS.tables) {
107
- throw new ServiceError("refused", bilingual(`Ein Space hat höchstens ${SPACE_DATA_LIMITS.tables} Tabellen.`, `A space has at most ${SPACE_DATA_LIMITS.tables} tables.`));
107
+ throw new ServiceError("refused", bilingual(`Ein Space hat höchstens ${SPACE_DATA_LIMITS.tables} Tabellen.`, `A space can have at most ${SPACE_DATA_LIMITS.tables} tables.`));
108
108
  }
109
109
  }
110
110
  export async function createTable(userId, projectId, input, lang = "en") {
@@ -187,7 +187,7 @@ export async function getTable(userId, tableId) {
187
187
  export async function updateTable(_userId, tableId, patch) {
188
188
  const table = await writableTable(tableId);
189
189
  if (table.list && (patch.columns !== undefined || patch.wizardId !== undefined)) {
190
- throw new ServiceError("refused", bilingual("Diese Tabelle gehört zu einer Liste des Wizards: ihre Spalten kommen aus dem Wizard.", "This table belongs to one of the wizard's lists: its columns come from the wizard."));
190
+ throw new ServiceError("refused", bilingual("Diese Tabelle gehört zu einer Liste des Wizards. Ihre Spalten kommen aus dem Wizard.", "This table belongs to one of the wizard’s lists. Its columns come from the wizard."));
191
191
  }
192
192
  const title = patch.title !== undefined ? patch.title.trim() || table.title : table.title;
193
193
  const wizardId = patch.wizardId !== undefined ? await ownerOf(table.projectId, patch.wizardId) : table.wizardId;
@@ -269,7 +269,7 @@ export async function addTableRow(_userId, tableId, cells = {}) {
269
269
  .from(schema.spaceTableRow)
270
270
  .where(eq(schema.spaceTableRow.tableId, table.id));
271
271
  if (n >= SPACE_DATA_LIMITS.rowsPerTable) {
272
- throw new ServiceError("refused", bilingual(`Eine Tabelle hat höchstens ${SPACE_DATA_LIMITS.rowsPerTable} Zeilen.`, `A table has at most ${SPACE_DATA_LIMITS.rowsPerTable} rows.`));
272
+ throw new ServiceError("refused", bilingual(`Eine Tabelle hat höchstens ${SPACE_DATA_LIMITS.rowsPerTable} Zeilen.`, `A table can have at most ${SPACE_DATA_LIMITS.rowsPerTable} rows.`));
273
273
  }
274
274
  let value;
275
275
  try {
@@ -334,7 +334,7 @@ async function pageRow(pageId) {
334
334
  }
335
335
  function checkMarkdown(markdown) {
336
336
  if (markdown.length > SPACE_DATA_LIMITS.pageChars) {
337
- throw new ServiceError("refused", bilingual(`Eine Seite hat höchstens ${SPACE_DATA_LIMITS.pageChars.toLocaleString("de")} Zeichen.`, `A page has at most ${SPACE_DATA_LIMITS.pageChars.toLocaleString("en")} characters.`));
337
+ throw new ServiceError("refused", bilingual(`Eine Seite hat höchstens ${SPACE_DATA_LIMITS.pageChars.toLocaleString("de")} Zeichen.`, `A page can have at most ${SPACE_DATA_LIMITS.pageChars.toLocaleString("en")} characters.`));
338
338
  }
339
339
  return markdown;
340
340
  }
@@ -344,7 +344,7 @@ async function pageCount(projectId, more = 1) {
344
344
  .from(schema.spacePage)
345
345
  .where(eq(schema.spacePage.projectId, projectId));
346
346
  if (n + more > SPACE_DATA_LIMITS.pages) {
347
- throw new ServiceError("refused", bilingual(`Ein Space hat höchstens ${SPACE_DATA_LIMITS.pages} Seiten.`, `A space has at most ${SPACE_DATA_LIMITS.pages} pages.`));
347
+ throw new ServiceError("refused", bilingual(`Ein Space hat höchstens ${SPACE_DATA_LIMITS.pages} Seiten.`, `A space can have at most ${SPACE_DATA_LIMITS.pages} pages.`));
348
348
  }
349
349
  }
350
350
  /** A page of the space's own that a sub-page may stand under: never one of its own sub-pages. */
@@ -357,11 +357,11 @@ async function parentOf(projectId, parentId, pageId) {
357
357
  columns: { id: true, wizardId: true, parentId: true },
358
358
  });
359
359
  if (!parent || parent.wizardId) {
360
- throw new ServiceError("invalid", bilingual("Unter diese Seite kann keine Seite.", "No page can go under this page."));
360
+ throw new ServiceError("invalid", bilingual("Diese Seite kann keine Unterseiten haben.", "This page can’t have subpages."));
361
361
  }
362
362
  for (let at = parent.id; at && pageId;) {
363
363
  if (at === pageId) {
364
- throw new ServiceError("invalid", bilingual("Eine Seite kann nicht unter sich selbst stehen.", "A page cannot stand under itself."));
364
+ throw new ServiceError("invalid", bilingual("Eine Seite kann nicht ihre eigene Unterseite sein.", "A page can’t be its own subpage."));
365
365
  }
366
366
  const up = await db.query.spacePage.findFirst({
367
367
  where: eq(schema.spacePage.id, at),
@@ -492,7 +492,7 @@ export async function runPages(projectId, wizardId) {
492
492
  export async function writeRunPage(projectId, wizardId, input) {
493
493
  const title = input.title.trim();
494
494
  if (!title) {
495
- throw new ServiceError("invalid", "A page needs a title.");
495
+ throw new ServiceError("invalid", bilingual("Eine Seite braucht einen Titel.", "A page needs a title."));
496
496
  }
497
497
  const existing = (await db.query.spacePage.findMany({
498
498
  where: and(eq(schema.spacePage.projectId, projectId), eq(schema.spacePage.wizardId, wizardId)),
@@ -513,7 +513,7 @@ export async function writeRunPage(projectId, wizardId, input) {
513
513
  .from(schema.spacePage)
514
514
  .where(eq(schema.spacePage.projectId, projectId));
515
515
  if (n >= SPACE_DATA_LIMITS.pages) {
516
- throw new ServiceError("refused", `A space keeps at most ${SPACE_DATA_LIMITS.pages} pages.`);
516
+ throw new ServiceError("refused", bilingual(`Ein Space hat höchstens ${SPACE_DATA_LIMITS.pages} Seiten.`, `A space can have at most ${SPACE_DATA_LIMITS.pages} pages.`));
517
517
  }
518
518
  const id = nanoid(12);
519
519
  await db.insert(schema.spacePage).values({
@@ -532,7 +532,7 @@ async function originSpace(space) {
532
532
  columns: { id: true },
533
533
  });
534
534
  if (!project) {
535
- throw new ServiceError("not_found", `There is no space "${space}".`);
535
+ throw new ServiceError("not_found", bilingual(`Den Space „${space}“ gibt es nicht.`, `There’s no space “${space}”.`));
536
536
  }
537
537
  await requireWritableProject(project.id);
538
538
  return project.id;
@@ -566,7 +566,7 @@ export async function putOriginPage(origin, input) {
566
566
  })
567
567
  : null;
568
568
  if (input.parent && !parent) {
569
- throw new ServiceError("invalid", `There is no page with the key "${input.parent}" to stand under.`);
569
+ throw new ServiceError("invalid", bilingual(`Es gibt keine Seite mit dem Schlüssel „${input.parent}“, unter die diese Seite könnte.`, `There’s no page with the key “${input.parent}” to put this page under.`));
570
570
  }
571
571
  const title = input.title.replace(/\s+/g, " ").trim().slice(0, 200) || "Seite";
572
572
  const markdown = checkMarkdown(input.markdown);
@@ -630,7 +630,7 @@ export async function putOriginTable(origin, input) {
630
630
  }
631
631
  const columns = checkColumns(input.columns);
632
632
  if (input.rows.length > SPACE_DATA_LIMITS.rowsPerTable) {
633
- throw new ServiceError("refused", `A table keeps at most ${SPACE_DATA_LIMITS.rowsPerTable} rows.`);
633
+ throw new ServiceError("refused", bilingual(`Eine Tabelle hat höchstens ${SPACE_DATA_LIMITS.rowsPerTable} Zeilen.`, `A table can have at most ${SPACE_DATA_LIMITS.rowsPerTable} rows.`));
634
634
  }
635
635
  let cells;
636
636
  try {
@@ -74,7 +74,7 @@ export function originData(origin) {
74
74
  const id = await putOriginFile(origin, { ...input, categories: undefined });
75
75
  const file = await db.query.projectFile.findFirst({ where: eq(schema.projectFile.id, id) });
76
76
  if (!file) {
77
- throw new ServiceError("not_found", "The document went while it was read.");
77
+ throw new ServiceError("not_found", "The document was removed while it was being read.");
78
78
  }
79
79
  const path = (await filePaths(input.space)).get(id) ?? "";
80
80
  const kept = (await db.query.spacePage.findFirst({
@@ -151,8 +151,8 @@ async function requireRoom(spaceId) {
151
151
  const limit = await projectLimit();
152
152
  if (others.length >= limit) {
153
153
  throw new ServiceError("refused", limit === 1
154
- ? bilingual("Dieses Konto hat hier schon ein Projekt.", "This account already has a project here.")
155
- : bilingual(`Dieses Konto hat hier schon ${limit} Projekte.`, `This account already has ${limit} projects here.`), {
154
+ ? bilingual("Dieses Konto hat hier schon einen Space.", "This account already has a space here.")
155
+ : bilingual(`Dieses Konto hat hier schon ${limit} Spaces.`, `This account already has ${limit} spaces here.`), {
156
156
  reason: "space_limit",
157
157
  limit,
158
158
  spaces: others.filter((p) => p.origin === "local").map((p) => ({ id: p.id, name: p.name })),
@@ -166,7 +166,7 @@ async function requireWizardRoom(spaceId) {
166
166
  .from(schema.wizard)
167
167
  .where(eq(schema.wizard.projectId, spaceId));
168
168
  if ((row?.n ?? 0) >= env.limits.syncedWizards) {
169
- throw new ServiceError("refused", bilingual(`Hier haben höchstens ${env.limits.syncedWizards} veröffentlichte Wizards Platz.`, `There is room for at most ${env.limits.syncedWizards} published wizards here.`), { reason: "wizard_limit", limit: env.limits.syncedWizards });
169
+ throw new ServiceError("refused", bilingual(`Hier haben höchstens ${env.limits.syncedWizards} veröffentlichte Wizards Platz.`, `There’s room for at most ${env.limits.syncedWizards} published wizards here.`), { reason: "wizard_limit", limit: env.limits.syncedWizards });
170
170
  }
171
171
  }
172
172
  const hashOf = (data) => createHash("sha256").update(data).digest("hex");
@@ -200,7 +200,7 @@ async function syncLogo(projectId, logo) {
200
200
  async function syncSpace(spaceId, space) {
201
201
  const existing = await syncedProject(spaceId);
202
202
  if (existing && existing.origin !== "local") {
203
- throw new ServiceError("refused", bilingual("Ein Projekt mit dieser Kennung wurde hier angelegt.", "A project with this id was created here."), {
203
+ throw new ServiceError("refused", bilingual("Einen Space mit dieser Kennung gibt es hier schon, und er kommt nicht von deinem Computer.", "A space with this ID already exists here and didn’t come from your computer."), {
204
204
  reason: "id_taken",
205
205
  });
206
206
  }
@@ -251,7 +251,7 @@ export async function syncWizard(rawSpaceId, rawWizardId, input) {
251
251
  where: and(eq(schema.wizard.id, wizardId), eq(schema.wizard.tenantId, currentTenant())),
252
252
  });
253
253
  if (existing && existing.projectId !== spaceId) {
254
- throw new ServiceError("refused", bilingual("Ein Wizard mit dieser Kennung gehört hier woanders hin.", "A wizard with this id belongs elsewhere here."), {
254
+ throw new ServiceError("refused", bilingual("Ein Wizard mit dieser Kennung gehört hier schon zu einem anderen Space.", "A wizard with this ID already belongs to another space here."), {
255
255
  reason: "id_taken",
256
256
  });
257
257
  }
@@ -507,7 +507,7 @@ export async function syncSpaceData(rawSpaceId, input) {
507
507
  : [],
508
508
  ]);
509
509
  if ([...tablesHere, ...pagesHere].some((x) => x.projectId !== spaceId || x.wizardId !== null)) {
510
- throw new ServiceError("refused", bilingual("Eine Kennung ist hier schon vergeben.", "An id is already taken here."), {
510
+ throw new ServiceError("refused", bilingual("Diese Kennung ist hier schon vergeben.", "This ID is already in use here."), {
511
511
  reason: "id_taken",
512
512
  });
513
513
  }
@@ -585,7 +585,7 @@ async function syncCategories(spaceId, categories, assignments) {
585
585
  })
586
586
  : [];
587
587
  if (taken.some((c) => c.projectId !== spaceId)) {
588
- throw new ServiceError("refused", bilingual("Eine Kennung ist hier schon vergeben.", "An id is already taken here."), {
588
+ throw new ServiceError("refused", bilingual("Diese Kennung ist hier schon vergeben.", "This ID is already in use here."), {
589
589
  reason: "id_taken",
590
590
  });
591
591
  }