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,keywordsandgroupare 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,shiftanddeleteKeylabels. - Number and date formats beyond what a label function formats:
fileSizeis a function, so useIntl.NumberFormatthere 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.