wizards 0.5.6 → 0.5.8

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 (113) hide show
  1. package/apps/runtime/dist/agents/architect.js +13 -26
  2. package/apps/runtime/dist/agents/studio-assistant.js +19 -22
  3. package/apps/runtime/dist/agents/subscription.js +10 -9
  4. package/apps/runtime/dist/app.js +9 -7
  5. package/apps/runtime/dist/assistant/knows.js +10 -9
  6. package/apps/runtime/dist/auth/index.js +7 -0
  7. package/apps/runtime/dist/connectors/external.js +3 -3
  8. package/apps/runtime/dist/connectors/index.js +23 -23
  9. package/apps/runtime/dist/connectors/via-account.js +6 -6
  10. package/apps/runtime/dist/documents/parse.js +3 -3
  11. package/apps/runtime/dist/engine/input.js +11 -11
  12. package/apps/runtime/dist/engine/requirements.js +7 -8
  13. package/apps/runtime/dist/engine/runner.js +10 -14
  14. package/apps/runtime/dist/engine/steps.js +6 -6
  15. package/apps/runtime/dist/engine/surface.js +2 -2
  16. package/apps/runtime/dist/film/step.js +5 -5
  17. package/apps/runtime/dist/harness/apple.js +2 -1
  18. package/apps/runtime/dist/harness/claude.js +2 -2
  19. package/apps/runtime/dist/harness/codex.js +2 -2
  20. package/apps/runtime/dist/harness/model.js +4 -5
  21. package/apps/runtime/dist/harness/terminal.js +4 -4
  22. package/apps/runtime/dist/media/conversation.js +18 -35
  23. package/apps/runtime/dist/media/transcribe.js +2 -1
  24. package/apps/runtime/dist/model-errors.js +1 -1
  25. package/apps/runtime/dist/models.js +32 -40
  26. package/apps/runtime/dist/plugins/runs.js +3 -5
  27. package/apps/runtime/dist/routes/api.js +9 -9
  28. package/apps/runtime/dist/routes/lang.js +14 -9
  29. package/apps/runtime/dist/routes/marketplace.js +6 -1
  30. package/apps/runtime/dist/routes/runs.js +5 -5
  31. package/apps/runtime/dist/routes/studio.js +38 -37
  32. package/apps/runtime/dist/services/access.js +4 -4
  33. package/apps/runtime/dist/services/architect.js +3 -12
  34. package/apps/runtime/dist/services/assistant-chats.js +2 -2
  35. package/apps/runtime/dist/services/brand.js +3 -3
  36. package/apps/runtime/dist/services/cloud.js +5 -5
  37. package/apps/runtime/dist/services/errors.js +2 -2
  38. package/apps/runtime/dist/services/knowledge-model.js +3 -3
  39. package/apps/runtime/dist/services/marketplace.js +18 -8
  40. package/apps/runtime/dist/services/package-file.js +9 -11
  41. package/apps/runtime/dist/services/package.js +8 -9
  42. package/apps/runtime/dist/services/profile.js +2 -1
  43. package/apps/runtime/dist/services/project-files.js +13 -13
  44. package/apps/runtime/dist/services/projects.js +7 -11
  45. package/apps/runtime/dist/services/runners.js +4 -4
  46. package/apps/runtime/dist/services/runs.js +7 -10
  47. package/apps/runtime/dist/services/shares.js +2 -2
  48. package/apps/runtime/dist/services/site.js +8 -11
  49. package/apps/runtime/dist/services/space-categories.js +7 -7
  50. package/apps/runtime/dist/services/space-data.js +20 -20
  51. package/apps/runtime/dist/services/spaces.js +7 -9
  52. package/apps/runtime/dist/services/wizards.js +11 -13
  53. package/apps/runtime/dist/store/index.js +2 -2
  54. package/apps/runtime/dist/texts.js +195 -0
  55. package/apps/runtime/dist/tools/plugin.js +7 -3
  56. package/apps/runtime/dist/widgets/step.js +2 -2
  57. package/apps/web/dist/assets/AboutDialog-BcTtA42P.js +1 -0
  58. package/apps/web/dist/assets/{ChangelogDialog-sdSC9eZj.js → ChangelogDialog-DMOWV_Iu.js} +1 -1
  59. package/apps/web/dist/assets/ChatView-CrnrYHjp.js +10 -0
  60. package/apps/web/dist/assets/EditorPage-DpEw3eOu.js +3 -0
  61. package/apps/web/dist/assets/{FlowPreview-B4-ipMdE.js → FlowPreview-CzmH4JUQ.js} +1 -1
  62. package/apps/web/dist/assets/{Harness-DwI4wFqj.js → Harness-DipH0Vec.js} +2 -2
  63. package/apps/web/dist/assets/{NewWizardChannels-DkI648G-.js → NewWizardChannels-DShu0PP6.js} +1 -1
  64. package/apps/web/dist/assets/NewWizardPage-HSa6PNJs.js +7 -0
  65. package/apps/web/dist/assets/OnboardingPage-CPADMs4c.js +1 -0
  66. package/apps/web/dist/assets/ProjectPage-DMPcrhgt.js +1 -0
  67. package/apps/web/dist/assets/PublicRunner-DZ5sWmtg.js +1 -0
  68. package/apps/web/dist/assets/{ReadOnly-Jvzgyelg.js → ReadOnly-B9xaq_M3.js} +1 -1
  69. package/apps/web/dist/assets/RunDrawer-9nMGhbSm.js +4 -0
  70. package/apps/web/dist/assets/{RunnerView-DN4sMYS6.js → RunnerView-NSSP3tc6.js} +1 -1
  71. package/apps/web/dist/assets/SettingsPage-Dy9MbwQh.js +1 -0
  72. package/apps/web/dist/assets/SetupPage-kemgI7v5.js +1 -0
  73. package/apps/web/dist/assets/{SharePage-CmowpiJG.js → SharePage-CQfIdcwy.js} +1 -1
  74. package/apps/web/dist/assets/{changelog-Cz8fZ-jG.js → changelog-BW40vzI6.js} +1 -1
  75. package/apps/web/dist/assets/{esm-J06O1nN_.js → esm-BF1nonbH.js} +1 -1
  76. package/apps/web/dist/assets/index-CnJ4Yzkf.css +1 -0
  77. package/apps/web/dist/assets/index-DQ9qAB4v.js +14 -0
  78. package/apps/web/dist/assets/{lock-ChDlFFHO.js → lock-DVvM6IsA.js} +1 -1
  79. package/apps/web/dist/assets/{phone-Cr7jz_kf.js → phone-3fG1okFs.js} +1 -1
  80. package/apps/web/dist/assets/{settings-menu-BAwHMXCR.js → settings-menu-D9fbi1Z0.js} +1 -1
  81. package/apps/web/dist/assets/ui-EuR3jezP.js +134 -0
  82. package/apps/web/dist/assets/{upload-DcAIMe8d.js → upload-BsMUhEfX.js} +1 -1
  83. package/apps/web/dist/index.html +3 -3
  84. package/apps/web/dist/mcp/flow.html +78 -78
  85. package/node_modules/@engenty-wizards/shared/dist/definition.d.ts +26 -9
  86. package/node_modules/@engenty-wizards/shared/dist/definition.js +125 -3
  87. package/node_modules/@engenty-wizards/shared/dist/marketplace-entry.d.ts +1 -1
  88. package/node_modules/@engenty-wizards/shared/dist/marketplace-entry.js +9 -8
  89. package/node_modules/@engenty-wizards/shared/dist/marketplace-search.js +6 -6
  90. package/node_modules/@engenty-wizards/shared/dist/marketplace.d.ts +78 -205
  91. package/node_modules/@engenty-wizards/shared/dist/marketplace.js +167 -68
  92. package/node_modules/@engenty-wizards/shared/dist/projects.d.ts +3 -6
  93. package/node_modules/@engenty-wizards/shared/dist/projects.js +9 -9
  94. package/node_modules/@engenty-wizards/shared/dist/providers.d.ts +33 -7
  95. package/node_modules/@engenty-wizards/shared/dist/providers.js +88 -37
  96. package/node_modules/@engenty-wizards/shared/dist/run.d.ts +298 -365
  97. package/node_modules/@engenty-wizards/shared/dist/run.js +582 -240
  98. package/node_modules/@engenty-wizards/shared/dist/texts.d.ts +36 -0
  99. package/node_modules/@engenty-wizards/shared/dist/texts.js +46 -0
  100. package/package.json +1 -1
  101. package/apps/web/dist/assets/AboutDialog-Ck6_TSVJ.js +0 -1
  102. package/apps/web/dist/assets/ChatView-S1H9Pcih.js +0 -10
  103. package/apps/web/dist/assets/EditorPage-BSfJXx4W.js +0 -3
  104. package/apps/web/dist/assets/NewWizardPage-dumNiSqM.js +0 -7
  105. package/apps/web/dist/assets/OnboardingPage-BAfHLLNT.js +0 -1
  106. package/apps/web/dist/assets/ProjectPage-fneLQr6R.js +0 -1
  107. package/apps/web/dist/assets/PublicRunner-CuxDAXK7.js +0 -1
  108. package/apps/web/dist/assets/RunDrawer-C_T1I77g.js +0 -4
  109. package/apps/web/dist/assets/SettingsPage-DjFvKMRz.js +0 -1
  110. package/apps/web/dist/assets/SetupPage-CzYwVUvh.js +0 -1
  111. package/apps/web/dist/assets/index-CZFfDGHy.css +0 -1
  112. package/apps/web/dist/assets/index-DZTDv2pA.js +0 -14
  113. package/apps/web/dist/assets/ui-C5_NcAmG.js +0 -134
@@ -8,7 +8,7 @@ import { createOpenAI } from "@ai-sdk/openai";
8
8
  import { createReplicate } from "@ai-sdk/replicate";
9
9
  import { MODEL_CLASSES, TEXT_CLASSES, } from "@engenty-wizards/shared/definition";
10
10
  import { creditsModelOf, isCreditsRef, PROVIDER_IDS, providerInfo, providerOfRef, } from "@engenty-wizards/shared/providers";
11
- import { bilingual, saidText } from "@engenty-wizards/shared/run";
11
+ import { note, saidText } from "@engenty-wizards/shared/run";
12
12
  import { generateSpeech, generateText, } from "ai";
13
13
  import { accountToken, linkedAccount } from "./auth/account.js";
14
14
  import { DeviceModel, deviceThinks } from "./engine/device.js";
@@ -22,6 +22,7 @@ import { seal, unseal } from "./secrets/crypto.js";
22
22
  import { vaultDelete, vaultGet, vaultSet } from "./secrets/vault.js";
23
23
  import { readSetting, writeSetting } from "./settings.js";
24
24
  import { currentTenant, currentTenantOrNull } from "./tenants/tenant.js";
25
+ import { serverText } from "./texts.js";
25
26
  export { isHarnessVendor } from "./harness/index.js";
26
27
  /**
27
28
  * A step names a model class, never a model. Where the class runs is decided here, in this order:
@@ -234,27 +235,26 @@ function ownRef(cfg, cls) {
234
235
  }
235
236
  /** What a class makes, to say that nothing makes it. */
236
237
  const MAKES = {
237
- classifier: { de: "Text", en: "text" },
238
- standard: { de: "Text", en: "text" },
239
- high: { de: "Text", en: "text" },
240
- highest: { de: "Text", en: "text" },
241
- image: { de: "Bilder", en: "images" },
242
- video: { de: "Videos", en: "videos" },
243
- speech: { de: "Sprachausgabe", en: "speech" },
244
- audio: { de: "Sprachnotizen", en: "voice notes" },
238
+ classifier: "makesText",
239
+ standard: "makesText",
240
+ high: "makesText",
241
+ highest: "makesText",
242
+ image: "makesImages",
243
+ video: "makesVideos",
244
+ speech: "makesSpeech",
245
+ audio: "makesVoiceNotes",
245
246
  };
246
247
  /** Why a class has no way: the provider picked for it lacks its key, or nothing is set up. */
247
248
  function missingText(cfg, cls) {
248
249
  const bound = cfg.settings.bindings[cls];
249
250
  if (isCreditsRef(bound)) {
250
- return bilingual("Dafür sind Credits gewählt, aber du bist mit keinem Konto angemeldet.", "This is set to run on credits, but you’re not signed in to an account.");
251
+ return note("creditsNotSignedIn");
251
252
  }
252
- const makes = MAKES[cls];
253
253
  const provider = bound ? providerInfo(providerOfRef(bound) ?? "") : null;
254
254
  if (provider) {
255
- return bilingual(`Für ${makes.de} ist ${provider.name} gewählt, aber kein API-Key hinterlegt.`, `${provider.name} is chosen for ${makes.en}, but no API key is set.`);
255
+ return note("providerKeyMissing", { provider: provider.name, makes: note(MAKES[cls]) });
256
256
  }
257
- return bilingual(`Für ${makes.de} ist kein Modell eingerichtet.`, `No model is set up for ${makes.en}.`);
257
+ return note("noModelFor", { makes: note(MAKES[cls]) });
258
258
  }
259
259
  /**
260
260
  * The way to the credits. On a Manage-App's runtime always; alone, through the linked account —
@@ -274,7 +274,7 @@ async function creditAccess(cfg, chosen) {
274
274
  const token = await accountToken();
275
275
  if (!token) {
276
276
  if (chosen) {
277
- throw new ModelUnavailableError(bilingual("Das Konto ist nicht mehr angemeldet. Melde dich unter Einstellungen → Konto neu an.", "The account is no longer signed in. Sign in again under Settings → Account."));
277
+ throw new ModelUnavailableError(note("accountNoLongerSigned"));
278
278
  }
279
279
  return null;
280
280
  }
@@ -301,7 +301,7 @@ async function routeOf(cls) {
301
301
  function need(cfg, name) {
302
302
  const key = cfg.keys[name];
303
303
  if (!key) {
304
- throw new ModelUnavailableError(bilingual(`Für ${providerInfo(name)?.name ?? name} ist kein API-Key hinterlegt.`, `No API key is set for ${providerInfo(name)?.name ?? name}.`));
304
+ throw new ModelUnavailableError(note("noApiKeyFor", { provider: providerInfo(name)?.name ?? name }));
305
305
  }
306
306
  return key;
307
307
  }
@@ -412,7 +412,7 @@ async function ownLanguageModel(cfg, ref, meta) {
412
412
  };
413
413
  }
414
414
  }
415
- throw new ModelUnavailableError(bilingual(`Das Modell „${ref}“ lässt sich noch nicht nutzen. Hinterleg unter Einstellungen → Modelle einen API-Key oder melde dich mit einem Konto an.`, `The model “${ref}” can’t be used yet. Add an API key under Settings → Models, or sign in to an account.`));
415
+ throw new ModelUnavailableError(note("modelNotUsedYet", { ref }));
416
416
  }
417
417
  /**
418
418
  * The language model a text class runs on. A run watched from a device that offers the class
@@ -492,7 +492,7 @@ export async function imageModel(meta = {}) {
492
492
  return own(createOpenAI({ apiKey: cfg.keys.openai }).image(aliasOf(id)));
493
493
  }
494
494
  }
495
- throw new ModelUnavailableError(bilingual("Für Bilder ist kein Modell eingerichtet.", "No model is set up for images."));
495
+ throw new ModelUnavailableError(note("noModelForImages"));
496
496
  }
497
497
  export async function videoModel(meta = {}) {
498
498
  const route = await routeOf("video");
@@ -521,7 +521,7 @@ export async function videoModel(meta = {}) {
521
521
  return own(createGateway({ apiKey: cfg.keys.gateway }).videoModel(id), true);
522
522
  }
523
523
  }
524
- throw new ModelUnavailableError(bilingual("Für Videos ist kein Modell eingerichtet.", "No model is set up for videos."));
524
+ throw new ModelUnavailableError(note("noModelForVideos"));
525
525
  }
526
526
  /** The model that reads a text aloud. */
527
527
  export async function speechModel(meta = {}) {
@@ -529,7 +529,7 @@ export async function speechModel(meta = {}) {
529
529
  if (route.kind === "credits") {
530
530
  const resolved = await viaCredits(route, "speech", meta, (c, id) => c.speechModel(id));
531
531
  if (!resolved.ref) {
532
- throw new ModelUnavailableError(bilingual("Für Sprachausgabe ist kein Modell eingerichtet.", "No model is set up for speech."));
532
+ throw new ModelUnavailableError(note("noModelForSpeech"));
533
533
  }
534
534
  return resolved;
535
535
  }
@@ -560,7 +560,7 @@ export async function speechModel(meta = {}) {
560
560
  return own(createOpenAI({ apiKey: cfg.keys.openai }).speech(aliasOf(id)));
561
561
  }
562
562
  }
563
- throw new ModelUnavailableError(bilingual("Für Sprachausgabe ist kein Modell eingerichtet.", "No model is set up for speech."));
563
+ throw new ModelUnavailableError(note("noModelForSpeech"));
564
564
  }
565
565
  /**
566
566
  * What turns a voice note into text: a transcription model (ElevenLabs Scribe, Whisper), or a
@@ -711,7 +711,10 @@ export function freeTierRefusal(err) {
711
711
  }
712
712
  /** What the person reads when Vercel's free tier leaves a model out, and how to fix it. */
713
713
  export function freeTierText(model) {
714
- return bilingual(`Dein AI-Gateway-Key nutzt bei Vercel die kostenlose Stufe, und die schließt ${model ? `„${model}“` : "dieses Modell"} aus. Lade bei Vercel Guthaben auf (${GATEWAY_TOP_UP}) oder wähle unter Einstellungen → Modelle ein anderes Modell.`, `Your AI Gateway key is on Vercel’s free tier, which doesn’t include ${model ? `“${model}”` : "this model"}. Top up at Vercel (${GATEWAY_TOP_UP}) or choose another model under Settings → Models.`);
714
+ return note("freeTier", {
715
+ model: model ? note("quoted", { text: model }) : note("thisModel"),
716
+ url: GATEWAY_TOP_UP,
717
+ });
715
718
  }
716
719
  /** What a key was answered for a model: refusals are asked again soon, as a top-up lifts them. */
717
720
  const gatewayProbes = new Map();
@@ -765,7 +768,7 @@ const CONVERSATION_MODEL = env.conversationModel || "gpt-realtime";
765
768
  export async function conversationAccess() {
766
769
  const cfg = await config();
767
770
  if (!cfg.keys.openai) {
768
- throw new ModelUnavailableError(bilingual("Für Sprachgespräche ist kein OpenAI-API-Key hinterlegt (Einstellungen → Modelle).", "No OpenAI API key is set for voice conversations (Settings → Models)."));
771
+ throw new ModelUnavailableError(note("noOpenaiApiKey"));
769
772
  }
770
773
  return { apiKey: cfg.keys.openai, model: CONVERSATION_MODEL };
771
774
  }
@@ -802,12 +805,10 @@ export async function classProblem(cls) {
802
805
  const named = creditsModelOf((await config()).settings.bindings[cls]);
803
806
  if (named) {
804
807
  return catalog?.models && !catalog.models.some((m) => m.id === named)
805
- ? bilingual("Dieses Modell gibt es mit deinem Konto nicht.", "This model isn’t available with your account.")
808
+ ? note("modelNotAvailableAccount")
806
809
  : null;
807
810
  }
808
- return catalog?.classes && !catalog.classes[cls]
809
- ? bilingual("Dafür ist bei deinem Konto kein Modell eingerichtet.", "Your account has no model set up for this.")
810
- : null;
811
+ return catalog?.classes && !catalog.classes[cls] ? note("accountNoModelSet") : null;
811
812
  }
812
813
  const client = harness(resolved.vendor);
813
814
  if (client && (await detectHarness(client.id))?.auth === "none") {
@@ -833,7 +834,7 @@ export async function filmClient(cls) {
833
834
  const client = managed ? null : harness(cfg.settings.source);
834
835
  if (!client || !FILM_CLIENTS.has(client.id)) {
835
836
  return {
836
- problem: bilingual("Filme macht Claude Code auf diesem Computer mit deinem Abo. Richte es unter Einstellungen → Modelle als KI-Abo ein.", "Claude Code makes films on this computer, using your subscription. Set it up as an AI subscription under Settings → Models."),
837
+ problem: note("claudeCodeMakesFilms"),
837
838
  };
838
839
  }
839
840
  const status = await detectHarness(client.id);
@@ -918,9 +919,7 @@ async function appleProblem(cls) {
918
919
  if (!status.supported || status.reason === "missing") {
919
920
  return appleUnavailableNote(status);
920
921
  }
921
- return (await hasFfmpeg())
922
- ? null
923
- : bilingual("Aufnahmen aus Chrome oder Firefox (WebM, Ogg) schreibt dieser Mac erst mit ffmpeg auf. Safari und die App brauchen es nicht. Installiere ffmpeg, z. B. mit „brew install ffmpeg“.", "This Mac needs ffmpeg to transcribe recordings from Chrome or Firefox (WebM, Ogg). Safari and the app don’t. Install ffmpeg, e.g. with “brew install ffmpeg”.");
922
+ return (await hasFfmpeg()) ? null : note("macNeedsFfmpegTranscribe");
924
923
  }
925
924
  /** At start the ways are asked once, so a model the key cannot reach shows before a run meets it. */
926
925
  export async function checkModelsAtStart() {
@@ -958,7 +957,6 @@ export async function modelSettings(lang = "en") {
958
957
  // --- Checking a key ------------------------------------------------------------------
959
958
  /** A cheap authenticated call: whether the provider takes the key. Nothing is generated. */
960
959
  export async function checkKey(name, key, lang = "en") {
961
- const de = lang === "de";
962
960
  const requests = {
963
961
  openai: ["https://api.openai.com/v1/models", { authorization: `Bearer ${key}` }],
964
962
  anthropic: [
@@ -991,20 +989,14 @@ export async function checkKey(name, key, lang = "en") {
991
989
  return {
992
990
  ok: false,
993
991
  message: res.status === 401
994
- ? de
995
- ? "Der Anbieter kennt diesen API-Key nicht."
996
- : "The provider doesn’t recognise this key."
997
- : de
998
- ? `Der Anbieter antwortet mit Fehler ${res.status}.`
999
- : `The provider answered with error ${res.status}.`,
992
+ ? serverText(lang, "keyUnknown")
993
+ : serverText(lang, "providerError", { status: String(res.status) }),
1000
994
  };
1001
995
  }
1002
996
  catch {
1003
997
  return {
1004
998
  ok: false,
1005
- message: de
1006
- ? "Der Anbieter ist gerade nicht erreichbar."
1007
- : "The provider cannot be reached right now.",
999
+ message: serverText(lang, "providerUnreachable"),
1008
1000
  };
1009
1001
  }
1010
1002
  }
@@ -1,5 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
- import { wizardLang } from "@engenty-wizards/shared/definition";
2
+ import { LANGS, wizardLang } from "@engenty-wizards/shared/definition";
3
3
  import { noteText } from "@engenty-wizards/shared/run";
4
4
  import { and, asc, eq, inArray, isNotNull } from "drizzle-orm";
5
5
  import { db, schema, withTenant } from "../db/client.js";
@@ -67,9 +67,7 @@ export function runsApiFor(pluginId) {
67
67
  throw new ServiceError("refused", errorText(err, wizardLang(version.definition)));
68
68
  }
69
69
  if (err instanceof NoCreditsError) {
70
- throw new ServiceError("no_credits", wizardLang(version.definition) === "de"
71
- ? "Dieser Wizard hat gerade kein Guthaben mehr. Versuch es später noch einmal."
72
- : "This wizard has run out of credits for now. Try again later.");
70
+ throw new ServiceError("no_credits", noteText({ code: "wizardRunOutCredits" }, wizardLang(version.definition)));
73
71
  }
74
72
  throw err;
75
73
  });
@@ -157,7 +155,7 @@ export function runsApiFor(pluginId) {
157
155
  note: event.note
158
156
  ? {
159
157
  code: event.note.code,
160
- text: { de: noteText(event.note, "de"), en: noteText(event.note, "en") },
158
+ text: Object.fromEntries(LANGS.map((lang) => [lang, noteText(event.note, lang)])),
161
159
  }
162
160
  : null,
163
161
  }
@@ -1,4 +1,4 @@
1
- import { bilingual } from "@engenty-wizards/shared/run";
1
+ import { note } from "@engenty-wizards/shared/run";
2
2
  import { PACKAGE_MIME } from "@engenty-wizards/shared/wizard-package";
3
3
  import { Hono } from "hono";
4
4
  import { bodyLimit } from "hono/body-limit";
@@ -20,7 +20,7 @@ import { siteJob, siteSchema, startSiteJob } from "../services/site.js";
20
20
  import { checkOnServer, checkSchema, listSyncedSpaces, patchSyncedWizard, removeSyncedSpace, removeSyncedWizard, rotateSyncedLink, syncDataSchema, syncSettingsSchema, syncSpaceData, syncWizard, syncWizardSchema, } from "../services/spaces.js";
21
21
  import { createWizard, ownedWizard, publishWizard, writeDraft } from "../services/wizards.js";
22
22
  import { codeOf, tenantStatus } from "../tenants/control.js";
23
- import { inLang, notFoundAnswer } from "./lang.js";
23
+ import { notFoundAnswer, say } from "./lang.js";
24
24
  import { dispatchAs } from "./plugins.js";
25
25
  const importSchema = z.object({
26
26
  /** An earlier import's wizard: it is updated instead of a new one being made. */
@@ -61,7 +61,7 @@ async function caller(c, scopes) {
61
61
  }
62
62
  if ((await tenantStatus(who.tenantId)) === "suspended") {
63
63
  return c.json({
64
- error: inLang(c, "Dieses Konto ist gesperrt.", "This account is suspended."),
64
+ error: say(c, "accountSuspended"),
65
65
  code: "tenant_suspended",
66
66
  }, 403);
67
67
  }
@@ -72,7 +72,7 @@ function withinSyncLimit(c, who) {
72
72
  return allowed(`sync:${who.tenantId}`, 240, HOUR)
73
73
  ? null
74
74
  : c.json({
75
- error: inLang(c, "Zu viele Übertragungen. Versuch es später noch einmal.", "Too many transfers. Try again later."),
75
+ error: say(c, "tooManyTransfersTry"),
76
76
  code: "rate_limited",
77
77
  }, 429);
78
78
  }
@@ -90,7 +90,7 @@ async function pluginCall(c) {
90
90
  if (!(await pluginIdsOf(who.tenantId)).has(id)) {
91
91
  return loadedPlugin(id) || PRO_PLUGINS.includes(id)
92
92
  ? c.json({
93
- error: inLang(c, "Dieses Plugin gehört nicht zu deinem Paket. Mit Pro ist es dabei.", "This plugin is not part of your plan. It comes with Pro."),
93
+ error: say(c, "pluginNotPartPlan"),
94
94
  code: "plugin",
95
95
  plugin: id,
96
96
  }, 403)
@@ -180,7 +180,7 @@ export const apiRoutes = new Hono()
180
180
  });
181
181
  if (!found) {
182
182
  return c.json({
183
- error: inLang(c, "Der Wizard ist noch nicht veröffentlicht.", "The wizard is not published yet."),
183
+ error: say(c, "wizardNotPublishedYet"),
184
184
  code: "not_found",
185
185
  }, 404);
186
186
  }
@@ -290,7 +290,7 @@ export const apiRoutes = new Hono()
290
290
  }
291
291
  if (!allowed(`site:${who.tenantId}`, 20, HOUR)) {
292
292
  return c.json({
293
- error: inLang(c, "Zu oft hintereinander. Versuch es später noch einmal.", "Too many times in a row. Try again later."),
293
+ error: say(c, "tooManyTimesRow"),
294
294
  code: "rate_limited",
295
295
  }, 429);
296
296
  }
@@ -396,7 +396,7 @@ export const apiRoutes = new Hono()
396
396
  }
397
397
  if ((await tenantStatus(who.tenantId)) === "suspended") {
398
398
  return c.json({
399
- error: inLang(c, "Dieses Konto ist gesperrt.", "This account is suspended."),
399
+ error: say(c, "accountSuspended"),
400
400
  code: "tenant_suspended",
401
401
  }, 403);
402
402
  }
@@ -427,7 +427,7 @@ export const apiRoutes = new Hono()
427
427
  return c.json({ wizardId, revision: written.revision, issues: written.issues });
428
428
  }
429
429
  if (written.issues.length) {
430
- throw new ServiceError("has_issues", bilingual("Der Wizard hat noch Fehler.", "The wizard still has errors."), {
430
+ throw new ServiceError("has_issues", note("wizardStillErrors"), {
431
431
  issues: written.issues,
432
432
  wizardId,
433
433
  });
@@ -1,20 +1,25 @@
1
+ import { localLang } from "@engenty-wizards/shared/definition";
2
+ import { serverText } from "../texts.js";
1
3
  /**
2
- * The language a visitor's request asks for: what the runner around it speaks, the browser's
3
- * first language (apps/web: `navigator.language`) — German for "de…", else English; English
4
- * without one, as the runner starts.
4
+ * The language a visitor's request asks for, as the studio starts in it (`localLang`): the
5
+ * first of the browser's languages engenty speaks other than English, else English. The
6
+ * browser's `Accept-Language` names them in its order; the server has no time zone to go by.
5
7
  */
6
8
  export function requestLang(c) {
7
- const asked = c.req.header("accept-language")?.trim().toLowerCase();
8
- return asked?.startsWith("de") ? "de" : "en";
9
+ const asked = (c.req.header("accept-language") ?? "")
10
+ .split(",")
11
+ .map((part) => part.split(";")[0]?.trim() ?? "")
12
+ .filter(Boolean);
13
+ return localLang(asked) ?? "en";
9
14
  }
10
- /** A line the person reads, in the language their request asks for. */
11
- export function inLang(c, de, en) {
12
- return requestLang(c) === "de" ? de : en;
15
+ /** A line of the server's table the person reads, in the language their request asks for. */
16
+ export function say(c, key, params) {
17
+ return serverText(requestLang(c), key, params);
13
18
  }
14
19
  /** A 404 the person may read: a sentence in their language, the machine code beside it. */
15
20
  export function notFoundAnswer(c) {
16
21
  return {
17
- error: inLang(c, "Das gibt es nicht oder nicht mehr.", "This doesn’t exist or was deleted."),
22
+ error: say(c, "notFound"),
18
23
  code: "not_found",
19
24
  };
20
25
  }
@@ -1,7 +1,7 @@
1
1
  import { CAPABILITY_IDS, INDUSTRY_IDS, ITEM_FORMAT_IDS, MARKETPLACE_PLAN_IDS, USE_CASE_IDS, } from "@engenty-wizards/shared/marketplace";
2
2
  import { Hono } from "hono";
3
3
  import { z } from "zod";
4
- import { asLang, marketplaceDetail, searchMarketplace, starEntry, } from "../services/marketplace.js";
4
+ import { asLang, marketplaceDetail, searchMarketplace, searchMarketplaceAsGuest, starEntry, } from "../services/marketplace.js";
5
5
  import { notFoundAnswer } from "./lang.js";
6
6
  const searchSchema = z.object({
7
7
  q: z.string().trim().max(200).default(""),
@@ -36,3 +36,8 @@ export const marketplaceStudio = new Hono()
36
36
  await starEntry(c.get("user").tenantId, c.req.param("id"), false);
37
37
  return c.json({ ok: true });
38
38
  });
39
+ /** The gallery for someone not signed in (the studio's start page for guests): only the search. */
40
+ export const marketplaceGuest = new Hono().get("/", async (c) => {
41
+ const { lang, starred: _starred, ...search } = searchSchema.parse(c.req.query());
42
+ return c.json(await searchMarketplaceAsGuest({ ...search, lang: asLang(lang) }));
43
+ });
@@ -1,4 +1,4 @@
1
- import { FORMATS, LIST_FORMATS, TEXT_CLASSES } from "@engenty-wizards/shared/definition";
1
+ import { FORMATS, LANGS, LIST_FORMATS, TEXT_CLASSES } from "@engenty-wizards/shared/definition";
2
2
  import { TableColumnValueError } from "@engenty-wizards/shared/engenty/data-tables";
3
3
  import { noteText } from "@engenty-wizards/shared/run";
4
4
  import { getConnInfo } from "@hono/node-server/conninfo";
@@ -39,7 +39,7 @@ import { isIconFile, wizardAvatarSvg, wizardIcon } from "../services/wizard-icon
39
39
  import { clearStore, deleteRows, listRows, listSecrets, readStoreFile, StoreError, saveRows, scopeOf, storeFiles, updateRow, } from "../store/index.js";
40
40
  import { tenantOfLink, tenantOfRun, tenantStatus, tokenOfCode, visitorOverLimit, } from "../tenants/control.js";
41
41
  import { bareMime, byteRange, inlineSafe, uploadLimit, uploadMime, wizardManifest, } from "./delivery.js";
42
- import { inLang, notFoundAnswer, requestLang } from "./lang.js";
42
+ import { notFoundAnswer, requestLang, say } from "./lang.js";
43
43
  const VISITOR_COOKIE = "wz_vid";
44
44
  /** Kept files a review shows inline. */
45
45
  const INLINE_TYPES = new Set(["application/pdf", "image/png", "image/jpeg", "image/webp"]);
@@ -205,7 +205,7 @@ export const publicRoutes = new Hono()
205
205
  const ip = clientIp(c);
206
206
  if (codeLookupBlocked(ip)) {
207
207
  return c.json({
208
- error: inLang(c, "Zu viele Versuche. Warte kurz und versuch es noch einmal.", "Too many tries. Wait a moment and try again."),
208
+ error: say(c, "tooManyTriesWait"),
209
209
  code: "too_many_tries",
210
210
  }, 429);
211
211
  }
@@ -633,7 +633,7 @@ export const runRoutes = new Hono()
633
633
  .object({
634
634
  lat: z.number().min(-90).max(90),
635
635
  lng: z.number().min(-180).max(180),
636
- language: z.enum(["de", "en"]).default("en"),
636
+ language: z.enum(LANGS).default("en"),
637
637
  })
638
638
  .parse(await c.req.json());
639
639
  return c.json({ label: await reverseGeocode(lat, lng, language) });
@@ -1012,7 +1012,7 @@ export const runRoutes = new Hono()
1012
1012
  .object({
1013
1013
  token: z.string().min(8).max(512),
1014
1014
  platform: z.enum(["ios", "android"]),
1015
- lang: z.enum(["en", "de"]).default("en"),
1015
+ lang: z.enum(LANGS).default("en"),
1016
1016
  })
1017
1017
  .parse(await c.req.json());
1018
1018
  await setPushDevice(run.id, body);