Skip to main content
Every user-facing label, tooltip, placeholder, and notice in FlowDrop is rendered from a single typed Messages tree. You can override any subset by passing a messages callback to <App>. Wire that callback to your i18n library and locale changes propagate into FlowDrop without a subscription. FlowDrop is not an i18n library. It is a consumer of one. Translations live in your app, alongside the rest of your UI copy.

Quick start

Override a single string with a value:
<script>
  import { App } from '@flowdrop/flowdrop';
</script>

<App messages={{ form: { schema: { save: 'Apply' } } }} />
messages is DeepPartial<Messages> — every key is optional, missing keys fall through to the English defaults. You can also pass a callback. The two forms are equivalent for static overrides; the callback is useful when your translations come from a function call you’d rather not invoke unless the prop is actually read:
<App messages={() => ({ form: { schema: { save: 'Apply' } } })} />

Translating with paraglide-js

Paraglide-js compiles your .json translation files into typed message functions. Wire them into FlowDrop’s messages prop:
<script>
  import { App } from '@flowdrop/flowdrop';
  import * as p from '$lib/paraglide/messages';

  const messages = () => ({
    common: {
      save: p.flowdrop_common_save(),
      cancel: p.flowdrop_common_cancel()
    },
    form: {
      array: {
        moveUp: p.flowdrop_form_array_moveUp(),
        moveDown: p.flowdrop_form_array_moveDown()
      }
    },
    interrupt: {
      // Parameterised messages take a typed params object.
      responseSubmittedBy: ({ name }) => p.flowdrop_interrupt_responseSubmittedBy({ name })
    }
  });
</script>

<App {messages} />
paraglide-js is one option — sveltekit-i18n, typesafe-i18n, or any reactive store will work. The contract is just: a callback returning a partial tree.
Passing messages as a callback (not a plain object) is what makes locale switching reactive. Paraglide’s reactive locale store causes Svelte to re-evaluate the expression; FlowDrop’s root then re-derives the merged message tree and re-renders.

The Messages shape

The full default tree lives at libs/flowdrop/src/lib/messages/defaults.ts and is exported as defaultMessages from @flowdrop/flowdrop/core. The shape is grouped by domain, not by component — file paths churn, domains don’t.
BranchWhat it covers
commonGeneric verbs reused everywhere: save, cancel, confirm, close, delete, yes, no.
formAll form components — array.* (move/delete/empty/limits), markdown.* (toolbar, status bar, placeholder), autocomplete.*, field.required, toggle.{enabled,disabled}, schema.{save,cancel,empty}, code.editor (JSON), template.editor (Mustache).
interruptInline interrupt prompts — confirmation.*, choice.* (with parameterised counter), review.* (accept/reject/diff/summary), text.* (placeholder, min, submit), form.*, bubble.* (per-kind required/submitted labels, retry, cancel). Plus shared responseSubmitted / responseSubmittedBy({ name }).
chatAI Assistant panel: aiAssistant, placeholder, send, autoRetry({ attempt, max }), plus commandPreview.* (apply / cancel / status).
playgroundPlayground chat: chat.{placeholder, predefinedRun}, states.* (welcome screens), actions.* (run/stop/send), roles.* (you/assistant/system/log/message), messageTooltips.*, sessions.* (list, empty, delete confirm, relative timestamps).
nodes.notesNotesNode placeholder, type names (info/warning/success/error/note), processing/error indicators, configure tooltip.
nodes.graphSvelteFlow node and port aria-labels — workflowNode({ name }), gatewayNode({ title }), ideaNode({ title }), connectInputPort({ name }), connectOutputPort({ name }), connectBranch({ name }).
navigationNavbar branding (appName, tagline), connection indicator, settings button, default primary action labels (save, export, import, workflowSettings), right-sidebar panel titles (workflowSettingsPanelTitle, workflowSettingsPanelSubtitle, nodeConfigDescription), close affordances (closeSettings, closeConfigModal, copyId), and bottom-panel tab labels (bottomPanel.console, bottomPanel.chat).
layoutSidebar/canvas landmarks (componentsSidebar, workflowCanvas, executionLogs, settingsCategories), search input (searchComponents), command console (commandConsole, closeConsole), resize handles (resizeLeftSidebar, resizeRightSidebar, resizeBottomPanel), sidebar toggle (expandSidebar, collapseSidebar), modal close affordances, swap workflow (swapNode, backToConfiguration, backToNodeSelection), and loadSession({ name }).
statuspipeline.* (refresh/view-logs/breadcrumbs) and overlay.* (NodeStatusOverlay tooltip and detail labels).
Parameterised entries are functions, not template strings:
// From defaults
interrupt: {
  responseSubmittedBy: ({ name }: { name: string }) => `Response submitted by ${name}`,
  choice: {
    selectedCount: ({ n, total }: { n: number; total: number }) => `${n} of ${total} selected`
  }
}
When you override a parameterised entry, you must supply a function with the same signature. Call sites invoke it with the params object, so a plain string would throw at runtime. If your translation doesn’t need the params, ignore them: ({ n: _n }) => 'Move up'.

Removed label props

Several components used to accept individual *Label props that duplicated the messages tree. These duplicates are removed — use the corresponding messages key instead:
ComponentRemoved propReplace with
<SchemaForm>saveLabelmessages.form.schema.save
<SchemaForm>cancelLabelmessages.form.schema.cancel
<AIChatPanel>placeholdermessages.chat.placeholder
<FormToggle>’s onLabel/offLabel and <FormArray>’s addLabel are not removed. They express per-instance labels the global messages system cannot (a toggle’s “Hidden”/“Visible”, a schema-derived “Add Header” button) and are documented overrides. Workflow-level overrides on interrupt configs (config.confirmLabel, config.acceptAllLabel, etc.) are not removed either — those are runtime data from the workflow author, not component-prop API. They keep their priority over the messages defaults.

Components used outside the <App> provider

If you mount a flowdrop component (e.g. <SchemaForm> standalone, or a Storybook story) outside the root <App>, it falls back silently to the English defaultMessages. There is no error. The trade-off: a missing provider always renders English regardless of locale. To get translations in standalone usage, set up the messages context yourself:
<script>
  import { setMessages, defaultMessages, mergeMessages } from '@flowdrop/flowdrop/core';

  setMessages(() => mergeMessages(defaultMessages, { /* ...overrides */ }));
</script>
setMessages accepts a getter so reactive overrides propagate the same way they do under <App>.

Reference

  • Defaults: libs/flowdrop/src/lib/messages/defaults.ts
  • Public types: Messages, MessagesOverride (re-exported from @flowdrop/flowdrop)
  • Source: libs/flowdrop/src/lib/messages/