# Localization

<a id="introduction">

## Introduction

All CKEditor 5 WYSIWYG editor features support message localization. It means that the user interface of any feature can be translated into various languages and regions depending on the user’s preferences.

CKEditor 5 translation system is open to third-party plugins. Any custom features that you introduce can be localized. The system also provides a way to add missing or overwrite existing translations and supports translating plural forms.

> **Warning**
>
> Make sure to use up-to-date CKEditor 5 development tool packages. Versions of the tools older than v60.0.0 do not provide support for features described in this guide.

<a id="open-translation-api">

### Open translation API

The CKEditor 5 localization system focuses on the following points:

* Supporting the localization of third-party plugins.
* Making it possible to pass your own translations to fix missing or invalid localizations.
* Generating deterministic builds.
* Exposing easy-to-use APIs for providing translations and writing localizable content.
* Supporting plural forms in each step of the localization system for better translations.

<a id="glossary-of-terms">

### Glossary of terms

Before we start, let us explain the meaning of terms that are crucial for the translation process:

* _A message_ – A string or an object that should be translated.\
  The string version works as a shortcut for the `{ id: message, string: message }` object form.
* _A message ID_ – A property used to distinguish messages.\
  It is useful for short messages where a collision might occur, like `%0 images`.
* _A message string_ – The default (English) form of the message.\
  When the message supports plural versions, this is the default singular version.
* _A message plural_ – An optional plural (English) version of the message.\
  The presence of this property indicates that the message should support both singular and plural forms.
* _A translation source (`.ts`)_ – A generated TypeScript module containing the dictionary for one language.\
  All localizable CKEditor 5 packages contain such files in the `lang/translations/` directory.
* _A translation asset_ – A JavaScript file or a part of the file with generated translations for one language.

<a id="writing-a-localizable-ui">

## Writing a localizable UI

All _messages_ that need localization should be passed to the special CKEditor 5’s [`t()` function](../../../api/module_utils_locale-Locale.md#member-t).

In JavaScript files, retrieve it as a standalone function, for example from the editor’s [`Locale`](../../../api/module_utils_locale-Locale.md) instance (`const { t } = editor.locale;`) or from any view method (`const t = this.t;`).

In TypeScript files, the translation tools can also detect direct `Locale#t()` calls based on type information, so `editor.t()`, `editor.locale.t()`, `locale.t()`, and `this.t()` are supported as well.

As the first argument, the `t()` function accepts either a string literal or an object literal containing the `id`, `string` and `plural` (optional) properties. The string literal will serve as both the _message ID_ and the _message string_.

As the second argument, the translation function accepts a value or an array of values. These values will be used to fill the placeholders in more advanced translation scenarios. If the `plural` property is specified, the first value will be used as the quantity determining the plural form.

> **Warning**
>
> Due to the fact that a static code analyzer is used in the translation process, the supported call patterns depend on the source file type. In JavaScript files, the analyzer looks for a function named exactly `t()`, so it should not be called on a `Locale` instance and it cannot have a different name. In TypeScript files, the analyzer also recognizes direct `Locale#t()` calls based on type information.
>
> For the same reason, the first argument can only be a string literal or an object literal. Variables cannot be passed.

When using the `t()` function, you can create your own _localizable messages_ or reuse _messages_ created in CKEditor 5 packages that your project depends on. In the case of reusing _messages_, you will not need to worry about translating them as all the work will be done by the CKEditor 5 team. Obviously, [your help in translating](../../contributing/contributing.md#translating) will still be appreciated!

For a simple _localizable messages_, use the string form for simplicity:

```js
const emojiName = 'cat';

// Assuming that the English language was picked:
t( 'insert emoji' ); // "insert emoji"
t( 'insert %0 emoji', emojiName ); // "insert cat emoji"
t( 'insert %0 emoji', [ emojiName ] ); // "insert cat emoji"
```

For more advanced scenarios, use plain object forms:

```js
const quantity = 3;

// Assuming that the English language was picked:
t( { string: '%0 emoji', id: 'ACTION_EMOJI' }, 'insert' ); // "insert emoji"
t( { string: '%0 emoji', plural: '%0 emojis', id: 'N_EMOJIS' }, quantity ); // "3 emojis"
t( { string: '%1 %0 emoji', plural: '%1 %0 emojis', id: 'ACTION_N_EMOJIS' }, [ quantity, 'Insert' ] ); // "Insert 3 emojis"
```

<a id="example-localizing-the-plugin-ui">

### Example: Localizing the plugin UI

This example shows how to create a localizable user interface of a plugin. Here is how you can create a button that will insert a smiling face emoji. The button will have a localizable tooltip.

```ts
// Custom plugin configuration, including necessary imports.
// The code below should be put into a custom plugin class extending the Plugin class.
// ...

editor.ui.componentFactory.add( 'smilingFaceEmoji', locale => {
	const buttonView = new ButtonView( locale );

	// The localized label.
	const label = editor.locale.t( 'Insert smiling face emoji' );

	buttonView.set( {
		label,
		icon: emojiIcon,
		tooltip: true
	} );

	buttonView.on( 'execute', () => {
		editor.execute( 'insertSmilingFaceEmoji' );
		editor.editing.view.focus();
	} );
} );

// The rest of the custom plugin configuration.
// ...
```

> **Warning**
>
> See [how to create a complete plugin](../../tutorials/crash-course/editor.md) to have a better understanding of creating CKEditor 5 plugins.

<a id="example-localizing-pending-actions">

### Example: Localizing pending actions

[Pending actions](../../../api/module_core_pendingactions-PendingActions.md) are used to inform the user that an action is in progress and they will lose data if they exit the editor at the given moment. Here is how you can localize them:

```js
class FileRepository {
	// More methods.
	// ...

	updatePendingAction() {
		const pendingActions = this.editor.plugins.get( PendingActions );

		const t = this.editor.t;
		const getMessage = value => t( 'Upload in progress (%0%).', value ); // Upload in progress (12%).

		this._pendingAction = pendingActions.add( getMessage( this.uploadedPercent ) );
		this._pendingAction.bind( 'message' ).to( this, 'uploadedPercent', getMessage );
	}
}
```

<a id="adding-translations-and-localizing-the-editor-ui">

## Adding translations and localizing the editor UI

First of all, if you found a missing or incorrect translation in any of CKEditor 5 features, [see how you can contribute to the project](../../contributing/contributing.md#translating). CKEditor 5 is an Open Source project used by people from all around the world, so your help will be appreciated by others.

Adding translations to the editor can be done in three ways to satisfy various needs.

* By [adding translations via the translation-service’s `add()` function](#using-the-add-function).\
  This can be done before initiating the CKEditor 5 editor instance but it requires importing the CKEditor 5 utility function.
* By [extending the global `window.CKEDITOR_TRANSLATIONS` object](#using-the-windowckeditor_translations-object).\
  This can be done before initiating the CKEditor 5 editor instance.
* By [creating TypeScript translation sources](#creating-translation-files) in the `lang/translations/` directory of the published package like other CKEditor 5 packages do.\
  This option will be useful for third-party plugin creators as it allows bundling translations only for needed languages during the build step.

<a id="using-the-add-function">

### Using the `add()` function

The first option for adding translations is via [the translation-service’s `add()` helper](../../../api/module_utils_translation-service.md#function-add). This utility adds translations to the global `window.CKEDITOR_TRANSLATIONS` object by extending it. Since it needs to be imported, it works only before building the editor.

Starting with the CKEditor 5 v19.0.0 release, the `add()` method now accepts an optional `getPluralForm()` function as the third argument. This function is only needed for defining the plural form if no language file was loaded for a particular language. It also accepts an array of translations for a _message_ if the _message_ should support singular and plural forms.

```js
add( 'pl', {
	'Add space': [ 'Dodaj spację', 'Dodaj %0 spacje', 'Dodaj %0 spacji' ]
} );

// Assuming that the Polish language was picked:
t( { string: 'Add space', plural: 'Add %0 spaces' }, 1 ) // "Dodaj spację"
t( { string: 'Add space', plural: 'Add %0 spaces' }, 2 ) // "Dodaj 2 spacje"
t( { string: 'Add space', plural: 'Add %0 spaces' }, 5 ) // "Dodaj 5 spacji"
```

<a id="using-the-windowckeditor_translations-object">

### Using the `window.CKEDITOR_TRANSLATIONS` object

The second option is adding translations via the global `window.CKEDITOR_TRANSLATIONS` object.

For each language that should be supported, the `dictionary` property of this object should be extended and the `getPluralForm()` function should be provided if missing.

The `dictionary` property is a `message ID ⇒ translations` map, where the `translations` can be either a string or an array of translations with plural forms for the given language if the message should support plural forms as well.

The `getPluralForm()` property should be a function that returns the plural form index for a given quantity. Note that when using CKEditor 5 translations, this property will be defined by _CKEditor 5 translation assets_.

Check an example below that demonstrates a part of the `window.CKEDITOR_TRANSLATIONS` object with Polish translations for the `Cancel` and `Add space` _message IDs_:

```js
{
	// Each key should be a valid language code.
	pl: {
		// A map of translations for the 'pl' language.
		dictionary: {
			'Cancel': 'Anuluj',
			'Add space': [ 'Dodaj spację', 'Dodaj %0 spacje', 'Dodaj %0 spacji' ]
		},

		// A function that returns the plural form index for the given language.
		// Note that you only need to pass this function when you add translations for a new language.
		getPluralForm: n => n == 1 ? 0 : n % 10 >= 2 && n % 10 <= 4 && ( n % 100 < 10 || n % 100 >= 20 ) ? 1 : 2
	}
	// Other languages.
	// ...
}
```

It is important to extend the existing properties in the `window.CKEDITOR_TRANSLATIONS` object to not lose other translations. This can be achieved easily using `Object.assign()` and the `||` operator.

```js
// Make sure that the global object is defined. If not, define it.
window.CKEDITOR_TRANSLATIONS = window.CKEDITOR_TRANSLATIONS || {};

// Make sure that the dictionary for Polish translations exist.
window.CKEDITOR_TRANSLATIONS[ 'pl' ] = window.CKEDITOR_TRANSLATIONS[ 'pl' ] || {};
window.CKEDITOR_TRANSLATIONS[ 'pl' ].dictionary =  window.CKEDITOR_TRANSLATIONS[ 'pl' ].dictionary || {};

// Extend the dictionary for Polish translations with your translations:
Object.assign( window.CKEDITOR_TRANSLATIONS[ 'pl' ].dictionary, {
	'Save': 'Zapisz'
} );
```

If you add a new language, remember to set the `getPluralForm()` function which should return a number (or a Boolean for languages with simple plural rules like English) that indicates which form should be used for the given value.

<a id="creating-translation-files">

### Creating translation files

The third option is intended primarily for plugins that contain many localizable messages. Create one generated TypeScript file per language code in the `lang/translations/` directory. The default export must match the [`Translations`](../../../api/module_utils_locale-Translations.md) type.

```ts
// lang/translations/es.ts
import type { Translations } from '@ckeditor/ckeditor5-utils';

const translations: Translations = {
	es: {
		dictionary: {
			// The label of the text alignment toolbar button.
			'Align left': 'Alinear a la izquierda'
		}
	}
};

export default translations;
```

> **Note**
>
> If you develop your own plugin outside the CKEditor 5 ecosystem, use the [package generator](../../development-tools/package-generator/using-package-generator.md) to create translation sources and assets. Its build setup handles the `lang/` directory, including translation synchronization.

Package translation sources contain dictionaries only. Load the matching translation from the `ckeditor5` package together with a package translation so the editor receives the language’s plural-form function.

To build and configure a localized editor, follow the steps from the [Setting the UI language guide](../../../getting-started/setup/ui-language.md).

<a id="re-using-translations-from-other-packages">

## Re-using translations from other packages

If you want to re-use a _message_ that already exists in another package, you should call the translation function through an alias with a different name instead of [using `t()` as a function](#writing-a-localizable-ui). This prevents the static code analyzer from treating it as a new source message.

We use this approach already in the [collaboration features](../../../features/collaboration/collaboration.md) and the [slash commands feature](../../../features/slash-commands.md). You can find an example from the [list of default commands](../../../api/module_slash-command_slashcommandconfig-SlashCommandConfig.md#function-getDefaultCommands) that we used in the slash commands feature below. Please note the difference between using `t()` and `translateVariableKey()`. `translateVariableKey( 'Block quote' )` will re-use a translation from another package whilst `t( 'Create a block quote' )` will be processed by a static code analyzer. As a result we make sure that the translation for the `title` is taken from the [block quote](../../../features/block-quote.md) feature where the _message_ “Block quote” is already translated. But for the `description` we create a new translation.

```ts
public getDefaultCommands() {
	const t = this.editor.t;
	const translateVariableKey = this.editor.locale.t;

	return [
		{
			id: 'blockQuote',
			commandName: 'blockQuote',
			icon: IconQuote,
			title: translateVariableKey( 'Block quote' ),
			description: t( 'Create a block quote' )
		},
		// More command definitions
		// ...
	]
}
```

<a id="known-limitations">

## Known limitations

* Currently it is impossible to change the chosen editor’s language at runtime without destroying the editor.

---

Full index of the CKEditor 5 documentation: [llms.txt](../../../../llms.txt)
