diff --git a/packages/core/editor/src/UI/BlockOptions/BlockOptions.tsx b/packages/core/editor/src/UI/BlockOptions/BlockOptions.tsx index 71352b313..bb3405150 100644 --- a/packages/core/editor/src/UI/BlockOptions/BlockOptions.tsx +++ b/packages/core/editor/src/UI/BlockOptions/BlockOptions.tsx @@ -33,9 +33,9 @@ const BlockOptionsSeparator = ({ className = '' }: BlockOptionsSeparatorProps) = ); export type BlockOptionsProps = { + refs: any; isOpen: boolean; onClose: () => void; - refs: any; style: CSSProperties; children?: React.ReactNode; actions?: ['delete', 'duplicate', 'turnInto', 'copy'] | null; @@ -79,7 +79,6 @@ const BlockOptions = ({ isOpen, onClose, refs, style, actions = DEFAULT_ACTIONS, const onDuplicate = () => { // [TEST] if (typeof editor.path.current !== 'number') return; - editor.duplicateBlock({ original: { path: editor.path.current }, focus: true }); onClose(); @@ -116,13 +115,13 @@ const BlockOptions = ({ isOpen, onClose, refs, style, actions = DEFAULT_ACTIONS, {!!ActionMenu && !isVoidElement && !editor.blocks[currentBlock?.type || '']?.hasCustomEditor && ( @@ -131,7 +130,6 @@ const BlockOptions = ({ isOpen, onClose, refs, style, actions = DEFAULT_ACTIONS, setIsActionMenuOpen(false)}>
- {/* @ts-ignore - fixme */}
@@ -144,14 +142,14 @@ const BlockOptions = ({ isOpen, onClose, refs, style, actions = DEFAULT_ACTIONS, onClick={() => setIsActionMenuOpen((open) => !open)} > - Turn into + {editor.getLabelText('editor.blockOptions.turnInto') || 'Turn into'} )} diff --git a/packages/core/editor/src/UI/BlockOptions/utils.ts b/packages/core/editor/src/UI/BlockOptions/utils.ts index ae02fbc1e..0f2f18c4c 100644 --- a/packages/core/editor/src/UI/BlockOptions/utils.ts +++ b/packages/core/editor/src/UI/BlockOptions/utils.ts @@ -27,11 +27,13 @@ export function buildActionMenuRenderProps({ editor, view, onClose, mode = 'togg items = items.filter((type) => filterToggleActions(editor, type)); } - return items.map((action) => { - const title = editor.blocks[action].options?.display?.title || action; - const description = editor.blocks[action].options?.display?.description; - const icon = editor.blocks[action].options?.display?.icon; - return { type: action, title, description, icon }; + return items.map((type) => { + const title = + editor.getLabelText(`plugins.${type}.display.title`) || editor.blocks[type].options?.display?.title || type; + const description = + editor.getLabelText(`plugins.${type}.display.description`) || editor.blocks[type].options?.display?.description; + const icon = editor.blocks[type].options?.display?.icon; + return { type: type, title, description, icon }; }); }; diff --git a/packages/core/editor/src/YooptaEditor.tsx b/packages/core/editor/src/YooptaEditor.tsx index 9fdce4788..d0acbfbb8 100644 --- a/packages/core/editor/src/YooptaEditor.tsx +++ b/packages/core/editor/src/YooptaEditor.tsx @@ -71,7 +71,9 @@ const YooptaEditor = ({ }, [marksProps]); const plugins = useMemo(() => { - return pluginsProps.map((plugin) => plugin.getPlugin as Plugin>); + return pluginsProps.map((plugin) => { + return plugin.getPlugin as Plugin>; + }); }, [pluginsProps]); const [editorState, setEditorState] = useState(() => { diff --git a/packages/core/editor/src/constants/labels.ts b/packages/core/editor/src/constants/labels.ts new file mode 100644 index 000000000..b2dfd533c --- /dev/null +++ b/packages/core/editor/src/constants/labels.ts @@ -0,0 +1,43 @@ +import { RecursiveDotNotation } from '../types/i18n'; + +export const DEFAULT_LABEL_TEXT_MAP = { + // editor: { + // blockOptions: { + // delete: 'Delete', + // duplicate: 'Duplicate', + // turnInto: 'Turn into', + // copyBlockLink: 'Copy link to block', + // }, + // placeholder: `Type '/' for commands`, + // }, + // tools: { + // toolbar: { + // highlightColor: { + // text: 'Text', + // background: 'Background', + // customColor: 'Custom color', + // }, + // linkTitle: 'Link', + // }, + // actionMenu: { + // noActionsAvailable: 'No actions available', + // }, + // link: { + // title: 'Link title', + // url: 'Link URL', + // target: 'Link target', + // rel: 'Link rel', + // update: 'Update', + // add: 'Add', + // delete: 'Delete link', + // additionalProps: 'Additional props', + // targetPlaceholder: 'Edit link target', + // relPlaceholder: 'Edit link rel', + // titlePlaceholder: 'Edit link title', + // urlPlaceholder: 'Edit link URL', + // }, + // }, +}; + +// [TODO] - add types +export type LabelKeys = string; diff --git a/packages/core/editor/src/editor/i18n/getLabelText.ts b/packages/core/editor/src/editor/i18n/getLabelText.ts new file mode 100644 index 000000000..aa7a6c389 --- /dev/null +++ b/packages/core/editor/src/editor/i18n/getLabelText.ts @@ -0,0 +1,17 @@ +import { DEFAULT_LABEL_TEXT_MAP, LabelKeys } from '../../constants/labels'; +import { YooEditor } from '../types'; + +function getNestedValue(obj: any, path: string[]): string | undefined { + return path.reduce((acc, part) => { + if (acc && typeof acc === 'object' && part in acc) { + return acc[part]; + } + return undefined; + }, obj); +} + +export function getLabelText(editor: YooEditor, key: LabelKeys) { + const keyParts = key.split('.'); + const currentLangValue = getNestedValue(DEFAULT_LABEL_TEXT_MAP, keyParts); + return currentLangValue || ''; +} diff --git a/packages/core/editor/src/editor/index.tsx b/packages/core/editor/src/editor/index.tsx index 7754d829e..ae501c021 100644 --- a/packages/core/editor/src/editor/index.tsx +++ b/packages/core/editor/src/editor/index.tsx @@ -4,7 +4,7 @@ import { moveBlock } from './blocks/moveBlock'; import { focusBlock } from './blocks/focusBlock'; import { splitBlock } from './blocks/splitBlock'; import { setPath } from './paths/setPath'; -import { YooEditor, YooptaContentValue } from './types'; +import { ExtendedYooEditor, YooptaContentValue } from './types'; import { increaseBlockDepth } from './blocks/increaseBlockDepth'; import { decreaseBlockDepth } from './blocks/decreaseBlockDepth'; import { getEditorValue } from './core/getEditorValue'; @@ -26,6 +26,7 @@ import { mergeBlock } from './blocks/mergeBlock'; import { UndoRedoOptions, YooptaHistory } from './core/history'; import EventEmitter from 'eventemitter3'; import { getEmail, EmailTemplateOptions } from '../parsers/getEmail'; +import { getLabelText } from './i18n/getLabelText'; const eventEmitter = new EventEmitter(); @@ -36,8 +37,8 @@ const Events = { emit: (event, payload) => eventEmitter.emit(event, payload), }; -export function createYooptaEditor(): YooEditor { - const editor: YooEditor = { +export function createYooptaEditor(): ExtendedYooEditor { + const editor: ExtendedYooEditor = { id: '', children: {}, blockEditorsMap: {}, @@ -68,6 +69,8 @@ export function createYooptaEditor(): YooEditor { applyTransforms: (operations, ...args) => applyTransforms(editor, operations, ...args), batchOperations: (callback) => batchOperations(editor, callback), + getLabelText: (key) => getLabelText(editor, key), + on: (event, callback) => Events.on(event, callback), off: (event, callback) => Events.off(event, callback), emit: (event, ...args) => Events.emit(event, ...args), diff --git a/packages/core/editor/src/editor/types.ts b/packages/core/editor/src/editor/types.ts index 97c0e7dfd..4a27e68f5 100644 --- a/packages/core/editor/src/editor/types.ts +++ b/packages/core/editor/src/editor/types.ts @@ -1,14 +1,14 @@ import { Descendant, Path, Point, Selection } from 'slate'; import { Plugin, PluginElementsMap, PluginOptions, PluginElementProps } from '../plugins/types'; import { EditorBlurOptions } from './core/blur'; -import { deleteBlock, DeleteBlockOptions } from './blocks/deleteBlock'; -import { duplicateBlock, DuplicateBlockOptions } from './blocks/duplicateBlock'; +import { deleteBlock } from './blocks/deleteBlock'; +import { duplicateBlock } from './blocks/duplicateBlock'; import { focusBlock } from './blocks/focusBlock'; -import { toggleBlock, ToggleBlockOptions } from './blocks/toggleBlock'; +import { toggleBlock } from './blocks/toggleBlock'; import { GetBlockOptions } from './blocks/getBlock'; import { ReactEditor } from 'slate-react'; -import { applyTransforms, ApplyTransformsOptions, YooptaOperation } from './core/applyTransforms'; -import { insertBlock, InsertBlockOptions } from './blocks/insertBlock'; +import { applyTransforms, YooptaOperation } from './core/applyTransforms'; +import { insertBlock } from './blocks/insertBlock'; import { increaseBlockDepth } from './blocks/increaseBlockDepth'; import { SplitBlockOptions } from './blocks/splitBlock'; import { HistoryStack, HistoryStackName, YooptaHistory } from './core/history'; @@ -21,6 +21,7 @@ import { getHTML } from '../parsers/getHTML'; import { getMarkdown } from '../parsers/getMarkdown'; import { getPlainText } from '../parsers/getPlainText'; import { getEmail } from '../parsers/getEmail'; +import { getLabelText } from './i18n/getLabelText'; export type YooptaBlockData = { id: string; @@ -83,6 +84,7 @@ export type YooptaEventsMap = { blur: boolean; 'block:copy': YooptaBlockData; 'path-change': YooptaPath; + [key: string]: any; }; export type BaseCommands = Record any>; @@ -126,6 +128,9 @@ export type YooEditor = { applyTransforms: WithoutFirstArg; batchOperations: (fn: () => void) => void; + // default labels. get label by current language. default: 'en' + getLabelText: WithoutFirstArg; + // events handlers on: (event: K, fn: (payload: YooptaEventsMap[K]) => void) => void; once: (event: K, fn: (payload: YooptaEventsMap[K]) => void) => void; @@ -175,3 +180,9 @@ export type SlateElement = { children: Descendant[]; props?: PluginElementProps; }; + +export interface CustomTypes { + Editor: YooEditor; +} + +export type ExtendedYooEditor = CustomTypes['Editor']; diff --git a/packages/core/editor/src/index.ts b/packages/core/editor/src/index.ts index 6a247984b..ff8f8d57d 100644 --- a/packages/core/editor/src/index.ts +++ b/packages/core/editor/src/index.ts @@ -53,6 +53,7 @@ export { PluginSerializeParser, YooptaMarkProps, PluginOptions, + Plugin, } from './plugins/types'; export { Elements } from './editor/elements'; diff --git a/packages/core/editor/src/plugins/SlateEditorComponent.tsx b/packages/core/editor/src/plugins/SlateEditorComponent.tsx index 2920fffb0..07661c0a1 100644 --- a/packages/core/editor/src/plugins/SlateEditorComponent.tsx +++ b/packages/core/editor/src/plugins/SlateEditorComponent.tsx @@ -14,7 +14,10 @@ import { deserializeHTML } from '../parsers/deserializeHTML'; import { useEventHandlers, useSlateEditor } from './hooks'; import { SlateElement } from '../editor/types'; -type Props, TOptions> = Plugin & { +type Props, TOptions> = Omit< + Plugin, + 'translations' +> & { id: string; marks?: YooptaMark[]; options: Plugin['options']; @@ -44,7 +47,7 @@ const SlateEditorComponent = , events, options, extensions: withExtensions, - placeholder = `Type '/' for commands`, + placeholder = 'Type / for commands', }: Props) => { const editor = useYooptaEditor(); const block = useBlockData(id); @@ -111,8 +114,10 @@ const SlateEditorComponent = , const isParentElementVoid = props.children?.props?.parent?.props?.nodeType === 'void'; const showPlaceholder = !isParentElementVoid && isCurrentPath && leaf.withPlaceholder; + const placeholderText = editor.getLabelText('editor.placeholder') || placeholder; + return ( - + {children} ); diff --git a/packages/core/editor/src/types/i18n.ts b/packages/core/editor/src/types/i18n.ts new file mode 100644 index 000000000..3b4525798 --- /dev/null +++ b/packages/core/editor/src/types/i18n.ts @@ -0,0 +1,11 @@ +export type RecursiveDotNotation = T extends object + ? { + [K in keyof T]: T[K] extends object + ? RecursiveDotNotation + : T[K] extends string + ? P extends '' + ? `${K & string}` + : `${P}.${K & string}` + : never; + }[keyof T] + : P; diff --git a/packages/core/i18n/README.md b/packages/core/i18n/README.md new file mode 100644 index 000000000..d0e09a7e0 --- /dev/null +++ b/packages/core/i18n/README.md @@ -0,0 +1,78 @@ +# @yoopta/i18n + +This package provides internationalization (i18n) support for **YooptaEditor** by extending the editor with translation capabilities. + +## 📦 Installation + +```bash +yarn add @yoopta/i18n +``` + +## 🔹 Exports + +```typescript +import { withTranslations, type I18nYooEditor } from '@yoopta/i18n'; +``` + +### **Modules** + +- **`withTranslations`** - Function to extend `YooEditor` with i18n support. +- **`I18nYooEditor`** - Extended type for `YooEditor` with i18n properties. + +--- + +## 🛠 Usage + +If you only need to extend the editor with i18n support, you can use `withTranslations()`. + +```tsx +import { withTranslations } from '@yoopta/i18n'; +import { createYooptaEditor, YooptaEditor } from '@yoopta/editor'; +import { useMemo, useEffect } from 'react'; + +import esTranslations from '@/locales/es.json'; +import ruTranslations from '@/locales/ru.json'; +import czTranslations from '@/locales/cz.json'; + +const TRANSLATIONS = { + es: esTranslations, + ru: ruTranslations, + cz: czTranslations, +}; + +const editor = useMemo(() => { + const baseEditor = createYooptaEditor(); + return withTranslations(baseEditor, { + translations: TRANSLATIONS, + defaultLanguage: 'en', + language: 'es', + }); +}, []); + +// track language change if you need +useEffect(() => { + const handleLanguageChange = (lang: string) => { + console.log('Language changed to:', lang); + }; + + editor.on('language-change', handleLanguageChange); + return () => { + editor.off('language-change', handleLanguageChange); + }; +}, [editor]); +``` + +## 📜 **I18n API** + +When `withTranslations` is applied to the editor, it extends `YooEditor` with the following additional properties and methods: + +```typescript +export type I18nYooEditor = YooEditor & { + translations: Record>; + language: string; + languages: string[]; + defaultLanguage: string; + setLanguage: (lang: string) => void; + t: (keyPath: string) => string; +}; +``` diff --git a/packages/core/i18n/package.json b/packages/core/i18n/package.json new file mode 100644 index 000000000..db785da6d --- /dev/null +++ b/packages/core/i18n/package.json @@ -0,0 +1,36 @@ +{ + "name": "@yoopta/i18n", + "version": "4.9.4", + "description": "Internalization package for Yoopta-Editor", + "author": "Darginec05 ", + "homepage": "https://github.com/Darginec05/Yoopta-Editor#readme", + "license": "MIT", + "private": false, + "main": "dist/index.js", + "type": "module", + "module": "dist/index.js", + "types": "./dist/index.d.ts", + "files": [ + "dist/" + ], + "peerDependencies": { + "@yoopta/editor": ">=4.0.0", + "react": ">=17.0.2", + "react-dom": ">=17.0.2" + }, + "publishConfig": { + "registry": "https://registry.npmjs.org" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/Darginec05/Yoopta-Editor.git" + }, + "scripts": { + "start": "rollup --config rollup.config.js --watch --bundleConfigAsCjs --environment NODE_ENV:development", + "prepublishOnly": "yarn build", + "build": "rollup --config rollup.config.js --bundleConfigAsCjs --environment NODE_ENV:production" + }, + "bugs": { + "url": "https://github.com/Darginec05/Yoopta-Editor/issues" + } +} diff --git a/packages/core/i18n/rollup.config.js b/packages/core/i18n/rollup.config.js new file mode 100644 index 000000000..3ae8a68b8 --- /dev/null +++ b/packages/core/i18n/rollup.config.js @@ -0,0 +1,6 @@ +import { createRollupConfig } from '../../../config/rollup'; + +const pkg = require('./package.json'); +export default createRollupConfig({ + pkg, +}); diff --git a/packages/core/i18n/src/extension/withTranslations.ts b/packages/core/i18n/src/extension/withTranslations.ts new file mode 100644 index 000000000..9d537aa2b --- /dev/null +++ b/packages/core/i18n/src/extension/withTranslations.ts @@ -0,0 +1,60 @@ +import { YooEditor } from '@yoopta/editor'; +import { I18nYooEditor, Translations } from '../types'; + +type TranslationOptions = { + language: K; + defaultLanguage: K; + translations: Translations; +}; + +function getNestedValue(obj: any, path: string[]): string | undefined { + return path.reduce((acc, part) => { + if (acc && typeof acc === 'object' && part in acc) { + return acc[part]; + } + return undefined; + }, obj); +} + +export function withTranslations( + editor: YooEditor, + options: TranslationOptions, +): I18nYooEditor { + const i18nEditor = editor as I18nYooEditor; + + const { translations, defaultLanguage, language } = options; + + const { getLabelText } = i18nEditor; + const languageKeys = Object.keys(translations) as K[]; + + i18nEditor.getLabelText = (key: string) => { + const keyParts = key.split('.'); + + const currentLangValue = getNestedValue( + translations[i18nEditor.language] || translations[i18nEditor.defaultLanguage], + keyParts, + ); + + if (typeof currentLangValue === 'string') { + return currentLangValue; + } + + return getLabelText(key); + }; + + i18nEditor.languages = languageKeys; + + i18nEditor.setLanguage = (lang: K) => { + if (translations[lang]) { + i18nEditor.language = lang; + i18nEditor.emit('language-change', lang); + } + }; + + i18nEditor.translations = translations; + i18nEditor.defaultLanguage = defaultLanguage; + i18nEditor.language = language; + i18nEditor.t = i18nEditor.getLabelText; + + return i18nEditor; +} diff --git a/packages/core/i18n/src/index.ts b/packages/core/i18n/src/index.ts new file mode 100644 index 000000000..6b1b6f55b --- /dev/null +++ b/packages/core/i18n/src/index.ts @@ -0,0 +1,2 @@ +export { withTranslations } from './extension/withTranslations'; +export { I18nYooEditor, Translation } from './types'; diff --git a/packages/core/i18n/src/types/i18n-yoo-editor.ts b/packages/core/i18n/src/types/i18n-yoo-editor.ts new file mode 100644 index 000000000..f2139e6e0 --- /dev/null +++ b/packages/core/i18n/src/types/i18n-yoo-editor.ts @@ -0,0 +1,64 @@ +import { YooEditor } from '@yoopta/editor'; + +export interface NestedTranslations { + [key: string]: string | NestedTranslations; +} + +/** + * The base `YooEditor` extended to add internationalization (i18n) support. + * + * This extension adds the ability to manage translations, track the current language, + * and switch between multiple available languages dynamically. + * + * @example + * ```ts + * const baseEditor = createYooptaEditor(); + * const editorWithI18n = withTranslations(baseEditor, { + * translations: { es: esTranslations, en: enTranslations }, + * defaultLanguage: "en", + * language: "es", + * }); + * ``` + */ +export type I18nYooEditor = YooEditor & { + /** + * A record of translations, where each key represents a language, + * and its value contains nested translation strings. + */ + translations: Record; + + /** + * The currently active language of the editor. + */ + language: string; + + /** + * A list of all supported languages available in the editor. + */ + languages: string[]; + + /** + * The default language of the editor (used as a fallback when translations are missing). + */ + defaultLanguage: string; + + /** + * Updates the editor's language. + * + * When `setLanguage` is called: + * - The `language` property is updated. + * - A `'language-change'` event is emitted, allowing users to listen for language updates. + * + * @example + * ```ts + * editor.setLanguage('es'); // Changes the editor language to Spanish. + * + * editor.on('language-change', (lang) => { + * console.log('Language changed to:', lang); + * }); + * ``` + * + * @param lang - The new language to set (must be one of the `languages` values). + */ + setLanguage: (lang: string) => void; +}; diff --git a/packages/core/i18n/src/types/index.ts b/packages/core/i18n/src/types/index.ts new file mode 100644 index 000000000..886ef05ec --- /dev/null +++ b/packages/core/i18n/src/types/index.ts @@ -0,0 +1,58 @@ +import { YooEditor, Plugin } from '@yoopta/editor'; + +export type TranslationPlugin = { + display: { + title: string; + description: string; + }; + options: Record; +}; + +export type Translation

[] = Plugin<{}>[]> = { + plugins: Record; + editor: { + blockOptions: { + delete: string; + duplicate: string; + turnInto: string; + copyBlockLink: string; + }; + placeholder: string; + }; + tools: { + toolbar: { + highlightColor: { + text: string; + background: string; + customColor: string; + }; + linkTitle: string; + }; + link: { + target: string; + rel: string; + update: string; + add: string; + delete: string; + url: string; + title: string; + additionalProps: string; + titlePlaceholder: string; + urlPlaceholder: string; + targetPlaceholder: string; + relPlaceholder: string; + }; + }; + [key: string]: unknown; +}; + +export type Translations = Record; + +export type I18nYooEditor = { + translations: Translations; + language: Keys; + defaultLanguage: Keys; + languages: Keys[]; + setLanguage: (lang: Keys) => void; + t: (key: string) => string; +}; diff --git a/packages/core/i18n/src/utils/index.ts b/packages/core/i18n/src/utils/index.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/packages/core/i18n/src/utils/index.ts @@ -0,0 +1 @@ +export {}; diff --git a/packages/core/i18n/tsconfig.json b/packages/core/i18n/tsconfig.json new file mode 100644 index 000000000..8c3461a8a --- /dev/null +++ b/packages/core/i18n/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../../config/tsconfig.base.json", + "include": ["react-svg.d.ts", "css-modules.d.ts", "src"], + "exclude": ["dist", "src/**/*.test.tsx", "src/**/*.stories.tsx"], + "compilerOptions": { + "rootDir": "./src", + "outDir": "./dist" + }, + "references": [ + { + "path": "../editor" + } + ] +} diff --git a/packages/development/package.json b/packages/development/package.json index f0c0f7ad8..2b1019e8c 100644 --- a/packages/development/package.json +++ b/packages/development/package.json @@ -38,6 +38,7 @@ "@yoopta/table": "*", "@yoopta/toolbar": "*", "@yoopta/video": "*", + "@yoopta/i18n": "*", "class-variance-authority": "^0.7.0", "classnames": "^2.5.1", "clsx": "^2.1.1", diff --git a/packages/development/src/components/FixedToolbar/FixedToolbar.tsx b/packages/development/src/components/FixedToolbar/FixedToolbar.tsx index 97f3802e1..aec9a8e7e 100644 --- a/packages/development/src/components/FixedToolbar/FixedToolbar.tsx +++ b/packages/development/src/components/FixedToolbar/FixedToolbar.tsx @@ -1,14 +1,14 @@ -import Paragraph, { ParagraphCommands } from '@yoopta/paragraph'; -import Embed, { EmbedCommands } from '@yoopta/embed'; -import Image, { ImageCommands } from '@yoopta/image'; -import Link, { LinkCommands } from '@yoopta/link'; -import Callout, { CalloutCommands } from '@yoopta/callout'; +import { ParagraphCommands } from '@yoopta/paragraph'; +import { EmbedCommands } from '@yoopta/embed'; +import { ImageCommands } from '@yoopta/image'; +import { LinkCommands } from '@yoopta/link'; +import { CalloutCommands } from '@yoopta/callout'; +import { TableCommands } from '@yoopta/table'; import { AccordionCommands } from '@yoopta/accordion'; import { TodoListCommands } from '@yoopta/lists'; -import { HeadingOne, HeadingOneCommands, HeadingThree, HeadingTwo } from '@yoopta/headings'; -import Table, { TableCommands } from '@yoopta/table'; -import { Blocks, Elements, YooEditor, Paths, YooptaPathIndex } from '@yoopta/editor'; +import { HeadingOneCommands } from '@yoopta/headings'; +import { Blocks, YooEditor, YooptaPathIndex } from '@yoopta/editor'; type Props = { editor: YooEditor; @@ -47,28 +47,42 @@ export const FixedToolbar = ({ editor, DEFAULT_DATA }: Props) => { }} className="p-2 text-xs shadow-md border-r hover:bg-[#64748b] hover:text-[#fff]" > - Insert Image + {/* Insert Image */} + {editor.t('editor.blockOptions.turnInto') || 'Turn into'} +

+ Languages +
+ {editor.languages.map((lang) => { + const isCurrent = lang === editor.language; + + return ( + + ); + })} +
+
@@ -67,7 +68,7 @@ export const CodeBlockOptions = ({ block, editor, element }: Props) => { > - Theme + {editor.getLabelText('plugins.Code.options.theme') || 'Theme'} @@ -85,7 +86,7 @@ export const CodeBlockOptions = ({ block, editor, element }: Props) => { > - Language + {editor.getLabelText('plugins.Code.options.language') || 'Language'} diff --git a/packages/plugins/embed/src/ui/EmbedBlockOptions.tsx b/packages/plugins/embed/src/ui/EmbedBlockOptions.tsx index 87f2c8e7c..881732b45 100644 --- a/packages/plugins/embed/src/ui/EmbedBlockOptions.tsx +++ b/packages/plugins/embed/src/ui/EmbedBlockOptions.tsx @@ -39,13 +39,13 @@ const EmbedBlockOptions = ({ editor, block, props: embedProps }: Props) => { diff --git a/packages/plugins/embed/src/ui/EmbedLinkUploader.tsx b/packages/plugins/embed/src/ui/EmbedLinkUploader.tsx index f51ef47c3..39ee09dec 100644 --- a/packages/plugins/embed/src/ui/EmbedLinkUploader.tsx +++ b/packages/plugins/embed/src/ui/EmbedLinkUploader.tsx @@ -36,7 +36,9 @@ const EmbedLinkUploader = ({ blockId, onClose }) => {
{ disabled={isEmpty} onClick={embed} > - Embed link + {editor.getLabelText('plugins.Embed.options.placeholder.external.button') || 'Embed link'}
); diff --git a/packages/plugins/embed/src/ui/EmbedUploader.tsx b/packages/plugins/embed/src/ui/EmbedUploader.tsx index 0480a6b6c..4a202fff2 100644 --- a/packages/plugins/embed/src/ui/EmbedUploader.tsx +++ b/packages/plugins/embed/src/ui/EmbedUploader.tsx @@ -1,5 +1,5 @@ import { CSSProperties } from 'react'; -import { UI } from '@yoopta/editor'; +import { UI, YooEditor } from '@yoopta/editor'; import { EmbedLinkUploader } from './EmbedLinkUploader'; type Props = { @@ -7,11 +7,12 @@ type Props = { refs: any; blockId: string; onClose: () => void; + editor: YooEditor; }; const { Overlay, Portal } = UI; -const EmbedUploader = ({ floatingStyles, refs, onClose, blockId }: Props) => { +const EmbedUploader = ({ editor, floatingStyles, refs, onClose, blockId }: Props) => { const getTabStyles = () => ({ borderBottom: '2px solid #2483e2', }); @@ -29,7 +30,7 @@ const EmbedUploader = ({ floatingStyles, refs, onClose, blockId }: Props) => { 'yoopta-button yoo-embed-py-[6px] yoo-embed-whitespace-nowrap yoo-embed-min-w-0 yoo-embed-flex-shrink-0 yoo-embed-text-[rgb(55,53,47)] yoo-embed-relative yoo-embed-cursor-pointer yoo-embed-user-select-none yoo-embed-bg-inherit yoo-embed-transition-[height_20ms_ease-in] yoo-embed-inline-flex yoo-embed-items-center yoo-embed-h-full yoo-embed-text-[14px] yoo-embed-leading-[1.2] yoo-embed-px-[8px]' } > - Embed link + {editor.getLabelText('plugins.Embed.options.placeholder.external.label') || 'Embed link'}
diff --git a/packages/plugins/embed/src/ui/Placeholder.tsx b/packages/plugins/embed/src/ui/Placeholder.tsx index faa5e1b87..4e9b4ba04 100644 --- a/packages/plugins/embed/src/ui/Placeholder.tsx +++ b/packages/plugins/embed/src/ui/Placeholder.tsx @@ -1,10 +1,12 @@ import { useFloating, inline, flip, shift, offset } from '@floating-ui/react'; import { CodeIcon } from '@radix-ui/react-icons'; +import { useYooptaEditor } from '@yoopta/editor'; import { useState } from 'react'; import { EmbedUploader } from './EmbedUploader'; const Placeholder = ({ attributes, children, blockId }) => { const [isUploaderOpen, setIsUploaderOpen] = useState(false); + const editor = useYooptaEditor(); const { refs, floatingStyles } = useFloating({ placement: 'bottom', @@ -26,7 +28,9 @@ const Placeholder = ({ attributes, children, blockId }) => { ref={refs.setReference} > - Click to add embed + + {editor.getLabelText('plugins.Embed.options.placeholder.title') || 'Click to add embed'} + {isUploaderOpen && ( { floatingStyles={floatingStyles} refs={refs} onClose={() => setIsUploaderOpen(false)} + editor={editor} /> )} {children} diff --git a/packages/plugins/file/src/ui/FileBlockOptions.tsx b/packages/plugins/file/src/ui/FileBlockOptions.tsx index 66585e074..e0ec52375 100644 --- a/packages/plugins/file/src/ui/FileBlockOptions.tsx +++ b/packages/plugins/file/src/ui/FileBlockOptions.tsx @@ -40,13 +40,13 @@ const FileBlockOptions = ({ editor, block, props: fileProps }: Props) => { diff --git a/packages/plugins/file/src/ui/FilePlaceholderUploader.tsx b/packages/plugins/file/src/ui/FilePlaceholderUploader.tsx index cf215da9c..266ec276f 100644 --- a/packages/plugins/file/src/ui/FilePlaceholderUploader.tsx +++ b/packages/plugins/file/src/ui/FilePlaceholderUploader.tsx @@ -1,4 +1,4 @@ -import { UI } from '@yoopta/editor'; +import { UI, YooEditor } from '@yoopta/editor'; import { CSSProperties } from 'react'; import { FileUploader } from './FileUploader'; @@ -8,11 +8,12 @@ type Props = { blockId: string; onClose: () => void; onSetLoading: (_s: boolean) => void; + editor: YooEditor; }; const { Overlay, Portal } = UI; -const FilePlaceholderUploader = ({ floatingStyles, refs, onClose, blockId, onSetLoading }: Props) => { +const FilePlaceholderUploader = ({ editor, floatingStyles, refs, onClose, blockId, onSetLoading }: Props) => { const getTabStyles = () => ({ borderBottom: '2px solid #2483e2', }); @@ -28,7 +29,7 @@ const FilePlaceholderUploader = ({ floatingStyles, refs, onClose, blockId, onSet style={getTabStyles()} className={`yoopta-button yoo-file-py-[6px] yoo-file-whitespace-nowrap yoo-file-min-w-0 yoo-file-flex-shrink-0 yoo-file-text-[rgb(55,53,47)] yoo-file-relative yoo-file-cursor-pointer yoo-file-user-select-none yoo-file-bg-inherit yoo-file-transition-[height_20ms_ease-in] yoo-file-inline-flex yoo-file-items-center yoo-file-h-full yoo-file-text-[14px] yoo-file-leading-[1.2] yoo-file-px-[8px]`} > - Upload + {editor.getLabelText('plugins.File.options.placeholder.upload.label') || 'Upload'}
diff --git a/packages/plugins/file/src/ui/FileUploader.tsx b/packages/plugins/file/src/ui/FileUploader.tsx index b0e7b0aff..e6fd65d1c 100644 --- a/packages/plugins/file/src/ui/FileUploader.tsx +++ b/packages/plugins/file/src/ui/FileUploader.tsx @@ -57,7 +57,7 @@ const FileUploader = ({ accept = '', onClose, blockId, onSetLoading }: Props) => onChange={onChange} multiple={false} /> - Upload file + {editor.getLabelText('plugins.File.options.placeholder.upload.label') || 'Upload file'}
); diff --git a/packages/plugins/file/src/ui/Placeholder.tsx b/packages/plugins/file/src/ui/Placeholder.tsx index 05d91d1ab..2dd8ba01c 100644 --- a/packages/plugins/file/src/ui/Placeholder.tsx +++ b/packages/plugins/file/src/ui/Placeholder.tsx @@ -1,5 +1,6 @@ import { useFloating, inline, flip, shift, offset } from '@floating-ui/react'; import { FileIcon } from '@radix-ui/react-icons'; +import { useYooptaEditor } from '@yoopta/editor'; import { CSSProperties, useState } from 'react'; import { FilePlaceholderUploader } from './FilePlaceholderUploader'; import { Loader } from './Loader'; @@ -7,6 +8,7 @@ import { Loader } from './Loader'; const Placeholder = ({ attributes, children, blockId }) => { const [isUploaderOpen, setIsUploaderOpen] = useState(false); const [loading, setLoading] = useState(false); + const editor = useYooptaEditor(); const { refs, floatingStyles } = useFloating({ placement: 'bottom', @@ -39,7 +41,11 @@ const Placeholder = ({ attributes, children, blockId }) => { ) : ( )} - {loading ? 'Loading...' : 'Click to add file'} + + {loading + ? editor.getLabelText('plugins.File.options.placeholder.loading') || 'Loading...' + : editor.getLabelText('plugins.File.options.placeholder.title') || 'Click to add file'} + {loading && (
{ refs={refs} onClose={() => setIsUploaderOpen(false)} onSetLoading={onSetLoading} + editor={editor} /> )} {children} diff --git a/packages/plugins/image/src/ui/EmbedUploader.tsx b/packages/plugins/image/src/ui/EmbedUploader.tsx index e1ac7b85f..f3d47c811 100644 --- a/packages/plugins/image/src/ui/EmbedUploader.tsx +++ b/packages/plugins/image/src/ui/EmbedUploader.tsx @@ -26,7 +26,9 @@ const EmbedUploader = ({ blockId, onClose }) => {
{ disabled={isEmpty} onClick={embed} > - Embed image + {editor.getLabelText('plugins.Image.options.placeholder.external.button') || 'Embed image'}
); diff --git a/packages/plugins/image/src/ui/FileUploader.tsx b/packages/plugins/image/src/ui/FileUploader.tsx index b09aab0c9..8e7a4219a 100644 --- a/packages/plugins/image/src/ui/FileUploader.tsx +++ b/packages/plugins/image/src/ui/FileUploader.tsx @@ -66,7 +66,7 @@ const FileUploader = ({ accept = 'image/*', onClose, blockId, onSetLoading }: Pr onChange={onChange} multiple={false} /> - Upload image + {editor.getLabelText('plugins.Image.options.placeholder.upload.label') || 'Upload image'}
); diff --git a/packages/plugins/image/src/ui/ImageBlockOptions.tsx b/packages/plugins/image/src/ui/ImageBlockOptions.tsx index fd3129797..2a23649f6 100644 --- a/packages/plugins/image/src/ui/ImageBlockOptions.tsx +++ b/packages/plugins/image/src/ui/ImageBlockOptions.tsx @@ -153,7 +153,7 @@ const ImageBlockOptions = ({ editor, block, props: imageProps }: Props) => { @@ -227,7 +228,7 @@ const ImageBlockOptions = ({ editor, block, props: imageProps }: Props) => { ) : ( )} - Replace image + {editor.getLabelText('plugins.Image.options.replaceImage') || 'Replace image'} @@ -240,13 +241,13 @@ const ImageBlockOptions = ({ editor, block, props: imageProps }: Props) => { onClick={onToggleAlign} > - Alignment + {editor.getLabelText('plugins.Image.options.align') || 'Align'} diff --git a/packages/plugins/image/src/ui/ImageUploader.tsx b/packages/plugins/image/src/ui/ImageUploader.tsx index 2c809806a..ecbdafbbf 100644 --- a/packages/plugins/image/src/ui/ImageUploader.tsx +++ b/packages/plugins/image/src/ui/ImageUploader.tsx @@ -1,4 +1,4 @@ -import { UI } from '@yoopta/editor'; +import { UI, YooEditor } from '@yoopta/editor'; import { CSSProperties, useState } from 'react'; import { EmbedUploader } from './EmbedUploader'; import { FileUploader } from './FileUploader'; @@ -9,12 +9,13 @@ type Props = { blockId: string; onClose: () => void; onSetLoading: (_s: boolean) => void; + editor: YooEditor; }; const { Overlay, Portal } = UI; type Tab = 'upload' | 'embed'; -const ImageUploader = ({ floatingStyles, refs, onClose, blockId, onSetLoading }: Props) => { +const ImageUploader = ({ floatingStyles, refs, onClose, blockId, editor, onSetLoading }: Props) => { const [activeTab, setActiveTab] = useState('upload'); const switchTab = (tab: Tab) => setActiveTab(tab); @@ -38,7 +39,7 @@ const ImageUploader = ({ floatingStyles, refs, onClose, blockId, onSetLoading }: style={getTabStyles(isUploader)} className={`yoopta-button yoo-image-py-[6px] yoo-image-whitespace-nowrap yoo-image-min-w-0 yoo-image-flex-shrink-0 yoo-image-text-[rgb(55,53,47)] yoo-image-relative yoo-image-cursor-pointer yoo-image-user-select-none yoo-image-bg-inherit yoo-image-transition-[height_20ms_ease-in] yoo-image-inline-flex yoo-image-items-center yoo-image-h-full yoo-image-text-[14px] yoo-image-leading-[1.2] yoo-image-px-[8px]`} > - Upload + {editor.getLabelText('plugins.Image.options.placeholder.upload.label') || 'Upload'}
diff --git a/packages/plugins/image/src/ui/InputAltText.tsx b/packages/plugins/image/src/ui/InputAltText.tsx index b43a0457d..9f445d2ea 100644 --- a/packages/plugins/image/src/ui/InputAltText.tsx +++ b/packages/plugins/image/src/ui/InputAltText.tsx @@ -2,7 +2,7 @@ import { UI } from '@yoopta/editor'; const { Overlay, Portal } = UI; -const InputAltText = ({ floatingStyles, onClose, refs, value, onChange, onSave, onDelete }) => { +const InputAltText = ({ editor, floatingStyles, onClose, refs, value, onChange, onSave, onDelete }) => { return ( @@ -10,7 +10,8 @@ const InputAltText = ({ floatingStyles, onClose, refs, value, onChange, onSave,
onChange(e.target.value)} - placeholder="Edit alt text" + // placeholder="Edit alt text" + placeholder={editor.getLabelText('plugins.Image.options.alt.placeholder') || 'Edit alt text'} autoComplete="off" />
@@ -31,14 +33,16 @@ const InputAltText = ({ floatingStyles, onClose, refs, value, onChange, onSave, disabled={!value} onClick={onSave} > - Update + {/* Update */} + {editor.getLabelText('plugins.Image.options.alt.save') || 'Save'}
diff --git a/packages/plugins/image/src/ui/Placeholder.tsx b/packages/plugins/image/src/ui/Placeholder.tsx index 859b21b69..869ba32b9 100644 --- a/packages/plugins/image/src/ui/Placeholder.tsx +++ b/packages/plugins/image/src/ui/Placeholder.tsx @@ -1,5 +1,6 @@ import { useFloating, inline, flip, shift, offset } from '@floating-ui/react'; import { ImageIcon } from '@radix-ui/react-icons'; +import { useYooptaEditor } from '@yoopta/editor'; import { CSSProperties, useState } from 'react'; import { ImageUploader } from './ImageUploader'; import { Loader } from './Loader'; @@ -7,6 +8,7 @@ import { Loader } from './Loader'; const Placeholder = ({ attributes, children, blockId }) => { const [isUploaderOpen, setIsUploaderOpen] = useState(false); const [loading, setLoading] = useState(false); + const editor = useYooptaEditor(); const { refs, floatingStyles } = useFloating({ placement: 'bottom', @@ -40,7 +42,11 @@ const Placeholder = ({ attributes, children, blockId }) => { ) : ( )} - {loading ? 'Loading...' : 'Click to add image'} + + {loading + ? editor.getLabelText('plugins.Image.options.placeholder.loading') || 'Loading...' + : editor.getLabelText('plugins.Image.options.placeholder.title') || 'Click to add image'} + {loading && (
{ refs={refs} onClose={() => setIsUploaderOpen(false)} onSetLoading={onSetLoading} + editor={editor} /> )} {children} diff --git a/packages/plugins/link/src/ui/LinkHoverPreview.tsx b/packages/plugins/link/src/ui/LinkHoverPreview.tsx index 811692391..936f159b9 100644 --- a/packages/plugins/link/src/ui/LinkHoverPreview.tsx +++ b/packages/plugins/link/src/ui/LinkHoverPreview.tsx @@ -115,7 +115,8 @@ const LinkHoverPreview = ({ style, setFloating, element, setHoldLinkTool, blockI setIsEditLinkToolsOpen((prev) => !prev); }} > - Edit + {/* Edit */} + {editor.getLabelText('tools.link.edit') || 'Edit'}
diff --git a/packages/plugins/paragraph/src/plugin/index.tsx b/packages/plugins/paragraph/src/plugin/index.tsx index cef386681..de5c5e309 100644 --- a/packages/plugins/paragraph/src/plugin/index.tsx +++ b/packages/plugins/paragraph/src/plugin/index.tsx @@ -1,7 +1,6 @@ import { serializeTextNodes, serializeTextNodesIntoMarkdown, YooptaPlugin } from '@yoopta/editor'; -import { Element, Transforms } from 'slate'; import { ParagraphCommands } from '../commands'; -import { ParagraphElement, ParagraphElementMap } from '../types'; +import { ParagraphElementMap } from '../types'; import { ParagraphRender } from '../ui/Paragraph'; const Paragraph = new YooptaPlugin({ diff --git a/packages/plugins/table/src/components/TableBlockOptions.tsx b/packages/plugins/table/src/components/TableBlockOptions.tsx index e68028540..6c781e08e 100644 --- a/packages/plugins/table/src/components/TableBlockOptions.tsx +++ b/packages/plugins/table/src/components/TableBlockOptions.tsx @@ -40,7 +40,8 @@ const TableBlockOptions = ({ editor, block, table }: Props) => { > - Header row + {/* Header row */} + {editor.getLabelText('plugins.Table.options.toggleHeaderRow') || 'Header row'} {isHeaderRowEnabled && } @@ -57,7 +58,8 @@ const TableBlockOptions = ({ editor, block, table }: Props) => { height={16} className="yoo-table-w-4 yoo-table-h-4 yoo-table-mr-2 yoo-table-rotate-180" /> - Header column + {/* Header column */} + {editor.getLabelText('plugins.Table.options.toggleHeaderColumn') || 'Header column'} {isHeaderColumnEnabled && } diff --git a/packages/plugins/table/src/components/TableColumnOptions.tsx b/packages/plugins/table/src/components/TableColumnOptions.tsx index 5b4e89c99..f1185d58d 100644 --- a/packages/plugins/table/src/components/TableColumnOptions.tsx +++ b/packages/plugins/table/src/components/TableColumnOptions.tsx @@ -93,26 +93,30 @@ const TableColumnOptions = ({ editor, blockId, element, onClose, ...props }: Pro @@ -120,7 +124,8 @@ const TableColumnOptions = ({ editor, blockId, element, onClose, ...props }: Pro diff --git a/packages/plugins/table/src/components/TableRowOptions.tsx b/packages/plugins/table/src/components/TableRowOptions.tsx index 3fc4f163a..14548797b 100644 --- a/packages/plugins/table/src/components/TableRowOptions.tsx +++ b/packages/plugins/table/src/components/TableRowOptions.tsx @@ -82,33 +82,33 @@ const TableRowOptions = ({ editor, blockId, onClose, tdElement, ...props }: Prop diff --git a/packages/plugins/video/src/ui/EmbedUploader.tsx b/packages/plugins/video/src/ui/EmbedUploader.tsx index 6f1054095..8a4dda946 100644 --- a/packages/plugins/video/src/ui/EmbedUploader.tsx +++ b/packages/plugins/video/src/ui/EmbedUploader.tsx @@ -34,7 +34,9 @@ const EmbedUploader = ({ blockId, onClose }) => {
{ disabled={isEmpty} onClick={embed} > - Embed video + {editor.getLabelText('plugins.Image.options.placeholder.external.button') || 'Embed video'}
); diff --git a/packages/plugins/video/src/ui/FileUploader.tsx b/packages/plugins/video/src/ui/FileUploader.tsx index 8860f8649..ade75acb9 100644 --- a/packages/plugins/video/src/ui/FileUploader.tsx +++ b/packages/plugins/video/src/ui/FileUploader.tsx @@ -67,7 +67,7 @@ const FileUploader = ({ accept = 'video/*', onClose, blockId, onSetLoading }: Pr onChange={onChange} multiple={false} /> - Upload video + {editor.getLabelText('plugins.Image.options.placeholder.upload.label') || 'Upload video'} ); diff --git a/packages/plugins/video/src/ui/Placeholder.tsx b/packages/plugins/video/src/ui/Placeholder.tsx index 18f2f43f7..ce5e36603 100644 --- a/packages/plugins/video/src/ui/Placeholder.tsx +++ b/packages/plugins/video/src/ui/Placeholder.tsx @@ -3,6 +3,7 @@ import { VideoIcon } from '@radix-ui/react-icons'; import { CSSProperties, useState } from 'react'; import { VideoUploader } from './VideoUploader'; import { Loader } from './Loader'; +import { useYooptaEditor } from '@yoopta/editor'; const loadingStyles: CSSProperties = { width: '100%', @@ -12,6 +13,7 @@ const loadingStyles: CSSProperties = { const Placeholder = ({ attributes, children, blockId }) => { const [isUploaderOpen, setIsUploaderOpen] = useState(false); const [loading, setLoading] = useState(false); + const editor = useYooptaEditor(); const { refs, floatingStyles } = useFloating({ placement: 'bottom', @@ -40,7 +42,11 @@ const Placeholder = ({ attributes, children, blockId }) => { ) : ( )} - {loading ? 'Loading...' : 'Click to add video'} + + {loading + ? editor.getLabelText('plugins.Image.options.placeholder.loading') || 'Loading...' + : editor.getLabelText('plugins.Image.options.placeholder.title') || 'Click to add video'} + {loading && (
{ refs={refs} onClose={() => setIsUploaderOpen(false)} onSetLoading={onSetLoading} + editor={editor} /> )} {children} diff --git a/packages/plugins/video/src/ui/VideoBlockOptions.tsx b/packages/plugins/video/src/ui/VideoBlockOptions.tsx index 7f264e943..0f341db5e 100644 --- a/packages/plugins/video/src/ui/VideoBlockOptions.tsx +++ b/packages/plugins/video/src/ui/VideoBlockOptions.tsx @@ -149,7 +149,7 @@ const VideoBlockOptions = ({ editor, block, props: videoProps }: Props) => { @@ -257,12 +260,12 @@ const VideoBlockOptions = ({ editor, block, props: videoProps }: Props) => { {isExternalVideo ? ( <> - Open + {editor.getLabelText('plugins.Video.options.openVideo') || 'Open'} ) : ( <> - Download + {editor.getLabelText('plugins.Video.options.downloadVideo') || 'Download'} )} diff --git a/packages/plugins/video/src/ui/VideoUploader.tsx b/packages/plugins/video/src/ui/VideoUploader.tsx index f8ea93c17..f65402dad 100644 --- a/packages/plugins/video/src/ui/VideoUploader.tsx +++ b/packages/plugins/video/src/ui/VideoUploader.tsx @@ -1,4 +1,4 @@ -import { UI } from '@yoopta/editor'; +import { UI, YooEditor } from '@yoopta/editor'; import { CSSProperties, useState } from 'react'; import { EmbedUploader } from './EmbedUploader'; import { FileUploader } from './FileUploader'; @@ -11,11 +11,12 @@ type Props = { blockId: string; onClose: () => void; onSetLoading: (_s: boolean) => void; + editor: YooEditor; }; type Tab = 'upload' | 'embed'; -const VideoUploader = ({ floatingStyles, refs, onClose, blockId, onSetLoading }: Props) => { +const VideoUploader = ({ floatingStyles, refs, onClose, blockId, onSetLoading, editor }: Props) => { const [activeTab, setActiveTab] = useState('upload'); const switchTab = (tab: Tab) => setActiveTab(tab); @@ -39,7 +40,7 @@ const VideoUploader = ({ floatingStyles, refs, onClose, blockId, onSetLoading }: style={getTabStyles(isUploader)} className={`yoopta-button yoo-video-py-[6px] yoo-video-whitespace-nowrap yoo-video-min-w-0 yoo-video-flex-shrink-0 yoo-video-text-[rgb(55,53,47)] yoo-video-relative yoo-video-cursor-pointer yoo-video-user-select-none yoo-video-bg-inherit yoo-video-transition-[height_20ms_ease-in] yoo-video-inline-flex yoo-video-items-center yoo-video-h-full yoo-video-text-[14px] yoo-video-leading-[1.2] yoo-video-px-[8px]`} > - Upload + {editor.getLabelText('plugins.Image.options.placeholder.upload.label') || 'Upload'}
diff --git a/packages/tools/action-menu/src/components/ActionMenuList.tsx b/packages/tools/action-menu/src/components/ActionMenuList.tsx index b11782889..740c73535 100644 --- a/packages/tools/action-menu/src/components/ActionMenuList.tsx +++ b/packages/tools/action-menu/src/components/ActionMenuList.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import {useEffect, useMemo, useState} from 'react'; import { DefaultActionMenuRender } from './DefaultActionMenuRender'; import { useFloating, offset, flip, shift, autoUpdate, useTransitionStyles } from '@floating-ui/react'; import { Editor, Element, NodeEntry, Path, Transforms } from 'slate'; @@ -95,11 +95,16 @@ const ActionMenuList = ({ items, render }: ActionMenuToolProps) => { duration: 100, }); - const blockTypes: ActionMenuToolItem[] = mapActionMenuItems(editor, items || Object.keys(editor.blocks)); + const blockTypes = useMemo(() => mapActionMenuItems(editor, items || Object.keys(editor.blocks)), [editor, items]); const [selectedAction, setSelectedAction] = useState(blockTypes[0]); const [actions, setActions] = useState(blockTypes); + useEffect(() => { + setActions(blockTypes); + setSelectedAction(blockTypes[0]); + }, [blockTypes]); + const onOpen = () => setIsMenuOpen(true); const onClose = () => setIsMenuOpen(false); @@ -354,28 +359,18 @@ const ActionMenuList = ({ items, render }: ActionMenuToolProps) => { const style = { ...floatingStyles, ...transitionStyles }; - if (render) { - return ( - // [TODO] - take care about SSR + const menuContent = render ? render({ ...renderProps, actions }) : ; + + + // [TODO] - take care about SSR + return ( {isMounted && ( -
- {/* [TODO] - pass key down handler */} - {render({ ...renderProps, actions })} -
+
+ {menuContent} +
)}
- ); - } - - return ( - - {isMounted && ( -
- -
- )} -
); }; diff --git a/packages/tools/action-menu/src/components/DefaultActionMenuRender.tsx b/packages/tools/action-menu/src/components/DefaultActionMenuRender.tsx index 363891229..004937dac 100644 --- a/packages/tools/action-menu/src/components/DefaultActionMenuRender.tsx +++ b/packages/tools/action-menu/src/components/DefaultActionMenuRender.tsx @@ -42,16 +42,20 @@ const DefaultActionMenuRender = ({ > {empty && (
- No actions available + {editor.getLabelText('tools.actionMenu.noActionsAvailable') || 'No actions available'}
)} - {actions.map((action, i) => { + {actions.map((action) => { const block = editor.blocks[action.type]; if (!block) return null; - const title = block.options?.display?.title || block.type; - const description = block.options?.display?.description || ''; + const title = + editor.getLabelText(`plugins.${block.type}.display.title`) || block.options?.display?.title || block.type; + const description = + editor.getLabelText(`plugins.${block.type}.display.description`) || + block.options?.display?.description || + ''; const Icon = action.icon || DEFAULT_ICONS_MAP[action.type]; return ( diff --git a/packages/tools/link-tool/src/components/DefaultLinkToolRender.tsx b/packages/tools/link-tool/src/components/DefaultLinkToolRender.tsx index 05f674cf4..2365b6ca3 100644 --- a/packages/tools/link-tool/src/components/DefaultLinkToolRender.tsx +++ b/packages/tools/link-tool/src/components/DefaultLinkToolRender.tsx @@ -42,7 +42,7 @@ const DefaultLinkToolRender = (props: LinkToolRenderProps) => { {withTitle && (
{ name="title" value={link.title || ''} onChange={onChange} - placeholder="Edit link title" + placeholder={editor.getLabelText('tools.link.titlePlaceholder') || 'Edit link title'} autoComplete="off" />
@@ -59,7 +59,7 @@ const DefaultLinkToolRender = (props: LinkToolRenderProps) => { {withLink && (
{ name="url" value={link.url || ''} onChange={onChange} - placeholder="Edit link URL" + placeholder={editor.getLabelText('tools.link.urlPlaceholder') || 'Edit link URL'} autoComplete="off" />
@@ -78,14 +78,14 @@ const DefaultLinkToolRender = (props: LinkToolRenderProps) => { className="yoopta-button yoopta-link-tool-label !yoo-link-tool-font-[500] yoo-link-tool-mt-2 !yoo-link-tool-mb-0 !yoo-link-tool-flex yoo-link-tool-justify-between yoo-link-tool-items-center yoo-link-tool-w-full" onClick={() => setAdditionPropsOpen((p) => !p)} > - Additional props + {editor.getLabelText('tools.link.additionalProps') || 'Additional props'} {isAdditionalPropsOpen && ( <>
{ name="target" value={link.target} onChange={onChange} - placeholder="Edit link target" + placeholder={editor.getLabelText('tools.link.targetPlaceholder') || 'Edit link target'} autoComplete="off" />
{ name="rel" value={link.rel} onChange={onChange} - placeholder="Edit link rel" + placeholder={editor.getLabelText('tools.link.relPlaceholder') || 'Edit link rel'} autoComplete="off" />
@@ -122,14 +122,16 @@ const DefaultLinkToolRender = (props: LinkToolRenderProps) => { disabled={!link.url} onClick={onSave} > - {props.link.url ? 'Update' : 'Add'} + {props.link.url + ? editor.getLabelText('tools.link.update') || 'Update' + : editor.getLabelText('tools.link.add') || 'Add'}
diff --git a/packages/tools/toolbar/src/components/DefaultToolbarRender.tsx b/packages/tools/toolbar/src/components/DefaultToolbarRender.tsx index 807fe3cab..fc03aac65 100644 --- a/packages/tools/toolbar/src/components/DefaultToolbarRender.tsx +++ b/packages/tools/toolbar/src/components/DefaultToolbarRender.tsx @@ -114,7 +114,11 @@ const DefaultToolbarRender = ({ activeBlock, editor, toggleHoldToolbar }: Toolba }; }; - const blockLabel = activeBlock?.options?.display?.title || activeBlock?.type || ''; + const blockLabel = + editor.getLabelText(`plugins.${activeBlock?.type}.display.title`) || + activeBlock?.options?.display?.title || + activeBlock?.type || + ''; const ActionMenu = tools.ActionMenu; const LinkTool = tools.LinkTool; @@ -269,7 +273,7 @@ const DefaultToolbarRender = ({ activeBlock, editor, toggleHoldToolbar }: Toolba }} style={getModalTriggerStyle('link')} > - Link + {editor.getLabelText('tools.toolbar.linkTitle') || 'Link'} {modals.link && !!LinkTool && ( diff --git a/packages/tools/toolbar/src/components/HighlightColor.tsx b/packages/tools/toolbar/src/components/HighlightColor.tsx index f35b27812..057008629 100644 --- a/packages/tools/toolbar/src/components/HighlightColor.tsx +++ b/packages/tools/toolbar/src/components/HighlightColor.tsx @@ -60,7 +60,7 @@ const HighlightColor = ({ editor, refs, floatingStyles, highlightColors = {} }: } setLocalColor(null); - }, 500); + }, 250); const handleColorChange = (type: 'color' | 'backgroundColor', color: string, shouldDebounce?: boolean) => { if (shouldDebounce) { @@ -105,7 +105,7 @@ const HighlightColor = ({ editor, refs, floatingStyles, highlightColors = {} }: }`} onClick={() => setTab('text')} > - Text + {editor.getLabelText('tools.toolbar.highlightColor.text') || 'Text'} @@ -139,7 +139,7 @@ const HighlightColor = ({ editor, refs, floatingStyles, highlightColors = {} }: className="yoo-toolbar-text-sm yoo-toolbar-text-gray-600 hover:yoo-toolbar-text-gray-900 yoo-toolbar-flex yoo-toolbar-items-center" onClick={() => setShowColorPicker(!showColorPicker)} > - Color Picker + {editor.getLabelText('tools.toolbar.highlightColor.customColor') || 'Custom color'} diff --git a/packages/tools/toolbar/src/components/utils.ts b/packages/tools/toolbar/src/components/utils.ts index 011206e84..a58813274 100644 --- a/packages/tools/toolbar/src/components/utils.ts +++ b/packages/tools/toolbar/src/components/utils.ts @@ -20,11 +20,14 @@ export function buildActionMenuRenderProps({ editor, view, onClose }: Params) { const getActions = () => { const items = Object.keys(editor.blocks) .filter((type) => filterToggleActions(editor, type)) - .map((action) => { - const title = editor.blocks[action].options?.display?.title || action; - const description = editor.blocks[action].options?.display?.description; - const icon = editor.blocks[action].options?.display?.icon; - return { type: action, title, description, icon }; + .map((type) => { + const title = + editor.getLabelText(`plugins.${type}.display.title`) || editor.blocks[type].options?.display?.title || type; + const description = + editor.getLabelText(`plugins.${type}.display.description`) || + editor.blocks[type].options?.display?.description; + const icon = editor.blocks[type].options?.display?.icon; + return { type: type, title, description, icon }; }); return items; diff --git a/web/next-example/src/components/examples/withTranslations/index.tsx b/web/next-example/src/components/examples/withTranslations/index.tsx new file mode 100644 index 000000000..e404a28c0 --- /dev/null +++ b/web/next-example/src/components/examples/withTranslations/index.tsx @@ -0,0 +1,111 @@ +import YooptaEditor, { createYooptaEditor, useTranslation } from '@yoopta/editor'; + +import Paragraph from '@yoopta/paragraph'; +import Blockquote from '@yoopta/blockquote'; +import Embed from '@yoopta/embed'; +import Link from '@yoopta/link'; +import Callout from '@yoopta/callout'; +import { NumberedList, BulletedList, TodoList } from '@yoopta/lists'; +import { Bold, Italic, CodeMark, Underline, Strike, Highlight } from '@yoopta/marks'; +import { HeadingOne, HeadingThree, HeadingTwo } from '@yoopta/headings'; +import Code from '@yoopta/code'; +import Table from '@yoopta/table'; +import Divider from '@yoopta/divider'; +import ActionMenuList, { DefaultActionMenuRender } from '@yoopta/action-menu-list'; +import Toolbar, { DefaultToolbarRender } from '@yoopta/toolbar'; +import LinkTool, { DefaultLinkToolRender } from '@yoopta/link-tool'; +import { useMemo, useRef } from 'react'; +import { INITIAL_VALUE } from './initValue'; + +const plugins = [ + Paragraph, + Table, + Divider, + HeadingOne, + HeadingTwo, + HeadingThree, + Blockquote, + Callout, + NumberedList, + BulletedList, + TodoList, + Code, + Link, + Embed, +]; + +const TOOLS = { + ActionMenu: { + render: DefaultActionMenuRender, + tool: ActionMenuList, + }, + Toolbar: { + render: DefaultToolbarRender, + tool: Toolbar, + }, + LinkTool: { + render: DefaultLinkToolRender, + tool: LinkTool, + }, +}; + +const MARKS = [Bold, Italic, CodeMark, Underline, Strike, Highlight]; + +const translations = { + bro: { + core: { + editor_placeholder: "Yo, smash '/' for options, bro!", + save: 'Bro, hit Save', + cancel: 'Nah, cancel it', + }, + paragraph: { + title: 'Type it out, dude...', + description: 'Just some chill text, bro', + }, + table: { + title: 'Table, bro!', + description: "Lines and boxes, keepin' it organized, my dude.", + }, + }, +}; + +function WithTranslationsExample() { + const editor = useMemo(() => createYooptaEditor(), []); + const selectionRef = useRef(null); + const { currentLanguage, setLanguage } = useTranslation(); + + return ( +
+
+ + +
+ console.log(value)} + translations={translations} + /> +
+ ); +} + +export default WithTranslationsExample; diff --git a/web/next-example/src/components/examples/withTranslations/initValue.ts b/web/next-example/src/components/examples/withTranslations/initValue.ts new file mode 100644 index 000000000..b5f15bd88 --- /dev/null +++ b/web/next-example/src/components/examples/withTranslations/initValue.ts @@ -0,0 +1,536 @@ +export const INITIAL_VALUE = { + "a3dcc117-7a3a-410f-98fa-8cbf620acebf": { + "id": "a3dcc117-7a3a-410f-98fa-8cbf620acebf", + "value": [ + { + "id": "e3599711-7674-498b-bcba-099b600d6eb3", + "type": "heading-one", + "children": [ + { + "text": "Let's start from lorem ipsum" + } + ], + "props": { + "nodeType": "block" + } + } + ], + "type": "HeadingOne", + "meta": { + "order": 0, + "depth": 0 + } + }, + "40fb1893-cc80-43c9-9a4c-95ce7ebd0cd8": { + "id": "40fb1893-cc80-43c9-9a4c-95ce7ebd0cd8", + "value": [ + { + "id": "2221b81d-11c3-4160-b40f-6db00a0af5b9", + "type": "paragraph", + "children": [ + { + "text": "" + } + ], + "props": { + "nodeType": "block" + } + } + ], + "type": "Paragraph", + "meta": { + "order": 1, + "depth": 0 + } + }, + "a98db6b6-496e-40ab-9893-c4778c7f967a": { + "id": "a98db6b6-496e-40ab-9893-c4778c7f967a", + "type": "Code", + "meta": { + "depth": 0, + "order": 6 + }, + "value": [ + { + "id": "c442332d-da41-4359-93f4-bffc445189fe", + "type": "code", + "props": { + "nodeType": "void", + "language": "javascript", + "theme": "GithubDark" + }, + "children": [ + { + "text": "const translations = {\n 'bro': {\n core: {\n editor_placeholder: \"Yo, smash '/' for options, bro!\",\n save: \"Bro, hit Save\",\n cancel: \"Nah, cancel it\"\n },\n paragraph: {\n title: \"Type it out, dude...\",\n description: \"Just some chill text, bro\"\n },\n table: {\n title: \"Table, bro!\",\n description: \"Lines and boxes, keepin' it organized, my dude.\"\n }\n};\n\n;" + } + ] + } + ] + }, + "8c49322d-1e9a-4d1c-8516-f9f563ea2853": { + "id": "8c49322d-1e9a-4d1c-8516-f9f563ea2853", + "type": "Paragraph", + "value": [ + { + "id": "e20bad2f-5309-485b-871d-ec0aaf319873", + "type": "paragraph", + "children": [ + { + "text": "The" + }, + { + "text": " " + }, + { + "text": "translations", + "code": true + }, + { + "text": " " + }, + { + "text": "property in" + }, + { + "text": " " + }, + { + "text": "YooptaEditorProps", + "code": true + }, + { + "text": " " + }, + { + "text": "allows you to provide custom translations for various labels, placeholders, and text elements in the editor. This makes it easy to localize the editor into different languages or customize specific text for your users." + } + ], + "props": { + "nodeType": "block" + } + } + ], + "meta": { + "align": "left", + "depth": 0, + "order": 2 + } + }, + "863ba1a3-17f7-4547-b9bb-03cda13e5cc7": { + "id": "863ba1a3-17f7-4547-b9bb-03cda13e5cc7", + "type": "BulletedList", + "value": [ + { + "id": "a55e57ed-9195-4dc0-95b1-a19010fab4c5", + "type": "bulleted-list", + "children": [ + { + "text": "Translations are grouped by " + }, + { + "bold": true, + "text": "languages" + }, + { + "text": " (e.g., " + }, + { + "code": true, + "text": "en" + }, + { + "text": ", " + }, + { + "code": true, + "text": "es" + }, + { + "text": ") and " + }, + { + "bold": true, + "text": "namespaces/plugins" + }, + { + "text": " (e.g., " + }, + { + "code": true, + "text": "core" + }, + { + "text": ", " + }, + { + "code": true, + "text": "paragraph" + }, + { + "text": ")." + } + ], + "props": { + "nodeType": "block" + } + } + ], + "meta": { + "align": "left", + "depth": 0, + "order": 3 + } + }, + "f684b4a4-0cfb-4a75-ab2b-efeea9265a04": { + "id": "f684b4a4-0cfb-4a75-ab2b-efeea9265a04", + "type": "BulletedList", + "value": [ + { + "id": "02e71ba2-0cab-44e6-9fc2-cf97703a871b", + "type": "bulleted-list", + "children": [ + { + "text": "You can override default translations or add new languages at initialization." + } + ], + "props": { + "nodeType": "block" + } + } + ], + "meta": { + "align": "left", + "depth": 0, + "order": 4 + } + }, + "eaea1bc7-813d-4d38-8cc2-081fbf9a2e20": { + "id": "eaea1bc7-813d-4d38-8cc2-081fbf9a2e20", + "type": "Paragraph", + "value": [ + { + "id": "3beaec7e-72a8-42c0-94c0-8a9fcc5b7315", + "type": "paragraph", + "children": [ + { + "text": "The" + }, + { + "text": " " + }, + { + "text": "setLanguage", + "code": true + }, + { + "text": " " + }, + { + "text": "function from the" + }, + { + "text": " " + }, + { + "text": "useTranslation", + "code": true + }, + { + "text": " " + }, + { + "text": "hook allows you to change the language at runtime:" + } + ], + "props": { + "nodeType": "block" + } + } + ], + "meta": { + "align": "left", + "depth": 0, + "order": 9 + } + }, + "5cadd71e-1ad7-4ab0-bc43-3dd6da8527f2": { + "id": "5cadd71e-1ad7-4ab0-bc43-3dd6da8527f2", + "type": "Code", + "value": [ + { + "children": [ + { + "text": "import {useTranslation} from '@yoopta-editor/core';\n\nconst { setLanguage } = useTranslation();\n\n// Switch to Spanish\nsetLanguage('bro');" + } + ], + "type": "code", + "id": "30489325-114b-4294-9e69-99f31120e373", + "props": { + "language": "javascript", + "theme": "VSCode", + "nodeType": "void" + } + } + ], + "meta": { + "align": "left", + "depth": 0, + "order": 10 + } + }, + "c2a332bc-ab08-42d8-a058-fc751980ae11": { + "id": "c2a332bc-ab08-42d8-a058-fc751980ae11", + "type": "HeadingThree", + "meta": { + "depth": 0, + "order": 8 + }, + "value": [ + { + "id": "4c3b33dd-b042-4e01-b72b-2309ac8533b0", + "type": "heading-three", + "props": { + "nodeType": "block" + }, + "children": [ + { + "text": "Changing the Language Dynamically" + } + ] + } + ] + }, + "ccbd5d66-0e7b-47f9-9486-5a0bc0e3b2bb": { + "id": "ccbd5d66-0e7b-47f9-9486-5a0bc0e3b2bb", + "type": "Paragraph", + "value": [ + { + "id": "d981af18-c168-4dc4-b433-96c88f3590ba", + "type": "paragraph", + "children": [ + { + "text": "" + } + ], + "props": { + "nodeType": "block" + } + } + ], + "meta": { + "align": "left", + "depth": 0, + "order": 7 + } + }, + "36f4b7ca-5bfb-4abf-84a2-168b0f59d0e9": { + "id": "36f4b7ca-5bfb-4abf-84a2-168b0f59d0e9", + "type": "BulletedList", + "value": [ + { + "id": "255b361e-3ef9-4e00-9917-67e9549da593", + "type": "bulleted-list", + "children": [ + { + "text": "If no translations prop is provided, the editor uses its default translations in English." + } + ], + "props": { + "nodeType": "block" + } + }, + { + "id": "255b361e-3ef9-4e00-9917-67e9549da593", + "type": "bulleted-list", + "props": { + "nodeType": "block" + }, + "children": [ + { + "text": "If a key is missing in the current language, the editor falls back to the key itself (e.g., 'core.save')." + } + ] + } + ], + "meta": { + "align": "left", + "depth": 0, + "order": 5 + } + }, + "d5477264-0e09-4a8b-8de7-26250d2aaabe": { + "id": "d5477264-0e09-4a8b-8de7-26250d2aaabe", + "type": "Paragraph", + "value": [ + { + "id": "aa9f258f-ddb5-46b3-a5c3-7e1531435f02", + "type": "paragraph", + "children": [ + { + "text": "The" + }, + { + "text": " " + }, + { + "text": "getAvailableKeys", + "code": true + }, + { + "text": " " + }, + { + "text": "utility allows you to retrieve all available translation keys for the current language at runtime. This is helpful for:" + } + ], + "props": { + "nodeType": "block" + } + } + ], + "meta": { + "align": "left", + "depth": 0, + "order": 13 + } + }, + "b73f6f07-f01e-4d83-ad10-d06d0408adbf": { + "id": "b73f6f07-f01e-4d83-ad10-d06d0408adbf", + "type": "BulletedList", + "value": [ + { + "id": "a8c988ba-5aea-4b20-928d-5c1c0598e0b3", + "type": "bulleted-list", + "children": [ + { + "text": "Getting a complete list of keys to customize." + } + ], + "props": { + "nodeType": "block" + } + } + ], + "meta": { + "align": "left", + "depth": 0, + "order": 14 + } + }, + "6e5020a6-8e33-4b10-b0cf-2834b2453ee9": { + "id": "6e5020a6-8e33-4b10-b0cf-2834b2453ee9", + "type": "BulletedList", + "value": [ + { + "id": "fae0fdb9-1f8c-406c-a22c-34052c5902ad", + "type": "bulleted-list", + "children": [ + { + "text": "Generating a template for translation." + } + ], + "props": { + "nodeType": "block" + } + } + ], + "meta": { + "align": "left", + "depth": 0, + "order": 15 + } + }, + "990035fe-8d82-4a50-a1f3-bc726ffcb026": { + "id": "990035fe-8d82-4a50-a1f3-bc726ffcb026", + "type": "HeadingThree", + "meta": { + "depth": 0, + "order": 12 + }, + "value": [ + { + "id": "adee5237-ee78-4a7c-bab9-4eb8cfe929fc", + "type": "heading-three", + "props": { + "nodeType": "block" + }, + "children": [ + { + "text": "Using " + }, + { + "text": "getAvailableKeys", + "code": true + }, + { + "text": " to Fetch Translation Keys" + } + ] + } + ] + }, + "a3ccf244-ad34-4a90-975d-f352454c4bed": { + "id": "a3ccf244-ad34-4a90-975d-f352454c4bed", + "type": "Paragraph", + "value": [ + { + "id": "3615202d-778d-4a99-8e9d-e9905b17bcbc", + "type": "paragraph", + "children": [ + { + "text": "" + } + ] + } + ], + "meta": { + "align": "left", + "depth": 0, + "order": 11 + } + }, + "bdafc963-4a6f-4ed3-a7bf-8048ff6bf607": { + "id": "bdafc963-4a6f-4ed3-a7bf-8048ff6bf607", + "type": "Code", + "meta": { + "depth": 0, + "order": 16 + }, + "value": [ + { + "id": "c3d018e8-68a6-41af-841c-1987add46055", + "type": "code", + "props": { + "nodeType": "void", + "language": "javascript", + "theme": "GithubDark" + }, + "children": [ + { + "text": "import { useTranslation } from '@yoopta/editor';\n\nconst TranslationKeysExample = () => {\n const { getAvailableKeys, currentLanguage } = useTranslation();\n\n const handleFetchKeys = () => {\n const keys = getAvailableKeys();\n console.log('Available Keys:', keys);\n };\n\n return (\n
\n

Current Language: {currentLanguage}

\n \n
\n );\n};" + } + ] + } + ] + }, + "3071d059-79fa-4327-aaba-e53f00ce4029": { + "id": "3071d059-79fa-4327-aaba-e53f00ce4029", + "type": "Paragraph", + "value": [ + { + "id": "55b4274b-7454-431a-8917-427ea941f1bc", + "type": "paragraph", + "children": [ + { + "text": "" + } + ] + } + ], + "meta": { + "align": "left", + "depth": 0, + "order": 17 + } + } +} diff --git a/web/next-example/src/pages/examples/[example].tsx b/web/next-example/src/pages/examples/[example].tsx index 92f58647d..82ef74552 100644 --- a/web/next-example/src/pages/examples/[example].tsx +++ b/web/next-example/src/pages/examples/[example].tsx @@ -25,6 +25,7 @@ import withShadcnUILibrary from '@/components/examples/withShadcnUILibrary'; import withEditorHistory from '@/components/examples/withEditorHistory'; import withEditorOperations from '@/components/examples/withEditorOperations'; import withEmailBuilder from '@/components/examples/withEmailBuilder'; +import withTranslations from '@/components/examples/withTranslations'; // import withCraftExample from '@/components/examples/withCraftExample'; // import withEditorFocusBlur from '@/components/examples/withEditorFocusBlur'; // import withStarterKit from '@/components/examples/withStarterKit'; @@ -61,6 +62,7 @@ export const EXAMPLES: Record React.JSX.Element> = { withSavingToDatabase, withCustomStyles, withLargeDocuments, + withTranslations // withStarterKit, // withCustomHTMLAttributes, // withEditorFocusBlur, @@ -186,6 +188,10 @@ const EXAMPLE_MAP: Record = { title: 'Meet Email-Builder based on Yoopta! (new)', description: '', }, + withTranslations: { + title: 'Add translations (new)', + description: '', + }, }; const ExampleComponentPage = () => {