> ## Documentation Index
> Fetch the complete documentation index at: https://flowdrop.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# i18n & custom messages

> Override or translate every user-facing string in FlowDrop via the messages prop.

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:

```svelte theme={null}
<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:

```svelte theme={null}
<App messages={() => ({ 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 theme={null}
<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.

<Accordion title="Why the callback re-runs on locale change">
  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.
</Accordion>

## 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 theme={null}
// 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                  |
| --------------- | ------------- | ----------------------------- |
| `<SchemaForm>`  | `saveLabel`   | `messages.form.schema.save`   |
| `<SchemaForm>`  | `cancelLabel` | `messages.form.schema.cancel` |
| `<AIChatPanel>` | `placeholder` | `messages.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:

```svelte theme={null}
<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/`
