Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
ab94ad0
feat(core): bundle localized emoji data and search
nperez0111 Oct 26, 2026
702749d
feat(react): replace emoji-mart with localized Frimousse pickers
nperez0111 Oct 26, 2026
6dced51
fix(core): ignore accents in emoji search
nperez0111 Oct 28, 2026
d21711b
fix(react): preserve emoji picker scrolling and customization
nperez0111 Oct 28, 2026
04147b8
fix: address localized emoji picker review findings
nperez0111 Oct 28, 2026
4b6b17a
refactor: simplify emoji data and picker architecture
nperez0111 Oct 28, 2026
7909d98
fix(react): avoid remounting emoji picker while searching
nperez0111 Oct 29, 2026
df1174b
fix(react): hide inline emoji search input
nperez0111 Oct 29, 2026
29e0eeb
fix(react): restore unmatched emoji query dismissal
nperez0111 Oct 30, 2026
bb53713
fix(react): require emoji picker dictionary
nperez0111 Oct 30, 2026
0902f2a
refactor!: remove legacy grid suggestion menus
nperez0111 Oct 30, 2026
8452aaa
fix(react): soften emoji search focus styling
nperez0111 Oct 30, 2026
4541140
refactor(react): narrow emoji picker mutation observation
nperez0111 Oct 30, 2026
d40b183
fix(core): preserve emoji shortcode search across locales
nperez0111 Oct 30, 2026
2d8a6e6
fix(core): default missing emoji picker dictionary
nperez0111 Oct 30, 2026
f41fd6b
refactor(react): replace emoji observer with button lifecycle registr…
nperez0111 Oct 30, 2026
5e6d141
Merge origin/main into split/emoji-picker-ui
nperez0111 Oct 30, 2026
83ea1c3
fix(tests): use packed packages for transitive Next.js dependencies
nperez0111 Oct 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 28 additions & 18 deletions docs/content/docs/features/localization.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@ BlockNote supports the following languages out of the box:
- **Japanese** (`ja`) - 日本語
- **Korean** (`ko`) - 한국어
- **Norwegian** (`no`) - Norsk
- **Persian** (`fa`) - فارسی
- **Polish** (`pl`) - Polski
- **Portuguese** (`pt`) - Português
- **Russian** (`ru`) - Русский
- **Slovak** (`sk`) - Slovenčina
- **Spanish** (`es`) - Español
- **Turkish** (`tr`) - Türkçe
- **Ukrainian** (`uk`) - Українська
- **Uzbek** (`uz`) - O'zbekcha
- **Vietnamese** (`vi`) - Tiếng Việt
Expand All @@ -53,25 +55,13 @@ function FrenchEditor() {

## Dynamic Language Switching

You can dynamically change the language based on user preferences or your app's locale:
The dictionary is selected when the editor is created. Pass `[language]` as the
second argument to `useCreateBlockNote` to recreate it when the language changes,
and key `BlockNoteView` by the language to reset its menus.

```tsx
import { useCreateBlockNote, BlockNoteView } from "@blocknote/react";
import * as locales from "@blocknote/core/locales";

function LocalizedEditor({ language = "en" }) {
const editor = useCreateBlockNote({
dictionary: locales[language as keyof typeof locales] || locales.en,
});

return <BlockNoteView editor={editor} />;
}

// Usage
<LocalizedEditor language="fr" />
<LocalizedEditor language="de" />
<LocalizedEditor language="ja" />
```
To preserve content, save the document outside the editor and pass it back as
`initialContent`. Recreating the editor resets its undo history and selection.
The [localization example](#basic-localization) shows the complete implementation.

## Customizing Text Strings

Expand Down Expand Up @@ -110,6 +100,24 @@ function CustomEditor() {

The dictionary object contains translations for various parts of the editor:

### Locale

The optional `locale` field selects the localized emoji names, categories,
skin-tone names, and search terms used by the emoji picker. BlockNote's bundled
dictionaries already set this field. When creating a custom dictionary, set it
to the matching locale code; if it is omitted or unsupported, the emoji data
falls back to English.

The picker interface strings use the same dictionary as the rest of BlockNote:

```tsx
emoji_picker: {
search: "Search",
search_no_results: "No emoji found",
loading: "Loading…",
}
```

### Placeholders

Text that appears when blocks are empty:
Expand Down Expand Up @@ -203,6 +211,8 @@ To add support for a new language, you can:

When creating translations, make sure to:

- Set `locale` to the matching locale code so the emoji picker uses the
appropriate localized data
- Translate all text strings in the dictionary
- Maintain the same structure as the English dictionary
- Test the translations with different content types
Expand Down
36 changes: 36 additions & 0 deletions docs/content/docs/react/components/emoji-picker.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
title: Emoji Picker
description: Insert emoji with the built-in localized picker and configure its trigger and visibility.
---

# Emoji Picker

The Emoji Picker is enabled by default. Type `:` followed by at least two non-whitespace characters to search, or select the emoji item in the Slash Menu.

The picker uses [Frimousse](https://frimousse.liveblocks.io/) and loads emoji labels and search keywords for the editor's [locale](/docs/features/localization).

## Disabling the Picker

Pass `emojiPicker={false}` to `BlockNoteView` to disable the default inline picker.

## Configuring the Picker

To configure the trigger or minimum query length, disable the default picker and mount `EmojiPickerController` inside `BlockNoteView`:

```tsx
import { BlockNoteView } from "@blocknote/mantine";
import "@blocknote/mantine/style.css";
import { EmojiPickerController, useCreateBlockNote } from "@blocknote/react";

export default function Editor() {
const editor = useCreateBlockNote();

return (
<BlockNoteView editor={editor} emojiPicker={false}>
<EmojiPickerController triggerCharacter=":" minQueryLength={3} />
</BlockNoteView>
);
}
```

The controller also accepts `shouldOpen`, `floatingUIOptions`, and `portalElement` for controlling when and where it opens. Set `minQueryLength={0}` to allow opening immediately after the trigger.
52 changes: 0 additions & 52 deletions docs/content/docs/react/components/grid-suggestion-menus.mdx

This file was deleted.

2 changes: 1 addition & 1 deletion examples/01-basic/10-localization/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Localization (i18n)

In this example, we pass in a custom dictionary to change the interface of the editor to use Dutch (NL) strings.
In this example, we use a dropdown to switch the editor between all of the languages that BlockNote ships with, by passing the matching dictionary from `@blocknote/core/locales` to the editor. Selecting a language re-mounts the editor so the interface strings update immediately. The document is saved outside the editor and restored on re-mount, preserving your content (but not the undo history or selection).

You can also provide your own dictionary to customize the strings used in the editor, or submit a Pull Request to add support for your language of your choice.

Expand Down
91 changes: 78 additions & 13 deletions examples/01-basic/10-localization/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,86 @@
import { nl } from "@blocknote/core/locales";
import type { Block } from "@blocknote/core";
import * as locales from "@blocknote/core/locales";
import "@blocknote/core/fonts/inter.css";
import { BlockNoteView } from "@blocknote/mantine";
import "@blocknote/mantine/style.css";
import { useCreateBlockNote } from "@blocknote/react";
// import { useTranslation } from "some-i18n-library"; // You can use any i18n library you like
import { useRef, useState } from "react";

type Language = keyof typeof locales;

function isLanguage(value: string): value is Language {
return Object.hasOwn(locales, value);
}

// Human-readable names shown in the language picker.
const languageNames = {
ar: "العربية",
de: "Deutsch",
en: "English",
es: "Español",
fa: "فارسی",
fr: "Français",
he: "עברית",
hr: "Hrvatski",
is: "Íslenska",
it: "Italiano",
ja: "日本語",
ko: "한국어",
nl: "Nederlands",
no: "Norsk",
pl: "Polski",
pt: "Português",
ru: "Русский",
sk: "Slovenčina",
tr: "Türkçe",
uk: "Українська",
uz: "Oʻzbekcha",
vi: "Tiếng Việt",
zh: "简体中文",
zhTW: "繁體中文",
} satisfies Record<Language, string>;

const languageKeys = Object.keys(locales)
.filter(isLanguage)
.sort((a, b) => languageNames[a].localeCompare(languageNames[b]));

export default function App() {
// const { lang } = useTranslation('common'); // You can get the current language from the i18n library or alternatively from a router
// Creates a new editor instance.
const editor = useCreateBlockNote({
// Passes the Dutch (NL) dictionary to the editor instance.
// You can also provide your own dictionary here to customize the strings used in the editor,
// or submit a Pull Request to add support for your language of your choice
dictionary: nl,
// dictionary: locales[lang as keyof typeof locales], // Use the language from the i18n library dynamically
});
const [language, setLanguage] = useState<Language>("en");
// Recreate the editor when the language changes, preserving its document.
const document = useRef<Block[] | undefined>(undefined);
const editor = useCreateBlockNote(
{ dictionary: locales[language], initialContent: document.current },
[language],
);

return (
<div>
<label>
Language:{" "}
<select
value={language}
onChange={(event) => {
if (isLanguage(event.target.value)) {
setLanguage(event.target.value);
}
}}
>
{languageKeys.map((key) => (
<option key={key} value={key}>
{languageNames[key]}
</option>
))}
</select>
</label>

// Renders the editor instance using a React component.
return <BlockNoteView editor={editor} />;
{/* Remount the UI so menus also reset to the new language. */}
<BlockNoteView
key={language}
editor={editor}
onChange={() => {
document.current = editor.document;
}}
/>
</div>
);
}

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Loading
Loading