Skip to content
Edytor
Esc
↑↓navigate↵open⌘Jpreview
On this page

Localization

Translate the words the package shows or says, from the kinds' names and the placeholders to the slash keywords and the screen-reader announcements, with one labels dictionary per view; your chrome's words are your markup's.

Two kinds of words show in an editor:

  • The package’s own words: what the package itself renders or says. The kinds’ and commands’ names (the slash menu, Turn into and the block menu search them; a handle’s payload names its block by them), the marks’ toolbar buttons, the placeholders, the to-do checkbox, an empty toggle’s or callout’s hint, the callout’s icon button, the page, table of contents, page link and equation blocks, a table’s and a column band’s accessible names, the block menu’s actions and colours (its search matches them), the file size of a file block, and what screen readers hear when blocks move or go. They come from one dictionary, Labels, one section per plugin; English is the default.
  • Your chrome’s words: the menus, toolbars, handles, panels, bars and cards are your markup (Menus and handles), so their words are yours, in your markup: the controllers hand your snippets state and actions, never words, and name none of your elements (give your menus and toolbars their aria-label).

To show another language, give the view its labels: any entries of any section. The entries you leave out stay English, and so does an entry of a nested group (kinds, placeholders, colors) you leave out. A label that says something about a value is a function of it, so each language orders and inflects its own words: movedUp: (what) => ..., columns: (count) => ..., fileSize: (bytes) => ....

Section of Labels Its words
editor Block move and delete announcements, a suggestion’s default name
richText Kinds and marks (menu rows, toolbar buttons, handle names), the to-do checkbox, an empty body’s hint, the callout’s icon button, the placeholders richTextPlaceholder shows
blockMenu The block menu’s actions and colour rows (and the /red colour commands)
image The image preset
media The embed, bookmark, file, video and audio presets, a file’s size
code The code preset (a language’s name is its languages row’s)
columns The layout commands, the column resize bands
page, toc, pageLink The page block and its command, the table of contents, an untitled page link
comments The toolbar’s Comment button, the sidebar’s and a card’s accessible names
table The table command and accessible name, its block menu rows and switches, a column band, a padded cell
equation The equation presets, an empty or invalid equation
<script lang="ts">
	import { Edytor, richTextPlaceholder } from 'edytor';
</script>

<!-- One word changed: the rest stays English. -->
<Edytor
	plugins={[() => ({ placeholder: richTextPlaceholder })]}
	labels={{ richText: { placeholders: { empty: 'Write, or press / for blocks' } } }}
/>

A French editor

Type the whole dictionary as Labels: when a release adds a label, your build fails until you translate it. englishLabels is the English one, to start from.

import { CODE_LANGUAGES, type Labels } from 'edytor';

const octets = (count: number) => {
	const units = ['o', 'Ko', 'Mo', 'Go', 'To'];
	let value = count;
	let unit = 0;
	while (value >= 1024 && unit < units.length - 1) {
		value /= 1024;
		unit++;
	}
	const shown = unit === 0 ? value : value.toFixed(value < 10 ? 1 : 0).replace('.', ',');
	return `${shown} ${units[unit]}`;
};

export const fr: Labels = {
	editor: {
		suggestion: 'Proposition',
		block: (kind) => `bloc ${kind}`,
		blocks: (count) => `${count} blocs`,
		movedUp: (what) => `${what} déplacé vers le haut`,
		movedDown: (what) => `${what} déplacé vers le bas`,
		indented: (what) => `${what} indenté`,
		outdented: (what) => `${what} désindenté`,
		moved: (what) => `${what} déplacé`,
		deleted: (what) => `${what} supprimé`
	},
	richText: {
		kinds: {
			paragraph: 'Texte',
			heading1: 'Titre 1',
			heading2: 'Titre 2',
			heading3: 'Titre 3',
			toggleHeading1: 'Titre dépliant 1',
			toggleHeading2: 'Titre dépliant 2',
			toggleHeading3: 'Titre dépliant 3',
			bulletedList: 'Liste à puces',
			numberedList: 'Liste numérotée',
			todoList: 'Liste de tâches',
			toggleList: 'Liste dépliante',
			callout: 'Encadré',
			quote: 'Citation',
			divider: 'Séparateur'
		},
		marks: {
			bold: 'Gras',
			italic: 'Italique',
			underline: 'Souligné',
			strike: 'Barré',
			code: 'Code'
		},
		checkbox: 'Fait',
		emptyBody: {
			toggle: 'Bloc dépliant vide. Cliquez ou déposez des blocs ici.',
			callout: 'Encadré vide. Cliquez ou déposez des blocs ici.'
		},
		calloutIcon: {
			change: 'Changer l’icône',
			add: 'Ajouter une icône'
		},
		placeholders: {
			heading: (level) => `Titre ${level}`,
			toggleHeading: (level) => `Titre dépliant ${level}`,
			list: 'Liste',
			todo: 'Tâche',
			toggle: 'Liste dépliante',
			quote: 'Citation vide',
			callout: 'Titre de l’encadré',
			caption: 'Ajoutez une légende…',
			empty: 'Tapez « / » pour les commandes'
		}
	},
	blockMenu: {
		turnInto: 'Transformer en',
		color: 'Couleur',
		colors: {
			default: 'Par défaut',
			gray: 'Gris',
			brown: 'Marron',
			orange: 'Orange',
			yellow: 'Jaune',
			green: 'Vert',
			blue: 'Bleu',
			purple: 'Violet',
			pink: 'Rose',
			red: 'Rouge'
		},
		colorText: (color) => `Texte ${color.toLowerCase()}`,
		colorBackground: (color) => `Fond ${color.toLowerCase()}`,
		copyLink: 'Copier le lien du bloc',
		duplicate: 'Dupliquer',
		moveUp: 'Monter',
		moveDown: 'Descendre',
		delete: 'Supprimer',
		ctrl: 'Ctrl',
		shift: 'Maj',
		deleteKey: 'Suppr'
	},
	image: {
		image: 'Image'
	},
	media: {
		embed: 'Intégration',
		bookmark: 'Signet web',
		file: 'Fichier',
		video: 'Vidéo',
		audio: 'Audio',
		fileSize: octets
	},
	code: {
		code: 'Code'
	},
	columns: {
		columns: (count) => `${count} colonnes`,
		resize: 'Redimensionner les colonnes'
	},
	page: {
		page: 'Page',
		untitled: 'Sans titre'
	},
	toc: {
		toc: 'Table des matières',
		untitled: 'Sans titre',
		empty: 'Ajoutez des titres pour créer une table des matières.'
	},
	pageLink: {
		untitled: 'Sans titre'
	},
	comments: {
		comment: 'Commenter',
		sidebar: 'Commentaires'
	},
	table: {
		table: 'Tableau',
		headerRow: 'Ligne d’en-tête',
		headerColumn: 'Colonne d’en-tête',
		insertRowAbove: 'Insérer une ligne au-dessus',
		insertRowBelow: 'Insérer une ligne en dessous',
		insertColumnLeft: 'Insérer une colonne à gauche',
		insertColumnRight: 'Insérer une colonne à droite',
		deleteRow: 'Supprimer la ligne',
		deleteColumn: 'Supprimer la colonne',
		resize: 'Redimensionner la colonne',
		emptyCell: 'Cellule vide'
	},
	equation: {
		block: 'Équation en bloc',
		inline: 'Équation en ligne',
		blockPlaceholder: 'Ajouter une équation TeX',
		inlinePlaceholder: 'Nouvelle équation',
		invalid: 'Équation invalide'
	}
};

/** The code languages, named in French where the name differs. */
export const frLanguages = CODE_LANGUAGES.map((language) =>
	language.id === 'plaintext' ? { ...language, label: 'Texte brut' } : language
);

/** The French slash keywords, by command id (they replace the English ones: the English names are kept in them). */
export const frKeywords: Record<string, string[]> = {
	'block.paragraph': ['texte', 'paragraphe', 'paragraph'],
	'block.heading1': ['titre', 'h1'],
	'block.heading2': ['sous-titre', 'h2'],
	'block.heading3': ['h3'],
	'block.bulleted-list-item': ['puce', 'ul'],
	'block.numbered-list-item': ['numéro', 'ol'],
	'block.todo-item': ['tâche', 'case', 'todo'],
	'block.toggle': ['dépliant', 'details'],
	'block.toggle-heading1': ['dépliant', 'replier'],
	'block.toggle-heading2': ['dépliant', 'replier'],
	'block.toggle-heading3': ['dépliant', 'replier'],
	'block.callout': ['note', 'astuce'],
	'block.quote': ['citation', 'quote'],
	'block.divider': ['séparateur', 'hr'],
	'block.toc': ['sommaire', 'plan', 'toc'],
	'page.new': ['sous-page', 'nouvelle page', 'document', 'lien']
};

Then give the view its words, and the code plugin its languages. Your chrome’s snippets write their own words, in French here:

<script lang="ts">
	import {
		Edytor,
		createCodePlugin,
		createColumnsPlugin,
		createPagePlugin,
		createTocPlugin,
		richTextPlaceholder
	} from 'edytor';
	import { fr, frKeywords, frLanguages } from '$lib/fr';

	const plugins = [
		// Notion's hints in empty blocks.
		() => ({ placeholder: richTextPlaceholder }),
		createCodePlugin({ languages: frLanguages }),
		createColumnsPlugin(),
		createPagePlugin(),
		createTocPlugin()
	];
</script>

<Edytor
	{plugins}
	labels={{ ...fr, keywords: frKeywords }}
	aria-label="Corps de la page"
/>

The view’s own accessible name is yours to give (aria-label, aria-labelledby): the editor has none of its own.

Slash keywords

The slash menu finds a kind by its label and by its keywords. A translated label is found as it is: /titre finds “Titre 1”. The English keywords stay unless you replace them: the view’s labels.keywords takes, by command id, the keywords that replace a command’s own (list the English ones in it to keep them). The rich text plugin’s command ids are block.paragraph, block.heading1 to block.heading3, block.toggle-heading1 to block.toggle-heading3, block.bulleted-list-item, block.numbered-list-item, block.todo-item, block.toggle, block.callout, block.quote and block.divider; the other plugins’ are block.image, block.code, block.embed, block.bookmark, block.file, block.video, block.audio, columns.2 to columns.5, page.new, block.toc, block.table, block.equation and equation.inline.

Your chrome’s words

Your markup writes its own words: the slash menu’s section headings (a preset’s group, such as Basic blocks or Media, is a key your markup shows in your words), your toolbar’s colours, a table grip’s menu rows (named by their item.id, insert-above …), the pasted-link menu’s rows (link, embed, bookmark), an empty media block’s texts (by its kind), the version history’s statuses (VersionHistory.status is a code: restored, denied …). The repository’s demo chrome keeps its words in one module, words.svelte.ts, which a French view replaces with setWords(frWords): a pattern to copy, not a package API.

What is not localized

  • Your document. Labels translate the package’s words, never the content: a document keeps the text people typed.
  • Your own kinds, marks and commands. Their label, keywords and group are what you write in their records: write them in your language, or build them from your own dictionary. So are your own triggers’ menus.
  • Key names on a Mac. The block menu’s shortcuts use the Mac’s symbols (⌘, ⇧) there; elsewhere the ctrl, shift and deleteKey labels.
  • Number and date formats beyond what a label function formats: fileSize is a function, so use Intl.NumberFormat there if you need it.
  • Right-to-left chrome. Blocks take their text’s direction; your menus and toolbars are laid out as your markup lays them out.

Was this page helpful?