wizards 0.5.11 → 0.5.13

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 (91) hide show
  1. package/apps/runtime/dist/browser/fast/action-space.js +0 -2
  2. package/apps/runtime/dist/cli/mcp.js +2 -15
  3. package/apps/runtime/dist/cli/report.js +2 -15
  4. package/apps/runtime/dist/cli/start.js +28 -14
  5. package/apps/runtime/dist/cli/status.js +1 -1
  6. package/apps/runtime/dist/credits/credits.js +0 -3
  7. package/apps/runtime/dist/db/client.js +0 -7
  8. package/apps/runtime/dist/db/control-schema.js +10 -0
  9. package/apps/runtime/dist/db/migrations/control/0010_tan_snowbird.sql +6 -0
  10. package/apps/runtime/dist/db/migrations/control/meta/0010_snapshot.json +600 -0
  11. package/apps/runtime/dist/db/migrations/control/meta/_journal.json +7 -0
  12. package/apps/runtime/dist/db/migrations/tenant/0016_even_trish_tilby.sql +12 -0
  13. package/apps/runtime/dist/db/migrations/tenant/meta/0016_snapshot.json +3326 -0
  14. package/apps/runtime/dist/db/migrations/tenant/meta/_journal.json +7 -0
  15. package/apps/runtime/dist/db/schema.js +20 -0
  16. package/apps/runtime/dist/docs/user/build-a-wizard.md +1 -1
  17. package/apps/runtime/dist/engine/runner.js +31 -10
  18. package/apps/runtime/dist/engine/steps.js +15 -4
  19. package/apps/runtime/dist/harness/index.js +0 -4
  20. package/apps/runtime/dist/manage.js +7 -7
  21. package/apps/runtime/dist/model-errors.js +20 -7
  22. package/apps/runtime/dist/plugins/pro.js +0 -6
  23. package/apps/runtime/dist/render/convert.js +0 -3
  24. package/apps/runtime/dist/routes/runs.js +170 -23
  25. package/apps/runtime/dist/routes/studio.js +83 -9
  26. package/apps/runtime/dist/services/api.js +8 -6
  27. package/apps/runtime/dist/services/cloud.js +1 -0
  28. package/apps/runtime/dist/services/spaces.js +1 -0
  29. package/apps/runtime/dist/services/translations.js +303 -0
  30. package/apps/runtime/dist/services/wizards.js +3 -1
  31. package/apps/runtime/dist/store/index.js +0 -11
  32. package/apps/runtime/dist/texts.js +47 -39
  33. package/apps/web/dist/assets/{AboutDialog-L1hcshAX.js → AboutDialog-CkE7uZNL.js} +1 -1
  34. package/apps/web/dist/assets/{ChangelogDialog-DZLYVOkl.js → ChangelogDialog-BCS3WIRA.js} +1 -1
  35. package/apps/web/dist/assets/ChatView-DClGzMte.js +10 -0
  36. package/apps/web/dist/assets/EditorPage-Dg27sIZ7.js +3 -0
  37. package/apps/web/dist/assets/{FlowPreview-CTEJi5TQ.js → FlowPreview-D2haAZwo.js} +1 -1
  38. package/apps/web/dist/assets/{Harness-CApAHvJE.js → Harness-CGI6ZMTA.js} +2 -2
  39. package/apps/web/dist/assets/{NewWizardChannels-tst10rkV.js → NewWizardChannels-C4b_phHw.js} +1 -1
  40. package/apps/web/dist/assets/{NewWizardPage-BAKaUY1g.js → NewWizardPage-DhDXrjKX.js} +2 -2
  41. package/apps/web/dist/assets/OnboardingPage-DgySdBgo.js +1 -0
  42. package/apps/web/dist/assets/ProjectPage-dvtJn_3c.js +1 -0
  43. package/apps/web/dist/assets/PublicRunner-lP4sVF1q.js +1 -0
  44. package/apps/web/dist/assets/{ReadOnly-Bb8khbuH.js → ReadOnly-B-4l3bVt.js} +1 -1
  45. package/apps/web/dist/assets/RunDrawer-B2bsB9Z6.js +4 -0
  46. package/apps/web/dist/assets/RunnerView-BZ83HUnO.js +1 -0
  47. package/apps/web/dist/assets/SettingsPage-C0_wAa_j.js +1 -0
  48. package/apps/web/dist/assets/SetupPage-kuvBk0G0.js +1 -0
  49. package/apps/web/dist/assets/{SharePage-Bb-BIvAX.js → SharePage-COLVxXMI.js} +1 -1
  50. package/apps/web/dist/assets/changelog-CIYdRaa5.js +1 -0
  51. package/apps/web/dist/assets/{esm-pbWES-Cu.js → esm-BqbWrbsa.js} +1 -1
  52. package/apps/web/dist/assets/index-Da9Pvzpj.js +14 -0
  53. package/apps/web/dist/assets/index-DaKjB676.css +1 -0
  54. package/apps/web/dist/assets/{lock-C6LaSCq_.js → lock-BkN0Fai6.js} +1 -1
  55. package/apps/web/dist/assets/{phone-BUWpzNWn.js → phone-Dip_wSUZ.js} +1 -1
  56. package/apps/web/dist/assets/{settings-menu-yO8b8LjQ.js → settings-menu-B7eXkvba.js} +1 -1
  57. package/apps/web/dist/assets/ui-B9AOCT0v.js +134 -0
  58. package/apps/web/dist/assets/{upload-D89fyY2-.js → upload-DPQ4pwnl.js} +1 -1
  59. package/apps/web/dist/index.html +3 -3
  60. package/apps/web/dist/mcp/flow.html +54 -54
  61. package/node_modules/@engenty-wizards/plugin-sdk/dist/thread.js +34 -34
  62. package/node_modules/@engenty-wizards/shared/dist/definition.d.ts +19 -6
  63. package/node_modules/@engenty-wizards/shared/dist/definition.js +51 -10
  64. package/node_modules/@engenty-wizards/shared/dist/features.d.ts +0 -1
  65. package/node_modules/@engenty-wizards/shared/dist/features.js +0 -1
  66. package/node_modules/@engenty-wizards/shared/dist/marketplace.d.ts +0 -5
  67. package/node_modules/@engenty-wizards/shared/dist/marketplace.js +0 -5
  68. package/node_modules/@engenty-wizards/shared/dist/providers.d.ts +1 -1
  69. package/node_modules/@engenty-wizards/shared/dist/providers.js +1 -1
  70. package/node_modules/@engenty-wizards/shared/dist/run.d.ts +46 -4
  71. package/node_modules/@engenty-wizards/shared/dist/run.js +24 -9
  72. package/node_modules/@engenty-wizards/shared/dist/runner-texts.d.ts +506 -0
  73. package/node_modules/@engenty-wizards/shared/dist/runner-texts.js +483 -0
  74. package/node_modules/@engenty-wizards/shared/dist/texts.d.ts +11 -2
  75. package/node_modules/@engenty-wizards/shared/dist/texts.js +16 -8
  76. package/node_modules/@engenty-wizards/shared/dist/translation.d.ts +23 -0
  77. package/node_modules/@engenty-wizards/shared/dist/translation.js +154 -0
  78. package/package.json +1 -1
  79. package/apps/web/dist/assets/ChatView-GNHi9ZnZ.js +0 -10
  80. package/apps/web/dist/assets/EditorPage-COJunH8K.js +0 -3
  81. package/apps/web/dist/assets/OnboardingPage-Di8AuZj_.js +0 -1
  82. package/apps/web/dist/assets/ProjectPage-DaerYm8A.js +0 -1
  83. package/apps/web/dist/assets/PublicRunner-Hms0Vi88.js +0 -1
  84. package/apps/web/dist/assets/RunDrawer-DWaC_ryo.js +0 -4
  85. package/apps/web/dist/assets/RunnerView-D5BQ99ah.js +0 -1
  86. package/apps/web/dist/assets/SettingsPage-CQX6fKsa.js +0 -1
  87. package/apps/web/dist/assets/SetupPage-BZkbpN_V.js +0 -1
  88. package/apps/web/dist/assets/changelog-BUHje4CT.js +0 -1
  89. package/apps/web/dist/assets/index-BVWLhiGX.js +0 -14
  90. package/apps/web/dist/assets/index-DmQEcSlY.css +0 -1
  91. package/apps/web/dist/assets/ui-i5saQjmD.js +0 -134
@@ -14,27 +14,27 @@ const texts = {
14
14
  nothing: "Hier ist noch kein Wizard verbunden.",
15
15
  which: "Welcher Wizard soll es sein?",
16
16
  choose: "Auswählen",
17
- over: "Dieser Durchlauf ist beendet. Mit dem Stichwort beginnt ein neuer.",
18
- stopped: "Abgebrochen. Mit dem Stichwort beginnt ein neuer Durchlauf.",
19
- optional: "(freiwillig – „weiter“ überspringt)",
20
- pickOne: "Bitte eine der Möglichkeiten wählen.",
17
+ over: "Dieser Lauf ist beendet. Mit dem Stichwort startest du einen neuen.",
18
+ stopped: "Abgebrochen. Mit dem Stichwort startest du einen neuen Lauf.",
19
+ optional: "(freiwillig – mit „weiter“ überspringst du es)",
20
+ pickOne: "Wähl eine der Möglichkeiten.",
21
21
  picked: "Gewählt: {list}",
22
22
  more: "Noch etwas?",
23
23
  done: "Fertig",
24
24
  yes: "Ja",
25
25
  no: "Nein",
26
- yesOrNo: "Bitte mit Ja oder Nein antworten.",
27
- asText: "Bitte als Text antworten.",
28
- asDate: "Bitte ein Datum, zum Beispiel 24.12.2026.",
29
- sendPhoto: "Bitte ein Foto schicken.",
30
- sendFile: "Bitte eine Datei schicken.",
31
- sendVoice: "Bitte eine Sprachnachricht schicken.",
26
+ yesOrNo: "Antworte mit Ja oder Nein.",
27
+ asText: "Antworte mit einem Text.",
28
+ asDate: "Schick ein Datum, zum Beispiel 24.12.2026.",
29
+ sendPhoto: "Schick ein Foto.",
30
+ sendFile: "Schick eine Datei.",
31
+ sendVoice: "Schick eine Sprachnachricht.",
32
32
  another: "Noch eins",
33
- whereAre: "Bitte den Standort teilen, oder die Adresse schreiben.",
34
- needed: "Dieses Feld braucht es.",
35
- screen: "Dafür braucht es den Bildschirm – danach geht es hier weiter.",
33
+ whereAre: "Teil deinen Standort oder schreib die Adresse.",
34
+ needed: "Dafür brauche ich eine Antwort.",
35
+ screen: "Dieser Teil braucht einen Bildschirm. Danach geht es hier weiter.",
36
36
  open: "Öffnen",
37
- working: "{step} … das dauert einen Moment.",
37
+ working: "{step} – das dauert einen Moment.",
38
38
  reviewAsk: "Passt das so?",
39
39
  reviewOk: "Passt",
40
40
  reviewRedo: "Neue Version",
@@ -42,43 +42,43 @@ const texts = {
42
42
  reviewNote: "Was soll anders sein?",
43
43
  allow: "Erlauben",
44
44
  skip: "Überspringen",
45
- finished: "Fertig! Alles liegt hier:",
46
- failed: "Der Wizard ist stehengeblieben. Hier geht es weiter:",
45
+ finished: "Fertig. Alles liegt hier:",
46
+ failed: "Der Wizard ist stehen geblieben. Hier geht es weiter:",
47
47
  },
48
48
  en: {
49
49
  nothing: "No wizard is connected here yet.",
50
50
  which: "Which wizard?",
51
51
  choose: "Choose",
52
- over: "This run is over. The keyword starts a new one.",
53
- stopped: "Stopped. The keyword starts a new run.",
54
- optional: "(optional – “skip” leaves it out)",
55
- pickOne: "Please pick one of the options.",
56
- picked: "Picked: {list}",
52
+ over: "This run is over. Send the keyword to start a new one.",
53
+ stopped: "Stopped. Send the keyword to start a new run.",
54
+ optional: "(optional – reply “skip” to leave it out)",
55
+ pickOne: "Choose one of the options.",
56
+ picked: "Chosen: {list}",
57
57
  more: "Anything else?",
58
58
  done: "Done",
59
59
  yes: "Yes",
60
60
  no: "No",
61
- yesOrNo: "Please answer yes or no.",
62
- asText: "Please answer as text.",
63
- asDate: "Please send a date, for example 2026-12-24.",
64
- sendPhoto: "Please send a photo.",
65
- sendFile: "Please send a file.",
66
- sendVoice: "Please send a voice message.",
61
+ yesOrNo: "Answer yes or no.",
62
+ asText: "Reply with text.",
63
+ asDate: "Send a date, for example 24/12/2026.",
64
+ sendPhoto: "Send a photo.",
65
+ sendFile: "Send a file.",
66
+ sendVoice: "Send a voice message.",
67
67
  another: "One more",
68
- whereAre: "Please share your location, or write the address.",
69
- needed: "This field is needed.",
70
- screen: "This needs the screen – it goes on here afterwards.",
68
+ whereAre: "Share your location or type the address.",
69
+ needed: "I need an answer for this one.",
70
+ screen: "This part needs a screen. You carry on here afterwards.",
71
71
  open: "Open",
72
- working: "{step} … this takes a moment.",
72
+ working: "{step} – this takes a moment.",
73
73
  reviewAsk: "Is this right?",
74
74
  reviewOk: "Looks good",
75
75
  reviewRedo: "New version",
76
- reviewWhich: "What should be made again?",
76
+ reviewWhich: "What should be redone?",
77
77
  reviewNote: "What should be different?",
78
78
  allow: "Allow",
79
79
  skip: "Skip",
80
- finished: "Done! Everything is here:",
81
- failed: "The wizard stopped. It goes on here:",
80
+ finished: "Done. Everything is here:",
81
+ failed: "The wizard stopped. You can carry on here:",
82
82
  },
83
83
  };
84
84
  function say(lang, key, vars = {}) {
@@ -25,6 +25,25 @@ export declare function langOf(tag: string | null | undefined): Lang | null;
25
25
  * in; null where neither names one.
26
26
  */
27
27
  export declare function localLang(languages: readonly string[], timeZone?: string | null): Lang | null;
28
+ /**
29
+ * A language a wizard can be translated into: any language, by its two- or three-letter code
30
+ * ("fr", "it", "pl"), not only those of `LANGS`.
31
+ */
32
+ export declare const LANG_CODE: RegExp;
33
+ /** The code of a tag such as "fr-CA" or "pt_BR"; null where it names no language. */
34
+ export declare function langCodeOf(tag: string | null | undefined): string | null;
35
+ /**
36
+ * The language the person reads, for a wizard translated on request: the first of their
37
+ * languages (the browser's, in its order), else the main language of the country their time
38
+ * zone lies in. Any language, not only one engenty's own strings are written in.
39
+ */
40
+ export declare function viewerLang(languages: readonly string[], timeZone?: string | null): string | null;
41
+ /** A language's name in that language, as a switch shows it: "Italiano", "Polski". */
42
+ export declare function langName(code: string): string;
43
+ /** A language's name in English, for a model told what to write in: "Italian". */
44
+ export declare function langNameEn(code: string): string;
45
+ /** The locale amounts, dates and plural forms are written in for any language. */
46
+ export declare function localeOf(code: string): string;
28
47
  export declare const FIELD_KINDS: readonly ["text", "textarea", "number", "select", "multiselect", "date", "email", "url", "toggle", "color", "image", "file", "items", "connection", "list", "location", "audio", "signature", "slot"];
29
48
  export type FieldKind = (typeof FIELD_KINDS)[number];
30
49
  /**
@@ -2549,12 +2568,6 @@ export declare const wizardSchema: z.ZodObject<{
2549
2568
  }, z.core.$strip>;
2550
2569
  export type WizardDefinition = z.infer<typeof wizardSchema>;
2551
2570
  export declare const INTERACTIVE: ReadonlySet<StepType>;
2552
- /**
2553
- * A wizard with a film step runs only where an AI client is installed and signed in, on the
2554
- * person's own subscription: one film takes a client many minutes and many tokens.
2555
- */
2556
- export declare function needsInstalledClient(def: WizardDefinition): boolean;
2557
- export declare function isInteractive(step: Step): boolean;
2558
2571
  /** Which formats each producing step can be downloaded in. */
2559
2572
  export declare function formatsFor(step: Step): Format[];
2560
2573
  /** Formats a stored list can be downloaded in; zip = the kept files its rows name, with the list. */
@@ -140,6 +140,57 @@ export function localLang(languages, timeZone) {
140
140
  const found = country ? langOf(COUNTRY_LANGUAGE[country]) : null;
141
141
  return found === "en" ? null : found;
142
142
  }
143
+ /**
144
+ * A language a wizard can be translated into: any language, by its two- or three-letter code
145
+ * ("fr", "it", "pl"), not only those of `LANGS`.
146
+ */
147
+ export const LANG_CODE = /^[a-z]{2,3}$/;
148
+ /** The code of a tag such as "fr-CA" or "pt_BR"; null where it names no language. */
149
+ export function langCodeOf(tag) {
150
+ const code = tag?.trim().toLowerCase().split(/[-_]/)[0] ?? "";
151
+ return LANG_CODE.test(code) ? code : null;
152
+ }
153
+ /**
154
+ * The language the person reads, for a wizard translated on request: the first of their
155
+ * languages (the browser's, in its order), else the main language of the country their time
156
+ * zone lies in. Any language, not only one engenty's own strings are written in.
157
+ */
158
+ export function viewerLang(languages, timeZone) {
159
+ for (const tag of languages) {
160
+ const code = langCodeOf(tag);
161
+ if (code) {
162
+ return code;
163
+ }
164
+ }
165
+ const country = timeZone ? ZONE_COUNTRY[timeZone] : undefined;
166
+ return country ? langCodeOf(COUNTRY_LANGUAGE[country]) : null;
167
+ }
168
+ /** A language's name in that language, as a switch shows it: "Italiano", "Polski". */
169
+ export function langName(code) {
170
+ if (isLang(code)) {
171
+ return LANG_NAMES[code];
172
+ }
173
+ try {
174
+ const name = new Intl.DisplayNames([code], { type: "language" }).of(code) ?? code;
175
+ return name.charAt(0).toLocaleUpperCase(code) + name.slice(1);
176
+ }
177
+ catch {
178
+ return code.toUpperCase();
179
+ }
180
+ }
181
+ /** A language's name in English, for a model told what to write in: "Italian". */
182
+ export function langNameEn(code) {
183
+ try {
184
+ return new Intl.DisplayNames(["en"], { type: "language" }).of(code) ?? code;
185
+ }
186
+ catch {
187
+ return code;
188
+ }
189
+ }
190
+ /** The locale amounts, dates and plural forms are written in for any language. */
191
+ export function localeOf(code) {
192
+ return isLang(code) ? LOCALES[code] : code;
193
+ }
143
194
  export const FIELD_KINDS = [
144
195
  "text",
145
196
  "textarea",
@@ -716,16 +767,6 @@ export const wizardSchema = z.object({
716
767
  steps: z.array(stepSchema).min(1),
717
768
  });
718
769
  export const INTERACTIVE = new Set(["page", "review", "result"]);
719
- /**
720
- * A wizard with a film step runs only where an AI client is installed and signed in, on the
721
- * person's own subscription: one film takes a client many minutes and many tokens.
722
- */
723
- export function needsInstalledClient(def) {
724
- return def.steps.some((s) => s.type === "film");
725
- }
726
- export function isInteractive(step) {
727
- return INTERACTIVE.has(step.type);
728
- }
729
770
  /** Which formats each producing step can be downloaded in. */
730
771
  export function formatsFor(step) {
731
772
  if (step.type === "generate") {
@@ -11,7 +11,6 @@ export type FeatureKind = (typeof FEATURES)[FeatureName]["kind"];
11
11
  export type Features = Record<string, boolean>;
12
12
  /** `teamPlans`, `contacts.leadScoring`. */
13
13
  export declare const FEATURE_NAME: RegExp;
14
- export declare const FEATURE_NAMES: FeatureName[];
15
14
  /** What a manifest declares for one switch; what is left out is a release switch, off, named by its key. */
16
15
  export interface FeatureDeclarationInput {
17
16
  kind?: "release" | "plan";
@@ -7,7 +7,6 @@ import { FEATURES } from "./feature-flags.js";
7
7
  export { FEATURES };
8
8
  /** `teamPlans`, `contacts.leadScoring`. */
9
9
  export const FEATURE_NAME = /^[a-z][a-zA-Z0-9]*(\.[a-z][a-zA-Z0-9]*)?$/;
10
- export const FEATURE_NAMES = Object.keys(FEATURES);
11
10
  /** A plugin's declarations under its id: `contacts` + `leadScoring` → `contacts.leadScoring`. */
12
11
  export function declaredByPlugin(pluginId, features) {
13
12
  const out = {};
@@ -109,11 +109,6 @@ export interface Effort {
109
109
  export declare function effortOf(def: WizardDefinition): Effort;
110
110
  /** How expensive a run is, where no price in credits is known: by what it makes and uses. */
111
111
  export declare function costTierOf(capabilities: Capability[]): CostTier;
112
- /**
113
- * The link that opens an entry in a local install at its default address — in the browser, or
114
- * in the studio installed as a Chrome app.
115
- */
116
- export declare const entryLocalLink: (id: string) => string;
117
112
  /** One entry as the marketplace lists it, in a language. */
118
113
  export interface MarketplaceSummary {
119
114
  id: string;
@@ -352,8 +352,3 @@ export function costTierOf(capabilities) {
352
352
  ? "medium"
353
353
  : "low";
354
354
  }
355
- /**
356
- * The link that opens an entry in a local install at its default address — in the browser, or
357
- * in the studio installed as a Chrome app.
358
- */
359
- export const entryLocalLink = (id) => `http://localhost:24368/studio/new?starter=${encodeURIComponent(id)}`;
@@ -61,7 +61,7 @@ declare const PROVIDER_EN: {
61
61
  readonly expressive: "expressive";
62
62
  readonly veryAccurate: "very accurate";
63
63
  readonly "about.replicate": "Open image and video models, billed per run.";
64
- readonly "about.gateway": "One key for many providers' models, billed by Vercel.";
64
+ readonly "about.gateway": "One key for many providers’ models, billed by Vercel.";
65
65
  };
66
66
  /** A line about a provider or one of its models, in `PROVIDER_TEXTS`. */
67
67
  export type ProviderWords = keyof typeof PROVIDER_EN;
@@ -69,7 +69,7 @@ const PROVIDER_EN = {
69
69
  expressive: "expressive",
70
70
  veryAccurate: "very accurate",
71
71
  "about.replicate": "Open image and video models, billed per run.",
72
- "about.gateway": "One key for many providers' models, billed by Vercel.",
72
+ "about.gateway": "One key for many providers’ models, billed by Vercel.",
73
73
  };
74
74
  const PROVIDER_DE = {
75
75
  "about.openai": "GPT-Modelle, GPT Image und Stimmen.",
@@ -1,7 +1,7 @@
1
- import type { CardOption, Format, Lang, ModelClass, Step, WizardDefinition } from "./definition.js";
1
+ import { type CardOption, type Format, type Lang, type ModelClass, type Step, type WizardDefinition } from "./definition.js";
2
2
  import type { ConnectionView, ListDef, ListRow } from "./store.js";
3
3
  import type { Surface } from "./surface.js";
4
- import { type Gap, type Texts } from "./texts.js";
4
+ import { type Gap, type Texts, type TextTable } from "./texts.js";
5
5
  export type RunStatus = "waiting_input" | "running" | "done" | "failed" | "cancelled";
6
6
  export interface AssetRef {
7
7
  id: string;
@@ -61,6 +61,11 @@ export interface RunState {
61
61
  * pages stay as they are; the copy goes with the run.
62
62
  */
63
63
  memory?: Record<string, string>;
64
+ /**
65
+ * Finished results translated on request, by language and step: the text the person reads in
66
+ * place of the step's own (`StepOutput.text`); the step's output stays as it was made.
67
+ */
68
+ translated?: Record<string, Record<string, string>>;
64
69
  }
65
70
  /** What a decision over a step's `ask` branches found. */
66
71
  export interface BranchDecision {
@@ -225,6 +230,9 @@ declare const NOTES_EN: {
225
230
  readonly filmFix: "The film didn’t render – Claude Code is fixing it";
226
231
  readonly filmResume: "Picking the film up where it stopped";
227
232
  readonly wizardUnavailable: "This wizard isn’t available right now.";
233
+ readonly translationFailed: "This wizard can’t be translated right now. It’s shown as it was written.";
234
+ readonly translationOff: "This wizard is only offered as it was written.";
235
+ readonly translationPlaceholder: "The translation of “{text}” needs every placeholder in curly brackets the original has.";
228
236
  readonly wizardLimit: "This wizard has reached its limit for today. Try again tomorrow.";
229
237
  readonly confirmHuman: "Confirm that you’re a person.";
230
238
  readonly tooManyRuns: "You’ve started a lot of runs just now. Try again in an hour.";
@@ -465,8 +473,11 @@ export type Said = string | RunNote;
465
473
  export declare function note(code: RunNoteCode, params?: Record<string, NoteGap | undefined>): RunNote;
466
474
  /** What is said, in a language. */
467
475
  export declare function saidText(said: Said, lang: Lang): string;
468
- /** A note in a language, its gaps filled; English where the language lacks the line. */
469
- export declare function noteText(note: RunNote, lang: Lang): string;
476
+ /**
477
+ * A note in a language, its gaps filled; English where the language lacks the line. A language
478
+ * outside `LANGS` reads its lines from `table`, the notes translated on request.
479
+ */
480
+ export declare function noteText(note: RunNote, lang: string, table?: TextTable): string;
470
481
  export interface RunEvent {
471
482
  id: number;
472
483
  at: string;
@@ -486,6 +497,12 @@ export interface RunView {
486
497
  wizard: Pick<WizardDefinition, "title" | "description" | "avatar" | "intro">;
487
498
  /** The language the wizard speaks: what is read aloud, and in which voice. */
488
499
  lang: Lang;
500
+ /**
501
+ * The run in the viewer's language (a wizard translated on request): every text of the view
502
+ * is written in it, the choices keep their values and show `labels[value]`. Null: the
503
+ * wizard's own words.
504
+ */
505
+ translation: RunTranslation | null;
489
506
  /** A live conversation can be started on this run: the runtime has a model for it and the wizard has live voice or video on (always in a test). */
490
507
  talk: boolean;
491
508
  /** The step the person is on (interactive) or the one running. */
@@ -539,6 +556,17 @@ export interface RunView {
539
556
  /** When the run and its shared link are deleted; null = kept. */
540
557
  expiresAt: string | null;
541
558
  }
559
+ /** How a run in the viewer's language shows. */
560
+ export interface RunTranslation {
561
+ /** The language, by its code ("fr"): any language, not only one of `LANGS`. */
562
+ lang: string;
563
+ /** What a choice shows, by its value. */
564
+ labels: Record<string, string>;
565
+ /** A model wrote some of it; false where the author wrote every text by hand. */
566
+ machine: boolean;
567
+ /** Results shown in this language, by step (`RunState.translated`). */
568
+ results: Record<string, string>;
569
+ }
542
570
  /** A shared result: what `/s/<token>` shows. Never the person's answers. */
543
571
  export interface ShareView {
544
572
  token: string;
@@ -577,6 +605,20 @@ export interface PublicWizard {
577
605
  kind: "page" | "channel";
578
606
  url: string | null;
579
607
  }[];
608
+ /** The language the wizard is written in: the page opens in it. */
609
+ lang: Lang;
610
+ /** The author offers the wizard in the viewer's language, translated on request. */
611
+ translatable: boolean;
612
+ /** The page in the viewer's language, where it was asked for (`?lang=`); null: the original. */
613
+ translation: Omit<RunTranslation, "results"> | null;
614
+ }
615
+ /** The runner's own words in a language outside `LANGS`: `GET /api/public/texts/:lang`. */
616
+ export interface UiTexts {
617
+ lang: string;
618
+ /** The runner's table (`packages/shared/src/runner-texts.ts`). */
619
+ runner: TextTable;
620
+ /** The run notes (`RUN_NOTES`). */
621
+ notes: TextTable;
580
622
  }
581
623
  /** What a step is expected to cost, in credits. `high` is the 90th percentile once measured. */
582
624
  export interface StepEstimate {
@@ -1,4 +1,6 @@
1
- import { textOf } from "./texts.js";
1
+ // SPDX-License-Identifier: FSL-1.1-MIT OR MIT
2
+ import { isLang, } from "./definition.js";
3
+ import { lineText, textOf } from "./texts.js";
2
4
  /**
3
5
  * What a step reports while it works, in words for each language: the runtime writes the English
4
6
  * line into the run's log, a runner shows it in the language the person reads. The runtime's
@@ -65,6 +67,9 @@ const NOTES_EN = {
65
67
  filmFix: "The film didn’t render – Claude Code is fixing it",
66
68
  filmResume: "Picking the film up where it stopped",
67
69
  wizardUnavailable: "This wizard isn’t available right now.",
70
+ translationFailed: "This wizard can’t be translated right now. It’s shown as it was written.",
71
+ translationOff: "This wizard is only offered as it was written.",
72
+ translationPlaceholder: "The translation of “{text}” needs every placeholder in curly brackets the original has.",
68
73
  wizardLimit: "This wizard has reached its limit for today. Try again tomorrow.",
69
74
  confirmHuman: "Confirm that you’re a person.",
70
75
  tooManyRuns: "You’ve started a lot of runs just now. Try again in an hour.",
@@ -350,6 +355,9 @@ const NOTES_DE = {
350
355
  filmFix: "Der Film ließ sich nicht rendern – Claude Code bessert nach",
351
356
  filmResume: "Macht dort weiter, wo der Film stehen geblieben ist",
352
357
  wizardUnavailable: "Dieser Wizard ist gerade nicht verfügbar.",
358
+ translationFailed: "Dieser Wizard lässt sich gerade nicht übersetzen. Du siehst ihn so, wie er geschrieben wurde.",
359
+ translationOff: "Diesen Wizard gibt es nur so, wie er geschrieben wurde.",
360
+ translationPlaceholder: "Die Übersetzung von „{text}“ braucht jeden Platzhalter in geschweiften Klammern, den das Original hat.",
353
361
  wizardLimit: "Für heute ist das Limit dieses Wizards erreicht. Versuch es morgen wieder.",
354
362
  confirmHuman: "Bestätige kurz, dass du ein Mensch bist.",
355
363
  tooManyRuns: "Du hast gerade viele Läufe gestartet. Versuch es in einer Stunde wieder.",
@@ -489,7 +497,7 @@ const NOTES_DE = {
489
497
  one: "Ein Space hat höchstens {count} Tabelle.",
490
498
  other: "Ein Space hat höchstens {count} Tabellen.",
491
499
  },
492
- tableBelongsOneWizard: "Diese Tabelle gehört zu einer Liste des Wizards. Ihre Spalten kommen aus dem Wizard.",
500
+ tableBelongsOneWizard: "Diese Tabelle gehört zu einer Liste des Wizards. Die Spalten kommen aus dem Wizard.",
493
501
  entryAlreadyExists: "Diesen Eintrag gibt es schon.",
494
502
  tableMostRows: {
495
503
  one: "Eine Tabelle hat höchstens {count} Zeile.",
@@ -578,23 +586,30 @@ export function note(code, params) {
578
586
  export function saidText(said, lang) {
579
587
  return typeof said === "string" ? said : noteText(said, lang);
580
588
  }
581
- function gapIn(value, lang) {
589
+ function gapIn(value, lang, table) {
582
590
  if (Array.isArray(value)) {
583
- return value.map((item) => gapIn(item, lang));
591
+ return value.map((item) => gapIn(item, lang, table));
584
592
  }
585
593
  if (typeof value === "object" && value !== null && "code" in value) {
586
- return noteText(value, lang);
594
+ return noteText(value, lang, table);
587
595
  }
588
596
  return value;
589
597
  }
590
- /** A note in a language, its gaps filled; English where the language lacks the line. */
591
- export function noteText(note, lang) {
598
+ /**
599
+ * A note in a language, its gaps filled; English where the language lacks the line. A language
600
+ * outside `LANGS` reads its lines from `table`, the notes translated on request.
601
+ */
602
+ export function noteText(note, lang, table) {
592
603
  if (!(note.code in NOTES_EN)) {
593
604
  return "";
594
605
  }
595
606
  const params = {};
596
607
  for (const [key, value] of Object.entries(note.params ?? {})) {
597
- params[key] = value === undefined ? undefined : gapIn(value, lang);
608
+ params[key] = value === undefined ? undefined : gapIn(value, lang, table);
609
+ }
610
+ const own = table?.[note.code];
611
+ if (own !== undefined) {
612
+ return lineText(own, lang, params);
598
613
  }
599
- return textOf(RUN_NOTES, note.code, lang, params);
614
+ return textOf(RUN_NOTES, note.code, isLang(lang) ? lang : "en", params);
600
615
  }