---
title: "Blok i18n API — translate the editor UI"
description: "Supply translations for tool names, toolbar labels, and the accessibility strings Blok renders."
source: https://blokeditor.com/docs/i18n-api/
lastmod: 2026-09-08
---

Framework JavaScript

Extending & system I18n

On this page i18n.t(dictKey, vars?)

# i18n API: translate Blok's interface

Internationalization support for translating UI strings. The runtime `i18n.update()` mutator switches language in place.

The locale catalogue itself ships as a separate published entry point, `@bloklabs/core/locales`. Only English is bundled, and the other 68 locales load on demand.

Use `normalizeLocale()` as the pre-flight check for a locale you did not hard-code. `i18n.update({ locale })` with an unsupported tag keeps the current locale and warns on the console instead of throwing.

Last updated Sep 8, 2026 [Edit this page on GitHub](https://github.com/JackUait/blok/blob/main/docs/src/components/api/api-data.ts)

### Reaching the editor instance

The methods below run on the editor you created with new Blok(). They are available once editor.isReady resolves.

TypeScript

```
// You already hold the instance returned by the constructor.
const editor = new Blok({ holder: 'editor' });
await editor.isReady;

// Call any API method on it.
editor.caret.setToLastBlock('end');
```

## Methods

### i18n.t(dictKey, vars?)

string

Translate a key from the global dictionary, optionally interpolating string or number values.

When to use

Look up a UI string from the active dictionary inside a tool, so your tool localises with the editor.

TypeScript

```
const text = editor.i18n.t('toolNames.text');
console.log(text); // 'Text' (or translated string)

const limit = editor.i18n.t('tools.image.emptyMaxSize', { size: '10 MB' });
console.log(limit); // 'max 10 MB' (or translated string)
```

### i18n.has(dictKey)

boolean

Check if a translation exists for the given key.

When to use

Check a key exists before translating. That lets you fall back gracefully when a tool ships partial dictionaries.

TypeScript

```
if (editor.i18n.has('toolNames.text')) {
  const translation = editor.i18n.t('toolNames.text');
}
```

### i18n.getEnglishTranslation(key)

string

Get the English translation for a key (used for multilingual search).

When to use

Returns the English value regardless of locale. Use it to index content for multilingual search.

TypeScript

```
const english = editor.i18n.getEnglishTranslation('toolNames.heading');
console.log(english); // 'Heading'
```

### i18n.getLocale()

string

Get the active locale code (e.g. 'en').

When to use

Returns the active locale code (e.g. `'en'`) so a tool can branch on the editor's current language.

TypeScript

```
const locale = editor.i18n.getLocale();
console.log(locale); // 'en'
```

### i18n.getDirection()

'ltr' | 'rtl'

Get the text direction currently in effect. It is derived from the active locale, unless an explicit `direction` override was set. This lives on the editor instance only.

The `api.i18n` handed to tools carries just `t`, `has`, `getEnglishTranslation` and `getLocale`. So a tool must take the direction from the host, either its own config or the `i18n:changed` event, rather than calling this.

When to use

Returns `'ltr'` or `'rtl'` for the active locale, so the host UI around the editor can mirror itself alongside it.

TypeScript

```
if (editor.i18n.getDirection() === 'rtl') {
  // mirror your own chrome next to the editor
}
```

### i18n.update({ locale?, messages?, direction? })

Promise<void>

Switch language at runtime. `config.i18n` is otherwise read once during boot, so a host with a language switcher had to recreate the editor to relabel it. That threw away caret, focus, selection and undo history. `update()` relabels in place instead: no recreation, nothing lost.

- `locale` accepts any supported code, or `'auto'` to re-run browser detection.
- `messages` merges host overrides over the locale dictionary. It is re-applied automatically after every later locale change, so a bare locale flip never silently drops your custom strings.
- `direction` overrides the direction implied by the locale, which you normally do not need.

Calls are serialized internally, so lazily-loaded locale chunks cannot land out of order. The last call wins.

The scope is everything.

- UI built on demand (block settings, the convert menu, notifications, screen-reader announcements) picks up the new locale the next time it opens.
- UI stamped up front (toolbar and plus-button labels, tooltips, the toolbox list) is relabelled immediately.
- Block content is repainted from your data: placeholders, media-toolbar labels, cell controls, anything a tool resolved while rendering. That includes tools that know nothing about locale changes.

The repaint is invisible to you. `onChange` does not fire, scroll is kept, and the caret returns to the block that had it.

Fires the `i18n:changed` event with `{ locale, direction }`. It is available synchronously after construction, and a call made before `isReady` is applied once the editor has booted.

`update()` and `getDirection()` are exposed on the editor instance only, not on the `api.i18n` handed to tools, which carries just `t`, `has`, `getEnglishTranslation` and `getLocale`. So a third-party tool cannot flip the host's locale.

The React, Vue and Angular adapters drive it reactively: change the `i18n` prop or input and the editor follows in place.

Note that `defaultLocale` is not accepted. It only decides the fallback while resolving the initial locale.

When to use

Switches locale and host message overrides at runtime, relabelling the editor in place. A language switcher no longer has to recreate the editor and throw away caret, focus and undo history.

TypeScript

```
// Host language switcher — no remount, caret and undo survive.
await editor.i18n.update({ locale: 'ru' });

// Locale plus your own overrides on top of it.
await editor.i18n.update({
  locale: 'fr',
  messages: { 'toolNames.text': 'Paragraphe' },
});

// Follow the browser again.
await editor.i18n.update({ locale: 'auto' });

editor.events.on('i18n:changed', ({ locale, direction }) => {
  document.documentElement.dir = direction;
});
```

### normalizeLocale(tag)

SupportedLocale | null

From `@bloklabs/core/locales`. Normalizes an arbitrary BCP-47 language tag to a supported Blok locale, and returns `null` when the tag is unsupported. The tag can be region-tagged (`'en-US'`), script-tagged (`'zh-Hant'`) or aliased (`'nb'` to `'no'`, `'ckb'` to `'ku'`).

The same normalizer runs on browser detection and on explicit `config.i18n.locale` or `i18n.update({ locale })`. So a `null` here is exactly the tag that `update()` would refuse: it keeps the current locale and warns on the console rather than throwing.

TypeScript

```
import { normalizeLocale } from '@bloklabs/core/locales';

// 'en-US' -> 'en'; null when the tag is not supported
const code = normalizeLocale(navigator.language);

if (code !== null) {
  await editor.i18n.update({ locale: code });
}
```

### loadLocale(code)

Promise<LocaleConfig>

From `@bloklabs/core/locales`. Loads one locale on demand. Only English is bundled. The other 68 are fetched when asked for.

TypeScript

```
import { loadLocale } from '@bloklabs/core/locales';

const fr = await loadLocale('fr');
```

### preloadLocales(codes)

Promise<void>

From `@bloklabs/core/locales`. Loads several locales up front, for example the ones your language switcher offers. A later switch then does not wait on a fetch.

TypeScript

```
import { preloadLocales } from '@bloklabs/core/locales';

await preloadLocales(['fr', 'de', 'ru']);
```

### buildRegistry(codes)

Promise<LocaleRegistry>

From `@bloklabs/core/locales`. Loads the given codes and returns them together as a `LocaleRegistry`.

TypeScript

```
import { buildRegistry } from '@bloklabs/core/locales';

const registry = await buildRegistry(['en', 'ru']);
```

### getLocaleSync(code)

LocaleConfig | undefined

From `@bloklabs/core/locales`. Returns an already-loaded locale synchronously, or `undefined` when it has not been loaded yet.

TypeScript

```
import { getLocaleSync, loadLocale } from '@bloklabs/core/locales';

const ru = getLocaleSync('ru') ?? await loadLocale('ru');
```

### getDirection(code)

'ltr' | 'rtl'

From `@bloklabs/core/locales`. Text direction for a locale CODE. This is not the same function as `editor.i18n.getDirection()`, which takes no argument and reports the direction the mounted editor is currently using.

TypeScript

```
import { getDirection } from '@bloklabs/core/locales';

document.documentElement.dir = getDirection('ar'); // 'rtl'
```

## Properties

| Property | Type | Description |
| --- | --- | --- |
| `DEFAULT_LOCALE` | `SupportedLocale` | From `@bloklabs/core/locales`. The default locale code, `'en'`. |
| `ALL_LOCALE_CODES` | `readonly SupportedLocale[]` | From `@bloklabs/core/locales`. All 69 supported locale codes. Use the list to build a language switcher, or hand it to `preloadLocales`. |
| `enLocale` | `LocaleConfig` | From `@bloklabs/core/locales`. The English dictionary. It is the only locale bundled by default, and the fallback for missing keys. |
