@flowdrop/flowdrop 1.6.0 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/README.md +10 -0
  2. package/dist/components/App.svelte +153 -84
  3. package/dist/components/App.svelte.d.ts +16 -1
  4. package/dist/components/ConfigModal.svelte +2 -1
  5. package/dist/components/ConfigPanel.svelte +3 -2
  6. package/dist/components/FlowDropZone.svelte +2 -1
  7. package/dist/components/LogsSidebar.svelte +3 -2
  8. package/dist/components/Navbar.svelte +10 -6
  9. package/dist/components/NodeSidebar.svelte +4 -3
  10. package/dist/components/NodeStatusOverlay.svelte +14 -7
  11. package/dist/components/NodeSwapPicker.svelte +2 -1
  12. package/dist/components/PipelineStatus.svelte +10 -7
  13. package/dist/components/ReadOnlyDetails.svelte +4 -2
  14. package/dist/components/SchemaForm.svelte +20 -9
  15. package/dist/components/SchemaForm.svelte.d.ts +2 -4
  16. package/dist/components/SettingsModal.svelte +4 -3
  17. package/dist/components/SettingsPanel.svelte +3 -2
  18. package/dist/components/SwapMappingEditor.svelte +2 -1
  19. package/dist/components/WorkflowEditor.svelte +3 -2
  20. package/dist/components/chat/AIChatPanel.svelte +22 -7
  21. package/dist/components/chat/AIChatPanel.svelte.d.ts +3 -0
  22. package/dist/components/chat/CommandPreview.svelte +10 -6
  23. package/dist/components/console/CommandConsole.svelte +4 -3
  24. package/dist/components/form/FormArray.svelte +33 -20
  25. package/dist/components/form/FormArray.svelte.d.ts +3 -1
  26. package/dist/components/form/FormAutocomplete.svelte +18 -7
  27. package/dist/components/form/FormCodeEditor.svelte +6 -4
  28. package/dist/components/form/FormFieldWrapper.svelte +2 -1
  29. package/dist/components/form/FormMarkdownEditor.svelte +152 -108
  30. package/dist/components/form/FormMarkdownEditor.svelte.d.ts +1 -1
  31. package/dist/components/form/FormTemplateEditor.svelte +2 -1
  32. package/dist/components/form/FormToggle.svelte +23 -5
  33. package/dist/components/form/FormToggle.svelte.d.ts +6 -2
  34. package/dist/components/interrupt/ChoicePrompt.svelte +14 -5
  35. package/dist/components/interrupt/ConfirmationPrompt.svelte +8 -5
  36. package/dist/components/interrupt/FormPrompt.svelte +28 -7
  37. package/dist/components/interrupt/InterruptBubble.svelte +27 -18
  38. package/dist/components/interrupt/ReviewPrompt.svelte +32 -22
  39. package/dist/components/interrupt/TextInputPrompt.svelte +12 -5
  40. package/dist/components/layouts/MainLayout.svelte +4 -3
  41. package/dist/components/nodes/GatewayNode.svelte +8 -3
  42. package/dist/components/nodes/IdeaNode.svelte +2 -1
  43. package/dist/components/nodes/NotesNode.svelte +18 -12
  44. package/dist/components/nodes/SimpleNode.svelte +8 -8
  45. package/dist/components/nodes/WorkflowNode.svelte +8 -3
  46. package/dist/components/playground/ChatPanel.svelte +36 -24
  47. package/dist/components/playground/MessageBubble.svelte +15 -7
  48. package/dist/components/playground/Playground.svelte +2 -1
  49. package/dist/components/playground/PlaygroundModal.svelte +2 -1
  50. package/dist/components/playground/SessionManager.svelte +14 -10
  51. package/dist/core/index.d.ts +2 -0
  52. package/dist/core/index.js +9 -0
  53. package/dist/editor/index.d.ts +1 -1
  54. package/dist/editor/index.js +1 -1
  55. package/dist/messages/context.d.ts +29 -0
  56. package/dist/messages/context.js +38 -0
  57. package/dist/messages/defaults.d.ts +396 -0
  58. package/dist/messages/defaults.js +356 -0
  59. package/dist/messages/deprecation.d.ts +20 -0
  60. package/dist/messages/deprecation.js +33 -0
  61. package/dist/messages/index.d.ts +11 -0
  62. package/dist/messages/index.js +10 -0
  63. package/dist/messages/merge.d.ts +28 -0
  64. package/dist/messages/merge.js +53 -0
  65. package/dist/messages/types.d.ts +29 -0
  66. package/dist/messages/types.js +13 -0
  67. package/dist/schemas/v1/workflow.schema.json +5 -0
  68. package/dist/services/draftStorage.d.ts +13 -0
  69. package/dist/services/draftStorage.js +36 -0
  70. package/dist/stores/workflowStore.svelte.d.ts +1 -0
  71. package/dist/stores/workflowStore.svelte.js +1 -0
  72. package/dist/styles/base.css +13 -4
  73. package/dist/svelte-app.d.ts +11 -0
  74. package/dist/svelte-app.js +11 -2
  75. package/dist/types/index.d.ts +2 -0
  76. package/dist/utils/connections.d.ts +4 -0
  77. package/dist/utils/connections.js +6 -0
  78. package/package.json +1 -1
@@ -27,6 +27,7 @@
27
27
  import type { FieldOption } from './types.js';
28
28
  import { buildFetchHeaders } from '../../utils/fetchWithAuth.js';
29
29
  import { logger } from '../../utils/logger.js';
30
+ import { m } from '../../messages/index.js';
30
31
 
31
32
  /**
32
33
  * Props interface for FormAutocomplete component
@@ -68,6 +69,10 @@
68
69
  );
69
70
  const getBaseUrl = getContext<(() => string) | undefined>('flowdrop:getBaseUrl');
70
71
 
72
+ // Hoist the autocomplete branch — seven reads in the template, one inside
73
+ // an {#each tag} loop. One getter walk per render.
74
+ const t = $derived(m().form.autocomplete);
75
+
71
76
  // Configuration with defaults
72
77
  const queryParam = $derived(autocomplete.queryParam ?? 'q');
73
78
  const minChars = $derived(autocomplete.minChars ?? 0);
@@ -579,7 +584,9 @@
579
584
  <button
580
585
  type="button"
581
586
  class="form-autocomplete__tag-remove"
582
- aria-label={`Remove ${getDisplayLabel(selectedVal)}`}
587
+ aria-label={t.removeTag({
588
+ label: getDisplayLabel(selectedVal)
589
+ })}
583
590
  onclick={(e) => {
584
591
  e.stopPropagation();
585
592
  removeTag(selectedVal);
@@ -622,14 +629,14 @@
622
629
  <!-- Status icons -->
623
630
  <div class="form-autocomplete__icons">
624
631
  {#if isLoading}
625
- <span class="form-autocomplete__spinner" aria-label="Loading suggestions">
632
+ <span class="form-autocomplete__spinner" aria-label={t.loading}>
626
633
  <Icon icon="heroicons:arrow-path" />
627
634
  </span>
628
635
  {:else if selectedValues.length > 0 && !disabled}
629
636
  <button
630
637
  type="button"
631
638
  class="form-autocomplete__clear"
632
- aria-label="Clear all selections"
639
+ aria-label={t.clearAll}
633
640
  onclick={(e) => {
634
641
  e.stopPropagation();
635
642
  handleClearAll();
@@ -657,24 +664,28 @@
657
664
  style={popoverStyle}
658
665
  onmousedown={(e) => e.preventDefault()}
659
666
  >
660
- <ul class="form-autocomplete__listbox" role="listbox" aria-label="Suggestions">
667
+ <ul
668
+ class="form-autocomplete__listbox"
669
+ role="listbox"
670
+ aria-label={t.suggestions}
671
+ >
661
672
  {#if isLoading}
662
673
  <li class="form-autocomplete__status form-autocomplete__status--loading">
663
674
  <Icon icon="heroicons:arrow-path" class="form-autocomplete__status-icon" />
664
- <span>Loading suggestions...</span>
675
+ <span>{t.loadingPending}</span>
665
676
  </li>
666
677
  {:else if error}
667
678
  <li class="form-autocomplete__status form-autocomplete__status--error">
668
679
  <Icon icon="heroicons:exclamation-triangle" class="form-autocomplete__status-icon" />
669
680
  <span>{error}</span>
670
681
  <button type="button" class="form-autocomplete__retry" onclick={handleRetry}>
671
- Retry
682
+ {t.retry}
672
683
  </button>
673
684
  </li>
674
685
  {:else if suggestions.length === 0}
675
686
  <li class="form-autocomplete__status form-autocomplete__status--empty">
676
687
  <Icon icon="heroicons:magnifying-glass" class="form-autocomplete__status-icon" />
677
- <span>No results found</span>
688
+ <span>{t.noResults}</span>
678
689
  </li>
679
690
  {:else}
680
691
  {#each suggestions as option, index (option.value)}
@@ -31,6 +31,7 @@
31
31
  import { json, jsonParseLinter } from '@codemirror/lang-json';
32
32
  import { oneDark } from '@codemirror/theme-one-dark';
33
33
  import { linter } from '@codemirror/lint';
34
+ import { m } from '../../messages/index.js';
34
35
 
35
36
  interface Props {
36
37
  /** Field identifier */
@@ -91,13 +92,14 @@
91
92
  return '';
92
93
  }
93
94
  if (typeof val === 'string') {
94
- // Check if it's already a valid JSON string
95
+ // If the string is already a valid JSON representation (e.g. '{"a":1}' or '"foo"'),
96
+ // use it as-is to avoid double-encoding
95
97
  try {
96
98
  JSON.parse(val);
97
99
  return val;
98
100
  } catch {
99
- // Not valid JSON, return as-is
100
- return val;
101
+ // Plain JS string value — serialize it as a JSON string literal (adds quotes)
102
+ return JSON.stringify(val);
101
103
  }
102
104
  }
103
105
  // Convert object to formatted JSON string
@@ -321,7 +323,7 @@
321
323
  class:form-code-editor__container--dark={darkTheme}
322
324
  role="textbox"
323
325
  aria-multiline="true"
324
- aria-label="JSON editor"
326
+ aria-label={m().form.code.editor}
325
327
  onblur={formatContent}
326
328
  ></div>
327
329
 
@@ -11,6 +11,7 @@
11
11
 
12
12
  <script lang="ts">
13
13
  import type { Snippet } from 'svelte';
14
+ import { m } from '../../messages/index.js';
14
15
 
15
16
  interface Props {
16
17
  /** Field identifier for label association */
@@ -42,7 +43,7 @@
42
43
  {label}
43
44
  </span>
44
45
  {#if required}
45
- <span class="form-field__required" aria-label="required">*</span>
46
+ <span class="form-field__required" aria-label={m().form.field.required}>*</span>
46
47
  {/if}
47
48
  </label>
48
49
 
@@ -18,20 +18,21 @@
18
18
 
19
19
  <script lang="ts">
20
20
  import { onMount, onDestroy } from 'svelte';
21
- import { EditorView, lineNumbers, drawSelection, keymap } from '@codemirror/view';
21
+ import { EditorView, lineNumbers, drawSelection, keymap, placeholder } from '@codemirror/view';
22
22
  import { EditorState, Compartment } from '@codemirror/state';
23
23
  import { history, historyKeymap, defaultKeymap, indentWithTab } from '@codemirror/commands';
24
24
  import { highlightSpecialChars, highlightActiveLine } from '@codemirror/view';
25
25
  import { syntaxHighlighting, defaultHighlightStyle } from '@codemirror/language';
26
26
  import { markdown } from '@codemirror/lang-markdown';
27
27
  import { oneDark } from '@codemirror/theme-one-dark';
28
+ import { m } from '../../messages/index.js';
28
29
 
29
30
  interface Props {
30
31
  /** Field identifier */
31
32
  id: string;
32
33
  /** Current value (markdown string) */
33
34
  value: string;
34
- /** Placeholder text shown when empty */
35
+ /** Placeholder text shown when empty. Falls back to `messages.form.markdown.placeholder`. */
35
36
  placeholder?: string;
36
37
  /** Whether the field is required */
37
38
  required?: boolean;
@@ -60,7 +61,7 @@
60
61
  let {
61
62
  id,
62
63
  value = '',
63
- placeholder = 'Write your markdown here...',
64
+ placeholder: placeholderProp,
64
65
  required = false,
65
66
  height = '300px',
66
67
  showToolbar = true,
@@ -97,6 +98,12 @@
97
98
  /** Theme compartment for dynamic theme switching */
98
99
  const themeCompartment = new Compartment();
99
100
 
101
+ /** Placeholder compartment so locale changes can reconfigure without rebuilding the editor */
102
+ const placeholderCompartment = new Compartment();
103
+
104
+ /** aria-label compartment so locale changes update the editor's accessible name */
105
+ const ariaLabelCompartment = new Compartment();
106
+
100
107
  // ── Toolbar actions ──────────────────────────────────────
101
108
 
102
109
  type ToolbarAction = {
@@ -165,104 +172,110 @@
165
172
  editorView.focus();
166
173
  }
167
174
 
168
- const toolbarActions: (ToolbarAction | '|')[] = [
169
- {
170
- id: 'bold',
171
- label: 'Bold',
172
- icon: 'B',
173
- shortcut: 'Mod-b',
174
- action: () => wrapSelection('**', '**')
175
- },
176
- {
177
- id: 'italic',
178
- label: 'Italic',
179
- icon: 'I',
180
- shortcut: 'Mod-i',
181
- action: () => wrapSelection('_', '_')
182
- },
183
- {
184
- id: 'strikethrough',
185
- label: 'Strikethrough',
186
- icon: 'S',
187
- action: () => wrapSelection('~~', '~~')
188
- },
189
- '|',
190
- {
191
- id: 'heading-1',
192
- label: 'Heading 1',
193
- icon: 'H1',
194
- action: () => prefixLine('# ')
195
- },
196
- {
197
- id: 'heading-2',
198
- label: 'Heading 2',
199
- icon: 'H2',
200
- action: () => prefixLine('## ')
201
- },
202
- {
203
- id: 'heading-3',
204
- label: 'Heading 3',
205
- icon: 'H3',
206
- action: () => prefixLine('### ')
207
- },
208
- '|',
209
- {
210
- id: 'quote',
211
- label: 'Quote',
212
- icon: '"',
213
- action: () => prefixLine('> ')
214
- },
215
- {
216
- id: 'unordered-list',
217
- label: 'Unordered List',
218
- icon: '',
219
- action: () => prefixLine('- ')
220
- },
221
- {
222
- id: 'ordered-list',
223
- label: 'Ordered List',
224
- icon: '1.',
225
- action: () => prefixLine('1. ')
226
- },
227
- '|',
228
- {
229
- id: 'link',
230
- label: 'Link',
231
- icon: icons.link,
232
- isSvg: true,
233
- shortcut: 'Mod-k',
234
- action: () => {
235
- if (!editorView) return;
236
- const { from, to } = editorView.state.selection.main;
237
- const selected = editorView.state.sliceDoc(from, to);
238
- const text = selected || 'link text';
239
- const replacement = `[${text}](url)`;
240
- editorView.dispatch({
241
- changes: { from, to, insert: replacement },
242
- selection: {
243
- anchor: from + text.length + 3,
244
- head: from + text.length + 6
245
- }
246
- });
247
- editorView.focus();
175
+ // Derived so `label` strings refresh whenever the consumer's i18n source
176
+ // changes locale. Action closures recreate alongside, but they're cheap —
177
+ // they only capture a stable reference to `editorView` (a state).
178
+ const toolbarActions: (ToolbarAction | '|')[] = $derived.by(() => {
179
+ const md = m().form.markdown;
180
+ return [
181
+ {
182
+ id: 'bold',
183
+ label: md.bold,
184
+ icon: 'B',
185
+ shortcut: 'Mod-b',
186
+ action: () => wrapSelection('**', '**')
187
+ },
188
+ {
189
+ id: 'italic',
190
+ label: md.italic,
191
+ icon: 'I',
192
+ shortcut: 'Mod-i',
193
+ action: () => wrapSelection('_', '_')
194
+ },
195
+ {
196
+ id: 'strikethrough',
197
+ label: md.strikethrough,
198
+ icon: 'S',
199
+ action: () => wrapSelection('~~', '~~')
200
+ },
201
+ '|',
202
+ {
203
+ id: 'heading-1',
204
+ label: md.heading1,
205
+ icon: 'H1',
206
+ action: () => prefixLine('# ')
207
+ },
208
+ {
209
+ id: 'heading-2',
210
+ label: md.heading2,
211
+ icon: 'H2',
212
+ action: () => prefixLine('## ')
213
+ },
214
+ {
215
+ id: 'heading-3',
216
+ label: md.heading3,
217
+ icon: 'H3',
218
+ action: () => prefixLine('### ')
219
+ },
220
+ '|',
221
+ {
222
+ id: 'quote',
223
+ label: md.quote,
224
+ icon: '"',
225
+ action: () => prefixLine('> ')
226
+ },
227
+ {
228
+ id: 'unordered-list',
229
+ label: md.unorderedList,
230
+ icon: '',
231
+ action: () => prefixLine('- ')
232
+ },
233
+ {
234
+ id: 'ordered-list',
235
+ label: md.orderedList,
236
+ icon: '1.',
237
+ action: () => prefixLine('1. ')
238
+ },
239
+ '|',
240
+ {
241
+ id: 'link',
242
+ label: md.link,
243
+ icon: icons.link,
244
+ isSvg: true,
245
+ shortcut: 'Mod-k',
246
+ action: () => {
247
+ if (!editorView) return;
248
+ const { from, to } = editorView.state.selection.main;
249
+ const selected = editorView.state.sliceDoc(from, to);
250
+ const text = selected || 'link text';
251
+ const replacement = `[${text}](url)`;
252
+ editorView.dispatch({
253
+ changes: { from, to, insert: replacement },
254
+ selection: {
255
+ anchor: from + text.length + 3,
256
+ head: from + text.length + 6
257
+ }
258
+ });
259
+ editorView.focus();
260
+ }
261
+ },
262
+ {
263
+ id: 'image',
264
+ label: md.image,
265
+ icon: icons.image,
266
+ isSvg: true,
267
+ action: () => insertAtCursor('![alt text](image-url)')
268
+ },
269
+ {
270
+ id: 'table',
271
+ label: md.table,
272
+ icon: icons.table,
273
+ isSvg: true,
274
+ action: () =>
275
+ insertAtCursor('\n| Header | Header |\n| ------ | ------ |\n| Cell | Cell |\n')
248
276
  }
249
- },
250
- {
251
- id: 'image',
252
- label: 'Image',
253
- icon: icons.image,
254
- isSvg: true,
255
- action: () => insertAtCursor('![alt text](image-url)')
256
- },
257
- {
258
- id: 'table',
259
- label: 'Table',
260
- icon: icons.table,
261
- isSvg: true,
262
- action: () =>
263
- insertAtCursor('\n| Header | Header |\n| ------ | ------ |\n| Cell | Cell |\n')
264
- }
265
- ];
277
+ ];
278
+ });
266
279
 
267
280
  // ── CM6 Keyboard shortcuts for toolbar actions ───────────
268
281
 
@@ -327,11 +340,14 @@
327
340
  // ── Editor setup ─────────────────────────────────────────
328
341
 
329
342
  function createExtensions() {
343
+ const placeholderText = placeholderProp ?? m().form.markdown.placeholder;
344
+
330
345
  const extensions = [
331
346
  lineNumbers(),
332
347
  highlightSpecialChars(),
333
348
  highlightActiveLine(),
334
349
  drawSelection(),
350
+ placeholderCompartment.of(placeholder(placeholderText)),
335
351
 
336
352
  // Editing features (skip when read-only)
337
353
  ...(disabled
@@ -394,10 +410,12 @@
394
410
  EditorView.lineWrapping,
395
411
 
396
412
  // Accessibility
397
- EditorView.contentAttributes.of({
398
- 'aria-label': 'Markdown editor',
399
- 'aria-multiline': 'true'
400
- })
413
+ ariaLabelCompartment.of(
414
+ EditorView.contentAttributes.of({
415
+ 'aria-label': m().form.markdown.editor,
416
+ 'aria-multiline': 'true'
417
+ })
418
+ )
401
419
  ];
402
420
 
403
421
  return extensions;
@@ -462,6 +480,27 @@
462
480
  updateStats(editorView.state.doc);
463
481
  }
464
482
  });
483
+
484
+ // Reconfigure CodeMirror-owned strings (placeholder, content aria-label)
485
+ // when the consumer's locale or `placeholder` prop changes. Toolbar and
486
+ // status-bar strings rerender via `$derived` in the template; CodeMirror
487
+ // lives outside Svelte's reactivity graph, so this effect bridges the gap.
488
+ $effect(() => {
489
+ const placeholderText = placeholderProp ?? m().form.markdown.placeholder;
490
+ const ariaLabel = m().form.markdown.editor;
491
+ if (!editorView) return;
492
+ editorView.dispatch({
493
+ effects: [
494
+ placeholderCompartment.reconfigure(placeholder(placeholderText)),
495
+ ariaLabelCompartment.reconfigure(
496
+ EditorView.contentAttributes.of({
497
+ 'aria-label': ariaLabel,
498
+ 'aria-multiline': 'true'
499
+ })
500
+ )
501
+ ]
502
+ });
503
+ });
465
504
  </script>
466
505
 
467
506
  <div
@@ -481,7 +520,11 @@
481
520
 
482
521
  <!-- Toolbar -->
483
522
  {#if showToolbar && !disabled}
484
- <div class="form-markdown-editor__toolbar" role="toolbar" aria-label="Markdown formatting">
523
+ <div
524
+ class="form-markdown-editor__toolbar"
525
+ role="toolbar"
526
+ aria-label={m().form.markdown.toolbar}
527
+ >
485
528
  {#each toolbarActions as item}
486
529
  {#if item === '|'}
487
530
  <span class="form-markdown-editor__separator"></span>
@@ -514,10 +557,11 @@
514
557
 
515
558
  <!-- Status bar -->
516
559
  {#if showStatusBar}
560
+ {@const md = m().form.markdown}
517
561
  <div class="form-markdown-editor__status">
518
- <span>words: {wordCount}</span>
519
- <span>lines: {lineCount}</span>
520
- <span>characters: {charCount}</span>
562
+ <span>{md.words}: {wordCount}</span>
563
+ <span>{md.lines}: {lineCount}</span>
564
+ <span>{md.characters}: {charCount}</span>
521
565
  </div>
522
566
  {/if}
523
567
  </div>
@@ -3,7 +3,7 @@ interface Props {
3
3
  id: string;
4
4
  /** Current value (markdown string) */
5
5
  value: string;
6
- /** Placeholder text shown when empty */
6
+ /** Placeholder text shown when empty. Falls back to `messages.form.markdown.placeholder`. */
7
7
  placeholder?: string;
8
8
  /** Whether the field is required */
9
9
  required?: boolean;
@@ -46,6 +46,7 @@
46
46
  import { createTemplateAutocomplete } from './templateAutocomplete.js';
47
47
  import { getVariableSchema } from '../../services/variableService.js';
48
48
  import { logger } from '../../utils/logger.js';
49
+ import { m } from '../../messages/index.js';
49
50
 
50
51
  interface Props {
51
52
  /** Field identifier */
@@ -506,7 +507,7 @@
506
507
  class:form-template-editor__container--dark={darkTheme}
507
508
  role="textbox"
508
509
  aria-multiline="true"
509
- aria-label="Template editor"
510
+ aria-label={m().form.template.editor}
510
511
  ></div>
511
512
 
512
513
  <!-- Loading banner (shown while fetching variables from API) -->
@@ -9,14 +9,20 @@
9
9
  -->
10
10
 
11
11
  <script lang="ts">
12
+ import { m, warnDeprecatedProp } from '../../messages/index.js';
13
+
12
14
  interface Props {
13
15
  /** Field identifier */
14
16
  id: string;
15
17
  /** Current value */
16
18
  value: boolean;
17
- /** Label shown when toggle is on */
19
+ /**
20
+ * @deprecated since v1.8 — use `messages.form.toggle.enabled`. Removed in v2.0.
21
+ */
18
22
  onLabel?: string;
19
- /** Label shown when toggle is off */
23
+ /**
24
+ * @deprecated since v1.8 — use `messages.form.toggle.disabled`. Removed in v2.0.
25
+ */
20
26
  offLabel?: string;
21
27
  /** Whether the field is disabled (read-only) */
22
28
  disabled?: boolean;
@@ -29,13 +35,25 @@
29
35
  let {
30
36
  id,
31
37
  value = false,
32
- onLabel = 'Enabled',
33
- offLabel = 'Disabled',
38
+ onLabel,
39
+ offLabel,
34
40
  disabled = false,
35
41
  ariaDescribedBy,
36
42
  onChange
37
43
  }: Props = $props();
38
44
 
45
+ // svelte-ignore state_referenced_locally — deprecation warns once per mount; later prop rebinds aren't relevant
46
+ if (onLabel !== undefined) {
47
+ warnDeprecatedProp('FormToggle', 'onLabel', 'messages.form.toggle.enabled');
48
+ }
49
+ // svelte-ignore state_referenced_locally
50
+ if (offLabel !== undefined) {
51
+ warnDeprecatedProp('FormToggle', 'offLabel', 'messages.form.toggle.disabled');
52
+ }
53
+
54
+ const resolvedOnLabel = $derived(onLabel ?? m().form.toggle.enabled);
55
+ const resolvedOffLabel = $derived(offLabel ?? m().form.toggle.disabled);
56
+
39
57
  /**
40
58
  * Handle toggle changes
41
59
  */
@@ -59,7 +77,7 @@
59
77
  <span class="form-toggle__thumb"></span>
60
78
  </span>
61
79
  <span class="form-toggle__label">
62
- {value ? onLabel : offLabel}
80
+ {value ? resolvedOnLabel : resolvedOffLabel}
63
81
  </span>
64
82
  </label>
65
83
 
@@ -3,9 +3,13 @@ interface Props {
3
3
  id: string;
4
4
  /** Current value */
5
5
  value: boolean;
6
- /** Label shown when toggle is on */
6
+ /**
7
+ * @deprecated since v1.8 — use `messages.form.toggle.enabled`. Removed in v2.0.
8
+ */
7
9
  onLabel?: string;
8
- /** Label shown when toggle is off */
10
+ /**
11
+ * @deprecated since v1.8 — use `messages.form.toggle.disabled`. Removed in v2.0.
12
+ */
9
13
  offLabel?: string;
10
14
  /** Whether the field is disabled (read-only) */
11
15
  disabled?: boolean;
@@ -10,6 +10,7 @@
10
10
  <script lang="ts">
11
11
  import Icon from '@iconify/svelte';
12
12
  import type { ChoiceConfig, InterruptChoice } from '../../types/interrupt.js';
13
+ import { m } from '../../messages/index.js';
13
14
 
14
15
  /**
15
16
  * Component props
@@ -41,6 +42,9 @@
41
42
  onSubmit
42
43
  }: Props = $props();
43
44
 
45
+ // Hoist the choice branch — counter, min, max, submit reads.
46
+ const t = $derived(m().interrupt.choice);
47
+
44
48
  /** Local state for selected values */
45
49
  let selectedValues = $state<Set<string>>(new Set());
46
50
 
@@ -165,12 +169,15 @@
165
169
  {#if isMultiple && !isResolved}
166
170
  <div class="choice-prompt__info">
167
171
  <span>
168
- {selectedValues.size} of {config.options.length} selected
172
+ {t.selectedCount({
173
+ n: selectedValues.size,
174
+ total: config.options.length
175
+ })}
169
176
  {#if minSelections > 0}
170
- (min: {minSelections})
177
+ {t.min({ n: minSelections })}
171
178
  {/if}
172
179
  {#if maxSelections < config.options.length}
173
- (max: {maxSelections})
180
+ {t.max({ n: maxSelections })}
174
181
  {/if}
175
182
  </span>
176
183
  </div>
@@ -190,7 +197,7 @@
190
197
  {:else}
191
198
  <Icon icon="mdi:check" />
192
199
  {/if}
193
- <span>Submit</span>
200
+ <span>{t.submit}</span>
194
201
  </button>
195
202
  </div>
196
203
  {/if}
@@ -200,7 +207,9 @@
200
207
  <div class="choice-prompt__resolved-badge">
201
208
  <Icon icon="mdi:check-circle" />
202
209
  <span>
203
- {resolvedByUserName ? `Response submitted by ${resolvedByUserName}` : 'Response submitted'}
210
+ {resolvedByUserName
211
+ ? m().interrupt.responseSubmittedBy({ name: resolvedByUserName })
212
+ : m().interrupt.responseSubmitted}
204
213
  </span>
205
214
  </div>
206
215
  {/if}
@@ -10,6 +10,7 @@
10
10
  <script lang="ts">
11
11
  import Icon from '@iconify/svelte';
12
12
  import type { ConfirmationConfig } from '../../types/interrupt.js';
13
+ import { m } from '../../messages/index.js';
13
14
 
14
15
  /**
15
16
  * Component props
@@ -44,11 +45,11 @@
44
45
  onDecline
45
46
  }: Props = $props();
46
47
 
47
- /** Computed label for confirm button */
48
- const confirmLabel = $derived(config.confirmLabel ?? 'Yes');
48
+ /** Computed label for confirm button — config wins, falls back to messages tree. */
49
+ const confirmLabel = $derived(config.confirmLabel ?? m().interrupt.confirmation.yes);
49
50
 
50
- /** Computed label for decline/cancel button */
51
- const declineLabel = $derived(config.cancelLabel ?? 'No');
51
+ /** Computed label for decline/cancel button — config wins, falls back to messages tree. */
52
+ const declineLabel = $derived(config.cancelLabel ?? m().interrupt.confirmation.no);
52
53
  </script>
53
54
 
54
55
  <div
@@ -119,7 +120,9 @@
119
120
  <div class="confirmation-prompt__resolved-badge">
120
121
  <Icon icon="mdi:check-circle" />
121
122
  <span>
122
- {resolvedByUserName ? `Response submitted by ${resolvedByUserName}` : 'Response submitted'}
123
+ {resolvedByUserName
124
+ ? m().interrupt.responseSubmittedBy({ name: resolvedByUserName })
125
+ : m().interrupt.responseSubmitted}
123
126
  </span>
124
127
  </div>
125
128
  {/if}