---
title: "Multilingual Templates"
description: "Multilingual Templates allow users to define and create various language mutations of the same template and switch between them as necessary."
url: https://docs.topol.io/email-editor/guide/multilingual-templates.html
---

# Multilingual Templates

> **This feature is available with the Plugin for Business plan and higher.**
>
> If you're on the Plugin for Startup or Plugin Expansion plan, consider upgrading to access this feature. For more details, visit our [pricing page](https://topol.io/pricing) or contact support.

**Multilingual Templates store several language versions (mutations) of one template in a single JSON file.** That file holds the main (Primary) template structure together with the text translations for every defined mutation, so no separate HTML and JSON files per language are needed.

When you need HTML output for a specific language mutation, send the multilingual JSON along with the selected language code to a special API endpoint. The endpoint then returns the corresponding HTML. The list of available languages for the template is stored in a `mutations` array, which is always included alongside `json` and `html` when saving a multilingual template via the `onSave` callback.

## How to Enable

To activate the Multilingual Templates feature, add the following option to your `TOPOL_OPTIONS` configuration object:

```ts
multilingual: true,
```

Defining [template language versions](https://docs.topol.io/email-editor/guide/default-settings.html#template-language-versions) through `defaultTemplateSettings.langs` turns the feature on by itself, so `multilingual` can be left out in that case.

Once enabled, the editor's Top Bar will display a Create mutation dropdown.

![Create mutation](https://docs.topol.io/create-mutation.png)

This dropdown serves as the main hub for managing all language variations in the Plugin UI. It allows you to create, switch, and delete mutations, or change which mutation is marked as Primary.

## Workflow Example

When the editor opens with the Multilingual feature enabled, the user can begin in two ways:

-   Design the template first and then create a language version (mutation), or
-   Create a mutation first and then start designing.

In both cases, the current template becomes linked to that mutation.

Once the first language version is ready, the user can add another language and select it. Initially, the new mutation looks identical because no content in that language has been added yet. While that language is active, the user can rewrite the content in the chosen language. This process can be repeated for any number of languages. When switching to a mutation that already has content, the editor instantly loads that version.

One language must always be marked as Primary, serving as the default version. The Primary language cannot be deleted, but its role can be reassigned to another mutation at any time. The Primary flag is especially important when saving the template (explained later). If a non-Primary mutation is deleted, the editor automatically returns to the Primary version.

> **AI-Powered Translation**
>
> The [Template Translation](./template-translation.html) feature translates template content into any language mutation using AI. When Multilingual Templates is enabled, a translate button appears next to each language in the language settings panel.

## Saving Multilingual Templates

When the user saves the multilingual template via the default or custom Save button, the `onSave` callback **always** receives four parameters: `json`, `html`, `mutations`, and `syncedSections`.

#### onSave callback structure:

```ts
const TOPOL_OPTIONS = {
  callbacks: {
    onSave(json, html, mutations, syncedSections) {
      // Implement your save logic here.
      // If multiple mutations exist,
      // define how to store each language version.
    },
  },
};
```

If your `onSave` callback integration currently only handles `json` and `html`, you must extend it to also process the `mutations` parameter in order to support multilingual templates feature.

Even if no mutations exist (or if the Multilingual feature is not enabled), the `mutations` parameter will still be passed. However, in these scenarios, it will be an empty array `[]`, which **can be ignored**.

The fourth `syncedSections` parameter is documented in detail in the [Callbacks reference](https://docs.topol.io/email-editor/guide/callbacks.html#onsave-json-html-mutations-syncedsections). It is an array of synced section **IDs** (`number[]`), so the matching definitions have to be fetched on your side. For multilingual templates that use synced sections, pass that content to the [Convert JSON template to HTML](https://docs.topol.io/email-editor/guide/operations/convertJson2Html.html) endpoint when rendering per-language HTML, otherwise translations defined on the synced section will not be applied. See [Synced Sections in Multilingual Templates](#synced-sections-in-multilingual-templates) below.

### Parameter details:

In this section, we will provide the necessary details for the `json`, `html`, and `mutations` parameters of the `onSave` callback. For the `syncedSections` parameter, see the [Callbacks reference](https://docs.topol.io/email-editor/guide/callbacks.html#onsave-json-html-mutations-syncedsections).

#### html

The HTML returned depends on how the save action is triggered:

#### 1\. Saving with the default Save button

The `onSave` callback **always returns only the HTML of the Primary mutation**. Even if other mutations exist, they are not included.

To retrieve HTML in a specific Language, call our [Convert JSON template to HTML](https://docs.topol.io/email-editor/guide/operations/convertJson2Html.html) API. Jump to [this section](https://docs.topol.io/email-editor/guide/multilingual-templates.html#api-for-retrieving-html-in-a-specific-language) for more instructions.

#### 2\. Saving with your custom Save button

If you implement **your own Save button**, it must call this function:

```ts
// The lang property is a language code and is optional
window.TopolPlugin.save({ lang: "de" });
```

> **WARNING**
>
> The argument is an **object**, not a bare string. `TopolPlugin.save("de")` is silently treated as a call with no language and returns the Primary mutation's HTML.

This call then triggers the usual `onSave(json, html, mutations)` callback, but the optional `lang` property gives you control over the HTML output:

-   If you **include** the `lang` parameter, you **receive the HTML output of that specific mutation**.

-   If the `lang` parameter is **omitted**, the response holds **only the HTML output of the Primary mutation**.


> **INFO**
>
> The `lang` value has to be known before calling `window.TopolPlugin.save({ lang })`.
>
> To handle this, create your own mechanism that lets users choose a language, such as a dropdown or selector. Store the selected language in your application state, and when the user clicks your custom save button, pass that stored value into `window.TopolPlugin.save({ lang })`.
>
> The `mutations` array, which contains information about available language mutations, is only returned inside the `onSave(json, html, mutations)` callback. Because this happens **after** the `window.TopolPlugin.save({ lang })` call, you cannot rely on the mutations array to decide which language to save.

> **WARNING**
>
> When saving a multilingual template, you may be tempted to call `window.TopolPlugin.save({ lang })` inside the `onSave(json, html, mutations)` callback for every language in the received `mutations` array. However, **this approach is strongly discouraged**. Each call to `window.TopolPlugin.save({ lang })` triggers another `onSave` callback, which would create an **infinite loop**.

#### json

The `json` parameter matches the structure of the Primary mutation. It also includes a special `langs` array that holds **all defined language mutations and their translations**.

#### Example:

```ts
// ...Primary mutation JSON
"langs": [
  {
    "key": "en",        // Language code
    "primary": true,    // false if not the primary mutation
    "translations": {   // All translated blocks for this mutation
      "ZF-vfFO4c": {    // UID of a block (same as in the primary)
        "content": "<p>Content in English.</p>" // Its content in this mutation
      },
      "w5ubrEmMN": {    // UID of another block (same as in the primary)
        "content": "Click me!", // Its content in this mutation
        "attributes": { // Its attributes in this mutation
          "href": "https://google.com"
        }
      },
      "mj-subject": {   // Email subject line for this mutation
        "content": "Welcome to our newsletter!"
      },
      "mj-preview": {   // Preheader text for this mutation
        "content": "Everything new this month"
      }
      // ...other translated blocks in this mutation
    }
  },
  // ...other language mutations
]
```

This structure ensures that all language-specific content is stored on one place (in this multilingual JSON file) alongside the Primary template.

#### mutations

The `mutations` parameter is always an **array containing information about all language mutations defined in the current multilingual JSON**. Each object inside this array holds only the language code and whether this language is Primary.

#### Data structure:

```ts
mutations: {
  lang: string,
  primary: boolean
}[]
```

#### Example:

```ts
[
  {
    "lang": "en", // language code
    "primary": true
  },
  {
    "lang": "de",
    "primary": false
  },
  // ...other mutations stored in multilingual JSON
]
```

## API for Retrieving HTML in a Specific Language

In order to retrieve the HTML version of a specific language from a Multilingual template, call our [Convert JSON template to HTML](https://docs.topol.io/email-editor/guide/operations/convertJson2Html.html) API.

> **INFO**
>
> This API call requires authorization using your Secret Key, which must be included in the `X-Secret-Key` header. Follow [this guide](https://docs.topol.io/email-editor/guide/how-to-get-secret-key.html) to learn how to obtain your Secret Key.

In the POST request, you must include the JSON code of your Multilingual template and the requested language (using the language code from the `mutations` array), along with all other required parameters.

The response is an object containing the HTML code of the template in the specified language.

## External Language Management

If you want to manage language mutations from your own UI (outside the editor iframe), you can use the Plugin's public API methods for full programmatic control and register callbacks to keep your UI synchronized with the editor's internal state.

### Available Methods

| Method | Description |
| --- | --- |
| `TopolPlugin.createLanguage(lang)` | Creates a new language mutation |
| `TopolPlugin.selectLanguage(lang)` | Switches the active editing language |
| `TopolPlugin.setPrimaryLanguage(lang)` | Changes the primary language |
| `TopolPlugin.deleteLanguage(lang)` | Deletes a language mutation (shows confirmation modal) |
| `TopolPlugin.getMutations()` | Requests the current list of mutations via `onGetMutations` callback |
| `TopolPlugin.translateLanguage(lang, sourceLang?)` | AI-translates a single mutation (from the primary language by default, or from `sourceLang`) |
| `TopolPlugin.translateAllLanguages()` | AI-translates every non-primary mutation from the primary language |

### State Synchronization Callbacks

Register these callbacks in your `TOPOL_OPTIONS` to receive notifications when the language state changes:

| Callback | Parameters | Fires when |
| --- | --- | --- |
| `onLanguageCreated` | `(lang, mutations)` | A new mutation is created |
| `onLanguageDeleted` | `(lang, mutations)` | A mutation is deleted |
| `onLanguageSelected` | `(lang)` | The active editing language changes |
| `onPrimaryLanguageChanged` | `(lang, mutations)` | The primary language changes |
| `onGetMutations` | `(mutations)` | `TopolPlugin.getMutations()` is called |

The `mutations` parameter is always an array of `{ lang: string, primary: boolean }` objects representing the current state of all language mutations. Note that the mutation objects use `lang`, while the `langs` array inside the saved JSON uses `key` for the same value.

These callbacks fire for both UI-initiated and API-initiated actions, so your external UI stays synchronized regardless of how the change was triggered.

> **INFO**
>
> `onLanguageSelected` also fires as a side effect of other actions, because creating a mutation, deleting one, or changing the Primary language all change which mutation is active. Treat it as a report of the current editing language rather than as a signal that the user picked one.

### Example: External Language Selector

```ts
const TOPOL_OPTIONS = {
  multilingual: true,
  callbacks: {
    onLanguageCreated(lang, mutations) {
      // A new language was added, so refresh the dropdown options
      updateLanguageDropdown(mutations);
    },
    onLanguageDeleted(lang, mutations) {
      // A language was removed, so refresh the dropdown options
      updateLanguageDropdown(mutations);
    },
    onLanguageSelected(lang) {
      // The active language changed, so highlight it in the selector
      setActiveLanguage(lang);
    },
    onPrimaryLanguageChanged(lang, mutations) {
      // The primary language changed, so move the primary badge
      updateLanguageDropdown(mutations);
      setActiveLanguage(lang);
    },
    onGetMutations(mutations) {
      // Initial load: populate the language selector
      updateLanguageDropdown(mutations);
    },
    onInit() {
      // Request the current mutations when the editor is ready
      TopolPlugin.getMutations();
    },
  },
};

// Switch language from your external UI
document.querySelector("#lang-selector").addEventListener("change", (e) => {
  TopolPlugin.selectLanguage(e.target.value);
});
```

For detailed callback parameter documentation, see the [Language State Callbacks](https://docs.topol.io/email-editor/guide/callbacks.html#language-state-callbacks) section.

## Synced Sections in Multilingual Templates

When a multilingual template contains [synced sections](https://docs.topol.io/email-editor/guide/saved-blocks.html#enabling-synced-sections), the language mutations and the synced section content come from two different places and have to be reconciled at render time.

### Where the translations live

-   **The template's own translations** live in the saved JSON, under `langs[].translations`, keyed by block UID - exactly as described in the [json](#json) section above. Each mutation has its own copy.
-   **The synced section's translations** live on the synced section entity itself, in its `translation` field - a locale-keyed map keyed by the synced section's _own_ block UIDs. There is a single copy, shared by every template that references the synced section.

When the canvas renders, the editor pulls the synced section's `translation` map, remaps the synced section's UIDs to the new UIDs generated inside the template, and merges the translated content into each mutation. The result is that **every mutation that already exists in the template gets translated content for the synced section in its own language**, as long as the synced section provides a `translation` entry for that locale.

### What integrators must do at HTML render time

When the user inserts a synced section into a multilingual template, the editor immediately merges the synced section's per-locale translations into the template's own `langs[].translations` (mapped onto freshly generated block UIDs). The `json` from `onSave` therefore already contains a snapshot of the synced section's translations alongside the regular `syncedId` reference on the section node. The fourth `syncedSections` argument of the same callback lists the synced section IDs used in the template, which saves walking the JSON to discover which sections need fetching at render time.

That snapshot is point-in-time. So that each render reflects the latest copy of the synced section, the [Convert JSON template to HTML](https://docs.topol.io/email-editor/guide/operations/convertJson2Html.html) endpoint re-resolves the synced section on every call:

1.  Look up each referenced synced section by `syncedId` in the `syncedSections` array supplied in the request.
2.  Drop any existing `langs[].translations` entries that belong to translatable blocks inside the placeholder section (so the snapshot does not stick around).
3.  Inline the synced section's MJML definition into the template, regenerating child block UIDs.
4.  For each translatable block in the inlined section, merge the appropriate locale's entry from the synced section's `translation` map into every mutation's `langs[].translations`.
5.  Render the resulting MJML to HTML for the requested language.

Omitting a referenced synced section from the `syncedSections` request field skips that section entirely: it keeps its existing children and its snapshotted `langs[].translations`, so it renders the point-in-time copy captured when the section was inserted. Nothing breaks, but the rendered HTML no longer matches the latest synced section content.

### Locale coverage inside a synced section

A synced section's `translation` map does not have to contain every locale that the host template defines. The rendering pipeline handles missing entries differently depending on whether the mutation is the primary one:

-   **Primary mutation, locale missing from the synced section's `translation`**: falls back to the first locale present in the synced section's translation map. This is the source-of-truth content for the section.
-   **Non-primary mutation, locale missing from the synced section's `translation`**: gets an empty translation entry (`{}`). An empty entry carries no content, so at render time the block falls back to the Primary mutation's content for that block.

Plan your translation coverage accordingly. If you only translate synced sections into a subset of the languages used by your templates, the untranslated mutations display the Primary mutation's copy of the section, which is itself the synced section's first available locale.

### What happens when a mutation is added after a synced section

If you create a new language mutation in a template that already contains a synced section, the new mutation starts with no entries for the section's blocks. At the next `convertJson2Html` call, the server will populate the mutation according to the rules above: it uses the synced section's translation for that locale if one exists, or leaves the entry empty otherwise. This means you can roll out a new language to a synced section centrally and every template that uses it will pick up the new translation on its next render without needing to be edited.

## Custom Language Preset

By default, the language selection dropdown shows Topol's predefined set of languages. If you want to customize this list, you can define your own languages by adding the `customLanguagePreset` option to your `TOPOL_OPTIONS` configuration object.

The `customLanguagePreset` object supports two properties:

1.  **`langs`** - an array of language definitions. Each language must include:

-   `name` - the display name shown in the dropdown
-   `code` - the language code used internally (for example, en, en-US, pt-BR)
-   `nativeName` - the language name written in its native form

2.  **`override`** - a boolean that controls how your custom languages are applied:

-   `true` - replaces the default language list (default)
-   `false` - appends your custom languages to the default list

```ts
customLanguagePreset: {
  langs: [
    {
      name: "Custom name",
      code: "custom-code",
      nativeName: "Custom native name",
    },
    // ...other custom languages
  ],
  override: true, // defaults to true
}
```

**Example:**

```ts
customLanguagePreset: {
  langs: [
    { name: "Spanish (Spain)", code: "es-ES", nativeName: "Español (España)" },
    { name: "Spanish (Mexico)", code: "es-MX", nativeName: "Español (México)" },
    { name: "Portuguese (Brazil)", code: "pt-BR", nativeName: "Português (Brasil)" },
    { name: "Portuguese (Portugal)", code: "pt-PT", nativeName: "Português (Portugal)" },
  ],
  override: true,
}
```

This example configuration replaces the default language list with a custom set of regional Spanish and Portuguese variants, giving users more precise language options when creating templates.

> **WARNING**
>
> Each of `name`, `code`, and `nativeName` is required on every entry, and an invalid `langs` array fails validation as a whole: the editor keeps its default language list and shows an error notification. Entries sharing a `code` are collapsed to one, and only `override: false` appends, so any other value replaces the defaults.

> **Default Template Languages**
>
> This feature works nicely with Default Template Settings, where you can define which languages are automatically created for every new template. For more details, see the documentation [here](https://docs.topol.io/email-editor/guide/default-settings.html#template-language-versions).
