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():
import { init } from '@templatical/editor';
const editor = await init({
container: '#editor',
locale: 'de',
});Built-in locales
| Code | Language |
|---|---|
en | English (default) |
de | German |
pt-BR | Portuguese (Brazil) |
es | Spanish |
ca | Catalan |
fr | French |
nl | Dutch |
Locale resolution
The editor normalizes locale codes by stripping region suffixes:
| Input | Resolved |
|---|---|
'en' | en |
'en-US' | en |
'en-GB' | en |
'de-AT' | de |
'fr-BE' | fr |
'it' | 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:
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:
{
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:
{
header: {
templatesUsed: '{used}/{max} templates used',
},
}Contributing a new locale
To add a new language:
- Copy
packages/editor/src/i18n/locales/en.tsto a new file named after your locale code (e.g.<locale>.ts) - Translate all string values, keeping the same key structure, and annotate the object with
typeof en(see the example below) - Run
pnpm run typecheck— thetypeof enannotation makes missing, extra, or misnested keys fail at compile time - Run
pnpm run testto 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 <locale>.ts is all it takes — init({ locale: '<locale>' }) picks it up automatically.
Example structure for a new locale:
// packages/editor/src/i18n/locales/<locale>.ts
import type en from './en';
const translations: typeof en = {
blocks: {
paragraph: '…',
image: '…',
button: '…',
section: '…',
divider: '…',
spacer: '…',
// ... all keys from en.ts
},
toolbar: {
duplicate: '…',
delete: '…',
// ...
},
// ... all sections from en.ts
};
export default translations;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, collaboration, template scoring, the lint save gate, and plan limits. You don't need to translate the cloud chunk: if locales/cloud/<locale>.ts doesn't exist, cloud features fall back to English while the rest of the editor renders in your language.
Submit a pull request with your translation file. Contributions for any language are welcome.