Skip to content

Internationalization ​

The editor UI supports locale switching. All labels, tooltips, placeholders, and messages are driven by translation keys — no hardcoded strings.

Setting the locale ​

Pass the locale option to init():

ts
import { init } from '@templatical/editor';

const editor = await init({
  container: '#editor',
  locale: 'de',
});

Built-in locales ​

CodeLanguage
enEnglish (default)
deGerman
pt-BRPortuguese (Brazil)
esSpanish
caCatalan

Locale resolution ​

The editor normalizes locale codes by stripping region suffixes:

InputResolved
'en'en
'en-US'en
'en-GB'en
'de-AT'de
'fr'en (unsupported, falls back to English)

If the resolved locale is not supported, the editor falls back to English silently.

Async loading ​

Locale files are loaded asynchronously using dynamic import(). Only the active locale is bundled into the client — the other locale files are not included in your build. This means switching locales at runtime requires re-initializing the editor:

ts
async function switchLocale(newLocale: string) {
  editor.unmount();
  editor = await init({
    container: '#editor',
    locale: newLocale,
  });
}

How translations work ​

Translations are nested objects organized by UI section:

ts
{
  blocks: {
    paragraph: 'Paragraph',
    image: 'Image',
    button: 'Button',
    // ...
  },
  toolbar: {
    duplicate: 'Duplicate',
    delete: 'Delete',
    // ...
  },
  blockSettings: {
    spacing: 'Spacing',
    padding: 'Padding',
    // ...
  },
  templateSettings: {
    layout: 'Layout',
    // ...
  },
}

Some strings support placeholder interpolation using {placeholder} syntax:

ts
{
  header: {
    templatesUsed: '{used}/{max} templates used',
  },
}

Contributing a new locale ​

To add a new language:

  1. Copy packages/editor/src/i18n/locales/en.ts to a new file (e.g., fr.ts)
  2. Translate all string values, keeping the same key structure, and annotate the object with typeof en (see the example below)
  3. Run pnpm run typecheck — the typeof en annotation makes missing, extra, or misnested keys fail at compile time
  4. Run pnpm run test to verify placeholder parity — tests check that every {placeholder} token in the English strings also appears in your translations

There is no registration step. The supported-locale list is derived from the files in locales/ at build time, so dropping in fr.ts is all it takes — init({ locale: 'fr' }) picks it up automatically.

Example structure for a new locale:

ts
// packages/editor/src/i18n/locales/fr.ts
import type en from './en';

const fr: typeof en = {
  blocks: {
    paragraph: 'Paragraphe',
    image: 'Image',
    button: 'Bouton',
    section: 'Section',
    divider: 'Séparateur',
    spacer: 'Espacement',
    // ... all keys from en.ts
  },
  toolbar: {
    duplicate: 'Dupliquer',
    delete: 'Supprimer',
    // ...
  },
  // ... all sections from en.ts
};

export default fr;

Cloud strings are optional ​

Translations are split into two chunks. The OSS chunk (locales/*.ts) covers everything in the open-source editor. A separate cloud chunk (locales/cloud/*.ts) covers features only available via initCloud() — AI, comments, collaboration, scoring, snapshots, plan limits. You don't need to translate the cloud chunk: if locales/cloud/fr.ts doesn't exist, cloud features fall back to English while the rest of the editor renders in French.

Submit a pull request with your translation file. Contributions for any language are welcome.