--- title: "i18n & custom messages" description: "Override or translate every user-facing string in FlowDrop via the messages prop." source: https://flowdrop.io/docs/editor/i18n site: FlowDrop documentation --- # i18n & custom messages 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 ``. 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: ```svelte ``` `messages` is `DeepPartial` — 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: ```svelte ({ form: { schema: { save: 'Apply' } } })} /> ``` ## Translating with paraglide-js [Paraglide-js](https://inlang.com/m/gerre34r/library-inlang-paraglideJs) compiles your `.json` translation files into typed message functions. Wire them into FlowDrop's `messages` prop: ```svelte ``` `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. | Branch | What it covers | | ------------- | ------------------------------------------------------------------------------------ | | `common` | Generic verbs reused everywhere: `save`, `cancel`, `confirm`, `close`, `delete`, `yes`, `no`. | | `form` | All 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). | | `interrupt` | Inline 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 })`. | | `chat` | AI Assistant panel: `aiAssistant`, `placeholder`, `send`, `autoRetry({ attempt, max })`, plus `commandPreview.*` (apply / cancel / status). | | `playground` | Playground 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.notes` | NotesNode placeholder, type names (info/warning/success/error/note), processing/error indicators, configure tooltip. | | `nodes.graph` | SvelteFlow node and port aria-labels — `workflowNode({ name })`, `gatewayNode({ title })`, `ideaNode({ title })`, `connectInputPort({ name })`, `connectOutputPort({ name })`, `connectBranch({ name })`. | | `navigation` | Navbar 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`). | | `layout` | Sidebar/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 })`. | | `status` | `pipeline.*` (refresh/view-logs/breadcrumbs) and `overlay.*` (NodeStatusOverlay tooltip and detail labels). | Parameterised entries are functions, not template strings: ```ts // 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: | Component | Removed prop | Replace with | | --------------- | ------------- | ----------------------------- | | `` | `saveLabel` | `messages.form.schema.save` | | `` | `cancelLabel` | `messages.form.schema.cancel` | | `` | `placeholder` | `messages.chat.placeholder` | ``'s `onLabel`/`offLabel` and ``'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 `` provider If you mount a flowdrop component (e.g. `` standalone, or a Storybook story) outside the root ``, 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: ```svelte ``` `setMessages` accepts a getter so reactive overrides propagate the same way they do under ``. ## Reference - Defaults: `libs/flowdrop/src/lib/messages/defaults.ts` - Public types: `Messages`, `MessagesOverride` (re-exported from `@flowdrop/flowdrop`) - Source: `libs/flowdrop/src/lib/messages/`