@flowdrop/flowdrop 1.7.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 (71) hide show
  1. package/README.md +10 -0
  2. package/dist/components/App.svelte +92 -54
  3. package/dist/components/App.svelte.d.ts +13 -0
  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 +2 -1
  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/WorkflowNode.svelte +8 -3
  45. package/dist/components/playground/ChatPanel.svelte +36 -24
  46. package/dist/components/playground/MessageBubble.svelte +15 -7
  47. package/dist/components/playground/Playground.svelte +2 -1
  48. package/dist/components/playground/PlaygroundModal.svelte +2 -1
  49. package/dist/components/playground/SessionManager.svelte +14 -10
  50. package/dist/core/index.d.ts +2 -0
  51. package/dist/core/index.js +9 -0
  52. package/dist/editor/index.d.ts +1 -1
  53. package/dist/editor/index.js +1 -1
  54. package/dist/messages/context.d.ts +29 -0
  55. package/dist/messages/context.js +38 -0
  56. package/dist/messages/defaults.d.ts +396 -0
  57. package/dist/messages/defaults.js +356 -0
  58. package/dist/messages/deprecation.d.ts +20 -0
  59. package/dist/messages/deprecation.js +33 -0
  60. package/dist/messages/index.d.ts +11 -0
  61. package/dist/messages/index.js +10 -0
  62. package/dist/messages/merge.d.ts +28 -0
  63. package/dist/messages/merge.js +53 -0
  64. package/dist/messages/types.d.ts +29 -0
  65. package/dist/messages/types.js +13 -0
  66. package/dist/services/draftStorage.d.ts +13 -0
  67. package/dist/services/draftStorage.js +36 -0
  68. package/dist/styles/base.css +13 -4
  69. package/dist/svelte-app.d.ts +11 -0
  70. package/dist/svelte-app.js +11 -2
  71. package/package.json +1 -1
@@ -61,6 +61,7 @@
61
61
  import { FormField } from './form/index.js';
62
62
  import FormUISchemaRenderer from './form/FormUISchemaRenderer.svelte';
63
63
  import type { FieldSchema } from './form/index.js';
64
+ import { m, warnDeprecatedProp } from '../messages/index.js';
64
65
 
65
66
  /**
66
67
  * Props interface for SchemaForm component
@@ -100,14 +101,12 @@
100
101
  showActions?: boolean;
101
102
 
102
103
  /**
103
- * Label for the save button.
104
- * @default "Save"
104
+ * @deprecated since v1.8 — use `messages.form.schema.save`. Removed in v2.0.
105
105
  */
106
106
  saveLabel?: string;
107
107
 
108
108
  /**
109
- * Label for the cancel button.
110
- * @default "Cancel"
109
+ * @deprecated since v1.8 — use `messages.form.schema.cancel`. Removed in v2.0.
111
110
  */
112
111
  cancelLabel?: string;
113
112
 
@@ -160,8 +159,8 @@
160
159
  values = {},
161
160
  onChange,
162
161
  showActions = false,
163
- saveLabel = 'Save',
164
- cancelLabel = 'Cancel',
162
+ saveLabel,
163
+ cancelLabel,
165
164
  onSave,
166
165
  onCancel,
167
166
  loading = false,
@@ -171,6 +170,18 @@
171
170
  baseUrl = ''
172
171
  }: Props = $props();
173
172
 
173
+ // svelte-ignore state_referenced_locally — deprecation warns once per mount; later prop rebinds aren't relevant
174
+ if (saveLabel !== undefined) {
175
+ warnDeprecatedProp('SchemaForm', 'saveLabel', 'messages.form.schema.save');
176
+ }
177
+ // svelte-ignore state_referenced_locally
178
+ if (cancelLabel !== undefined) {
179
+ warnDeprecatedProp('SchemaForm', 'cancelLabel', 'messages.form.schema.cancel');
180
+ }
181
+
182
+ const resolvedSaveLabel = $derived(saveLabel ?? m().form.schema.save);
183
+ const resolvedCancelLabel = $derived(cancelLabel ?? m().form.schema.cancel);
184
+
174
185
  // Set context for child components (e.g., FormAutocomplete)
175
186
  // Use getter functions to ensure child components always get the current prop value,
176
187
  // even if the prop changes after initial mount
@@ -352,7 +363,7 @@
352
363
  disabled={loading}
353
364
  >
354
365
  <Icon icon="heroicons:x-mark" class="schema-form__button-icon" />
355
- <span>{cancelLabel}</span>
366
+ <span>{resolvedCancelLabel}</span>
356
367
  </button>
357
368
  <button
358
369
  type="submit"
@@ -364,7 +375,7 @@
364
375
  {:else}
365
376
  <Icon icon="heroicons:check" class="schema-form__button-icon" />
366
377
  {/if}
367
- <span>{saveLabel}</span>
378
+ <span>{resolvedSaveLabel}</span>
368
379
  </button>
369
380
  </div>
370
381
  {/if}
@@ -374,7 +385,7 @@
374
385
  <div class="schema-form__empty-icon">
375
386
  <Icon icon="heroicons:document-text" />
376
387
  </div>
377
- <p class="schema-form__empty-text">No schema properties defined.</p>
388
+ <p class="schema-form__empty-text">{m().form.schema.empty}</p>
378
389
  </div>
379
390
  {/if}
380
391
 
@@ -33,13 +33,11 @@ interface Props {
33
33
  */
34
34
  showActions?: boolean;
35
35
  /**
36
- * Label for the save button.
37
- * @default "Save"
36
+ * @deprecated since v1.8 — use `messages.form.schema.save`. Removed in v2.0.
38
37
  */
39
38
  saveLabel?: string;
40
39
  /**
41
- * Label for the cancel button.
42
- * @default "Cancel"
40
+ * @deprecated since v1.8 — use `messages.form.schema.cancel`. Removed in v2.0.
43
41
  */
44
42
  cancelLabel?: string;
45
43
  /**
@@ -27,6 +27,7 @@
27
27
  import Icon from '@iconify/svelte';
28
28
  import SettingsPanel from './SettingsPanel.svelte';
29
29
  import type { SettingsCategory } from '../types/settings.js';
30
+ import { m } from '../messages/index.js';
30
31
 
31
32
  /**
32
33
  * Props interface for SettingsModal component
@@ -131,13 +132,13 @@
131
132
  <div class="flowdrop-settings-modal__header">
132
133
  <h2 id="settings-modal-title" class="flowdrop-settings-modal__title">
133
134
  <Icon icon="mdi:cog" class="flowdrop-settings-modal__title-icon" />
134
- Settings
135
+ {m().navigation.settingsTitle}
135
136
  </h2>
136
137
  <button
137
138
  class="flowdrop-settings-modal__close"
138
139
  onclick={closeModal}
139
- aria-label="Close settings"
140
- title="Close"
140
+ aria-label={m().navigation.closeSettings}
141
+ title={m().common.close}
141
142
  >
142
143
  <Icon icon="mdi:close" />
143
144
  </button>
@@ -30,6 +30,7 @@
30
30
  <script lang="ts">
31
31
  import Icon from '@iconify/svelte';
32
32
  import { SchemaForm } from '../form/index.js';
33
+ import { m } from '../messages/index.js';
33
34
  import type { ConfigSchema } from '../types/index.js';
34
35
  import type { SettingsCategory } from '../types/settings.js';
35
36
  import {
@@ -231,7 +232,7 @@
231
232
  },
232
233
  chatAutoRetry: {
233
234
  type: 'boolean',
234
- title: 'AI Chat Auto-retry',
235
+ title: 'AI Assistant Auto-retry',
235
236
  description: 'Automatically ask the AI to self-correct when commands fail',
236
237
  default: true
237
238
  }
@@ -361,7 +362,7 @@
361
362
 
362
363
  <div class="flowdrop-settings-panel {className}">
363
364
  <!-- Tab Navigation -->
364
- <div class="flowdrop-settings-panel__tabs" role="tablist" aria-label="Settings categories">
365
+ <div class="flowdrop-settings-panel__tabs" role="tablist" aria-label={m().layout.settingsCategories}>
365
366
  {#each categories as category, index (category)}
366
367
  <button
367
368
  class="flowdrop-settings-panel__tab"
@@ -14,6 +14,7 @@
14
14
  import { getCategoryColorToken } from '../utils/colors.js';
15
15
  import PortMappingRow from './PortMappingRow.svelte';
16
16
  import ConfigMappingRow from './ConfigMappingRow.svelte';
17
+ import { m } from '../messages/index.js';
17
18
 
18
19
  interface Props {
19
20
  interactiveState: InteractiveSwapState;
@@ -129,7 +130,7 @@
129
130
  <div class="swap-editor">
130
131
  <!-- Header -->
131
132
  <div class="swap-editor__header">
132
- <button class="swap-editor__back" onclick={onBack} aria-label="Back to node selection">
133
+ <button class="swap-editor__back" onclick={onBack} aria-label={m().layout.backToNodeSelection}>
133
134
  <Icon icon="heroicons:arrow-left" />
134
135
  </button>
135
136
  <h2 class="swap-editor__title">Swap Mapping</h2>
@@ -35,6 +35,7 @@
35
35
  import type { EndpointConfig } from '../config/endpoints.js';
36
36
  import ConnectionLine from './ConnectionLine.svelte';
37
37
  import FlowDropEdge from './FlowDropEdge.svelte';
38
+ import { m } from '../messages/index.js';
38
39
  import { getWorkflowStore, workflowActions } from '../stores/workflowStore.svelte.js';
39
40
  import { historyActions, setOnRestoreCallback } from '../stores/historyStore.svelte.js';
40
41
  import UniversalNode from './UniversalNode.svelte';
@@ -835,8 +836,8 @@
835
836
  class="flowdrop-console-toggle"
836
837
  class:flowdrop-console-toggle--active={props.consoleOpen}
837
838
  onclick={props.onToggleConsole}
838
- aria-label="Command Console (`)"
839
- title="Command Console (`)"
839
+ aria-label={m().layout.commandConsole}
840
+ title={m().layout.commandConsole}
840
841
  type="button"
841
842
  >
842
843
  <Icon icon="heroicons:command-line" width="18" height="18" />
@@ -15,6 +15,7 @@
15
15
  import MarkdownDisplay from '../MarkdownDisplay.svelte';
16
16
  import { tick } from 'svelte';
17
17
  import Icon from '@iconify/svelte';
18
+ import { m, warnDeprecatedProp } from '../../messages/index.js';
18
19
 
19
20
  // =========================================================================
20
21
  // Internal Display Message Type
@@ -35,12 +36,26 @@
35
36
  nodeTypes: NodeMetadata[];
36
37
  workflowId?: string;
37
38
  onUIAction?: (action: UIAction) => void;
39
+ /**
40
+ * @deprecated since v1.8 — use `messages.chat.placeholder`. Removed in v2.0.
41
+ */
38
42
  placeholder?: string;
39
43
  endpointConfig?: EndpointConfig | null;
40
44
  }
41
45
 
42
46
  let { nodeTypes, workflowId, onUIAction, placeholder, endpointConfig }: Props = $props();
43
47
 
48
+ // svelte-ignore state_referenced_locally — deprecation warns once per mount; later prop rebinds aren't relevant
49
+ if (placeholder !== undefined) {
50
+ warnDeprecatedProp('AIChatPanel', 'placeholder', 'messages.chat.placeholder');
51
+ }
52
+
53
+ // Hoist the chat branch — read in placeholder, header, three welcome states,
54
+ // auto-retry banner, and the send button aria-label.
55
+ const t = $derived(m().chat);
56
+
57
+ const resolvedPlaceholder = $derived(placeholder ?? t.placeholder);
58
+
44
59
  // =========================================================================
45
60
  // State
46
61
  // =========================================================================
@@ -365,18 +380,18 @@
365
380
  }
366
381
  </script>
367
382
 
368
- <div class="ai-chat-panel" role="region" aria-label="AI Chat">
383
+ <div class="ai-chat-panel" role="region" aria-label={t.aiAssistant}>
369
384
  {#if !isChatConfigured}
370
385
  <!-- No backend configured -->
371
386
  <div class="ai-chat-panel__notice">
372
387
  <Icon icon="mdi:robot-off-outline" />
373
- <span>AI Chat requires backend configuration</span>
388
+ <span>{t.requiresBackend}</span>
374
389
  </div>
375
390
  {:else if isDisabled}
376
391
  <!-- No workflow loaded -->
377
392
  <div class="ai-chat-panel__notice">
378
393
  <Icon icon="mdi:chat-sleep-outline" />
379
- <span>Load a workflow to start chatting</span>
394
+ <span>{t.loadWorkflow}</span>
380
395
  </div>
381
396
  {:else}
382
397
  <!-- Messages area -->
@@ -384,7 +399,7 @@
384
399
  {#if displayMessages.length === 0}
385
400
  <div class="ai-chat-panel__empty">
386
401
  <Icon icon="mdi:chat-outline" />
387
- <span>Ask the AI to help build your workflow</span>
402
+ <span>{t.helpBuild}</span>
388
403
  </div>
389
404
  {/if}
390
405
  {#each displayMessages as message, msgIndex}
@@ -395,7 +410,7 @@
395
410
  msgIndex === displayMessages.length - 1}
396
411
  >
397
412
  <Icon icon="mdi:autorenew" />
398
- <span>Auto-retrying (attempt {message.retryAttempt}/{MAX_AUTO_RETRIES})…</span>
413
+ <span>{t.autoRetry({ attempt: message.retryAttempt, max: MAX_AUTO_RETRIES })}</span>
399
414
  </div>
400
415
  {:else}
401
416
  <div class="ai-chat-panel__bubble ai-chat-panel__bubble--{message.role}">
@@ -443,7 +458,7 @@
443
458
  bind:value={inputValue}
444
459
  onkeydown={handleKeydown}
445
460
  class="ai-chat-panel__input"
446
- placeholder={placeholder ?? 'Describe what you want to build...'}
461
+ placeholder={resolvedPlaceholder}
447
462
  rows="1"
448
463
  disabled={isLoading}
449
464
  ></textarea>
@@ -451,7 +466,7 @@
451
466
  class="ai-chat-panel__send"
452
467
  onclick={sendMessage}
453
468
  disabled={!canSend}
454
- aria-label="Send message"
469
+ aria-label={t.send}
455
470
  >
456
471
  <Icon icon="mdi:send" />
457
472
  </button>
@@ -5,6 +5,9 @@ interface Props {
5
5
  nodeTypes: NodeMetadata[];
6
6
  workflowId?: string;
7
7
  onUIAction?: (action: UIAction) => void;
8
+ /**
9
+ * @deprecated since v1.8 — use `messages.chat.placeholder`. Removed in v2.0.
10
+ */
8
11
  placeholder?: string;
9
12
  endpointConfig?: EndpointConfig | null;
10
13
  }
@@ -1,6 +1,7 @@
1
1
  <script lang="ts">
2
2
  import type { CommandPreviewItem } from '../../types/chat.js';
3
3
  import Icon from '@iconify/svelte';
4
+ import { m } from '../../messages/index.js';
4
5
 
5
6
  interface Props {
6
7
  commands: CommandPreviewItem[];
@@ -24,9 +25,12 @@
24
25
  resolvedAction = 'cancelled';
25
26
  onCancel();
26
27
  }
28
+
29
+ // Hoist the commandPreview branch — six template reads.
30
+ const t = $derived(m().chat.commandPreview);
27
31
  </script>
28
32
 
29
- <div class="command-preview" role="region" aria-label="Command preview">
33
+ <div class="command-preview" role="region" aria-label={t.ariaLabel}>
30
34
  <div class="command-preview__list">
31
35
  {#each commands as command, i}
32
36
  <div class="command-preview__item command-preview__item--{command.status}">
@@ -54,16 +58,16 @@
54
58
  <span class="command-preview__resolved command-preview__resolved--applied">
55
59
  {#if isExecuting}
56
60
  <Icon icon="mdi:loading" />
57
- Applying…
61
+ {t.applying}
58
62
  {:else}
59
63
  <Icon icon="mdi:check-all" />
60
- Applied
64
+ {t.applied}
61
65
  {/if}
62
66
  </span>
63
67
  {:else if resolvedAction === 'cancelled'}
64
68
  <span class="command-preview__resolved command-preview__resolved--cancelled">
65
69
  <Icon icon="mdi:close" />
66
- Dismissed
70
+ {t.dismissed}
67
71
  </span>
68
72
  {:else}
69
73
  <button
@@ -72,14 +76,14 @@
72
76
  disabled={!hasPending || isExecuting}
73
77
  >
74
78
  <Icon icon="mdi:check-all" />
75
- Apply All
79
+ {t.applyAll}
76
80
  </button>
77
81
  <button
78
82
  class="command-preview__btn command-preview__btn--cancel"
79
83
  onclick={handleCancel}
80
84
  disabled={isExecuting}
81
85
  >
82
- Cancel
86
+ {t.cancel}
83
87
  </button>
84
88
  {/if}
85
89
  </div>
@@ -23,6 +23,7 @@
23
23
  import { updateSettings, getUiSettings } from '../../stores/settingsStore.svelte.js';
24
24
  import ConsoleInput from './ConsoleInput.svelte';
25
25
  import ConsoleOutput, { type ConsoleEntry } from './ConsoleOutput.svelte';
26
+ import { m } from '../../messages/index.js';
26
27
  import {
27
28
  formatListNodes,
28
29
  formatListEdges,
@@ -186,13 +187,13 @@
186
187
  }
187
188
  </script>
188
189
 
189
- <div class="command-console" role="region" aria-label="Command Console">
190
+ <div class="command-console" role="region" aria-label={m().layout.commandConsole}>
190
191
  <div class="command-console__header">
191
- <h2 class="command-console__title">Console</h2>
192
+ <h2 class="command-console__title">{m().navigation.bottomPanel.console}</h2>
192
193
  <button
193
194
  class="command-console__close"
194
195
  onclick={closeConsole}
195
- aria-label="Close console"
196
+ aria-label={m().layout.closeConsole}
196
197
  type="button"
197
198
  >
198
199
  &times;
@@ -20,6 +20,7 @@
20
20
  <script lang="ts">
21
21
  import Icon from '@iconify/svelte';
22
22
  import type { FieldSchema } from './types.js';
23
+ import { m, warnDeprecatedProp } from '../../messages/index.js';
23
24
 
24
25
  interface Props {
25
26
  /** Field identifier */
@@ -32,7 +33,9 @@
32
33
  minItems?: number;
33
34
  /** Maximum number of items allowed */
34
35
  maxItems?: number;
35
- /** Label for add button */
36
+ /**
37
+ * @deprecated since v1.8 — use `messages.form.array.add`. Removed in v2.0.
38
+ */
36
39
  addLabel?: string;
37
40
  /** Whether the field is disabled */
38
41
  disabled?: boolean;
@@ -46,11 +49,21 @@
46
49
  itemSchema,
47
50
  minItems = 0,
48
51
  maxItems,
49
- addLabel = 'Add Item',
52
+ addLabel,
50
53
  disabled = false,
51
54
  onChange
52
55
  }: Props = $props();
53
56
 
57
+ // svelte-ignore state_referenced_locally — deprecation warns once per mount; later prop rebinds aren't relevant
58
+ if (addLabel !== undefined) {
59
+ warnDeprecatedProp('FormArray', 'addLabel', 'messages.form.array.add');
60
+ }
61
+
62
+ // Hoist the array branch — every {#each} iteration would otherwise re-walk
63
+ // `m().form.array.*` for ~6 keys per item. One getter call instead of N×6.
64
+ const t = $derived(m().form.array);
65
+ const resolvedAddLabel = $derived(addLabel ?? t.add);
66
+
54
67
  /**
55
68
  * Ensure value is always an array
56
69
  */
@@ -197,7 +210,7 @@
197
210
  const itemStr = String(item);
198
211
  return itemStr.length > 30
199
212
  ? `${itemStr.substring(0, 30)}...`
200
- : itemStr || `Item ${index + 1}`;
213
+ : itemStr || t.itemLabel({ n: index + 1 });
201
214
  }
202
215
 
203
216
  // For objects, try to find a name/label/title property
@@ -211,7 +224,7 @@
211
224
  }
212
225
  }
213
226
 
214
- return `Item ${index + 1}`;
227
+ return t.itemLabel({ n: index + 1 });
215
228
  }
216
229
 
217
230
  /**
@@ -260,7 +273,7 @@
260
273
  class="form-array__item-toggle"
261
274
  onclick={() => toggleCollapse(index)}
262
275
  aria-expanded={!isCollapsed(index)}
263
- aria-label={isCollapsed(index) ? 'Expand item' : 'Collapse item'}
276
+ aria-label={isCollapsed(index) ? t.expandItem : t.collapseItem}
264
277
  >
265
278
  <Icon
266
279
  icon={isCollapsed(index) ? 'heroicons:chevron-right' : 'heroicons:chevron-down'}
@@ -280,8 +293,8 @@
280
293
  class="form-array__action-btn form-array__action-btn--move"
281
294
  onclick={() => moveItemUp(index)}
282
295
  disabled={index === 0 || disabled}
283
- aria-label="Move item {index + 1} up"
284
- title="Move up"
296
+ aria-label={t.moveItemUp({ n: index + 1 })}
297
+ title={t.moveUp}
285
298
  >
286
299
  <Icon icon="heroicons:arrow-up" />
287
300
  </button>
@@ -292,8 +305,8 @@
292
305
  class="form-array__action-btn form-array__action-btn--move"
293
306
  onclick={() => moveItemDown(index)}
294
307
  disabled={index === items.length - 1 || disabled}
295
- aria-label="Move item {index + 1} down"
296
- title="Move down"
308
+ aria-label={t.moveItemDown({ n: index + 1 })}
309
+ title={t.moveDown}
297
310
  >
298
311
  <Icon icon="heroicons:arrow-down" />
299
312
  </button>
@@ -304,8 +317,8 @@
304
317
  class="form-array__action-btn form-array__action-btn--delete"
305
318
  onclick={() => removeItem(index)}
306
319
  disabled={!canRemoveItem || disabled}
307
- aria-label="Delete item {index + 1}"
308
- title="Delete item"
320
+ aria-label={t.deleteItem({ n: index + 1 })}
321
+ title={t.delete}
309
322
  >
310
323
  <Icon icon="heroicons:trash" />
311
324
  </button>
@@ -366,7 +379,7 @@
366
379
  <span class="form-array__toggle-thumb"></span>
367
380
  </span>
368
381
  <span class="form-array__toggle-label">
369
- {item ? 'Yes' : 'No'}
382
+ {item ? t.yes : t.no}
370
383
  </span>
371
384
  </label>
372
385
  {:else if itemSchema.enum}
@@ -480,7 +493,7 @@
480
493
  <span class="form-array__toggle-thumb"></span>
481
494
  </span>
482
495
  <span class="form-array__toggle-label">
483
- {propValue ? 'Yes' : 'No'}
496
+ {propValue ? t.yes : t.no}
484
497
  </span>
485
498
  </label>
486
499
  {:else}
@@ -510,7 +523,7 @@
510
523
  <!-- Unknown complex type -->
511
524
  <div class="form-array__unsupported">
512
525
  <p>
513
- Complex item type "{itemSchema.type}" is not fully supported.
526
+ {t.unsupported({ type: String(itemSchema.type ?? '') })}
514
527
  </p>
515
528
  </div>
516
529
  {/if}
@@ -522,7 +535,7 @@
522
535
  <!-- Empty State -->
523
536
  <div class="form-array__empty">
524
537
  <Icon icon="heroicons:squares-plus" class="form-array__empty-icon" />
525
- <p class="form-array__empty-text">No items yet</p>
538
+ <p class="form-array__empty-text">{t.empty}</p>
526
539
  </div>
527
540
  {/if}
528
541
 
@@ -532,21 +545,21 @@
532
545
  class="form-array__add-btn"
533
546
  onclick={addItem}
534
547
  disabled={!canAddItem || disabled}
535
- aria-label={addLabel}
548
+ aria-label={resolvedAddLabel}
536
549
  >
537
550
  <Icon icon="heroicons:plus" />
538
- <span>{addLabel}</span>
551
+ <span>{resolvedAddLabel}</span>
539
552
  </button>
540
553
 
541
554
  <!-- Item count and limits -->
542
555
  {#if minItems > 0 || maxItems !== undefined}
543
556
  <div class="form-array__info">
544
- <span class="form-array__count">{items.length} item{items.length !== 1 ? 's' : ''}</span>
557
+ <span class="form-array__count">{t.count({ n: items.length })}</span>
545
558
  {#if minItems > 0}
546
- <span class="form-array__limit">Min: {minItems}</span>
559
+ <span class="form-array__limit">{t.min({ n: minItems })}</span>
547
560
  {/if}
548
561
  {#if maxItems !== undefined}
549
- <span class="form-array__limit">Max: {maxItems}</span>
562
+ <span class="form-array__limit">{t.max({ n: maxItems })}</span>
550
563
  {/if}
551
564
  </div>
552
565
  {/if}
@@ -10,7 +10,9 @@ interface Props {
10
10
  minItems?: number;
11
11
  /** Maximum number of items allowed */
12
12
  maxItems?: number;
13
- /** Label for add button */
13
+ /**
14
+ * @deprecated since v1.8 — use `messages.form.array.add`. Removed in v2.0.
15
+ */
14
16
  addLabel?: string;
15
17
  /** Whether the field is disabled */
16
18
  disabled?: boolean;
@@ -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 */
@@ -322,7 +323,7 @@
322
323
  class:form-code-editor__container--dark={darkTheme}
323
324
  role="textbox"
324
325
  aria-multiline="true"
325
- aria-label="JSON editor"
326
+ aria-label={m().form.code.editor}
326
327
  onblur={formatContent}
327
328
  ></div>
328
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