Language
Catalogs
Every string the editor shows (toolbar, slash menu, Block actions, settings, AI task names, comments, notices) comes from the catalogs src/i18n/{de,en}/*.ts, keyed by stable dotted ids (toolbar.bold, comments.tab, ai.task.translate.label); de is the default and the source of the key set, en has exactly the same keys (a missing one is a type error). Catalog helpers are exported by /core: createTranslator, CATALOGS, LOCALES, DEFAULT_LOCALE, isLocale, Locale, MessageKey, Messages, Translate.
Choosing the language
Choose the language once per app: createEditorContext({ locale: 'en', messages: { 'toolbar.bold': 'Strong' } }) (React and framework-free hosts), app.use(createEditor({ locale, messages })) (Vue), or the Nuxt module options tiptapEditor: { locale: 'en', messages: { … } } (JSON-serialisable; forwarded to the client plugin). messages is a partial catalog applied on top of locale; {name} placeholders take params. An unknown locale throws. The locale is fixed for the life of the context: a host that switches languages at run time recreates the context (Vue: remount the app or key it; React: new EditorContextProvider value).
Messages in features and components
Features read ctx.t (FeatureContext.t, EditorContext.t, ctx.locale) in their factories: toolbar items, slash commands, Block actions, conversion labels (ConversionProvider.label(target, t)) and settingsTabs(ctx) are built in the context's language. Components call useEditorI18n() (/vue and /react) and get { t, locale }. A feature of your own adds its keys by passing its own strings or by extending the messages override; the shipped keys are the MessageKey union.
Setting values stored as ids ('margin', 'Deutsch', 'flat', …) never change with the language: only their display label comes from the catalog (*_KEYS tables mapping an id to a key; authorStyleItems(t), defaultBlockTypes(t)).
Markdown
Markdown: the source view of MarkdownSurface (aria label, Apply, Cancel; keys markdown.source.*; override through messages) and the message of every MarkdownDiagnostic (markdown.diagnostic.*) are in the context's language. markdownToContent(editor, body, { policy, t }) and createSourceMode({ getEditor, t }) take an explicit translator; without one they use the editor's registered surface, else the default locale.
The i18n check
bun run check:editor-i18n (packages/editor/checks/i18n.ts) fails when a user-facing literal (label/description/placeholder/aria/alt/title properties and attributes, JSX and template text, German words) appears in src outside src/i18n, or when a catalog key is unused or missing in en. Text that is not UI (model prompts, stored ids, developer errors) is marked // i18n-ignore: <reason>; the run lists every honoured pragma.