From bda3b01ea844409b8bb731cee3e245a38e70df8b Mon Sep 17 00:00:00 2001 From: Guillermo Loaysa Date: Sat, 4 Jan 2025 17:10:00 +0100 Subject: [PATCH 01/18] feat: first draft at implementing translations --- packages/core/editor/src/YooptaEditor.tsx | 23 +++- .../src/components/TextLeaf/TextLeaf.tsx | 4 +- .../editor/src/i18n/TranslationManager.ts | 125 ++++++++++++++++++ packages/core/editor/src/i18n/hooks/types.ts | 52 ++++++++ .../src/i18n/hooks/useAddTranslations.ts | 14 ++ .../editor/src/i18n/hooks/useTranslation.ts | 34 +++++ packages/core/editor/src/i18n/locales/en.ts | 9 ++ packages/core/editor/src/i18n/locales/es.ts | 9 ++ .../core/editor/src/i18n/locales/index.ts | 8 ++ packages/core/editor/src/i18n/types.ts | 42 ++++++ packages/core/editor/src/index.ts | 1 + .../src/plugins/SlateEditorComponent.tsx | 2 +- packages/core/editor/src/plugins/types.ts | 3 + .../MarkdownPreview/MarkdownPreview.tsx | 2 +- packages/development/src/pages/dev/index.tsx | 53 ++++++-- .../plugins/lists/src/plugin/BulletedList.tsx | 6 + .../components/DefaultActionMenuRender.tsx | 6 +- 17 files changed, 373 insertions(+), 20 deletions(-) create mode 100644 packages/core/editor/src/i18n/TranslationManager.ts create mode 100644 packages/core/editor/src/i18n/hooks/types.ts create mode 100644 packages/core/editor/src/i18n/hooks/useAddTranslations.ts create mode 100644 packages/core/editor/src/i18n/hooks/useTranslation.ts create mode 100644 packages/core/editor/src/i18n/locales/en.ts create mode 100644 packages/core/editor/src/i18n/locales/es.ts create mode 100644 packages/core/editor/src/i18n/locales/index.ts create mode 100644 packages/core/editor/src/i18n/types.ts diff --git a/packages/core/editor/src/YooptaEditor.tsx b/packages/core/editor/src/YooptaEditor.tsx index 9fdce4788..e35af5f97 100644 --- a/packages/core/editor/src/YooptaEditor.tsx +++ b/packages/core/editor/src/YooptaEditor.tsx @@ -18,6 +18,8 @@ import { FakeSelectionMark } from './marks/FakeSelectionMark'; import { generateId } from './utils/generateId'; import { YooptaOperation } from './editor/core/applyTransforms'; import { validateYooptaValue } from './utils/validateYooptaValue'; +import {Translations} from './i18n/types'; +import {useAddTranslations} from './i18n/hooks/useAddTranslations'; export type YooptaOnChangeOptions = { operations: YooptaOperation[]; @@ -40,6 +42,8 @@ export type YooptaEditorProps = { readOnly?: boolean; width?: number | string; style?: CSSProperties; + + translations?: Translations; }; type EditorState = { @@ -64,14 +68,25 @@ const YooptaEditor = ({ style, onChange, onPathChange, + translations: userTranslations = {}, }: YooptaEditorProps) => { + const {addTranslations} = useAddTranslations(); const marks = useMemo(() => { if (marksProps) return [FakeSelectionMark, ...marksProps]; return [FakeSelectionMark]; }, [marksProps]); const plugins = useMemo(() => { - return pluginsProps.map((plugin) => plugin.getPlugin as Plugin>); + return pluginsProps.map((plugin) => { + const pluginInstance = plugin.getPlugin as Plugin>; + + // Merge plugin translations into the global translation registry + Object.entries(pluginInstance.translations || {}).forEach(([language, pluginTranslations]) => { + addTranslations(language, pluginInstance.type.toLowerCase(), pluginTranslations); + }); + + return pluginInstance; + }); }, [pluginsProps]); const [editorState, setEditorState] = useState(() => { @@ -88,6 +103,12 @@ const YooptaEditor = ({ ); } + Object.entries(userTranslations).forEach(([language, translations]) => { + Object.entries(translations).forEach(([namespace, translation]) => { + addTranslations(language, namespace, translation); + }); + }); + editor.children = (isValueValid ? value : {}) as YooptaContentValue; editor.blockEditorsMap = buildBlockSlateEditors(editor); editor.shortcuts = buildBlockShortcuts(editor); diff --git a/packages/core/editor/src/components/TextLeaf/TextLeaf.tsx b/packages/core/editor/src/components/TextLeaf/TextLeaf.tsx index 5b8023e93..c495862cd 100644 --- a/packages/core/editor/src/components/TextLeaf/TextLeaf.tsx +++ b/packages/core/editor/src/components/TextLeaf/TextLeaf.tsx @@ -1,6 +1,7 @@ import { HTMLAttributes } from 'react'; import { RenderLeafProps, useSelected } from 'slate-react'; import { ExtendedLeafProps } from '../../plugins/types'; +import {useTranslation} from '../../i18n/hooks/useTranslation'; type Props = Pick, 'attributes' | 'children'> & { placeholder?: string; @@ -8,13 +9,14 @@ type Props = Pick, 'attributes' | 'children'> & { const TextLeaf = ({ children, attributes, placeholder }: Props) => { const selected = useSelected(); + const {t} = useTranslation(); const attrs: HTMLAttributes & RenderLeafProps['attributes'] = { ...attributes, }; if (selected && placeholder) { - attrs['data-placeholder'] = placeholder; + attrs['data-placeholder'] = t(placeholder); attrs.className = `yoopta-placeholder`; } diff --git a/packages/core/editor/src/i18n/TranslationManager.ts b/packages/core/editor/src/i18n/TranslationManager.ts new file mode 100644 index 000000000..1449e9f28 --- /dev/null +++ b/packages/core/editor/src/i18n/TranslationManager.ts @@ -0,0 +1,125 @@ +import {defaultLocales} from './locales'; +import {Translations} from './types'; + +type Listener = () => void; + +class TranslationManager { + private static instance: TranslationManager; + private translations: Translations = defaultLocales; + private currentLanguage: string = 'en'; + private userOverrides: Translations = {}; + private listeners: Listener[] = []; + + static getInstance(): TranslationManager { + if (!TranslationManager.instance) { + this.instance = new TranslationManager(); + } + return this.instance; + } + + /** + * Add translations for a specific namespace and language. + */ + addTranslations(language: string, namespace: string, newTranslations: Record): void { + if (!this.translations[language]) { + this.translations[language] = {}; + } + + if (!this.translations[language][namespace]) { + this.translations[language][namespace] = {}; + } + + this.translations[language][namespace] = { + ...this.translations[language][namespace], + ...newTranslations, + }; + } + + /** + * Override translations provided by the user. + */ + overrideTranslations(overrides: Translations): void { + Object.entries(overrides).forEach(([language, namespaces]) => { + if (!this.userOverrides[language]) { + this.userOverrides[language] = {}; + } + + Object.entries(namespaces).forEach(([namespace, translations]) => { + if (!this.userOverrides[language][namespace]) { + this.userOverrides[language][namespace] = {}; + } + + this.userOverrides[language][namespace] = { + ...this.userOverrides[language][namespace], + ...translations, + }; + }); + }); + } + + /** + * Fetch a translation for a specific key. + */ + translate(key: string): string { + const [namespace, ...rest] = key.split('.'); + const finalKey = rest.join('.'); + + return ( + this.userOverrides?.[this.currentLanguage]?.[namespace]?.[finalKey] ?? + this.translations?.[this.currentLanguage]?.[namespace]?.[finalKey] ?? + key + ); + } + + /** + * Set the current language and notify listeners. + */ + setLanguage(language: string): void { + if (this.translations[language]) { + this.currentLanguage = language; + this.notifyListeners(); + } else { + console.warn(`Language ${language} not found. Falling back to ${this.currentLanguage}.`); + } + } + + /** + * Get the current language. + */ + getCurrentLanguage(): string { + return this.currentLanguage; + } + + /** + * Fetch all available keys for the current language. + */ + getAvailableKeys(): Record { + const availableKeys: Record = {}; + const langTranslations = this.translations[this.currentLanguage] || {}; + + Object.entries(langTranslations).forEach(([namespace, keys]) => { + availableKeys[namespace] = Object.keys(keys); + }); + + return availableKeys; + } + + /** + * Subscribe to language changes. + */ + subscribe(listener: Listener): () => void { + this.listeners.push(listener); + return () => { + this.listeners = this.listeners.filter((l) => l !== listener); + }; + } + + /** + * Notify all listeners about language changes. + */ + private notifyListeners(): void { + this.listeners.forEach((listener) => listener()); + } +} + +export const YooptaTranslationManager = TranslationManager.getInstance(); diff --git a/packages/core/editor/src/i18n/hooks/types.ts b/packages/core/editor/src/i18n/hooks/types.ts new file mode 100644 index 000000000..1ab6e2d6d --- /dev/null +++ b/packages/core/editor/src/i18n/hooks/types.ts @@ -0,0 +1,52 @@ +export interface UseTranslationReturn { + /** + * Translates a key into the current language. + * The key format is `namespace.key`, e.g., `core.save`. + * If the translation for the key is not found, it returns the key provided. + * + * @param key - The key to translate, formatted as `namespace.key`. + * @returns The translated string or a fallback message. + */ + t: (key: string) => string; + + /** + * The current language set in the translation manager. + * This value updates reactively when the language is changed. + */ + currentLanguage: string; + + /** + * Changes the current language to the specified value. + * Notifies all subscribers of the language change. + * + * @param lang - The language code to set, e.g., 'en' or 'es' or 'fr'... + */ + setLanguage: (lang: string) => void; + + /** + * Retrieves all available keys for the current language. + * This is provided as a utility function to know all available keys at runtime. + * The keys are grouped by namespace and provide introspection into all registered translations. + * + * @returns A record where the keys are namespaces and the values are arrays of available keys. + */ + getAvailableKeys: () => Record; +} + +export interface UseAddTranslationsReturn { + /** + * Enables adding new translations at runtime for specific languages and namespaces. + * + * Example Usage: + * ``` + * const { addTranslations } = useAddTranslations(); + * addTranslations('es', 'core', { save: 'Guardar', cancel: 'Cancelar' }); + * addTranslations('en', 'paragraph', { placeholder: 'Type a paragraph...' }); + * ``` + * + * @param language - The language code to add translations for (e.g., 'en' or 'es'). + * @param namespace - The namespace grouping the translations (e.g., 'core', 'plugin'). + * @param translations - A record of key-value pairs representing the translations. + */ + addTranslations: (language: string, namespace: string, translations: Record) => void; +} diff --git a/packages/core/editor/src/i18n/hooks/useAddTranslations.ts b/packages/core/editor/src/i18n/hooks/useAddTranslations.ts new file mode 100644 index 000000000..014d9608c --- /dev/null +++ b/packages/core/editor/src/i18n/hooks/useAddTranslations.ts @@ -0,0 +1,14 @@ +import {UseAddTranslationsReturn} from './types'; +import {useCallback} from 'react'; +import {YooptaTranslationManager} from '../TranslationManager'; + +export const useAddTranslations = (): UseAddTranslationsReturn => { + const addTranslations = useCallback( + (language: string, namespace: string, translations: Record) => { + YooptaTranslationManager.addTranslations(language, namespace, translations); + }, + [] + ); + + return { addTranslations } +} diff --git a/packages/core/editor/src/i18n/hooks/useTranslation.ts b/packages/core/editor/src/i18n/hooks/useTranslation.ts new file mode 100644 index 000000000..84ddba0e5 --- /dev/null +++ b/packages/core/editor/src/i18n/hooks/useTranslation.ts @@ -0,0 +1,34 @@ +import { useEffect, useState, useCallback } from 'react'; +import { YooptaTranslationManager } from '../TranslationManager'; +import {UseTranslationReturn, UseAddTranslationsReturn} from './types'; + +export const useTranslation = (): UseTranslationReturn => { + const [language, setLanguage] = useState(YooptaTranslationManager.getCurrentLanguage()); + const [t, setT] = useState(() => (key: string) => YooptaTranslationManager.translate(key)); + + // Subscribe to language changes + useEffect(() => { + const unsubscribe = YooptaTranslationManager.subscribe(() => { + setLanguage(YooptaTranslationManager.getCurrentLanguage()); + setT(() => (key: string) => YooptaTranslationManager.translate(key)); + }); + + return () => unsubscribe(); + }, []); + + // Function to change the language + const changeLanguage = useCallback((lang: string) => { + YooptaTranslationManager.setLanguage(lang); + }, []); + + const getAvailableKeys = useCallback(() => { + return YooptaTranslationManager.getAvailableKeys(); + }, []); + + return { + t, + currentLanguage: language, + setLanguage: changeLanguage, + getAvailableKeys, + }; +}; diff --git a/packages/core/editor/src/i18n/locales/en.ts b/packages/core/editor/src/i18n/locales/en.ts new file mode 100644 index 000000000..3d6b0614c --- /dev/null +++ b/packages/core/editor/src/i18n/locales/en.ts @@ -0,0 +1,9 @@ +import {Translations} from '../types'; + +export const enLocale: Translations = { + en: { + core: { + editor_placeholder: "Type '/' for commands" + } + } +} diff --git a/packages/core/editor/src/i18n/locales/es.ts b/packages/core/editor/src/i18n/locales/es.ts new file mode 100644 index 000000000..46baf815b --- /dev/null +++ b/packages/core/editor/src/i18n/locales/es.ts @@ -0,0 +1,9 @@ +import {Translations} from '../types'; + +export const esLocale: Translations = { + es: { + core: { + editor_placeholder: "Escribe / para abrir el menú" + } + } +} diff --git a/packages/core/editor/src/i18n/locales/index.ts b/packages/core/editor/src/i18n/locales/index.ts new file mode 100644 index 000000000..92f77ccd0 --- /dev/null +++ b/packages/core/editor/src/i18n/locales/index.ts @@ -0,0 +1,8 @@ +import {enLocale} from './en'; +import {esLocale} from './es'; +import {Translations} from '../types'; + +export const defaultLocales: Translations = { + ...enLocale, + ...esLocale, +} diff --git a/packages/core/editor/src/i18n/types.ts b/packages/core/editor/src/i18n/types.ts new file mode 100644 index 000000000..e64470f4c --- /dev/null +++ b/packages/core/editor/src/i18n/types.ts @@ -0,0 +1,42 @@ +/** + * Interface representing the structure of the translations used in YooptaTranslationManager. + * + * This structure organizes translations by language and namespace, making it easy to + * group related keys and support multiple languages. + * + * Example Usage: + * ``` + * const translations: Translations = { + * en: { + * core: { + * save: 'Save', + * cancel: 'Cancel', + * }, + * paragraph: { + * placeholder: 'Type a paragraph...', + * }, + * }, + * es: { + * core: { + * save: 'Guardar', + * cancel: 'Cancelar', + * }, + * paragraph: { + * placeholder: 'Escribe un párrafo...', + * }, + * }, + * }; + * ``` + * + * Structure: + * - `language` (string): Represents the language code, e.g., 'en', 'es'. + * - `namespace` (string): Groups related translations, such as 'core' or 'paragraph'. + * - `Record`: Contains key-value pairs where: + * - The key (string) is the identifier for a specific translation. + * - The value (string) is the translated text for that key. + */ +export interface Translations { + [language: string]: { + [namespace: string]: Record; + }; +} diff --git a/packages/core/editor/src/index.ts b/packages/core/editor/src/index.ts index 6a247984b..9b0ef168b 100644 --- a/packages/core/editor/src/index.ts +++ b/packages/core/editor/src/index.ts @@ -7,6 +7,7 @@ export { useYooptaReadOnly, useYooptaPluginOptions, } from './contexts/YooptaContext/YooptaContext'; +export { useTranslation } from './i18n/hooks/useTranslation'; import { YooptaEditor, type YooptaEditorProps, type YooptaOnChangeOptions } from './YooptaEditor'; export { deserializeHTML } from './parsers/deserializeHTML'; export { type EmailTemplateOptions } from './parsers/getEmail'; diff --git a/packages/core/editor/src/plugins/SlateEditorComponent.tsx b/packages/core/editor/src/plugins/SlateEditorComponent.tsx index 2920fffb0..fe50babbe 100644 --- a/packages/core/editor/src/plugins/SlateEditorComponent.tsx +++ b/packages/core/editor/src/plugins/SlateEditorComponent.tsx @@ -44,7 +44,7 @@ const SlateEditorComponent = , events, options, extensions: withExtensions, - placeholder = `Type '/' for commands`, + placeholder = 'core.editor_placeholder', }: Props) => { const editor = useYooptaEditor(); const block = useBlockData(id); diff --git a/packages/core/editor/src/plugins/types.ts b/packages/core/editor/src/plugins/types.ts index 54bf8523c..acd88a3c3 100644 --- a/packages/core/editor/src/plugins/types.ts +++ b/packages/core/editor/src/plugins/types.ts @@ -89,6 +89,9 @@ export type Plugin, TPluginOpti events?: PluginEvents; options?: PluginOptions; parsers?: Partial>; + translations?: { + [language: string]: Record; + }; }; export type PluginParsers = { diff --git a/packages/development/src/components/Exports/markdown/MarkdownPreview/MarkdownPreview.tsx b/packages/development/src/components/Exports/markdown/MarkdownPreview/MarkdownPreview.tsx index 440796825..50db169e2 100644 --- a/packages/development/src/components/Exports/markdown/MarkdownPreview/MarkdownPreview.tsx +++ b/packages/development/src/components/Exports/markdown/MarkdownPreview/MarkdownPreview.tsx @@ -39,7 +39,7 @@ type ResultHTMLProps = { }; const ResultMD = ({ editor, value }: ResultHTMLProps) => { - const [debounceValue] = useDebounce(value, 1000); + const [debounceValue] = useDebounce(value, 100); const [markdown, setMarkdown] = useState(''); useEffect(() => { diff --git a/packages/development/src/pages/dev/index.tsx b/packages/development/src/pages/dev/index.tsx index f4c07464f..f4d32c627 100644 --- a/packages/development/src/pages/dev/index.tsx +++ b/packages/development/src/pages/dev/index.tsx @@ -6,7 +6,7 @@ import YooptaEditor, { YooEditor, YooptaBlockData, YooptaContentValue, - YooptaPath, + YooptaPath, useTranslation, } from '@yoopta/editor'; import { useEffect, useMemo, useRef, useState } from 'react'; @@ -789,6 +789,7 @@ const BasicExample = () => { const editor: YooEditor = useMemo(() => createYooptaEditor(), []); const selectionRef = useRef(null); const [value, setValue] = useState(data); + const {setLanguage, getAvailableKeys} = useTranslation(); const onChange = (value: YooptaContentValue, options: YooptaOnChangeOptions) => { console.log('onChange', value, options); @@ -809,20 +810,44 @@ const BasicExample = () => { return ( <>
- + +
+ + +
diff --git a/packages/plugins/lists/src/plugin/BulletedList.tsx b/packages/plugins/lists/src/plugin/BulletedList.tsx index 9b14a4e03..24982c6e1 100644 --- a/packages/plugins/lists/src/plugin/BulletedList.tsx +++ b/packages/plugins/lists/src/plugin/BulletedList.tsx @@ -88,6 +88,12 @@ const BulletedList = new YooptaPlugin>({ }, }, }, + translations: { + en: { + title: 'Bulleted List', + description: 'Description of bulleted list', + } + } }); export { BulletedList }; diff --git a/packages/tools/action-menu/src/components/DefaultActionMenuRender.tsx b/packages/tools/action-menu/src/components/DefaultActionMenuRender.tsx index 363891229..67da35653 100644 --- a/packages/tools/action-menu/src/components/DefaultActionMenuRender.tsx +++ b/packages/tools/action-menu/src/components/DefaultActionMenuRender.tsx @@ -1,6 +1,7 @@ import { cloneElement, isValidElement } from 'react'; import { ActionMenuRenderProps } from '../types'; import { DEFAULT_ICONS_MAP } from './icons'; +import {useTranslation} from '@yoopta/editor'; const DefaultActionMenuRender = ({ actions, @@ -10,6 +11,7 @@ const DefaultActionMenuRender = ({ getRootProps, view = 'default', }: ActionMenuRenderProps) => { + const { t } = useTranslation(); const isViewSmall = view === 'small'; const wrapStyles = { @@ -50,8 +52,8 @@ const DefaultActionMenuRender = ({ if (!block) return null; - const title = block.options?.display?.title || block.type; - const description = block.options?.display?.description || ''; + const title = t(`${block.type.toLowerCase()}.title`) || block.options?.display?.title || block.type; + const description = t(`${block.type.toLowerCase()}.description`) || block.options?.display?.description || ''; const Icon = action.icon || DEFAULT_ICONS_MAP[action.type]; return ( From 7975ef850d0f8c3adb4500bbd21070a25a47a545 Mon Sep 17 00:00:00 2001 From: Guillermo Loaysa Date: Sat, 4 Jan 2025 17:30:15 +0100 Subject: [PATCH 02/18] revert: debounce value in ResultMD back to 1000 --- .../Exports/markdown/MarkdownPreview/MarkdownPreview.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/development/src/components/Exports/markdown/MarkdownPreview/MarkdownPreview.tsx b/packages/development/src/components/Exports/markdown/MarkdownPreview/MarkdownPreview.tsx index 50db169e2..440796825 100644 --- a/packages/development/src/components/Exports/markdown/MarkdownPreview/MarkdownPreview.tsx +++ b/packages/development/src/components/Exports/markdown/MarkdownPreview/MarkdownPreview.tsx @@ -39,7 +39,7 @@ type ResultHTMLProps = { }; const ResultMD = ({ editor, value }: ResultHTMLProps) => { - const [debounceValue] = useDebounce(value, 100); + const [debounceValue] = useDebounce(value, 1000); const [markdown, setMarkdown] = useState(''); useEffect(() => { From 0c08677361cab8106ff2b2b62070569ef0f6958d Mon Sep 17 00:00:00 2001 From: Guillermo Loaysa Date: Sat, 4 Jan 2025 17:35:48 +0100 Subject: [PATCH 03/18] feat: add example for plugin translation --- packages/plugins/lists/src/locales/en.ts | 6 ++++++ packages/plugins/lists/src/locales/es.ts | 6 ++++++ packages/plugins/lists/src/locales/index.ts | 7 +++++++ packages/plugins/lists/src/plugin/BulletedList.tsx | 8 ++------ 4 files changed, 21 insertions(+), 6 deletions(-) create mode 100644 packages/plugins/lists/src/locales/en.ts create mode 100644 packages/plugins/lists/src/locales/es.ts create mode 100644 packages/plugins/lists/src/locales/index.ts diff --git a/packages/plugins/lists/src/locales/en.ts b/packages/plugins/lists/src/locales/en.ts new file mode 100644 index 000000000..2924adafe --- /dev/null +++ b/packages/plugins/lists/src/locales/en.ts @@ -0,0 +1,6 @@ +export const enLocale = { + en: { + title: 'Bulleted List', + description: 'Description of bulleted list', + } +} diff --git a/packages/plugins/lists/src/locales/es.ts b/packages/plugins/lists/src/locales/es.ts new file mode 100644 index 000000000..80311277a --- /dev/null +++ b/packages/plugins/lists/src/locales/es.ts @@ -0,0 +1,6 @@ +export const esLocale = { + es: { + title: 'Lista de viñetas', + description: 'Descripción de la lista de viñetas.', + } +} diff --git a/packages/plugins/lists/src/locales/index.ts b/packages/plugins/lists/src/locales/index.ts new file mode 100644 index 000000000..290b43dc9 --- /dev/null +++ b/packages/plugins/lists/src/locales/index.ts @@ -0,0 +1,7 @@ +import {enLocale} from './en'; +import {esLocale} from './es'; + +export const defaultLocales = { + ...enLocale, + ...esLocale, +} diff --git a/packages/plugins/lists/src/plugin/BulletedList.tsx b/packages/plugins/lists/src/plugin/BulletedList.tsx index 24982c6e1..7ed3c9f38 100644 --- a/packages/plugins/lists/src/plugin/BulletedList.tsx +++ b/packages/plugins/lists/src/plugin/BulletedList.tsx @@ -10,6 +10,7 @@ import { BulletedListRender } from '../elements/BulletedList'; import { onKeyDown } from '../events/onKeyDown'; import { ListElementMap } from '../types'; import { deserializeListNodes } from '../utils/deserializeListNodes'; +import {defaultLocales} from '../locales'; const BulletedList = new YooptaPlugin>({ type: 'BulletedList', @@ -88,12 +89,7 @@ const BulletedList = new YooptaPlugin>({ }, }, }, - translations: { - en: { - title: 'Bulleted List', - description: 'Description of bulleted list', - } - } + translations: defaultLocales }); export { BulletedList }; From 181e51e723bf1fa2ad6f58b4426fae5efde79604 Mon Sep 17 00:00:00 2001 From: Guillermo Loaysa Date: Sun, 5 Jan 2025 12:48:57 +0100 Subject: [PATCH 04/18] chore: add plugins translations --- .../editor/src/i18n/hooks/useTranslation.ts | 10 +++---- packages/development/src/pages/dev/index.tsx | 26 +++++-------------- packages/plugins/accordion/src/locales/en.ts | 4 +++ packages/plugins/accordion/src/locales/es.ts | 4 +++ .../plugins/accordion/src/locales/index.ts | 7 +++++ .../plugins/accordion/src/plugin/index.tsx | 2 ++ packages/plugins/blockquote/src/locales/en.ts | 4 +++ packages/plugins/blockquote/src/locales/es.ts | 4 +++ .../plugins/blockquote/src/locales/index.ts | 7 +++++ .../plugins/blockquote/src/plugin/index.tsx | 2 ++ packages/plugins/callout/src/locales/en.ts | 4 +++ packages/plugins/callout/src/locales/es.ts | 4 +++ packages/plugins/callout/src/locales/index.ts | 7 +++++ packages/plugins/callout/src/plugin/index.tsx | 2 ++ packages/plugins/code/src/locales/en.ts | 4 +++ packages/plugins/code/src/locales/es.ts | 4 +++ packages/plugins/code/src/locales/index.ts | 7 +++++ packages/plugins/code/src/plugin/index.tsx | 2 ++ packages/plugins/divider/src/locales/en.ts | 4 +++ packages/plugins/divider/src/locales/es.ts | 4 +++ packages/plugins/divider/src/locales/index.ts | 7 +++++ packages/plugins/divider/src/plugin/index.tsx | 2 ++ packages/plugins/embed/src/locales/en.ts | 4 +++ packages/plugins/embed/src/locales/es.ts | 4 +++ packages/plugins/embed/src/locales/index.ts | 7 +++++ packages/plugins/embed/src/plugin/index.tsx | 2 ++ packages/plugins/file/src/locales/en.ts | 4 +++ packages/plugins/file/src/locales/es.ts | 4 +++ packages/plugins/file/src/locales/index.ts | 7 +++++ packages/plugins/file/src/plugin/index.tsx | 2 ++ packages/plugins/headings/src/locales/en.ts | 14 ++++++++++ packages/plugins/headings/src/locales/es.ts | 14 ++++++++++ .../plugins/headings/src/locales/index.ts | 17 ++++++++++++ .../headings/src/plugin/HeadingOne.tsx | 2 ++ .../headings/src/plugin/HeadingThree.tsx | 2 ++ .../headings/src/plugin/HeadingTwo.tsx | 2 ++ packages/plugins/image/src/locales/en.ts | 4 +++ packages/plugins/image/src/locales/es.ts | 5 ++++ packages/plugins/image/src/locales/index.ts | 7 +++++ packages/plugins/image/src/plugin/index.tsx | 2 ++ packages/plugins/link/src/locales/en.ts | 4 +++ packages/plugins/link/src/locales/es.ts | 4 +++ packages/plugins/link/src/locales/index.ts | 7 +++++ packages/plugins/link/src/plugin/index.tsx | 2 ++ packages/plugins/lists/src/locales/en.ts | 20 ++++++++++---- packages/plugins/lists/src/locales/es.ts | 18 +++++++++---- packages/plugins/lists/src/locales/index.ts | 21 +++++++++++---- .../plugins/lists/src/plugin/BulletedList.tsx | 4 +-- .../plugins/lists/src/plugin/NumberedList.tsx | 2 ++ .../plugins/lists/src/plugin/TodoList.tsx | 2 ++ packages/plugins/paragraph/src/locales/en.ts | 4 +++ packages/plugins/paragraph/src/locales/es.ts | 4 +++ .../plugins/paragraph/src/locales/index.ts | 7 +++++ .../plugins/paragraph/src/plugin/index.tsx | 2 ++ packages/plugins/table/src/locales/en.ts | 4 +++ packages/plugins/table/src/locales/es.ts | 4 +++ packages/plugins/table/src/locales/index.ts | 7 +++++ packages/plugins/table/src/plugin/Table.tsx | 2 ++ packages/plugins/video/src/locales/en.ts | 4 +++ packages/plugins/video/src/locales/es.ts | 4 +++ packages/plugins/video/src/locales/index.ts | 7 +++++ packages/plugins/video/src/plugin/index.tsx | 2 ++ 62 files changed, 318 insertions(+), 41 deletions(-) create mode 100644 packages/plugins/accordion/src/locales/en.ts create mode 100644 packages/plugins/accordion/src/locales/es.ts create mode 100644 packages/plugins/accordion/src/locales/index.ts create mode 100644 packages/plugins/blockquote/src/locales/en.ts create mode 100644 packages/plugins/blockquote/src/locales/es.ts create mode 100644 packages/plugins/blockquote/src/locales/index.ts create mode 100644 packages/plugins/callout/src/locales/en.ts create mode 100644 packages/plugins/callout/src/locales/es.ts create mode 100644 packages/plugins/callout/src/locales/index.ts create mode 100644 packages/plugins/code/src/locales/en.ts create mode 100644 packages/plugins/code/src/locales/es.ts create mode 100644 packages/plugins/code/src/locales/index.ts create mode 100644 packages/plugins/divider/src/locales/en.ts create mode 100644 packages/plugins/divider/src/locales/es.ts create mode 100644 packages/plugins/divider/src/locales/index.ts create mode 100644 packages/plugins/embed/src/locales/en.ts create mode 100644 packages/plugins/embed/src/locales/es.ts create mode 100644 packages/plugins/embed/src/locales/index.ts create mode 100644 packages/plugins/file/src/locales/en.ts create mode 100644 packages/plugins/file/src/locales/es.ts create mode 100644 packages/plugins/file/src/locales/index.ts create mode 100644 packages/plugins/headings/src/locales/en.ts create mode 100644 packages/plugins/headings/src/locales/es.ts create mode 100644 packages/plugins/headings/src/locales/index.ts create mode 100644 packages/plugins/image/src/locales/en.ts create mode 100644 packages/plugins/image/src/locales/es.ts create mode 100644 packages/plugins/image/src/locales/index.ts create mode 100644 packages/plugins/link/src/locales/en.ts create mode 100644 packages/plugins/link/src/locales/es.ts create mode 100644 packages/plugins/link/src/locales/index.ts create mode 100644 packages/plugins/paragraph/src/locales/en.ts create mode 100644 packages/plugins/paragraph/src/locales/es.ts create mode 100644 packages/plugins/paragraph/src/locales/index.ts create mode 100644 packages/plugins/table/src/locales/en.ts create mode 100644 packages/plugins/table/src/locales/es.ts create mode 100644 packages/plugins/table/src/locales/index.ts create mode 100644 packages/plugins/video/src/locales/en.ts create mode 100644 packages/plugins/video/src/locales/es.ts create mode 100644 packages/plugins/video/src/locales/index.ts diff --git a/packages/core/editor/src/i18n/hooks/useTranslation.ts b/packages/core/editor/src/i18n/hooks/useTranslation.ts index 84ddba0e5..45a647c58 100644 --- a/packages/core/editor/src/i18n/hooks/useTranslation.ts +++ b/packages/core/editor/src/i18n/hooks/useTranslation.ts @@ -1,22 +1,22 @@ import { useEffect, useState, useCallback } from 'react'; import { YooptaTranslationManager } from '../TranslationManager'; -import {UseTranslationReturn, UseAddTranslationsReturn} from './types'; +import { UseTranslationReturn } from './types'; export const useTranslation = (): UseTranslationReturn => { const [language, setLanguage] = useState(YooptaTranslationManager.getCurrentLanguage()); - const [t, setT] = useState(() => (key: string) => YooptaTranslationManager.translate(key)); - // Subscribe to language changes + // Directly reference the translation function without using state + const t = useCallback((key: string) => YooptaTranslationManager.translate(key), []); + useEffect(() => { + // Update the language state when the translation manager's language changes const unsubscribe = YooptaTranslationManager.subscribe(() => { setLanguage(YooptaTranslationManager.getCurrentLanguage()); - setT(() => (key: string) => YooptaTranslationManager.translate(key)); }); return () => unsubscribe(); }, []); - // Function to change the language const changeLanguage = useCallback((lang: string) => { YooptaTranslationManager.setLanguage(lang); }, []); diff --git a/packages/development/src/pages/dev/index.tsx b/packages/development/src/pages/dev/index.tsx index f4d32c627..256f9b084 100644 --- a/packages/development/src/pages/dev/index.tsx +++ b/packages/development/src/pages/dev/index.tsx @@ -789,7 +789,7 @@ const BasicExample = () => { const editor: YooEditor = useMemo(() => createYooptaEditor(), []); const selectionRef = useRef(null); const [value, setValue] = useState(data); - const {setLanguage, getAvailableKeys} = useTranslation(); + const {setLanguage} = useTranslation(); const onChange = (value: YooptaContentValue, options: YooptaOnChangeOptions) => { console.log('onChange', value, options); @@ -812,8 +812,9 @@ const BasicExample = () => {
- - + + +
{ onChange={onChange} onPathChange={onPathChange} translations={{ - en: { - core: { - editor_placeholder: 'Write whatever...' - }, + fr: { table: { - title: 'This is the title of Table', - description: 'This is the description of Table' + title: 'Table', + description: 'Table est Table en français' } }, - es: { - core: { - editor_placeholder: 'Hey, this is Spanish!' - }, - table: { - title: 'Tabla', - description: 'Tabla is Table in Spanish' - } - }, - }} />
diff --git a/packages/plugins/accordion/src/locales/en.ts b/packages/plugins/accordion/src/locales/en.ts new file mode 100644 index 000000000..a8318b1f5 --- /dev/null +++ b/packages/plugins/accordion/src/locales/en.ts @@ -0,0 +1,4 @@ +export const enLocale = { + title: 'Accordion', + description: 'Expands and collapses sections of content.', +} diff --git a/packages/plugins/accordion/src/locales/es.ts b/packages/plugins/accordion/src/locales/es.ts new file mode 100644 index 000000000..0adce1380 --- /dev/null +++ b/packages/plugins/accordion/src/locales/es.ts @@ -0,0 +1,4 @@ +export const esLocale = { + title: 'Acordeón', + description: 'Expande y colapsa secciones de contenido.', +} diff --git a/packages/plugins/accordion/src/locales/index.ts b/packages/plugins/accordion/src/locales/index.ts new file mode 100644 index 000000000..1651fca2b --- /dev/null +++ b/packages/plugins/accordion/src/locales/index.ts @@ -0,0 +1,7 @@ +import {enLocale} from './en'; +import {esLocale} from './es'; + +export const defaultLocales = { + en: enLocale, + es: esLocale +} diff --git a/packages/plugins/accordion/src/plugin/index.tsx b/packages/plugins/accordion/src/plugin/index.tsx index 11aa48ae8..d9e87f830 100644 --- a/packages/plugins/accordion/src/plugin/index.tsx +++ b/packages/plugins/accordion/src/plugin/index.tsx @@ -7,6 +7,7 @@ import { AccordionItemContent } from '../renders/AccordionItemContent'; import { Transforms } from 'slate'; import { ListCollapse } from 'lucide-react'; import { AccordionCommands } from '../commands'; +import {defaultLocales} from '../locales'; const ACCORDION_ELEMENTS = { AccordionList: 'accordion-list', @@ -221,6 +222,7 @@ const Accordion = new YooptaPlugin({ }, }, }, + translations: defaultLocales, }); export { Accordion }; diff --git a/packages/plugins/blockquote/src/locales/en.ts b/packages/plugins/blockquote/src/locales/en.ts new file mode 100644 index 000000000..c203891e8 --- /dev/null +++ b/packages/plugins/blockquote/src/locales/en.ts @@ -0,0 +1,4 @@ +export const enLocale = { + title: 'Blockquote', + description: 'Highlights quoted text.', +} diff --git a/packages/plugins/blockquote/src/locales/es.ts b/packages/plugins/blockquote/src/locales/es.ts new file mode 100644 index 000000000..9783837f3 --- /dev/null +++ b/packages/plugins/blockquote/src/locales/es.ts @@ -0,0 +1,4 @@ +export const esLocale = { + title: 'Cita', + description: 'Resalta texto citado.', +} diff --git a/packages/plugins/blockquote/src/locales/index.ts b/packages/plugins/blockquote/src/locales/index.ts new file mode 100644 index 000000000..1651fca2b --- /dev/null +++ b/packages/plugins/blockquote/src/locales/index.ts @@ -0,0 +1,7 @@ +import {enLocale} from './en'; +import {esLocale} from './es'; + +export const defaultLocales = { + en: enLocale, + es: esLocale +} diff --git a/packages/plugins/blockquote/src/plugin/index.tsx b/packages/plugins/blockquote/src/plugin/index.tsx index 3e025283b..cb752effc 100644 --- a/packages/plugins/blockquote/src/plugin/index.tsx +++ b/packages/plugins/blockquote/src/plugin/index.tsx @@ -2,6 +2,7 @@ import { serializeTextNodes, serializeTextNodesIntoMarkdown, YooptaPlugin } from import { BlockquoteCommands } from '../commands'; import { BlockquoteElement } from '../types'; import { BlockquoteRender } from '../ui/Blockquote'; +import {defaultLocales} from '../locales'; const Blockquote = new YooptaPlugin>({ type: 'Blockquote', @@ -59,6 +60,7 @@ const Blockquote = new YooptaPlugin>({ }, }, }, + translations: defaultLocales, }); export { Blockquote }; diff --git a/packages/plugins/callout/src/locales/en.ts b/packages/plugins/callout/src/locales/en.ts new file mode 100644 index 000000000..39156e042 --- /dev/null +++ b/packages/plugins/callout/src/locales/en.ts @@ -0,0 +1,4 @@ +export const enLocale = { + title: 'Callout', + description: 'Emphasizes important content.', +} diff --git a/packages/plugins/callout/src/locales/es.ts b/packages/plugins/callout/src/locales/es.ts new file mode 100644 index 000000000..059ca736e --- /dev/null +++ b/packages/plugins/callout/src/locales/es.ts @@ -0,0 +1,4 @@ +export const esLocale = { + title: 'Destacado', + description: 'Para contenido importante.', +} diff --git a/packages/plugins/callout/src/locales/index.ts b/packages/plugins/callout/src/locales/index.ts new file mode 100644 index 000000000..1651fca2b --- /dev/null +++ b/packages/plugins/callout/src/locales/index.ts @@ -0,0 +1,7 @@ +import {enLocale} from './en'; +import {esLocale} from './es'; + +export const defaultLocales = { + en: enLocale, + es: esLocale +} diff --git a/packages/plugins/callout/src/plugin/index.tsx b/packages/plugins/callout/src/plugin/index.tsx index 7aeffcf04..04c80808b 100644 --- a/packages/plugins/callout/src/plugin/index.tsx +++ b/packages/plugins/callout/src/plugin/index.tsx @@ -10,6 +10,7 @@ import { CalloutCommands } from '../commands'; import { CalloutElementMap, CalloutTheme } from '../types'; import { CalloutRender } from '../ui/Callout'; import { CALLOUT_THEME_STYLES } from '../utils'; +import {defaultLocales} from '../locales'; const Callout = new YooptaPlugin({ type: 'Callout', @@ -96,6 +97,7 @@ const Callout = new YooptaPlugin({ }, }, }, + translations: defaultLocales, }); export { Callout }; diff --git a/packages/plugins/code/src/locales/en.ts b/packages/plugins/code/src/locales/en.ts new file mode 100644 index 000000000..954fbf991 --- /dev/null +++ b/packages/plugins/code/src/locales/en.ts @@ -0,0 +1,4 @@ +export const enLocale = { + title: 'Code', + description: 'Displays formatted code snippets.', +} diff --git a/packages/plugins/code/src/locales/es.ts b/packages/plugins/code/src/locales/es.ts new file mode 100644 index 000000000..eaa8bbbf9 --- /dev/null +++ b/packages/plugins/code/src/locales/es.ts @@ -0,0 +1,4 @@ +export const esLocale = { + title: 'Código', + description: 'Muestra fragmentos de código formateados.', +} diff --git a/packages/plugins/code/src/locales/index.ts b/packages/plugins/code/src/locales/index.ts new file mode 100644 index 000000000..1651fca2b --- /dev/null +++ b/packages/plugins/code/src/locales/index.ts @@ -0,0 +1,7 @@ +import {enLocale} from './en'; +import {esLocale} from './es'; + +export const defaultLocales = { + en: enLocale, + es: esLocale +} diff --git a/packages/plugins/code/src/plugin/index.tsx b/packages/plugins/code/src/plugin/index.tsx index 4779c5ba1..8e3a23ffa 100644 --- a/packages/plugins/code/src/plugin/index.tsx +++ b/packages/plugins/code/src/plugin/index.tsx @@ -2,6 +2,7 @@ import { generateId, YooptaPlugin } from '@yoopta/editor'; import { CodeCommands } from '../commands'; import { CodeElementMap, CodeElementProps, CodePluginBlockOptions, CodePluginElements } from '../types'; import { CodeEditor } from '../ui/Code'; +import {defaultLocales} from '../locales'; const ALIGNS_TO_JUSTIFY = { left: 'flex-start', @@ -100,6 +101,7 @@ const Code = new YooptaPlugin({ }, }, }, + translations: defaultLocales, }); function escapeHTML(text) { diff --git a/packages/plugins/divider/src/locales/en.ts b/packages/plugins/divider/src/locales/en.ts new file mode 100644 index 000000000..da5957f87 --- /dev/null +++ b/packages/plugins/divider/src/locales/en.ts @@ -0,0 +1,4 @@ +export const enLocale = { + title: 'Divider', + description: 'Separates content with a horizontal line.', +} diff --git a/packages/plugins/divider/src/locales/es.ts b/packages/plugins/divider/src/locales/es.ts new file mode 100644 index 000000000..ed1a05491 --- /dev/null +++ b/packages/plugins/divider/src/locales/es.ts @@ -0,0 +1,4 @@ +export const esLocale = { + title: 'Divisor', + description: 'Separa contenido con una línea horizontal.', +} diff --git a/packages/plugins/divider/src/locales/index.ts b/packages/plugins/divider/src/locales/index.ts new file mode 100644 index 000000000..1651fca2b --- /dev/null +++ b/packages/plugins/divider/src/locales/index.ts @@ -0,0 +1,7 @@ +import {enLocale} from './en'; +import {esLocale} from './es'; + +export const defaultLocales = { + en: enLocale, + es: esLocale +} diff --git a/packages/plugins/divider/src/plugin/index.tsx b/packages/plugins/divider/src/plugin/index.tsx index f6220e844..3294c71b6 100644 --- a/packages/plugins/divider/src/plugin/index.tsx +++ b/packages/plugins/divider/src/plugin/index.tsx @@ -3,6 +3,7 @@ import { DividerCommands } from '../commands'; import { onKeyDown } from '../events/onKeyDown'; import { DividerElementMap } from '../types'; import { DividerRender } from '../elements/Divider'; +import {defaultLocales} from '../locales'; const Divider = new YooptaPlugin({ type: 'Divider', @@ -76,6 +77,7 @@ const Divider = new YooptaPlugin({ events: { onKeyDown, }, + translations: defaultLocales, }); export { Divider }; diff --git a/packages/plugins/embed/src/locales/en.ts b/packages/plugins/embed/src/locales/en.ts new file mode 100644 index 000000000..38a38235b --- /dev/null +++ b/packages/plugins/embed/src/locales/en.ts @@ -0,0 +1,4 @@ +export const enLocale = { + title: 'Embed', + description: 'Inserts external content like videos, Google Maps, etc', +} diff --git a/packages/plugins/embed/src/locales/es.ts b/packages/plugins/embed/src/locales/es.ts new file mode 100644 index 000000000..5b847cfad --- /dev/null +++ b/packages/plugins/embed/src/locales/es.ts @@ -0,0 +1,4 @@ +export const esLocale = { + title: 'Insertar contenido', + description: 'Inserta contenido externo como vídeos, Google Maps, widgets...', +} diff --git a/packages/plugins/embed/src/locales/index.ts b/packages/plugins/embed/src/locales/index.ts new file mode 100644 index 000000000..1651fca2b --- /dev/null +++ b/packages/plugins/embed/src/locales/index.ts @@ -0,0 +1,7 @@ +import {enLocale} from './en'; +import {esLocale} from './es'; + +export const defaultLocales = { + en: enLocale, + es: esLocale +} diff --git a/packages/plugins/embed/src/plugin/index.tsx b/packages/plugins/embed/src/plugin/index.tsx index b68e80ddb..c48850e12 100644 --- a/packages/plugins/embed/src/plugin/index.tsx +++ b/packages/plugins/embed/src/plugin/index.tsx @@ -2,6 +2,7 @@ import { generateId, YooptaPlugin } from '@yoopta/editor'; import { EmbedCommands } from '../commands'; import { EmbedElementMap, EmbedPluginOptions } from '../types'; import { EmbedRender } from '../ui/Embed'; +import {defaultLocales} from '../locales'; const ALIGNS_TO_JUSTIFY = { left: 'flex-start', @@ -89,6 +90,7 @@ const Embed = new YooptaPlugin({ }, }, }, + translations: defaultLocales, }); export { Embed }; diff --git a/packages/plugins/file/src/locales/en.ts b/packages/plugins/file/src/locales/en.ts new file mode 100644 index 000000000..273e9cf82 --- /dev/null +++ b/packages/plugins/file/src/locales/en.ts @@ -0,0 +1,4 @@ +export const enLocale = { + title: 'File', + description: 'Adds downloadable file attachments.', +} diff --git a/packages/plugins/file/src/locales/es.ts b/packages/plugins/file/src/locales/es.ts new file mode 100644 index 000000000..bbc98071d --- /dev/null +++ b/packages/plugins/file/src/locales/es.ts @@ -0,0 +1,4 @@ +export const esLocale = { + title: 'Archivo', + description: 'Agrega archivos adjuntos descargables.', +} diff --git a/packages/plugins/file/src/locales/index.ts b/packages/plugins/file/src/locales/index.ts new file mode 100644 index 000000000..1651fca2b --- /dev/null +++ b/packages/plugins/file/src/locales/index.ts @@ -0,0 +1,7 @@ +import {enLocale} from './en'; +import {esLocale} from './es'; + +export const defaultLocales = { + en: enLocale, + es: esLocale +} diff --git a/packages/plugins/file/src/plugin/index.tsx b/packages/plugins/file/src/plugin/index.tsx index 598034886..9b9a80ebd 100644 --- a/packages/plugins/file/src/plugin/index.tsx +++ b/packages/plugins/file/src/plugin/index.tsx @@ -2,6 +2,7 @@ import { generateId, YooptaPlugin } from '@yoopta/editor'; import { FileCommands } from '../commands'; import { FileElementMap, FilePluginOptions } from '../types'; import { FileRender } from '../ui/File'; +import {defaultLocales} from '../locales'; const ALIGNS_TO_JUSTIFY = { left: 'flex-start', @@ -112,6 +113,7 @@ const File = new YooptaPlugin({ }, }, }, + translations: defaultLocales, }); export { File }; diff --git a/packages/plugins/headings/src/locales/en.ts b/packages/plugins/headings/src/locales/en.ts new file mode 100644 index 000000000..4e6c5f2a0 --- /dev/null +++ b/packages/plugins/headings/src/locales/en.ts @@ -0,0 +1,14 @@ +export const headingOneEnLocale = { + title: 'Heading 1', + description: 'Adds structured big headings for content hierarchy.', +} + +export const headingTwoEnLocale = { + title: 'Heading 2', + description: 'Adds structured medium headings for content hierarchy.', +} + +export const headingThreeEnLocale = { + title: 'Heading 3', + description: 'Adds structured small headings for content hierarchy.', +} diff --git a/packages/plugins/headings/src/locales/es.ts b/packages/plugins/headings/src/locales/es.ts new file mode 100644 index 000000000..b1f35f55f --- /dev/null +++ b/packages/plugins/headings/src/locales/es.ts @@ -0,0 +1,14 @@ +export const headingOneEsLocale = { + title: 'Encabezado Uno', + description: 'Agrega encabezados grandes estructurados para la jerarquía del contenido.', +} + +export const headingTwoEsLocale = { + title: 'Encabezado Dos', + description: 'Agrega encabezados medianos estructurados para la jerarquía del contenido.', +} + +export const headingThreeEsLocale = { + title: 'Encabezado Tres', + description: 'Agrega encabezados pequeños estructurados para la jerarquía del contenido.', +} diff --git a/packages/plugins/headings/src/locales/index.ts b/packages/plugins/headings/src/locales/index.ts new file mode 100644 index 000000000..3da1bd685 --- /dev/null +++ b/packages/plugins/headings/src/locales/index.ts @@ -0,0 +1,17 @@ +import {headingOneEnLocale, headingThreeEnLocale, headingTwoEnLocale} from './en'; +import {headingOneEsLocale, headingThreeEsLocale, headingTwoEsLocale} from './es'; + +export const defaultHeadingOneLocales = { + en: headingOneEnLocale, + es: headingOneEsLocale, +} + +export const defaultHeadingTwoLocales = { + en: headingTwoEnLocale, + es: headingTwoEsLocale, +} + +export const defaultHeadingThreeLocales = { + en: headingThreeEnLocale, + es: headingThreeEsLocale, +} diff --git a/packages/plugins/headings/src/plugin/HeadingOne.tsx b/packages/plugins/headings/src/plugin/HeadingOne.tsx index cdcd1664d..ba786429f 100644 --- a/packages/plugins/headings/src/plugin/HeadingOne.tsx +++ b/packages/plugins/headings/src/plugin/HeadingOne.tsx @@ -6,6 +6,7 @@ import { } from '@yoopta/editor'; import { HeadingOneCommands } from '../commands'; import { HeadingOneElement } from '../types'; +import {defaultHeadingOneLocales} from '../locales'; const HeadingOneRender = ({ extendRender, ...props }: PluginElementRenderProps) => { const { element, HTMLAttributes = {}, attributes, children } = props; @@ -83,6 +84,7 @@ const HeadingOne = new YooptaPlugin>({ }, }, }, + translations: defaultHeadingOneLocales, }); export { HeadingOne }; diff --git a/packages/plugins/headings/src/plugin/HeadingThree.tsx b/packages/plugins/headings/src/plugin/HeadingThree.tsx index b37fab3fc..c1091599e 100644 --- a/packages/plugins/headings/src/plugin/HeadingThree.tsx +++ b/packages/plugins/headings/src/plugin/HeadingThree.tsx @@ -6,6 +6,7 @@ import { } from '@yoopta/editor'; import { HeadingThreeCommands } from '../commands'; import { HeadingThreeElement } from '../types'; +import {defaultHeadingThreeLocales} from '../locales'; const HeadingThreeRender = ({ extendRender, ...props }: PluginElementRenderProps) => { const { element, HTMLAttributes = {}, attributes, children } = props; @@ -91,6 +92,7 @@ const HeadingThree = new YooptaPlugin { const { element, HTMLAttributes = {}, attributes, children } = props; @@ -86,6 +87,7 @@ const HeadingTwo = new YooptaPlugin>({ }, }, }, + translations: defaultHeadingTwoLocales }); export { HeadingTwo }; diff --git a/packages/plugins/image/src/locales/en.ts b/packages/plugins/image/src/locales/en.ts new file mode 100644 index 000000000..45376749e --- /dev/null +++ b/packages/plugins/image/src/locales/en.ts @@ -0,0 +1,4 @@ +export const imageEnLocale = { + title: 'Image', + description: 'Upload from device or insert with link.', +} diff --git a/packages/plugins/image/src/locales/es.ts b/packages/plugins/image/src/locales/es.ts new file mode 100644 index 000000000..227dec495 --- /dev/null +++ b/packages/plugins/image/src/locales/es.ts @@ -0,0 +1,5 @@ +export const imageEsLocale = { + title: 'Imagen', + description: 'Inserta imágenes desde un archivo o enlace.', +} + diff --git a/packages/plugins/image/src/locales/index.ts b/packages/plugins/image/src/locales/index.ts new file mode 100644 index 000000000..43411b283 --- /dev/null +++ b/packages/plugins/image/src/locales/index.ts @@ -0,0 +1,7 @@ +import {imageEnLocale} from './en'; +import {imageEsLocale} from './es'; + +export const defaultLocales = { + en: imageEnLocale, + es: imageEsLocale, +} diff --git a/packages/plugins/image/src/plugin/index.tsx b/packages/plugins/image/src/plugin/index.tsx index bdd1088cc..3b65c41ab 100644 --- a/packages/plugins/image/src/plugin/index.tsx +++ b/packages/plugins/image/src/plugin/index.tsx @@ -3,6 +3,7 @@ import { ImageCommands } from '../commands'; import { ImageElementMap, ImageElementProps, ImagePluginElements, ImagePluginOptions } from '../types'; import { ImageRender } from '../ui/Image'; import { limitSizes } from '../utils/limitSizes'; +import {defaultLocales} from '../locales'; const ALIGNS_TO_JUSTIFY = { left: 'flex-start', @@ -117,6 +118,7 @@ const Image = new YooptaPlugin({ }, }, }, + translations: defaultLocales, }); export { Image }; diff --git a/packages/plugins/link/src/locales/en.ts b/packages/plugins/link/src/locales/en.ts new file mode 100644 index 000000000..e15593099 --- /dev/null +++ b/packages/plugins/link/src/locales/en.ts @@ -0,0 +1,4 @@ +export const enLocale = { + title: 'Link', + description: 'Adds clickable hyperlinks to content.', +} diff --git a/packages/plugins/link/src/locales/es.ts b/packages/plugins/link/src/locales/es.ts new file mode 100644 index 000000000..f87123a8d --- /dev/null +++ b/packages/plugins/link/src/locales/es.ts @@ -0,0 +1,4 @@ +export const esLocale = { + title: 'Enlace', + description: 'Agrega hipervínculos clicables al contenido.', +} diff --git a/packages/plugins/link/src/locales/index.ts b/packages/plugins/link/src/locales/index.ts new file mode 100644 index 000000000..1651fca2b --- /dev/null +++ b/packages/plugins/link/src/locales/index.ts @@ -0,0 +1,7 @@ +import {enLocale} from './en'; +import {esLocale} from './es'; + +export const defaultLocales = { + en: enLocale, + es: esLocale +} diff --git a/packages/plugins/link/src/plugin/index.tsx b/packages/plugins/link/src/plugin/index.tsx index 23966a85a..e79428e7c 100644 --- a/packages/plugins/link/src/plugin/index.tsx +++ b/packages/plugins/link/src/plugin/index.tsx @@ -2,6 +2,7 @@ import { deserializeTextNodes, generateId, serializeTextNodes, YooptaPlugin } fr import { LinkCommands } from '../commands'; import { LinkElementMap, LinkElementProps } from '../types'; import { LinkRender } from '../ui/LinkRender'; +import {defaultLocales} from '../locales'; const Link = new YooptaPlugin({ type: 'LinkPlugin', @@ -77,6 +78,7 @@ const Link = new YooptaPlugin({ }, }, }, + translations: defaultLocales, }); export { Link }; diff --git a/packages/plugins/lists/src/locales/en.ts b/packages/plugins/lists/src/locales/en.ts index 2924adafe..36a453e82 100644 --- a/packages/plugins/lists/src/locales/en.ts +++ b/packages/plugins/lists/src/locales/en.ts @@ -1,6 +1,16 @@ -export const enLocale = { - en: { - title: 'Bulleted List', - description: 'Description of bulleted list', - } +export const bulletedListEnLocale = { + title: 'Bulleted List', + description: 'Creates unordered lists with bullet points.', } + +export const numberedListEnLocale = { + title: 'Bulleted List', + description: 'Creates ordered lists with numbers.', +} + + +export const todoListEnLocale = { + title: 'Todo List', + description: 'Creates lists with checkable items.', +} + diff --git a/packages/plugins/lists/src/locales/es.ts b/packages/plugins/lists/src/locales/es.ts index 80311277a..067022e08 100644 --- a/packages/plugins/lists/src/locales/es.ts +++ b/packages/plugins/lists/src/locales/es.ts @@ -1,6 +1,14 @@ -export const esLocale = { - es: { - title: 'Lista de viñetas', - description: 'Descripción de la lista de viñetas.', - } +export const bulletedListEsLocale = { + title: 'Lista de viñetas', + description: 'Crea listas con viñetas.', +} + +export const numberedListEsLocale = { + title: 'Lista enumerada', + description: 'Crea listas ordenadas con números.', +} + +export const todoListEsLocale = { + title: 'Lista de tareas', + description: 'Crea listas con elementos marcables.', } diff --git a/packages/plugins/lists/src/locales/index.ts b/packages/plugins/lists/src/locales/index.ts index 290b43dc9..31d2ce0ad 100644 --- a/packages/plugins/lists/src/locales/index.ts +++ b/packages/plugins/lists/src/locales/index.ts @@ -1,7 +1,18 @@ -import {enLocale} from './en'; -import {esLocale} from './es'; +import {bulletedListEnLocale, numberedListEnLocale, todoListEnLocale} from './en'; +import {bulletedListEsLocale, numberedListEsLocale, todoListEsLocale} from './es'; -export const defaultLocales = { - ...enLocale, - ...esLocale, + +export const defaultBulletedListLocales = { + en: bulletedListEnLocale, + es: bulletedListEsLocale +} + +export const defaultNumberedListLocales = { + en: numberedListEnLocale, + es: numberedListEsLocale +} + +export const defaultTodoListLocales = { + en: todoListEnLocale, + es: todoListEsLocale } diff --git a/packages/plugins/lists/src/plugin/BulletedList.tsx b/packages/plugins/lists/src/plugin/BulletedList.tsx index 7ed3c9f38..e4ef8a597 100644 --- a/packages/plugins/lists/src/plugin/BulletedList.tsx +++ b/packages/plugins/lists/src/plugin/BulletedList.tsx @@ -10,7 +10,7 @@ import { BulletedListRender } from '../elements/BulletedList'; import { onKeyDown } from '../events/onKeyDown'; import { ListElementMap } from '../types'; import { deserializeListNodes } from '../utils/deserializeListNodes'; -import {defaultLocales} from '../locales'; +import {defaultBulletedListLocales} from '../locales'; const BulletedList = new YooptaPlugin>({ type: 'BulletedList', @@ -89,7 +89,7 @@ const BulletedList = new YooptaPlugin>({ }, }, }, - translations: defaultLocales + translations: defaultBulletedListLocales }); export { BulletedList }; diff --git a/packages/plugins/lists/src/plugin/NumberedList.tsx b/packages/plugins/lists/src/plugin/NumberedList.tsx index b5405d6d4..d6867fea1 100644 --- a/packages/plugins/lists/src/plugin/NumberedList.tsx +++ b/packages/plugins/lists/src/plugin/NumberedList.tsx @@ -4,6 +4,7 @@ import { NumberedListRender } from '../elements/NumberedList'; import { onKeyDown } from '../events/onKeyDown'; import { ListElementMap } from '../types'; import { deserializeListNodes } from '../utils/deserializeListNodes'; +import { defaultNumberedListLocales } from '../locales'; const NumberedList = new YooptaPlugin>({ type: 'NumberedList', @@ -81,6 +82,7 @@ const NumberedList = new YooptaPlugin>({ }, }, }, + translations: defaultNumberedListLocales }); export { NumberedList }; diff --git a/packages/plugins/lists/src/plugin/TodoList.tsx b/packages/plugins/lists/src/plugin/TodoList.tsx index d789d46df..f98c91c00 100644 --- a/packages/plugins/lists/src/plugin/TodoList.tsx +++ b/packages/plugins/lists/src/plugin/TodoList.tsx @@ -4,6 +4,7 @@ import { TodoListRender } from '../elements/TodoList'; import { onKeyDown } from '../events/onKeyDown'; import { ListElementMap } from '../types'; import { deserializeListNodes } from '../utils/deserializeListNodes'; +import { defaultTodoListLocales } from '../locales'; const TodoList = new YooptaPlugin>({ type: 'TodoList', @@ -87,6 +88,7 @@ const TodoList = new YooptaPlugin>({ }, }, }, + translations: defaultTodoListLocales }); export { TodoList }; diff --git a/packages/plugins/paragraph/src/locales/en.ts b/packages/plugins/paragraph/src/locales/en.ts new file mode 100644 index 000000000..9916f3403 --- /dev/null +++ b/packages/plugins/paragraph/src/locales/en.ts @@ -0,0 +1,4 @@ +export const enLocale = { + title: 'Paragraph', + description: 'Adds blocks of text for content.', +} diff --git a/packages/plugins/paragraph/src/locales/es.ts b/packages/plugins/paragraph/src/locales/es.ts new file mode 100644 index 000000000..61c1e3d10 --- /dev/null +++ b/packages/plugins/paragraph/src/locales/es.ts @@ -0,0 +1,4 @@ +export const esLocale = { + title: 'Párrafo', + description: 'Agrega bloques de texto.', +} diff --git a/packages/plugins/paragraph/src/locales/index.ts b/packages/plugins/paragraph/src/locales/index.ts new file mode 100644 index 000000000..1651fca2b --- /dev/null +++ b/packages/plugins/paragraph/src/locales/index.ts @@ -0,0 +1,7 @@ +import {enLocale} from './en'; +import {esLocale} from './es'; + +export const defaultLocales = { + en: enLocale, + es: esLocale +} diff --git a/packages/plugins/paragraph/src/plugin/index.tsx b/packages/plugins/paragraph/src/plugin/index.tsx index cef386681..f808667db 100644 --- a/packages/plugins/paragraph/src/plugin/index.tsx +++ b/packages/plugins/paragraph/src/plugin/index.tsx @@ -3,6 +3,7 @@ import { Element, Transforms } from 'slate'; import { ParagraphCommands } from '../commands'; import { ParagraphElement, ParagraphElementMap } from '../types'; import { ParagraphRender } from '../ui/Paragraph'; +import {defaultLocales} from '../locales'; const Paragraph = new YooptaPlugin({ type: 'Paragraph', @@ -56,6 +57,7 @@ const Paragraph = new YooptaPlugin({ }, }, commands: ParagraphCommands, + translations: defaultLocales, }); export { Paragraph }; diff --git a/packages/plugins/table/src/locales/en.ts b/packages/plugins/table/src/locales/en.ts new file mode 100644 index 000000000..d6897d2f3 --- /dev/null +++ b/packages/plugins/table/src/locales/en.ts @@ -0,0 +1,4 @@ +export const enLocale = { + title: 'Table', + description: 'Organizes content into rows and columns.', +} diff --git a/packages/plugins/table/src/locales/es.ts b/packages/plugins/table/src/locales/es.ts new file mode 100644 index 000000000..0ad8236d6 --- /dev/null +++ b/packages/plugins/table/src/locales/es.ts @@ -0,0 +1,4 @@ +export const esLocale = { + title: 'Tabla', + description: 'Organiza contenido en filas y columnas.', +} diff --git a/packages/plugins/table/src/locales/index.ts b/packages/plugins/table/src/locales/index.ts new file mode 100644 index 000000000..1651fca2b --- /dev/null +++ b/packages/plugins/table/src/locales/index.ts @@ -0,0 +1,7 @@ +import {enLocale} from './en'; +import {esLocale} from './es'; + +export const defaultLocales = { + en: enLocale, + es: esLocale +} diff --git a/packages/plugins/table/src/plugin/Table.tsx b/packages/plugins/table/src/plugin/Table.tsx index e764f1a6b..47bd8d67b 100644 --- a/packages/plugins/table/src/plugin/Table.tsx +++ b/packages/plugins/table/src/plugin/Table.tsx @@ -12,6 +12,7 @@ import { deserializeTable } from '../parsers/html/deserialize'; import { serializeTable } from '../parsers/html/serialize'; import { serializeMarkown } from '../parsers/markdown/serialize'; import { serializeTableToEmail } from '../parsers/email/serialize'; +import { defaultLocales } from '../locales'; const Table = new YooptaPlugin({ type: 'Table', @@ -70,6 +71,7 @@ const Table = new YooptaPlugin({ shortcuts: ['table', '||', '3x3'], }, commands: TableCommands, + translations: defaultLocales }); export { Table }; diff --git a/packages/plugins/video/src/locales/en.ts b/packages/plugins/video/src/locales/en.ts new file mode 100644 index 000000000..5d859a9e6 --- /dev/null +++ b/packages/plugins/video/src/locales/en.ts @@ -0,0 +1,4 @@ +export const enLocale = { + title: 'Video', + description: 'Upload from device, insert from Youtube, Vimeo, DailyMotion, Loom, Wistia...', +} diff --git a/packages/plugins/video/src/locales/es.ts b/packages/plugins/video/src/locales/es.ts new file mode 100644 index 000000000..f0698714c --- /dev/null +++ b/packages/plugins/video/src/locales/es.ts @@ -0,0 +1,4 @@ +export const esLocale = { + title: 'Vídeo', + description: 'Subir desde el dispositivo, insertar desde YouTube, Vimeo, DailyMotion, Loom, Wistia...', +} diff --git a/packages/plugins/video/src/locales/index.ts b/packages/plugins/video/src/locales/index.ts new file mode 100644 index 000000000..1651fca2b --- /dev/null +++ b/packages/plugins/video/src/locales/index.ts @@ -0,0 +1,7 @@ +import {enLocale} from './en'; +import {esLocale} from './es'; + +export const defaultLocales = { + en: enLocale, + es: esLocale +} diff --git a/packages/plugins/video/src/plugin/index.tsx b/packages/plugins/video/src/plugin/index.tsx index 63505cacd..6aa267f53 100644 --- a/packages/plugins/video/src/plugin/index.tsx +++ b/packages/plugins/video/src/plugin/index.tsx @@ -3,6 +3,7 @@ import { VideoCommands } from '../commands'; import { VideoElementMap, VideoPluginOptions } from '../types'; import { VideoRender } from '../ui/Video'; import { limitSizes } from '../utils/limitSizes'; +import {defaultLocales} from '../locales'; const ALIGNS_TO_JUSTIFY = { left: 'flex-start', @@ -216,6 +217,7 @@ const Video = new YooptaPlugin({ }, }, }, + translations: defaultLocales, }); export { Video }; From 6c1fe3d421468fe2253f669b6cdb1342bd102f5a Mon Sep 17 00:00:00 2001 From: Guillermo Loaysa Date: Sun, 5 Jan 2025 13:38:45 +0100 Subject: [PATCH 05/18] fix: ActionMenuList component tree changing on re-render --- .../editor/src/i18n/hooks/useTranslation.ts | 27 +++++++------- packages/development/src/pages/dev/index.tsx | 17 ++++++--- .../src/components/ActionMenuList.tsx | 35 ++++++++----------- 3 files changed, 42 insertions(+), 37 deletions(-) diff --git a/packages/core/editor/src/i18n/hooks/useTranslation.ts b/packages/core/editor/src/i18n/hooks/useTranslation.ts index 45a647c58..cf649f30f 100644 --- a/packages/core/editor/src/i18n/hooks/useTranslation.ts +++ b/packages/core/editor/src/i18n/hooks/useTranslation.ts @@ -1,29 +1,30 @@ -import { useEffect, useState, useCallback } from 'react'; +import { useEffect, useState } from 'react'; import { YooptaTranslationManager } from '../TranslationManager'; import { UseTranslationReturn } from './types'; export const useTranslation = (): UseTranslationReturn => { - const [language, setLanguage] = useState(YooptaTranslationManager.getCurrentLanguage()); - - // Directly reference the translation function without using state - const t = useCallback((key: string) => YooptaTranslationManager.translate(key), []); + const [language, setLanguage] = useState(() => YooptaTranslationManager.getCurrentLanguage()); + const [t, setT] = useState(() => (key: string) => YooptaTranslationManager.translate(key)); useEffect(() => { - // Update the language state when the translation manager's language changes - const unsubscribe = YooptaTranslationManager.subscribe(() => { + const handleUpdate = () => { setLanguage(YooptaTranslationManager.getCurrentLanguage()); - }); + setT(() => (key: string) => YooptaTranslationManager.translate(key)); + }; + + const unsubscribe = YooptaTranslationManager.subscribe(handleUpdate); + + // Initialize the state in case it changes before mounting + handleUpdate(); return () => unsubscribe(); }, []); - const changeLanguage = useCallback((lang: string) => { + const changeLanguage = (lang: string) => { YooptaTranslationManager.setLanguage(lang); - }, []); + }; - const getAvailableKeys = useCallback(() => { - return YooptaTranslationManager.getAvailableKeys(); - }, []); + const getAvailableKeys = () => YooptaTranslationManager.getAvailableKeys(); return { t, diff --git a/packages/development/src/pages/dev/index.tsx b/packages/development/src/pages/dev/index.tsx index 256f9b084..bed38b4fb 100644 --- a/packages/development/src/pages/dev/index.tsx +++ b/packages/development/src/pages/dev/index.tsx @@ -789,7 +789,7 @@ const BasicExample = () => { const editor: YooEditor = useMemo(() => createYooptaEditor(), []); const selectionRef = useRef(null); const [value, setValue] = useState(data); - const {setLanguage} = useTranslation(); + const {setLanguage, currentLanguage} = useTranslation(); const onChange = (value: YooptaContentValue, options: YooptaOnChangeOptions) => { console.log('onChange', value, options); @@ -812,9 +812,18 @@ const BasicExample = () => {
- - - + + +
{ 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: blockTypes }) : ; + + + // [TODO] - take care about SSR + return ( {isMounted && ( -
- {/* [TODO] - pass key down handler */} - {render({ ...renderProps, actions })} -
+
+ {menuContent} +
)}
- ); - } - - return ( - - {isMounted && ( -
- -
- )} -
); }; From ff2a8279d9178f08d079b678022a33f851cff94b Mon Sep 17 00:00:00 2001 From: Guillermo Loaysa Date: Sun, 5 Jan 2025 13:57:54 +0100 Subject: [PATCH 06/18] feat: make translations mandatory for Plugin --- packages/core/editor/src/i18n/types.ts | 14 +++++++++++++- packages/core/editor/src/plugins/types.ts | 5 ++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/packages/core/editor/src/i18n/types.ts b/packages/core/editor/src/i18n/types.ts index e64470f4c..e6a15eb6e 100644 --- a/packages/core/editor/src/i18n/types.ts +++ b/packages/core/editor/src/i18n/types.ts @@ -37,6 +37,18 @@ */ export interface Translations { [language: string]: { - [namespace: string]: Record; + [namespace: string]: NestedTranslations; }; } + +export interface PluginTranslations { + [language: string]: { + [key: string]: string | NestedTranslations; + title: string; + description: string; + }; +} + +interface NestedTranslations { + [key: string]: string | NestedTranslations; +} diff --git a/packages/core/editor/src/plugins/types.ts b/packages/core/editor/src/plugins/types.ts index acd88a3c3..d13bec248 100644 --- a/packages/core/editor/src/plugins/types.ts +++ b/packages/core/editor/src/plugins/types.ts @@ -3,6 +3,7 @@ import { RenderElementProps as RenderSlateElementProps, RenderLeafProps } from ' import { SlateEditor, SlateElement, YooEditor, YooptaBlockBaseMeta, YooptaBlockData } from '../editor/types'; import { EditorEventHandlers } from '../types/eventHandlers'; import { HOTKEYS_TYPE } from '../utils/hotkeys'; +import {PluginTranslations} from '../i18n/types'; export enum NodeType { Block = 'block', @@ -89,9 +90,7 @@ export type Plugin, TPluginOpti events?: PluginEvents; options?: PluginOptions; parsers?: Partial>; - translations?: { - [language: string]: Record; - }; + translations: PluginTranslations; }; export type PluginParsers = { From 20c7620ca7e5eb4964a4dbe8dda0d1b993578e37 Mon Sep 17 00:00:00 2001 From: Guillermo Loaysa Date: Tue, 7 Jan 2025 11:55:56 +0100 Subject: [PATCH 07/18] chore: add docs and improve typings --- packages/core/editor/README.md | 13 ++++++ .../editor/src/i18n/TranslationManager.ts | 42 ++++++------------- packages/core/editor/src/i18n/hooks/types.ts | 4 +- .../src/i18n/hooks/useAddTranslations.ts | 3 +- packages/core/editor/src/i18n/types.ts | 13 +++--- .../src/plugins/SlateEditorComponent.tsx | 2 +- 6 files changed, 38 insertions(+), 39 deletions(-) diff --git a/packages/core/editor/README.md b/packages/core/editor/README.md index eea11aad4..d67477127 100644 --- a/packages/core/editor/README.md +++ b/packages/core/editor/README.md @@ -77,6 +77,8 @@ type Props = { onChange?: (value: YooptaContentValue, options: YooptaOnChangeOptions) => void; /* Path change handler */ onPathChange?: (path: YooptaPath) => void; + /* Custom translations for the editor. Allows you to override current translations or add a new whole language. */ + translations?: Translations; }; ``` @@ -188,4 +190,15 @@ useBlockData(blockId); * @returns {PluginOptions} The options of the plugin. */ useYooptaPluginOptions(blockType); + +/** + * Hook to interact with the Yoopta Editor translation system. + * + * @returns {UseTranslationReturn} An object with: + * - `t(key: string)`: Fetch a translation by key (e.g., `t('core.save')`). + * - `currentLanguage`: The active language (e.g., `'en'`). + * - `setLanguage(lang: string)`: Change the active language (e.g., `setLanguage('es')`). + * - `getAvailableKeys()`: Get all translation keys for the current language. Provided as a utility function to know all available keys at runtime. + */ +useTranslation() ``` diff --git a/packages/core/editor/src/i18n/TranslationManager.ts b/packages/core/editor/src/i18n/TranslationManager.ts index 1449e9f28..718a05833 100644 --- a/packages/core/editor/src/i18n/TranslationManager.ts +++ b/packages/core/editor/src/i18n/TranslationManager.ts @@ -1,5 +1,5 @@ import {defaultLocales} from './locales'; -import {Translations} from './types'; +import {NestedTranslations, Translations} from './types'; type Listener = () => void; @@ -20,7 +20,7 @@ class TranslationManager { /** * Add translations for a specific namespace and language. */ - addTranslations(language: string, namespace: string, newTranslations: Record): void { + addTranslations(language: string, namespace: string, newTranslations: NestedTranslations): void { if (!this.translations[language]) { this.translations[language] = {}; } @@ -35,40 +35,22 @@ class TranslationManager { }; } - /** - * Override translations provided by the user. - */ - overrideTranslations(overrides: Translations): void { - Object.entries(overrides).forEach(([language, namespaces]) => { - if (!this.userOverrides[language]) { - this.userOverrides[language] = {}; - } - - Object.entries(namespaces).forEach(([namespace, translations]) => { - if (!this.userOverrides[language][namespace]) { - this.userOverrides[language][namespace] = {}; - } - - this.userOverrides[language][namespace] = { - ...this.userOverrides[language][namespace], - ...translations, - }; - }); - }); - } - /** * Fetch a translation for a specific key. */ translate(key: string): string { const [namespace, ...rest] = key.split('.'); - const finalKey = rest.join('.'); - return ( - this.userOverrides?.[this.currentLanguage]?.[namespace]?.[finalKey] ?? - this.translations?.[this.currentLanguage]?.[namespace]?.[finalKey] ?? - key - ); + // Helper function to safely access nested translations + const getNestedValue = (obj: any, keyPath: string[]): any => { + return keyPath.reduce((acc, key) => (acc && typeof acc === 'object' ? acc[key] : undefined), obj); + }; + + const resolvedTranslation = + getNestedValue(this.userOverrides?.[this.currentLanguage]?.[namespace], rest) ?? + getNestedValue(this.translations?.[this.currentLanguage]?.[namespace], rest); + + return typeof resolvedTranslation === 'string' ? resolvedTranslation : key; } /** diff --git a/packages/core/editor/src/i18n/hooks/types.ts b/packages/core/editor/src/i18n/hooks/types.ts index 1ab6e2d6d..74f92f4bc 100644 --- a/packages/core/editor/src/i18n/hooks/types.ts +++ b/packages/core/editor/src/i18n/hooks/types.ts @@ -1,3 +1,5 @@ +import { NestedTranslations } from '../types'; + export interface UseTranslationReturn { /** * Translates a key into the current language. @@ -48,5 +50,5 @@ export interface UseAddTranslationsReturn { * @param namespace - The namespace grouping the translations (e.g., 'core', 'plugin'). * @param translations - A record of key-value pairs representing the translations. */ - addTranslations: (language: string, namespace: string, translations: Record) => void; + addTranslations: (language: string, namespace: string, translations: NestedTranslations) => void; } diff --git a/packages/core/editor/src/i18n/hooks/useAddTranslations.ts b/packages/core/editor/src/i18n/hooks/useAddTranslations.ts index 014d9608c..2baf76e27 100644 --- a/packages/core/editor/src/i18n/hooks/useAddTranslations.ts +++ b/packages/core/editor/src/i18n/hooks/useAddTranslations.ts @@ -1,10 +1,11 @@ import {UseAddTranslationsReturn} from './types'; import {useCallback} from 'react'; import {YooptaTranslationManager} from '../TranslationManager'; +import {NestedTranslations} from '../types'; export const useAddTranslations = (): UseAddTranslationsReturn => { const addTranslations = useCallback( - (language: string, namespace: string, translations: Record) => { + (language: string, namespace: string, translations: NestedTranslations) => { YooptaTranslationManager.addTranslations(language, namespace, translations); }, [] diff --git a/packages/core/editor/src/i18n/types.ts b/packages/core/editor/src/i18n/types.ts index e6a15eb6e..9bbe3110f 100644 --- a/packages/core/editor/src/i18n/types.ts +++ b/packages/core/editor/src/i18n/types.ts @@ -42,13 +42,14 @@ export interface Translations { } export interface PluginTranslations { - [language: string]: { - [key: string]: string | NestedTranslations; - title: string; - description: string; - }; + [language: string]: MandatoryPluginTranslations & NestedTranslations; } -interface NestedTranslations { +type MandatoryPluginTranslations = { + title: string; + description: string; +}; + +export interface NestedTranslations { [key: string]: string | NestedTranslations; } diff --git a/packages/core/editor/src/plugins/SlateEditorComponent.tsx b/packages/core/editor/src/plugins/SlateEditorComponent.tsx index fe50babbe..c936232f6 100644 --- a/packages/core/editor/src/plugins/SlateEditorComponent.tsx +++ b/packages/core/editor/src/plugins/SlateEditorComponent.tsx @@ -14,7 +14,7 @@ import { deserializeHTML } from '../parsers/deserializeHTML'; import { useEventHandlers, useSlateEditor } from './hooks'; import { SlateElement } from '../editor/types'; -type Props, TOptions> = Plugin & { +type Props, TOptions> = Omit, 'translations'> & { id: string; marks?: YooptaMark[]; options: Plugin['options']; From 8873d3938e2647b35f95870ff2b6ecada67d0537 Mon Sep 17 00:00:00 2001 From: Guillermo Loaysa Date: Tue, 7 Jan 2025 12:54:52 +0100 Subject: [PATCH 08/18] docs: add example on how to use translations --- README.md | 2 + packages/development/src/pages/dev/index.tsx | 28 +- .../examples/withTranslations/index.tsx | 110 ++++ .../examples/withTranslations/initValue.ts | 536 ++++++++++++++++++ .../src/pages/examples/[example].tsx | 6 + 5 files changed, 655 insertions(+), 27 deletions(-) create mode 100644 web/next-example/src/components/examples/withTranslations/index.tsx create mode 100644 web/next-example/src/components/examples/withTranslations/initValue.ts diff --git a/README.md b/README.md index c7aedccb1..894e8ad50 100644 --- a/README.md +++ b/README.md @@ -155,6 +155,8 @@ type YooptaEditor = { style?: number | string; /* Id for your editor instance. Can be useful for multiple editors */ id?: number | string; + /* Custom translations for the editor. Allows you to override current translations or add a new whole language. */ + translations?: Translations; }; ``` diff --git a/packages/development/src/pages/dev/index.tsx b/packages/development/src/pages/dev/index.tsx index bed38b4fb..2828e8768 100644 --- a/packages/development/src/pages/dev/index.tsx +++ b/packages/development/src/pages/dev/index.tsx @@ -1,12 +1,9 @@ import YooptaEditor, { - Blocks, createYooptaEditor, - generateId, YooptaOnChangeOptions, YooEditor, - YooptaBlockData, YooptaContentValue, - YooptaPath, useTranslation, + YooptaPath, } from '@yoopta/editor'; import { useEffect, useMemo, useRef, useState } from 'react'; @@ -789,7 +786,6 @@ const BasicExample = () => { const editor: YooEditor = useMemo(() => createYooptaEditor(), []); const selectionRef = useRef(null); const [value, setValue] = useState(data); - const {setLanguage, currentLanguage} = useTranslation(); const onChange = (value: YooptaContentValue, options: YooptaOnChangeOptions) => { console.log('onChange', value, options); @@ -811,20 +807,6 @@ const BasicExample = () => { <>
-
- - - -
{ value={value} onChange={onChange} onPathChange={onPathChange} - translations={{ - fr: { - table: { - title: 'Table', - description: 'Table est Table en français' - } - }, - }} />
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..8a9926b70 --- /dev/null +++ b/web/next-example/src/components/examples/withTranslations/index.tsx @@ -0,0 +1,110 @@ +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 = () => { From 7613ba5c786365a6b7948cdfd3a41a53ebe11677 Mon Sep 17 00:00:00 2001 From: Guillermo Loaysa Date: Tue, 7 Jan 2025 16:14:18 +0100 Subject: [PATCH 09/18] fix: pass action to DefaultActionMenuRender and render function --- packages/tools/action-menu/src/components/ActionMenuList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tools/action-menu/src/components/ActionMenuList.tsx b/packages/tools/action-menu/src/components/ActionMenuList.tsx index 9cb2fec28..740c73535 100644 --- a/packages/tools/action-menu/src/components/ActionMenuList.tsx +++ b/packages/tools/action-menu/src/components/ActionMenuList.tsx @@ -359,7 +359,7 @@ const ActionMenuList = ({ items, render }: ActionMenuToolProps) => { const style = { ...floatingStyles, ...transitionStyles }; - const menuContent = render ? render({ ...renderProps, actions: blockTypes }) : ; + const menuContent = render ? render({ ...renderProps, actions }) : ; // [TODO] - take care about SSR From 1988ad47eb28ed02efcc5bc2e8072a764dc102d6 Mon Sep 17 00:00:00 2001 From: Darginec05 Date: Tue, 28 Jan 2025 00:24:15 +0300 Subject: [PATCH 10/18] feature/add-translations rewrite approach for translations architecture --- .../src/UI/BlockOptions/BlockOptions.tsx | 12 +- .../core/editor/src/UI/BlockOptions/utils.ts | 12 +- packages/core/editor/src/constants/labels.ts | 43 + .../editor/src/editor/i18n/getLabelText.ts | 17 + packages/core/editor/src/editor/index.tsx | 3 + packages/core/editor/src/editor/types.ts | 4 + .../editor/src/i18n/TranslationManager.ts | 188 +-- .../editor/src/i18n/hooks/useTranslation.ts | 44 +- packages/core/editor/src/i18n/types.ts | 14 +- .../src/plugins/SlateEditorComponent.tsx | 11 +- packages/core/editor/src/plugins/types.ts | 3 +- packages/core/editor/src/types/i18n.ts | 11 + packages/core/i18n/README.md | 126 ++ packages/core/i18n/package.json | 36 + packages/core/i18n/rollup.config.js | 7 + .../i18n/src/context/YooptaI18nProvider.tsx | 1 + .../i18n/src/extenstion/withTranslations.ts | 55 + .../core/i18n/src/hooks/useTranslation.ts | 6 + packages/core/i18n/src/index.ts | 2 + packages/core/i18n/src/types/index.ts | 9 + packages/core/i18n/src/utils/index.ts | 1 + packages/core/i18n/tsconfig.json | 14 + packages/development/package.json | 1 + .../components/FixedToolbar/FixedToolbar.tsx | 53 +- packages/development/src/pages/dev/index.tsx | 1097 +++++------------ .../development/src/utils/yoopta/value.ts | 767 ++++++++++++ packages/plugins/link/src/plugin/index.tsx | 3 +- .../plugins/link/src/ui/LinkHoverPreview.tsx | 3 +- .../plugins/paragraph/src/plugin/index.tsx | 3 +- .../components/DefaultActionMenuRender.tsx | 14 +- .../src/components/DefaultLinkToolRender.tsx | 24 +- .../src/components/DefaultToolbarRender.tsx | 8 +- .../toolbar/src/components/HighlightColor.tsx | 8 +- .../tools/toolbar/src/components/utils.ts | 13 +- .../examples/withTranslations/index.tsx | 153 +-- 35 files changed, 1716 insertions(+), 1050 deletions(-) create mode 100644 packages/core/editor/src/constants/labels.ts create mode 100644 packages/core/editor/src/editor/i18n/getLabelText.ts create mode 100644 packages/core/editor/src/types/i18n.ts create mode 100644 packages/core/i18n/README.md create mode 100644 packages/core/i18n/package.json create mode 100644 packages/core/i18n/rollup.config.js create mode 100644 packages/core/i18n/src/context/YooptaI18nProvider.tsx create mode 100644 packages/core/i18n/src/extenstion/withTranslations.ts create mode 100644 packages/core/i18n/src/hooks/useTranslation.ts create mode 100644 packages/core/i18n/src/index.ts create mode 100644 packages/core/i18n/src/types/index.ts create mode 100644 packages/core/i18n/src/utils/index.ts create mode 100644 packages/core/i18n/tsconfig.json create mode 100644 packages/development/src/utils/yoopta/value.ts 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/constants/labels.ts b/packages/core/editor/src/constants/labels.ts new file mode 100644 index 000000000..b2bcd1fdf --- /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 = {}; 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..548107ca6 100644 --- a/packages/core/editor/src/editor/index.tsx +++ b/packages/core/editor/src/editor/index.tsx @@ -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(); @@ -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..609bca8a8 100644 --- a/packages/core/editor/src/editor/types.ts +++ b/packages/core/editor/src/editor/types.ts @@ -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; @@ -126,6 +127,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; diff --git a/packages/core/editor/src/i18n/TranslationManager.ts b/packages/core/editor/src/i18n/TranslationManager.ts index 718a05833..68f63a184 100644 --- a/packages/core/editor/src/i18n/TranslationManager.ts +++ b/packages/core/editor/src/i18n/TranslationManager.ts @@ -1,107 +1,107 @@ -import {defaultLocales} from './locales'; -import {NestedTranslations, Translations} from './types'; +import { defaultLocales } from './locales'; +import { NestedTranslations, Translations } from './types'; type Listener = () => void; class TranslationManager { - private static instance: TranslationManager; - private translations: Translations = defaultLocales; - private currentLanguage: string = 'en'; - private userOverrides: Translations = {}; - private listeners: Listener[] = []; - - static getInstance(): TranslationManager { - if (!TranslationManager.instance) { - this.instance = new TranslationManager(); - } - return this.instance; + private static instance: TranslationManager; + private translations: Translations = defaultLocales; + private currentLanguage: string = 'en'; + private userOverrides: Translations = {}; + private listeners: Listener[] = []; + + static getInstance(): TranslationManager { + if (!TranslationManager.instance) { + this.instance = new TranslationManager(); } - - /** - * Add translations for a specific namespace and language. - */ - addTranslations(language: string, namespace: string, newTranslations: NestedTranslations): void { - if (!this.translations[language]) { - this.translations[language] = {}; - } - - if (!this.translations[language][namespace]) { - this.translations[language][namespace] = {}; - } - - this.translations[language][namespace] = { - ...this.translations[language][namespace], - ...newTranslations, - }; - } - - /** - * Fetch a translation for a specific key. - */ - translate(key: string): string { - const [namespace, ...rest] = key.split('.'); - - // Helper function to safely access nested translations - const getNestedValue = (obj: any, keyPath: string[]): any => { - return keyPath.reduce((acc, key) => (acc && typeof acc === 'object' ? acc[key] : undefined), obj); - }; - - const resolvedTranslation = - getNestedValue(this.userOverrides?.[this.currentLanguage]?.[namespace], rest) ?? - getNestedValue(this.translations?.[this.currentLanguage]?.[namespace], rest); - - return typeof resolvedTranslation === 'string' ? resolvedTranslation : key; - } - - /** - * Set the current language and notify listeners. - */ - setLanguage(language: string): void { - if (this.translations[language]) { - this.currentLanguage = language; - this.notifyListeners(); - } else { - console.warn(`Language ${language} not found. Falling back to ${this.currentLanguage}.`); - } - } - - /** - * Get the current language. - */ - getCurrentLanguage(): string { - return this.currentLanguage; - } - - /** - * Fetch all available keys for the current language. - */ - getAvailableKeys(): Record { - const availableKeys: Record = {}; - const langTranslations = this.translations[this.currentLanguage] || {}; - - Object.entries(langTranslations).forEach(([namespace, keys]) => { - availableKeys[namespace] = Object.keys(keys); - }); - - return availableKeys; + return this.instance; + } + + /** + * Add translations for a specific namespace and language. + */ + addTranslations(language: string, namespace: string, newTranslations: NestedTranslations): void { + if (!this.translations[language]) { + this.translations[language] = {}; } - /** - * Subscribe to language changes. - */ - subscribe(listener: Listener): () => void { - this.listeners.push(listener); - return () => { - this.listeners = this.listeners.filter((l) => l !== listener); - }; + if (!this.translations[language][namespace]) { + this.translations[language][namespace] = {}; } - /** - * Notify all listeners about language changes. - */ - private notifyListeners(): void { - this.listeners.forEach((listener) => listener()); + this.translations[language][namespace] = { + ...this.translations[language][namespace], + ...newTranslations, + }; + } + + /** + * Fetch a translation for a specific key. + */ + translate(key: string): string { + const [namespace, ...rest] = key.split('.'); + + // Helper function to safely access nested translations + const getNestedValue = (obj: any, keyPath: string[]): any => { + return keyPath.reduce((acc, key) => (acc && typeof acc === 'object' ? acc[key] : undefined), obj); + }; + + const resolvedTranslation = + getNestedValue(this.userOverrides?.[this.currentLanguage]?.[namespace], rest) ?? + getNestedValue(this.translations?.[this.currentLanguage]?.[namespace], rest); + + return typeof resolvedTranslation === 'string' ? resolvedTranslation : key; + } + + /** + * Set the current language and notify listeners. + */ + setLanguage(language: string): void { + if (this.translations[language]) { + this.currentLanguage = language; + this.notifyListeners(); + } else { + console.warn(`Language ${language} not found. Falling back to ${this.currentLanguage}.`); } + } + + /** + * Get the current language. + */ + getCurrentLanguage(): string { + return this.currentLanguage; + } + + /** + * Fetch all available keys for the current language. + */ + getAvailableKeys(): Record { + const availableKeys: Record = {}; + const langTranslations = this.translations[this.currentLanguage] || {}; + + Object.entries(langTranslations).forEach(([namespace, keys]) => { + availableKeys[namespace] = Object.keys(keys); + }); + + return availableKeys; + } + + /** + * Subscribe to language changes. + */ + subscribe(listener: Listener): () => void { + this.listeners.push(listener); + return () => { + this.listeners = this.listeners.filter((l) => l !== listener); + }; + } + + /** + * Notify all listeners about language changes. + */ + private notifyListeners(): void { + this.listeners.forEach((listener) => listener()); + } } export const YooptaTranslationManager = TranslationManager.getInstance(); diff --git a/packages/core/editor/src/i18n/hooks/useTranslation.ts b/packages/core/editor/src/i18n/hooks/useTranslation.ts index cf649f30f..88a872e91 100644 --- a/packages/core/editor/src/i18n/hooks/useTranslation.ts +++ b/packages/core/editor/src/i18n/hooks/useTranslation.ts @@ -3,33 +3,33 @@ import { YooptaTranslationManager } from '../TranslationManager'; import { UseTranslationReturn } from './types'; export const useTranslation = (): UseTranslationReturn => { - const [language, setLanguage] = useState(() => YooptaTranslationManager.getCurrentLanguage()); - const [t, setT] = useState(() => (key: string) => YooptaTranslationManager.translate(key)); + const [language, setLanguage] = useState(() => YooptaTranslationManager.getCurrentLanguage()); + const [t, setT] = useState(() => (key: string) => YooptaTranslationManager.translate(key)); - useEffect(() => { - const handleUpdate = () => { - setLanguage(YooptaTranslationManager.getCurrentLanguage()); - setT(() => (key: string) => YooptaTranslationManager.translate(key)); - }; + useEffect(() => { + const handleUpdate = () => { + setLanguage(YooptaTranslationManager.getCurrentLanguage()); + setT(() => (key: string) => YooptaTranslationManager.translate(key)); + }; - const unsubscribe = YooptaTranslationManager.subscribe(handleUpdate); + const unsubscribe = YooptaTranslationManager.subscribe(handleUpdate); - // Initialize the state in case it changes before mounting - handleUpdate(); + // Initialize the state in case it changes before mounting + handleUpdate(); - return () => unsubscribe(); - }, []); + return () => unsubscribe(); + }, []); - const changeLanguage = (lang: string) => { - YooptaTranslationManager.setLanguage(lang); - }; + const changeLanguage = (lang: string) => { + YooptaTranslationManager.setLanguage(lang); + }; - const getAvailableKeys = () => YooptaTranslationManager.getAvailableKeys(); + const getAvailableKeys = () => YooptaTranslationManager.getAvailableKeys(); - return { - t, - currentLanguage: language, - setLanguage: changeLanguage, - getAvailableKeys, - }; + return { + t, + currentLanguage: language, + setLanguage: changeLanguage, + getAvailableKeys, + }; }; diff --git a/packages/core/editor/src/i18n/types.ts b/packages/core/editor/src/i18n/types.ts index 9bbe3110f..cee41182c 100644 --- a/packages/core/editor/src/i18n/types.ts +++ b/packages/core/editor/src/i18n/types.ts @@ -36,20 +36,20 @@ * - The value (string) is the translated text for that key. */ export interface Translations { - [language: string]: { - [namespace: string]: NestedTranslations; - }; + [language: string]: { + [namespace: string]: NestedTranslations; + }; } export interface PluginTranslations { - [language: string]: MandatoryPluginTranslations & NestedTranslations; + [language: string]: MandatoryPluginTranslations & NestedTranslations; } type MandatoryPluginTranslations = { - title: string; - description: string; + title: string; + description: string; }; export interface NestedTranslations { - [key: string]: string | NestedTranslations; + [key: string]: string | NestedTranslations; } diff --git a/packages/core/editor/src/plugins/SlateEditorComponent.tsx b/packages/core/editor/src/plugins/SlateEditorComponent.tsx index c936232f6..e87f235fa 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> = Omit, 'translations'> & { +type Props, TOptions> = Omit< + Plugin, + 'translations' +> & { id: string; marks?: YooptaMark[]; options: Plugin['options']; @@ -44,7 +47,7 @@ const SlateEditorComponent = , events, options, extensions: withExtensions, - placeholder = 'core.editor_placeholder', + placeholder = 'editor.placeholder', }: 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/plugins/types.ts b/packages/core/editor/src/plugins/types.ts index d13bec248..f2dcaf939 100644 --- a/packages/core/editor/src/plugins/types.ts +++ b/packages/core/editor/src/plugins/types.ts @@ -3,7 +3,7 @@ import { RenderElementProps as RenderSlateElementProps, RenderLeafProps } from ' import { SlateEditor, SlateElement, YooEditor, YooptaBlockBaseMeta, YooptaBlockData } from '../editor/types'; import { EditorEventHandlers } from '../types/eventHandlers'; import { HOTKEYS_TYPE } from '../utils/hotkeys'; -import {PluginTranslations} from '../i18n/types'; +import { PluginTranslations } from '../i18n/types'; export enum NodeType { Block = 'block', @@ -90,7 +90,6 @@ export type Plugin, TPluginOpti events?: PluginEvents; options?: PluginOptions; parsers?: Partial>; - translations: PluginTranslations; }; export type PluginParsers = { 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..87bf50d61 --- /dev/null +++ b/packages/core/i18n/README.md @@ -0,0 +1,126 @@ +# Exports + +Exports is core package for exporting/importing yoopta content in different formats +The package `@yoopta/exports` supports exporting/importing in the next formats: + +- HTML +- Markdown +- Plain text + +### Installation + +```bash +yarn add @yoopta/exports +``` + +### Usage + +HTML exports/imports example + +```jsx +import { html } from '@yoopta/exports'; + +const Editor = () => { + const editor = useMemo(() => createYooptaEditor(), []); + + // from html to @yoopta content + const deserializeHTML = () => { + const htmlString = '

First title

'; + const content = html.deserialize(editor, htmlString); + + editor.setEditorValue(content); + }; + + // from @yoopta content to html string + const serializeHTML = () => { + const data = editor.getEditorValue(); + const htmlString = html.serialize(editor, data); + console.log('html string', htmlString); + }; + + return ( +
+ + + + +
+ ); +}; +``` + +--- + +Markdown exports/imports example + +```jsx +import { markdown } from '@yoopta/exports'; + +const Editor = () => { + const editor = useMemo(() => createYooptaEditor(), []); + + // from markdown to @yoopta content + const deserializeMarkdown = () => { + const markdownString = '# First title'; + const value = markdown.deserialize(editor, markdownString); + + editor.setEditorValue(value); + }; + + // from @yoopta content to markdown string + const serializeMarkdown = () => { + const data = editor.getEditorValue(); + const markdownString = markdown.serialize(editor, data); + console.log('markdown string', markdownString); + }; + + return ( +
+ + + + +
+ ); +}; +``` + +Plain text exports/imports example + +```jsx +import { plainText } from '@yoopta/exports'; + +const Editor = () => { + const editor = useMemo(() => createYooptaEditor(), []); + + // from plain text to @yoopta content + const deserializeText = () => { + const textString = '# First title'; + const value = plainText.deserialize(editor, textString); + + editor.setEditorValue(value); + }; + + // from @yoopta content to plain text string + const serializeText = () => { + const data = editor.getEditorValue(); + const textString = plainText.serialize(editor, data); + console.log('plain text string', textString); + }; + + return ( +
+ + + + +
+ ); +}; +``` + +Examples + +- Page - [https://yoopta.dev/examples/withExports](https://yoopta.dev/examples/withExports) + - Example with HTML - [https://yoopta.dev/examples/withExports/html](https://yoopta.dev/examples/withExports/html) + - Example with Markdown - [https://yoopta.dev/examples/withExports/markdown](https://yoopta.dev/examples/withExports/markdown) 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..d8f0b5d46 --- /dev/null +++ b/packages/core/i18n/rollup.config.js @@ -0,0 +1,7 @@ +import { createRollupConfig } from '../../../config/rollup'; + +const pkg = require('./package.json'); +export default createRollupConfig({ + pkg, + tailwindConfig: { content: ['./src/**/*.{js,ts,jsx,tsx,mdx}'] }, +}); diff --git a/packages/core/i18n/src/context/YooptaI18nProvider.tsx b/packages/core/i18n/src/context/YooptaI18nProvider.tsx new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/packages/core/i18n/src/context/YooptaI18nProvider.tsx @@ -0,0 +1 @@ +export {}; diff --git a/packages/core/i18n/src/extenstion/withTranslations.ts b/packages/core/i18n/src/extenstion/withTranslations.ts new file mode 100644 index 000000000..520919f18 --- /dev/null +++ b/packages/core/i18n/src/extenstion/withTranslations.ts @@ -0,0 +1,55 @@ +import { I18nYooEditor } from '../types'; + +type TranslationOptions = { + language: string; + defaultLanguage: string; + translations: I18nYooEditor['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: I18nYooEditor, options: TranslationOptions): I18nYooEditor { + const { translations, defaultLanguage, language } = options; + + const { getLabelText } = editor; + const languages = Object.keys(translations); + + editor.getLabelText = (key) => { + const keyParts = key.split('.'); + + const currentLangValue = getNestedValue(translations[editor.language], keyParts); + if (typeof currentLangValue === 'string') { + return currentLangValue; + } + + const defaultLangValue = getNestedValue(translations[editor.defaultLanguage], keyParts); + + if (typeof defaultLangValue === 'string') { + return defaultLangValue; + } + + return getLabelText(key); + }; + + editor.languages = languages; + + editor.setLanguage = (lang: string) => { + if (translations[lang]) { + editor.language = lang; + // editor.emit + } + }; + + editor.translations = translations; + editor.defaultLanguage = defaultLanguage; + editor.language = language; + + return editor; +} diff --git a/packages/core/i18n/src/hooks/useTranslation.ts b/packages/core/i18n/src/hooks/useTranslation.ts new file mode 100644 index 000000000..0cdfe09c7 --- /dev/null +++ b/packages/core/i18n/src/hooks/useTranslation.ts @@ -0,0 +1,6 @@ +import { useYooptaEditor } from '@yoopta/editor'; + +export function useTranslation() { + const editor = useYooptaEditor(); + return { t: editor.getLabelText }; +} diff --git a/packages/core/i18n/src/index.ts b/packages/core/i18n/src/index.ts new file mode 100644 index 000000000..6d458ef8c --- /dev/null +++ b/packages/core/i18n/src/index.ts @@ -0,0 +1,2 @@ +export { withTranslations } from './extenstion/withTranslations'; +export { useTranslation } from './hooks/useTranslation'; diff --git a/packages/core/i18n/src/types/index.ts b/packages/core/i18n/src/types/index.ts new file mode 100644 index 000000000..4a3072138 --- /dev/null +++ b/packages/core/i18n/src/types/index.ts @@ -0,0 +1,9 @@ +import { YooEditor } from '@yoopta/editor'; + +export type I18nYooEditor = YooEditor & { + translations: Record>; + language: string; + languages: string[]; + defaultLanguage: string; + setLanguage: (lang: string) => void; +}; 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..4054b7b87 100644 --- a/packages/development/src/components/FixedToolbar/FixedToolbar.tsx +++ b/packages/development/src/components/FixedToolbar/FixedToolbar.tsx @@ -1,14 +1,15 @@ -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'; +import { useTranslation } from '@yoopta/i18n'; type Props = { editor: YooEditor; @@ -16,6 +17,8 @@ type Props = { }; export const FixedToolbar = ({ editor, DEFAULT_DATA }: Props) => { + const { t } = useTranslation(); + return (
@@ -47,28 +50,38 @@ 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 */} + {t('editor.blockOptions.turnInto')} +
+ {editor.languages.map((lang) => { + const isCurrent = lang === editor.language; + + return ( + + ); + })} +
diff --git a/packages/plugins/paragraph/src/plugin/index.tsx b/packages/plugins/paragraph/src/plugin/index.tsx index f808667db..e50e08f03 100644 --- a/packages/plugins/paragraph/src/plugin/index.tsx +++ b/packages/plugins/paragraph/src/plugin/index.tsx @@ -3,7 +3,7 @@ import { Element, Transforms } from 'slate'; import { ParagraphCommands } from '../commands'; import { ParagraphElement, ParagraphElementMap } from '../types'; import { ParagraphRender } from '../ui/Paragraph'; -import {defaultLocales} from '../locales'; +import { defaultLocales } from '../locales'; const Paragraph = new YooptaPlugin({ type: 'Paragraph', @@ -57,7 +57,6 @@ const Paragraph = new YooptaPlugin({ }, }, commands: ParagraphCommands, - translations: defaultLocales, }); export { Paragraph }; diff --git a/packages/tools/action-menu/src/components/DefaultActionMenuRender.tsx b/packages/tools/action-menu/src/components/DefaultActionMenuRender.tsx index 67da35653..004937dac 100644 --- a/packages/tools/action-menu/src/components/DefaultActionMenuRender.tsx +++ b/packages/tools/action-menu/src/components/DefaultActionMenuRender.tsx @@ -1,7 +1,6 @@ import { cloneElement, isValidElement } from 'react'; import { ActionMenuRenderProps } from '../types'; import { DEFAULT_ICONS_MAP } from './icons'; -import {useTranslation} from '@yoopta/editor'; const DefaultActionMenuRender = ({ actions, @@ -11,7 +10,6 @@ const DefaultActionMenuRender = ({ getRootProps, view = 'default', }: ActionMenuRenderProps) => { - const { t } = useTranslation(); const isViewSmall = view === 'small'; const wrapStyles = { @@ -44,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 = t(`${block.type.toLowerCase()}.title`) || block.options?.display?.title || block.type; - const description = t(`${block.type.toLowerCase()}.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 index 8a9926b70..e404a28c0 100644 --- a/web/next-example/src/components/examples/withTranslations/index.tsx +++ b/web/next-example/src/components/examples/withTranslations/index.tsx @@ -1,4 +1,4 @@ -import YooptaEditor, {createYooptaEditor, useTranslation} from '@yoopta/editor'; +import YooptaEditor, { createYooptaEditor, useTranslation } from '@yoopta/editor'; import Paragraph from '@yoopta/paragraph'; import Blockquote from '@yoopta/blockquote'; @@ -18,93 +18,94 @@ 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, + 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, - }, + 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." - } - } -} + 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() + const editor = useMemo(() => createYooptaEditor(), []); + const selectionRef = useRef(null); + const { currentLanguage, setLanguage } = useTranslation(); - - return ( -
+
+ + - -
- console.log(value)} - translations={translations} - /> -
- ); + Switch to Bro + + + console.log(value)} + translations={translations} + /> + + ); } export default WithTranslationsExample; From 14421969c670ac7cff20540caa622a7303dc45ba Mon Sep 17 00:00:00 2001 From: Darginec05 Date: Tue, 28 Jan 2025 02:00:10 +0300 Subject: [PATCH 11/18] move translations to /locales folder --- packages/development/src/locales/cz.json | 133 ++++++++ packages/development/src/locales/es.json | 133 ++++++++ packages/development/src/locales/ru.json | 134 ++++++++ packages/development/src/pages/dev/index.tsx | 326 +------------------ 4 files changed, 415 insertions(+), 311 deletions(-) create mode 100644 packages/development/src/locales/cz.json create mode 100644 packages/development/src/locales/es.json create mode 100644 packages/development/src/locales/ru.json diff --git a/packages/development/src/locales/cz.json b/packages/development/src/locales/cz.json new file mode 100644 index 000000000..14eea5875 --- /dev/null +++ b/packages/development/src/locales/cz.json @@ -0,0 +1,133 @@ +{ + "editor": { + "blockOptions": { + "delete": "Smazat", + "duplicate": "Duplikovat", + "turnInto": "Převést na", + "copyBlockLink": "Kopírovat odkaz na blok" + }, + "placeholder": "Zadejte '/' pro příkazy" + }, + "plugins": { + "Paragraph": { + "display": { + "title": "Odstavec", + "description": "Textový odstavec" + } + }, + "Blockquote": { + "display": { + "title": "Citace", + "description": "Bloková citace" + } + }, + "Callout": { + "display": { + "title": "Výzva", + "description": "Blokové volání" + } + }, + "Accordion": { + "display": { + "title": "Akordeon", + "description": "Blokový akordeon" + } + }, + "BulletedList": { + "display": { + "title": "Seznam s odrážkami", + "description": "Seznam s odrážkami" + } + }, + "NumberedList": { + "display": { + "title": "Číslovaný seznam", + "description": "Číslovaný seznam" + } + }, + "TodoList": { + "display": { + "title": "Seznam úkolů", + "description": "Seznam úkolů" + } + }, + "Image": { + "display": { + "title": "Obrázek", + "description": "Obrázek" + } + }, + "Video": { + "display": { + "title": "Video", + "description": "Video" + } + }, + "File": { + "display": { + "title": "Soubor", + "description": "Soubor ke stažení" + } + }, + "Table": { + "display": { + "title": "Tabulka", + "description": "Tabulka" + } + }, + "Code": { + "display": { + "title": "Kód", + "description": "Blokový kód" + } + }, + "Divider": { + "display": { + "title": "Oddělovač", + "description": "Oddělovač" + } + }, + "HeadingOne": { + "display": { + "title": "Nadpis 1", + "description": "Nadpis 1. úrovně" + } + }, + "HeadingTwo": { + "display": { + "title": "Nadpis 2", + "description": "Nadpis 2. úrovně" + } + }, + "HeadingThree": { + "display": { + "title": "Nadpis 3", + "description": "Nadpis 3. úrovně" + } + } + }, + "tools": { + "toolbar": { + "highlightColor": { + "text": "Text", + "background": "Pozadí", + "customColor": "Vlastní barva" + }, + "linkTitle": "Odkaz" + }, + "link": { + "target": "Target", + "rel": "Rel", + "update": "Obnovit", + "add": "Pridat", + "delete": "Smazat", + "url": "URL", + "title": "Titul", + "additionalProps": "Další vlastnosti", + "titlePlaceholder": "Upravit titul odkazu", + "urlPlaceholder": "Upravit URL odkazu", + "targetPlaceholder": "Upravit cíl odkazu", + "relPlaceholder": "Upravit rel odkazu" + } + } +} diff --git a/packages/development/src/locales/es.json b/packages/development/src/locales/es.json new file mode 100644 index 000000000..dbdeca46a --- /dev/null +++ b/packages/development/src/locales/es.json @@ -0,0 +1,133 @@ +{ + "plugins": { + "Paragraph": { + "display": { + "title": "Párrafo", + "description": "Párrafo de texto" + } + }, + "Blockquote": { + "display": { + "title": "Cita", + "description": "Bloque de cita" + } + }, + "Callout": { + "display": { + "title": "Llamada", + "description": "Bloque de llamada" + } + }, + "Accordion": { + "display": { + "title": "Acordeón", + "description": "Bloque de acordeón" + } + }, + "BulletedList": { + "display": { + "title": "Lista con viñetas", + "description": "Lista con viñetas" + } + }, + "NumberedList": { + "display": { + "title": "Lista numerada", + "description": "Lista numerada" + } + }, + "TodoList": { + "display": { + "title": "Lista de tareas", + "description": "Lista de tareas" + } + }, + "Image": { + "display": { + "title": "Imagen", + "description": "Imagen" + } + }, + "Video": { + "display": { + "title": "Vídeo", + "description": "Vídeo" + } + }, + "File": { + "display": { + "title": "Archivo", + "description": "Archivo para descargar" + } + }, + "Table": { + "display": { + "title": "Tabla", + "description": "Tabla" + } + }, + "Code": { + "display": { + "title": "Código", + "description": "Bloque de código" + } + }, + "Divider": { + "display": { + "title": "Separador", + "description": "Separador" + } + }, + "HeadingOne": { + "display": { + "title": "Título 1", + "description": "Título de primer nivel" + } + }, + "HeadingTwo": { + "display": { + "title": "Título 2", + "description": "Título de segundo nivel" + } + }, + "HeadingThree": { + "display": { + "title": "Título 3", + "description": "Título de tercer nivel" + } + } + }, + "editor": { + "blockOptions": { + "delete": "Borrar", + "duplicate": "Duplicar", + "turnInto": "Convertir en", + "copyBlockLink": "Copiar enlace al bloque" + }, + "placeholder": "Escribe '/' para comandos" + }, + "tools": { + "toolbar": { + "highlightColor": { + "text": "Texto", + "background": "Fondo", + "customColor": "Color personalizado" + }, + "linkTitle": "Enlace" + }, + "link": { + "target": "Objetivo del enlace", + "rel": "Relación del enlace", + "update": "Actualizar", + "add": "Añadir", + "delete": "Eliminar enlace", + "url": "URL", + "title": "Título", + "additionalProps": "Propiedades adicionales", + "titlePlaceholder": "Editar título del enlace", + "urlPlaceholder": "Editar URL del enlace", + "targetPlaceholder": "Editar objetivo del enlace", + "relPlaceholder": "Editar relación del enlace" + } + } +} diff --git a/packages/development/src/locales/ru.json b/packages/development/src/locales/ru.json new file mode 100644 index 000000000..bcfeae9ab --- /dev/null +++ b/packages/development/src/locales/ru.json @@ -0,0 +1,134 @@ +{ + "editor": { + "blockOptions": { + "delete": "Удалить", + "duplicate": "Дублировать", + "turnInto": "Преобразовать в", + "copyBlockLink": "Скопировать ссылку на блок" + }, + "placeholder": "Введите '/' для команд" + }, + "plugins": { + "Paragraph": { + "display": { + "title": "Параграф", + "description": "Текстовый параграф" + } + }, + "Blockquote": { + "display": { + "title": "Цитата", + "description": "Цитата блока" + } + }, + "Callout": { + "display": { + "title": "Вызов", + "description": "Блок вызова" + } + }, + "Accordion": { + "display": { + "title": "Аккордеон", + "description": "Блок аккордеона" + } + }, + "BulletedList": { + "display": { + "title": "Маркированный список", + "description": "Маркированный список" + } + }, + "NumberedList": { + "display": { + "title": "Нумерованный список", + "description": "Нумерованный список" + } + }, + "TodoList": { + "display": { + "title": "Список задач", + "description": "Список задач" + } + }, + "Image": { + "display": { + "title": "Изображение", + "description": "Изображение" + } + }, + "Video": { + "display": { + "title": "Видео", + "description": "Видео" + } + }, + "File": { + "display": { + "title": "Файл", + "description": "Файл для скачивания" + } + }, + "Table": { + "display": { + "title": "Таблица", + "description": "Таблица" + } + }, + "Code": { + "display": { + "title": "Код", + "description": "Блок кода" + } + }, + "Divider": { + "display": { + "title": "Разделитель", + "description": "Разделитель" + } + }, + "HeadingOne": { + "display": { + "title": "Заголовок 1", + "description": "Заголовок 1 уровня" + } + }, + "HeadingTwo": { + "display": { + "title": "Заголовок 2", + "description": "Заголовок 2 уровня" + } + }, + "HeadingThree": { + "display": { + "title": "Заголовок 3", + "description": "Заголовок 3 уровня" + } + } + }, + "tools": { + "toolbar": { + "highlightColor": { + "text": "Текст", + "background": "Фон", + "customColor": "Пользовательский цвет" + }, + "linkTitle": "Ссылка" + }, + "link": { + "target": "Цель ссылки", + "rel": "Рел", + "update": "Обновить", + "add": "Добавить", + "delete": "Удалить ссылку", + "edit": "Редактировать", + "url": "URL", + "title": "Заголовок", + "additionalProps": "Дополнительные свойства", + "titlePlaceholder": "Изменить заголовок ссылки", + "urlPlaceholder": "Изменить URL ссылки", + "targetPlaceholder": "Изменить цель ссылки", + "relPlaceholder": "Изменить rel ссылки" + } + } +} diff --git a/packages/development/src/pages/dev/index.tsx b/packages/development/src/pages/dev/index.tsx index 96464be59..19b7e3959 100644 --- a/packages/development/src/pages/dev/index.tsx +++ b/packages/development/src/pages/dev/index.tsx @@ -14,325 +14,29 @@ import { TOOLS } from '../../utils/yoopta/tools'; import { FixedToolbar } from '../../components/FixedToolbar/FixedToolbar'; import { YOOPTA_DEFAULT_VALUE } from '@/utils/yoopta/value'; +import esTranslations from '@/locales/es.json'; +import ruTranslations from '@/locales/ru.json'; +import czTranslations from '@/locales/cz.json'; + const EDITOR_STYLE = { width: 750, }; const TRANSLATIONS = { - es: { - plugins: { - Paragraph: { - display: { - title: 'Párrafo', - description: 'Párrafo de texto', - }, - }, - Blockquote: { - display: { - title: 'Cita', - description: 'Bloque de cita', - }, - }, - Callout: { - display: { - title: 'Llamada', - description: 'Bloque de llamada', - }, - }, - Accordion: { - display: { - title: 'Acordeón', - description: 'Bloque de acordeón', - }, - }, - BulletedList: { - display: { - title: 'Lista con viñetas', - description: 'Lista con viñetas', - }, - }, - NumberedList: { - display: { - title: 'Lista numerada', - description: 'Lista numerada', - }, - }, - TodoList: { - display: { - title: 'Lista de tareas', - description: 'Lista de tareas', - }, - }, - Image: { - display: { - title: 'Imagen', - description: 'Imagen', - }, - }, - Video: { - display: { - title: 'Vídeo', - description: 'Vídeo', - }, - }, - File: { - display: { - title: 'Archivo', - description: 'Archivo para descargar', - }, - }, - Table: { - display: { - title: 'Tabla', - description: 'Tabla', - }, - }, - Code: { - display: { - title: 'Código', - description: 'Bloque de código', - }, - }, - Divider: { - display: { - title: 'Separador', - description: 'Separador', - }, - }, - HeadingOne: { - display: { - title: 'Título 1', - description: 'Título de primer nivel', - }, - }, - HeadingTwo: { - display: { - title: 'Título 2', - description: 'Título de segundo nivel', - }, - }, - HeadingThree: { - display: { - title: 'Título 3', - description: 'Título de tercer nivel', - }, - }, - }, - editor: { - blockOptions: { - delete: 'Borrar', - duplicate: 'Duplicar', - turnInto: 'Convertir en', - copyBlockLink: 'Copiar enlace al bloque', - }, - placeholder: "Escribe '/' para comandos", - }, - tools: { - toolbar: { - highlightColor: { - text: 'Texto', - background: 'Fondo', - customColor: 'Color personalizado', - }, - linkTitle: 'Enlace', - }, - link: { - target: 'Objetivo del enlace', - rel: 'Relación del enlace', - update: 'Actualizar', - add: 'Añadir', - delete: 'Eliminar enlace', - url: 'URL', - title: 'Título', - additionalProps: 'Propiedades adicionales', - titlePlaceholder: 'Editar título del enlace', - urlPlaceholder: 'Editar URL del enlace', - targetPlaceholder: 'Editar objetivo del enlace', - relPlaceholder: 'Editar relación del enlace', - }, - }, - }, - ru: { - editor: { - blockOptions: { - delete: 'Удалить', - duplicate: 'Дублировать', - turnInto: 'Преобразовать в', - copyBlockLink: 'Скопировать ссылку на блок', - }, - placeholder: "Введите '/' для команд", - }, - plugins: { - Paragraph: { - display: { - title: 'Параграф', - description: 'Текстовый параграф', - }, - }, - Blockquote: { - display: { - title: 'Цитата', - description: 'Цитата блока', - }, - }, - Callout: { - display: { - title: 'Вызов', - description: 'Блок вызова', - }, - }, - Accordion: { - display: { - title: 'Аккордеон', - description: 'Блок аккордеона', - }, - }, - BulletedList: { - display: { - title: 'Маркированный список', - description: 'Маркированный список', - }, - }, - NumberedList: { - display: { - title: 'Нумерованный список', - description: 'Нумерованный список', - }, - }, - TodoList: { - display: { - title: 'Список задач', - description: 'Список задач', - }, - }, - Image: { - display: { - title: 'Изображение', - description: 'Изображение', - }, - }, - Video: { - display: { - title: 'Видео', - description: 'Видео', - }, - }, - File: { - display: { - title: 'Файл', - description: 'Файл для скачивания', - }, - }, - Table: { - display: { - title: 'Таблица', - description: 'Таблица', - }, - }, - Code: { - display: { - title: 'Код', - description: 'Блок кода', - }, - }, - Divider: { - display: { - title: 'Разделитель', - description: 'Разделитель', - }, - }, - HeadingOne: { - display: { - title: 'Заголовок 1', - description: 'Заголовок 1 уровня', - }, - }, - HeadingTwo: { - display: { - title: 'Заголовок 2', - description: 'Заголовок 2 уровня', - }, - }, - HeadingThree: { - display: { - title: 'Заголовок 3', - description: 'Заголовок 3 уровня', - }, - }, - }, - tools: { - toolbar: { - highlightColor: { - text: 'Текст', - background: 'Фон', - customColor: 'Пользовательский цвет', - }, - linkTitle: 'Ссылка', - }, - link: { - target: 'Цель ссылки', - rel: 'Рел', - update: 'Обновить', - add: 'Добавить', - delete: 'Удалить ссылку', - edit: 'Редактировать', - url: 'URL', - title: 'Заголовок', - additionalProps: 'Дополнительные свойства', - titlePlaceholder: 'Изменить заголовок ссылки', - urlPlaceholder: 'Изменить URL ссылки', - targetPlaceholder: 'Изменить цель ссылки', - relPlaceholder: 'Изменить rel ссылки', - }, - }, - }, - cz: { - editor: { - blockOptions: { - delete: 'Smazat', - duplicate: 'Duplikovat', - turnInto: 'Převést na', - copyBlockLink: 'Kopírovat odkaz na blok', - }, - placeholder: "Zadejte '/' pro příkazy", - }, - tools: { - toolbar: { - highlightColor: { - text: 'Text', - background: 'Pozadí', - customColor: 'Vlastní barva', - }, - linkTitle: 'Odkaz', - }, - link: { - target: 'Target', - rel: 'Rel', - update: 'Obnovit', - add: 'Pridat', - delete: 'Smazat', - url: 'URL', - title: 'Titul', - additionalProps: 'Další vlastnosti', - titlePlaceholder: 'Upravit titul odkazu', - urlPlaceholder: 'Upravit URL odkazu', - targetPlaceholder: 'Upravit cíl odkazu', - relPlaceholder: 'Upravit rel odkazu', - }, - }, - }, + es: esTranslations, + ru: ruTranslations, + cz: czTranslations, }; const BasicExample = () => { - const editor: YooEditor = useMemo( - () => - withTranslations(createYooptaEditor(), { - translations: TRANSLATIONS, - defaultLanguage: 'en', - language: 'ru', - }), - [], - ); + const editor: YooEditor = useMemo(() => { + const baseEditor = createYooptaEditor(); + return withTranslations(baseEditor, { + translations: TRANSLATIONS, + defaultLanguage: 'en', + language: 'ru', + }); + }, []); const selectionRef = useRef(null); const [value, setValue] = useState(YOOPTA_DEFAULT_VALUE); From 33ddcc3ac50dccdebba843e7c89e8093fa917686 Mon Sep 17 00:00:00 2001 From: Darginec05 Date: Wed, 29 Jan 2025 17:53:58 +0300 Subject: [PATCH 12/18] remove unused code --- packages/core/editor/README.md | 11 -- packages/core/editor/src/YooptaEditor.tsx | 24 +--- .../src/components/TextLeaf/TextLeaf.tsx | 4 +- .../editor/src/i18n/TranslationManager.ts | 107 ------------------ packages/core/editor/src/i18n/hooks/types.ts | 54 --------- .../src/i18n/hooks/useAddTranslations.ts | 15 --- .../editor/src/i18n/hooks/useTranslation.ts | 35 ------ packages/core/editor/src/i18n/locales/en.ts | 9 -- packages/core/editor/src/i18n/locales/es.ts | 9 -- .../core/editor/src/i18n/locales/index.ts | 8 -- packages/core/editor/src/i18n/types.ts | 55 --------- packages/core/editor/src/plugins/types.ts | 1 - packages/development/src/pages/dev/index.tsx | 5 +- packages/plugins/accordion/src/locales/en.ts | 4 - packages/plugins/accordion/src/locales/es.ts | 4 - .../plugins/accordion/src/locales/index.ts | 7 -- .../plugins/accordion/src/plugin/index.tsx | 2 - packages/plugins/blockquote/src/locales/en.ts | 4 - packages/plugins/blockquote/src/locales/es.ts | 4 - .../plugins/blockquote/src/locales/index.ts | 7 -- .../plugins/blockquote/src/plugin/index.tsx | 2 - packages/plugins/callout/src/locales/en.ts | 4 - packages/plugins/callout/src/locales/es.ts | 4 - packages/plugins/callout/src/locales/index.ts | 7 -- packages/plugins/callout/src/plugin/index.tsx | 2 - packages/plugins/code/src/locales/en.ts | 4 - packages/plugins/code/src/locales/es.ts | 4 - packages/plugins/code/src/locales/index.ts | 7 -- packages/plugins/code/src/plugin/index.tsx | 2 - packages/plugins/divider/src/locales/en.ts | 4 - packages/plugins/divider/src/locales/es.ts | 4 - packages/plugins/divider/src/locales/index.ts | 7 -- packages/plugins/divider/src/plugin/index.tsx | 2 - packages/plugins/embed/src/locales/en.ts | 4 - packages/plugins/embed/src/locales/es.ts | 4 - packages/plugins/embed/src/locales/index.ts | 7 -- packages/plugins/embed/src/plugin/index.tsx | 2 - packages/plugins/file/src/locales/en.ts | 4 - packages/plugins/file/src/locales/es.ts | 4 - packages/plugins/file/src/locales/index.ts | 7 -- packages/plugins/file/src/plugin/index.tsx | 2 - packages/plugins/headings/src/locales/en.ts | 14 --- packages/plugins/headings/src/locales/es.ts | 14 --- .../plugins/headings/src/locales/index.ts | 17 --- .../headings/src/plugin/HeadingOne.tsx | 2 - .../headings/src/plugin/HeadingThree.tsx | 2 - .../headings/src/plugin/HeadingTwo.tsx | 2 - packages/plugins/image/src/locales/en.ts | 4 - packages/plugins/image/src/locales/es.ts | 5 - packages/plugins/image/src/locales/index.ts | 7 -- packages/plugins/image/src/plugin/index.tsx | 2 - packages/plugins/link/src/locales/en.ts | 4 - packages/plugins/link/src/locales/es.ts | 4 - packages/plugins/link/src/locales/index.ts | 7 -- packages/plugins/link/src/plugin/index.tsx | 1 - packages/plugins/lists/src/locales/en.ts | 16 --- packages/plugins/lists/src/locales/es.ts | 14 --- packages/plugins/lists/src/locales/index.ts | 18 --- .../plugins/lists/src/plugin/BulletedList.tsx | 2 - .../plugins/lists/src/plugin/NumberedList.tsx | 2 - .../plugins/lists/src/plugin/TodoList.tsx | 2 - packages/plugins/paragraph/src/locales/en.ts | 4 - packages/plugins/paragraph/src/locales/es.ts | 4 - .../plugins/paragraph/src/locales/index.ts | 7 -- .../plugins/paragraph/src/plugin/index.tsx | 4 +- packages/plugins/table/src/locales/en.ts | 4 - packages/plugins/table/src/locales/es.ts | 4 - packages/plugins/table/src/locales/index.ts | 7 -- packages/plugins/table/src/plugin/Table.tsx | 2 - packages/plugins/video/src/locales/en.ts | 4 - packages/plugins/video/src/locales/es.ts | 4 - packages/plugins/video/src/locales/index.ts | 7 -- packages/plugins/video/src/plugin/index.tsx | 2 - 73 files changed, 9 insertions(+), 639 deletions(-) delete mode 100644 packages/core/editor/src/i18n/TranslationManager.ts delete mode 100644 packages/core/editor/src/i18n/hooks/types.ts delete mode 100644 packages/core/editor/src/i18n/hooks/useAddTranslations.ts delete mode 100644 packages/core/editor/src/i18n/hooks/useTranslation.ts delete mode 100644 packages/core/editor/src/i18n/locales/en.ts delete mode 100644 packages/core/editor/src/i18n/locales/es.ts delete mode 100644 packages/core/editor/src/i18n/locales/index.ts delete mode 100644 packages/core/editor/src/i18n/types.ts delete mode 100644 packages/plugins/accordion/src/locales/en.ts delete mode 100644 packages/plugins/accordion/src/locales/es.ts delete mode 100644 packages/plugins/accordion/src/locales/index.ts delete mode 100644 packages/plugins/blockquote/src/locales/en.ts delete mode 100644 packages/plugins/blockquote/src/locales/es.ts delete mode 100644 packages/plugins/blockquote/src/locales/index.ts delete mode 100644 packages/plugins/callout/src/locales/en.ts delete mode 100644 packages/plugins/callout/src/locales/es.ts delete mode 100644 packages/plugins/callout/src/locales/index.ts delete mode 100644 packages/plugins/code/src/locales/en.ts delete mode 100644 packages/plugins/code/src/locales/es.ts delete mode 100644 packages/plugins/code/src/locales/index.ts delete mode 100644 packages/plugins/divider/src/locales/en.ts delete mode 100644 packages/plugins/divider/src/locales/es.ts delete mode 100644 packages/plugins/divider/src/locales/index.ts delete mode 100644 packages/plugins/embed/src/locales/en.ts delete mode 100644 packages/plugins/embed/src/locales/es.ts delete mode 100644 packages/plugins/embed/src/locales/index.ts delete mode 100644 packages/plugins/file/src/locales/en.ts delete mode 100644 packages/plugins/file/src/locales/es.ts delete mode 100644 packages/plugins/file/src/locales/index.ts delete mode 100644 packages/plugins/headings/src/locales/en.ts delete mode 100644 packages/plugins/headings/src/locales/es.ts delete mode 100644 packages/plugins/headings/src/locales/index.ts delete mode 100644 packages/plugins/image/src/locales/en.ts delete mode 100644 packages/plugins/image/src/locales/es.ts delete mode 100644 packages/plugins/image/src/locales/index.ts delete mode 100644 packages/plugins/link/src/locales/en.ts delete mode 100644 packages/plugins/link/src/locales/es.ts delete mode 100644 packages/plugins/link/src/locales/index.ts delete mode 100644 packages/plugins/lists/src/locales/en.ts delete mode 100644 packages/plugins/lists/src/locales/es.ts delete mode 100644 packages/plugins/lists/src/locales/index.ts delete mode 100644 packages/plugins/paragraph/src/locales/en.ts delete mode 100644 packages/plugins/paragraph/src/locales/es.ts delete mode 100644 packages/plugins/paragraph/src/locales/index.ts delete mode 100644 packages/plugins/table/src/locales/en.ts delete mode 100644 packages/plugins/table/src/locales/es.ts delete mode 100644 packages/plugins/table/src/locales/index.ts delete mode 100644 packages/plugins/video/src/locales/en.ts delete mode 100644 packages/plugins/video/src/locales/es.ts delete mode 100644 packages/plugins/video/src/locales/index.ts diff --git a/packages/core/editor/README.md b/packages/core/editor/README.md index d67477127..4f023072d 100644 --- a/packages/core/editor/README.md +++ b/packages/core/editor/README.md @@ -190,15 +190,4 @@ useBlockData(blockId); * @returns {PluginOptions} The options of the plugin. */ useYooptaPluginOptions(blockType); - -/** - * Hook to interact with the Yoopta Editor translation system. - * - * @returns {UseTranslationReturn} An object with: - * - `t(key: string)`: Fetch a translation by key (e.g., `t('core.save')`). - * - `currentLanguage`: The active language (e.g., `'en'`). - * - `setLanguage(lang: string)`: Change the active language (e.g., `setLanguage('es')`). - * - `getAvailableKeys()`: Get all translation keys for the current language. Provided as a utility function to know all available keys at runtime. - */ -useTranslation() ``` diff --git a/packages/core/editor/src/YooptaEditor.tsx b/packages/core/editor/src/YooptaEditor.tsx index e35af5f97..b3484ed0f 100644 --- a/packages/core/editor/src/YooptaEditor.tsx +++ b/packages/core/editor/src/YooptaEditor.tsx @@ -18,8 +18,6 @@ import { FakeSelectionMark } from './marks/FakeSelectionMark'; import { generateId } from './utils/generateId'; import { YooptaOperation } from './editor/core/applyTransforms'; import { validateYooptaValue } from './utils/validateYooptaValue'; -import {Translations} from './i18n/types'; -import {useAddTranslations} from './i18n/hooks/useAddTranslations'; export type YooptaOnChangeOptions = { operations: YooptaOperation[]; @@ -42,8 +40,6 @@ export type YooptaEditorProps = { readOnly?: boolean; width?: number | string; style?: CSSProperties; - - translations?: Translations; }; type EditorState = { @@ -68,24 +64,16 @@ const YooptaEditor = ({ style, onChange, onPathChange, - translations: userTranslations = {}, }: YooptaEditorProps) => { - const {addTranslations} = useAddTranslations(); const marks = useMemo(() => { if (marksProps) return [FakeSelectionMark, ...marksProps]; return [FakeSelectionMark]; }, [marksProps]); const plugins = useMemo(() => { - return pluginsProps.map((plugin) => { - const pluginInstance = plugin.getPlugin as Plugin>; - - // Merge plugin translations into the global translation registry - Object.entries(pluginInstance.translations || {}).forEach(([language, pluginTranslations]) => { - addTranslations(language, pluginInstance.type.toLowerCase(), pluginTranslations); - }); - - return pluginInstance; + return pluginsProps.map((pluginInstance) => { + const plugin = pluginInstance.getPlugin as Plugin>; + return plugin; }); }, [pluginsProps]); @@ -103,12 +91,6 @@ const YooptaEditor = ({ ); } - Object.entries(userTranslations).forEach(([language, translations]) => { - Object.entries(translations).forEach(([namespace, translation]) => { - addTranslations(language, namespace, translation); - }); - }); - editor.children = (isValueValid ? value : {}) as YooptaContentValue; editor.blockEditorsMap = buildBlockSlateEditors(editor); editor.shortcuts = buildBlockShortcuts(editor); diff --git a/packages/core/editor/src/components/TextLeaf/TextLeaf.tsx b/packages/core/editor/src/components/TextLeaf/TextLeaf.tsx index c495862cd..5b8023e93 100644 --- a/packages/core/editor/src/components/TextLeaf/TextLeaf.tsx +++ b/packages/core/editor/src/components/TextLeaf/TextLeaf.tsx @@ -1,7 +1,6 @@ import { HTMLAttributes } from 'react'; import { RenderLeafProps, useSelected } from 'slate-react'; import { ExtendedLeafProps } from '../../plugins/types'; -import {useTranslation} from '../../i18n/hooks/useTranslation'; type Props = Pick, 'attributes' | 'children'> & { placeholder?: string; @@ -9,14 +8,13 @@ type Props = Pick, 'attributes' | 'children'> & { const TextLeaf = ({ children, attributes, placeholder }: Props) => { const selected = useSelected(); - const {t} = useTranslation(); const attrs: HTMLAttributes & RenderLeafProps['attributes'] = { ...attributes, }; if (selected && placeholder) { - attrs['data-placeholder'] = t(placeholder); + attrs['data-placeholder'] = placeholder; attrs.className = `yoopta-placeholder`; } diff --git a/packages/core/editor/src/i18n/TranslationManager.ts b/packages/core/editor/src/i18n/TranslationManager.ts deleted file mode 100644 index 68f63a184..000000000 --- a/packages/core/editor/src/i18n/TranslationManager.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { defaultLocales } from './locales'; -import { NestedTranslations, Translations } from './types'; - -type Listener = () => void; - -class TranslationManager { - private static instance: TranslationManager; - private translations: Translations = defaultLocales; - private currentLanguage: string = 'en'; - private userOverrides: Translations = {}; - private listeners: Listener[] = []; - - static getInstance(): TranslationManager { - if (!TranslationManager.instance) { - this.instance = new TranslationManager(); - } - return this.instance; - } - - /** - * Add translations for a specific namespace and language. - */ - addTranslations(language: string, namespace: string, newTranslations: NestedTranslations): void { - if (!this.translations[language]) { - this.translations[language] = {}; - } - - if (!this.translations[language][namespace]) { - this.translations[language][namespace] = {}; - } - - this.translations[language][namespace] = { - ...this.translations[language][namespace], - ...newTranslations, - }; - } - - /** - * Fetch a translation for a specific key. - */ - translate(key: string): string { - const [namespace, ...rest] = key.split('.'); - - // Helper function to safely access nested translations - const getNestedValue = (obj: any, keyPath: string[]): any => { - return keyPath.reduce((acc, key) => (acc && typeof acc === 'object' ? acc[key] : undefined), obj); - }; - - const resolvedTranslation = - getNestedValue(this.userOverrides?.[this.currentLanguage]?.[namespace], rest) ?? - getNestedValue(this.translations?.[this.currentLanguage]?.[namespace], rest); - - return typeof resolvedTranslation === 'string' ? resolvedTranslation : key; - } - - /** - * Set the current language and notify listeners. - */ - setLanguage(language: string): void { - if (this.translations[language]) { - this.currentLanguage = language; - this.notifyListeners(); - } else { - console.warn(`Language ${language} not found. Falling back to ${this.currentLanguage}.`); - } - } - - /** - * Get the current language. - */ - getCurrentLanguage(): string { - return this.currentLanguage; - } - - /** - * Fetch all available keys for the current language. - */ - getAvailableKeys(): Record { - const availableKeys: Record = {}; - const langTranslations = this.translations[this.currentLanguage] || {}; - - Object.entries(langTranslations).forEach(([namespace, keys]) => { - availableKeys[namespace] = Object.keys(keys); - }); - - return availableKeys; - } - - /** - * Subscribe to language changes. - */ - subscribe(listener: Listener): () => void { - this.listeners.push(listener); - return () => { - this.listeners = this.listeners.filter((l) => l !== listener); - }; - } - - /** - * Notify all listeners about language changes. - */ - private notifyListeners(): void { - this.listeners.forEach((listener) => listener()); - } -} - -export const YooptaTranslationManager = TranslationManager.getInstance(); diff --git a/packages/core/editor/src/i18n/hooks/types.ts b/packages/core/editor/src/i18n/hooks/types.ts deleted file mode 100644 index 74f92f4bc..000000000 --- a/packages/core/editor/src/i18n/hooks/types.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { NestedTranslations } from '../types'; - -export interface UseTranslationReturn { - /** - * Translates a key into the current language. - * The key format is `namespace.key`, e.g., `core.save`. - * If the translation for the key is not found, it returns the key provided. - * - * @param key - The key to translate, formatted as `namespace.key`. - * @returns The translated string or a fallback message. - */ - t: (key: string) => string; - - /** - * The current language set in the translation manager. - * This value updates reactively when the language is changed. - */ - currentLanguage: string; - - /** - * Changes the current language to the specified value. - * Notifies all subscribers of the language change. - * - * @param lang - The language code to set, e.g., 'en' or 'es' or 'fr'... - */ - setLanguage: (lang: string) => void; - - /** - * Retrieves all available keys for the current language. - * This is provided as a utility function to know all available keys at runtime. - * The keys are grouped by namespace and provide introspection into all registered translations. - * - * @returns A record where the keys are namespaces and the values are arrays of available keys. - */ - getAvailableKeys: () => Record; -} - -export interface UseAddTranslationsReturn { - /** - * Enables adding new translations at runtime for specific languages and namespaces. - * - * Example Usage: - * ``` - * const { addTranslations } = useAddTranslations(); - * addTranslations('es', 'core', { save: 'Guardar', cancel: 'Cancelar' }); - * addTranslations('en', 'paragraph', { placeholder: 'Type a paragraph...' }); - * ``` - * - * @param language - The language code to add translations for (e.g., 'en' or 'es'). - * @param namespace - The namespace grouping the translations (e.g., 'core', 'plugin'). - * @param translations - A record of key-value pairs representing the translations. - */ - addTranslations: (language: string, namespace: string, translations: NestedTranslations) => void; -} diff --git a/packages/core/editor/src/i18n/hooks/useAddTranslations.ts b/packages/core/editor/src/i18n/hooks/useAddTranslations.ts deleted file mode 100644 index 2baf76e27..000000000 --- a/packages/core/editor/src/i18n/hooks/useAddTranslations.ts +++ /dev/null @@ -1,15 +0,0 @@ -import {UseAddTranslationsReturn} from './types'; -import {useCallback} from 'react'; -import {YooptaTranslationManager} from '../TranslationManager'; -import {NestedTranslations} from '../types'; - -export const useAddTranslations = (): UseAddTranslationsReturn => { - const addTranslations = useCallback( - (language: string, namespace: string, translations: NestedTranslations) => { - YooptaTranslationManager.addTranslations(language, namespace, translations); - }, - [] - ); - - return { addTranslations } -} diff --git a/packages/core/editor/src/i18n/hooks/useTranslation.ts b/packages/core/editor/src/i18n/hooks/useTranslation.ts deleted file mode 100644 index 88a872e91..000000000 --- a/packages/core/editor/src/i18n/hooks/useTranslation.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { useEffect, useState } from 'react'; -import { YooptaTranslationManager } from '../TranslationManager'; -import { UseTranslationReturn } from './types'; - -export const useTranslation = (): UseTranslationReturn => { - const [language, setLanguage] = useState(() => YooptaTranslationManager.getCurrentLanguage()); - const [t, setT] = useState(() => (key: string) => YooptaTranslationManager.translate(key)); - - useEffect(() => { - const handleUpdate = () => { - setLanguage(YooptaTranslationManager.getCurrentLanguage()); - setT(() => (key: string) => YooptaTranslationManager.translate(key)); - }; - - const unsubscribe = YooptaTranslationManager.subscribe(handleUpdate); - - // Initialize the state in case it changes before mounting - handleUpdate(); - - return () => unsubscribe(); - }, []); - - const changeLanguage = (lang: string) => { - YooptaTranslationManager.setLanguage(lang); - }; - - const getAvailableKeys = () => YooptaTranslationManager.getAvailableKeys(); - - return { - t, - currentLanguage: language, - setLanguage: changeLanguage, - getAvailableKeys, - }; -}; diff --git a/packages/core/editor/src/i18n/locales/en.ts b/packages/core/editor/src/i18n/locales/en.ts deleted file mode 100644 index 3d6b0614c..000000000 --- a/packages/core/editor/src/i18n/locales/en.ts +++ /dev/null @@ -1,9 +0,0 @@ -import {Translations} from '../types'; - -export const enLocale: Translations = { - en: { - core: { - editor_placeholder: "Type '/' for commands" - } - } -} diff --git a/packages/core/editor/src/i18n/locales/es.ts b/packages/core/editor/src/i18n/locales/es.ts deleted file mode 100644 index 46baf815b..000000000 --- a/packages/core/editor/src/i18n/locales/es.ts +++ /dev/null @@ -1,9 +0,0 @@ -import {Translations} from '../types'; - -export const esLocale: Translations = { - es: { - core: { - editor_placeholder: "Escribe / para abrir el menú" - } - } -} diff --git a/packages/core/editor/src/i18n/locales/index.ts b/packages/core/editor/src/i18n/locales/index.ts deleted file mode 100644 index 92f77ccd0..000000000 --- a/packages/core/editor/src/i18n/locales/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -import {enLocale} from './en'; -import {esLocale} from './es'; -import {Translations} from '../types'; - -export const defaultLocales: Translations = { - ...enLocale, - ...esLocale, -} diff --git a/packages/core/editor/src/i18n/types.ts b/packages/core/editor/src/i18n/types.ts deleted file mode 100644 index cee41182c..000000000 --- a/packages/core/editor/src/i18n/types.ts +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Interface representing the structure of the translations used in YooptaTranslationManager. - * - * This structure organizes translations by language and namespace, making it easy to - * group related keys and support multiple languages. - * - * Example Usage: - * ``` - * const translations: Translations = { - * en: { - * core: { - * save: 'Save', - * cancel: 'Cancel', - * }, - * paragraph: { - * placeholder: 'Type a paragraph...', - * }, - * }, - * es: { - * core: { - * save: 'Guardar', - * cancel: 'Cancelar', - * }, - * paragraph: { - * placeholder: 'Escribe un párrafo...', - * }, - * }, - * }; - * ``` - * - * Structure: - * - `language` (string): Represents the language code, e.g., 'en', 'es'. - * - `namespace` (string): Groups related translations, such as 'core' or 'paragraph'. - * - `Record`: Contains key-value pairs where: - * - The key (string) is the identifier for a specific translation. - * - The value (string) is the translated text for that key. - */ -export interface Translations { - [language: string]: { - [namespace: string]: NestedTranslations; - }; -} - -export interface PluginTranslations { - [language: string]: MandatoryPluginTranslations & NestedTranslations; -} - -type MandatoryPluginTranslations = { - title: string; - description: string; -}; - -export interface NestedTranslations { - [key: string]: string | NestedTranslations; -} diff --git a/packages/core/editor/src/plugins/types.ts b/packages/core/editor/src/plugins/types.ts index f2dcaf939..54bf8523c 100644 --- a/packages/core/editor/src/plugins/types.ts +++ b/packages/core/editor/src/plugins/types.ts @@ -3,7 +3,6 @@ import { RenderElementProps as RenderSlateElementProps, RenderLeafProps } from ' import { SlateEditor, SlateElement, YooEditor, YooptaBlockBaseMeta, YooptaBlockData } from '../editor/types'; import { EditorEventHandlers } from '../types/eventHandlers'; import { HOTKEYS_TYPE } from '../utils/hotkeys'; -import { PluginTranslations } from '../i18n/types'; export enum NodeType { Block = 'block', diff --git a/packages/development/src/pages/dev/index.tsx b/packages/development/src/pages/dev/index.tsx index 19b7e3959..a8937ed93 100644 --- a/packages/development/src/pages/dev/index.tsx +++ b/packages/development/src/pages/dev/index.tsx @@ -4,6 +4,7 @@ import YooptaEditor, { YooptaContentValue, YooptaPath, createYooptaEditor, + buildBlockData, } from '@yoopta/editor'; import { useMemo, useRef, useState } from 'react'; import { withTranslations } from '@yoopta/i18n'; @@ -39,7 +40,9 @@ const BasicExample = () => { }, []); const selectionRef = useRef(null); - const [value, setValue] = useState(YOOPTA_DEFAULT_VALUE); + const [value, setValue] = useState({ + 'block-1': buildBlockData({ id: 'block-1' }), + }); const onChange = (value: YooptaContentValue, options: YooptaOnChangeOptions) => { console.log('onChange', value, options); diff --git a/packages/plugins/accordion/src/locales/en.ts b/packages/plugins/accordion/src/locales/en.ts deleted file mode 100644 index a8318b1f5..000000000 --- a/packages/plugins/accordion/src/locales/en.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const enLocale = { - title: 'Accordion', - description: 'Expands and collapses sections of content.', -} diff --git a/packages/plugins/accordion/src/locales/es.ts b/packages/plugins/accordion/src/locales/es.ts deleted file mode 100644 index 0adce1380..000000000 --- a/packages/plugins/accordion/src/locales/es.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const esLocale = { - title: 'Acordeón', - description: 'Expande y colapsa secciones de contenido.', -} diff --git a/packages/plugins/accordion/src/locales/index.ts b/packages/plugins/accordion/src/locales/index.ts deleted file mode 100644 index 1651fca2b..000000000 --- a/packages/plugins/accordion/src/locales/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import {enLocale} from './en'; -import {esLocale} from './es'; - -export const defaultLocales = { - en: enLocale, - es: esLocale -} diff --git a/packages/plugins/accordion/src/plugin/index.tsx b/packages/plugins/accordion/src/plugin/index.tsx index d9e87f830..11aa48ae8 100644 --- a/packages/plugins/accordion/src/plugin/index.tsx +++ b/packages/plugins/accordion/src/plugin/index.tsx @@ -7,7 +7,6 @@ import { AccordionItemContent } from '../renders/AccordionItemContent'; import { Transforms } from 'slate'; import { ListCollapse } from 'lucide-react'; import { AccordionCommands } from '../commands'; -import {defaultLocales} from '../locales'; const ACCORDION_ELEMENTS = { AccordionList: 'accordion-list', @@ -222,7 +221,6 @@ const Accordion = new YooptaPlugin({ }, }, }, - translations: defaultLocales, }); export { Accordion }; diff --git a/packages/plugins/blockquote/src/locales/en.ts b/packages/plugins/blockquote/src/locales/en.ts deleted file mode 100644 index c203891e8..000000000 --- a/packages/plugins/blockquote/src/locales/en.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const enLocale = { - title: 'Blockquote', - description: 'Highlights quoted text.', -} diff --git a/packages/plugins/blockquote/src/locales/es.ts b/packages/plugins/blockquote/src/locales/es.ts deleted file mode 100644 index 9783837f3..000000000 --- a/packages/plugins/blockquote/src/locales/es.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const esLocale = { - title: 'Cita', - description: 'Resalta texto citado.', -} diff --git a/packages/plugins/blockquote/src/locales/index.ts b/packages/plugins/blockquote/src/locales/index.ts deleted file mode 100644 index 1651fca2b..000000000 --- a/packages/plugins/blockquote/src/locales/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import {enLocale} from './en'; -import {esLocale} from './es'; - -export const defaultLocales = { - en: enLocale, - es: esLocale -} diff --git a/packages/plugins/blockquote/src/plugin/index.tsx b/packages/plugins/blockquote/src/plugin/index.tsx index cb752effc..3e025283b 100644 --- a/packages/plugins/blockquote/src/plugin/index.tsx +++ b/packages/plugins/blockquote/src/plugin/index.tsx @@ -2,7 +2,6 @@ import { serializeTextNodes, serializeTextNodesIntoMarkdown, YooptaPlugin } from import { BlockquoteCommands } from '../commands'; import { BlockquoteElement } from '../types'; import { BlockquoteRender } from '../ui/Blockquote'; -import {defaultLocales} from '../locales'; const Blockquote = new YooptaPlugin>({ type: 'Blockquote', @@ -60,7 +59,6 @@ const Blockquote = new YooptaPlugin>({ }, }, }, - translations: defaultLocales, }); export { Blockquote }; diff --git a/packages/plugins/callout/src/locales/en.ts b/packages/plugins/callout/src/locales/en.ts deleted file mode 100644 index 39156e042..000000000 --- a/packages/plugins/callout/src/locales/en.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const enLocale = { - title: 'Callout', - description: 'Emphasizes important content.', -} diff --git a/packages/plugins/callout/src/locales/es.ts b/packages/plugins/callout/src/locales/es.ts deleted file mode 100644 index 059ca736e..000000000 --- a/packages/plugins/callout/src/locales/es.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const esLocale = { - title: 'Destacado', - description: 'Para contenido importante.', -} diff --git a/packages/plugins/callout/src/locales/index.ts b/packages/plugins/callout/src/locales/index.ts deleted file mode 100644 index 1651fca2b..000000000 --- a/packages/plugins/callout/src/locales/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import {enLocale} from './en'; -import {esLocale} from './es'; - -export const defaultLocales = { - en: enLocale, - es: esLocale -} diff --git a/packages/plugins/callout/src/plugin/index.tsx b/packages/plugins/callout/src/plugin/index.tsx index 04c80808b..7aeffcf04 100644 --- a/packages/plugins/callout/src/plugin/index.tsx +++ b/packages/plugins/callout/src/plugin/index.tsx @@ -10,7 +10,6 @@ import { CalloutCommands } from '../commands'; import { CalloutElementMap, CalloutTheme } from '../types'; import { CalloutRender } from '../ui/Callout'; import { CALLOUT_THEME_STYLES } from '../utils'; -import {defaultLocales} from '../locales'; const Callout = new YooptaPlugin({ type: 'Callout', @@ -97,7 +96,6 @@ const Callout = new YooptaPlugin({ }, }, }, - translations: defaultLocales, }); export { Callout }; diff --git a/packages/plugins/code/src/locales/en.ts b/packages/plugins/code/src/locales/en.ts deleted file mode 100644 index 954fbf991..000000000 --- a/packages/plugins/code/src/locales/en.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const enLocale = { - title: 'Code', - description: 'Displays formatted code snippets.', -} diff --git a/packages/plugins/code/src/locales/es.ts b/packages/plugins/code/src/locales/es.ts deleted file mode 100644 index eaa8bbbf9..000000000 --- a/packages/plugins/code/src/locales/es.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const esLocale = { - title: 'Código', - description: 'Muestra fragmentos de código formateados.', -} diff --git a/packages/plugins/code/src/locales/index.ts b/packages/plugins/code/src/locales/index.ts deleted file mode 100644 index 1651fca2b..000000000 --- a/packages/plugins/code/src/locales/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import {enLocale} from './en'; -import {esLocale} from './es'; - -export const defaultLocales = { - en: enLocale, - es: esLocale -} diff --git a/packages/plugins/code/src/plugin/index.tsx b/packages/plugins/code/src/plugin/index.tsx index 8e3a23ffa..4779c5ba1 100644 --- a/packages/plugins/code/src/plugin/index.tsx +++ b/packages/plugins/code/src/plugin/index.tsx @@ -2,7 +2,6 @@ import { generateId, YooptaPlugin } from '@yoopta/editor'; import { CodeCommands } from '../commands'; import { CodeElementMap, CodeElementProps, CodePluginBlockOptions, CodePluginElements } from '../types'; import { CodeEditor } from '../ui/Code'; -import {defaultLocales} from '../locales'; const ALIGNS_TO_JUSTIFY = { left: 'flex-start', @@ -101,7 +100,6 @@ const Code = new YooptaPlugin({ }, }, }, - translations: defaultLocales, }); function escapeHTML(text) { diff --git a/packages/plugins/divider/src/locales/en.ts b/packages/plugins/divider/src/locales/en.ts deleted file mode 100644 index da5957f87..000000000 --- a/packages/plugins/divider/src/locales/en.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const enLocale = { - title: 'Divider', - description: 'Separates content with a horizontal line.', -} diff --git a/packages/plugins/divider/src/locales/es.ts b/packages/plugins/divider/src/locales/es.ts deleted file mode 100644 index ed1a05491..000000000 --- a/packages/plugins/divider/src/locales/es.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const esLocale = { - title: 'Divisor', - description: 'Separa contenido con una línea horizontal.', -} diff --git a/packages/plugins/divider/src/locales/index.ts b/packages/plugins/divider/src/locales/index.ts deleted file mode 100644 index 1651fca2b..000000000 --- a/packages/plugins/divider/src/locales/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import {enLocale} from './en'; -import {esLocale} from './es'; - -export const defaultLocales = { - en: enLocale, - es: esLocale -} diff --git a/packages/plugins/divider/src/plugin/index.tsx b/packages/plugins/divider/src/plugin/index.tsx index 3294c71b6..f6220e844 100644 --- a/packages/plugins/divider/src/plugin/index.tsx +++ b/packages/plugins/divider/src/plugin/index.tsx @@ -3,7 +3,6 @@ import { DividerCommands } from '../commands'; import { onKeyDown } from '../events/onKeyDown'; import { DividerElementMap } from '../types'; import { DividerRender } from '../elements/Divider'; -import {defaultLocales} from '../locales'; const Divider = new YooptaPlugin({ type: 'Divider', @@ -77,7 +76,6 @@ const Divider = new YooptaPlugin({ events: { onKeyDown, }, - translations: defaultLocales, }); export { Divider }; diff --git a/packages/plugins/embed/src/locales/en.ts b/packages/plugins/embed/src/locales/en.ts deleted file mode 100644 index 38a38235b..000000000 --- a/packages/plugins/embed/src/locales/en.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const enLocale = { - title: 'Embed', - description: 'Inserts external content like videos, Google Maps, etc', -} diff --git a/packages/plugins/embed/src/locales/es.ts b/packages/plugins/embed/src/locales/es.ts deleted file mode 100644 index 5b847cfad..000000000 --- a/packages/plugins/embed/src/locales/es.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const esLocale = { - title: 'Insertar contenido', - description: 'Inserta contenido externo como vídeos, Google Maps, widgets...', -} diff --git a/packages/plugins/embed/src/locales/index.ts b/packages/plugins/embed/src/locales/index.ts deleted file mode 100644 index 1651fca2b..000000000 --- a/packages/plugins/embed/src/locales/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import {enLocale} from './en'; -import {esLocale} from './es'; - -export const defaultLocales = { - en: enLocale, - es: esLocale -} diff --git a/packages/plugins/embed/src/plugin/index.tsx b/packages/plugins/embed/src/plugin/index.tsx index c48850e12..b68e80ddb 100644 --- a/packages/plugins/embed/src/plugin/index.tsx +++ b/packages/plugins/embed/src/plugin/index.tsx @@ -2,7 +2,6 @@ import { generateId, YooptaPlugin } from '@yoopta/editor'; import { EmbedCommands } from '../commands'; import { EmbedElementMap, EmbedPluginOptions } from '../types'; import { EmbedRender } from '../ui/Embed'; -import {defaultLocales} from '../locales'; const ALIGNS_TO_JUSTIFY = { left: 'flex-start', @@ -90,7 +89,6 @@ const Embed = new YooptaPlugin({ }, }, }, - translations: defaultLocales, }); export { Embed }; diff --git a/packages/plugins/file/src/locales/en.ts b/packages/plugins/file/src/locales/en.ts deleted file mode 100644 index 273e9cf82..000000000 --- a/packages/plugins/file/src/locales/en.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const enLocale = { - title: 'File', - description: 'Adds downloadable file attachments.', -} diff --git a/packages/plugins/file/src/locales/es.ts b/packages/plugins/file/src/locales/es.ts deleted file mode 100644 index bbc98071d..000000000 --- a/packages/plugins/file/src/locales/es.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const esLocale = { - title: 'Archivo', - description: 'Agrega archivos adjuntos descargables.', -} diff --git a/packages/plugins/file/src/locales/index.ts b/packages/plugins/file/src/locales/index.ts deleted file mode 100644 index 1651fca2b..000000000 --- a/packages/plugins/file/src/locales/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import {enLocale} from './en'; -import {esLocale} from './es'; - -export const defaultLocales = { - en: enLocale, - es: esLocale -} diff --git a/packages/plugins/file/src/plugin/index.tsx b/packages/plugins/file/src/plugin/index.tsx index 9b9a80ebd..598034886 100644 --- a/packages/plugins/file/src/plugin/index.tsx +++ b/packages/plugins/file/src/plugin/index.tsx @@ -2,7 +2,6 @@ import { generateId, YooptaPlugin } from '@yoopta/editor'; import { FileCommands } from '../commands'; import { FileElementMap, FilePluginOptions } from '../types'; import { FileRender } from '../ui/File'; -import {defaultLocales} from '../locales'; const ALIGNS_TO_JUSTIFY = { left: 'flex-start', @@ -113,7 +112,6 @@ const File = new YooptaPlugin({ }, }, }, - translations: defaultLocales, }); export { File }; diff --git a/packages/plugins/headings/src/locales/en.ts b/packages/plugins/headings/src/locales/en.ts deleted file mode 100644 index 4e6c5f2a0..000000000 --- a/packages/plugins/headings/src/locales/en.ts +++ /dev/null @@ -1,14 +0,0 @@ -export const headingOneEnLocale = { - title: 'Heading 1', - description: 'Adds structured big headings for content hierarchy.', -} - -export const headingTwoEnLocale = { - title: 'Heading 2', - description: 'Adds structured medium headings for content hierarchy.', -} - -export const headingThreeEnLocale = { - title: 'Heading 3', - description: 'Adds structured small headings for content hierarchy.', -} diff --git a/packages/plugins/headings/src/locales/es.ts b/packages/plugins/headings/src/locales/es.ts deleted file mode 100644 index b1f35f55f..000000000 --- a/packages/plugins/headings/src/locales/es.ts +++ /dev/null @@ -1,14 +0,0 @@ -export const headingOneEsLocale = { - title: 'Encabezado Uno', - description: 'Agrega encabezados grandes estructurados para la jerarquía del contenido.', -} - -export const headingTwoEsLocale = { - title: 'Encabezado Dos', - description: 'Agrega encabezados medianos estructurados para la jerarquía del contenido.', -} - -export const headingThreeEsLocale = { - title: 'Encabezado Tres', - description: 'Agrega encabezados pequeños estructurados para la jerarquía del contenido.', -} diff --git a/packages/plugins/headings/src/locales/index.ts b/packages/plugins/headings/src/locales/index.ts deleted file mode 100644 index 3da1bd685..000000000 --- a/packages/plugins/headings/src/locales/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -import {headingOneEnLocale, headingThreeEnLocale, headingTwoEnLocale} from './en'; -import {headingOneEsLocale, headingThreeEsLocale, headingTwoEsLocale} from './es'; - -export const defaultHeadingOneLocales = { - en: headingOneEnLocale, - es: headingOneEsLocale, -} - -export const defaultHeadingTwoLocales = { - en: headingTwoEnLocale, - es: headingTwoEsLocale, -} - -export const defaultHeadingThreeLocales = { - en: headingThreeEnLocale, - es: headingThreeEsLocale, -} diff --git a/packages/plugins/headings/src/plugin/HeadingOne.tsx b/packages/plugins/headings/src/plugin/HeadingOne.tsx index ba786429f..cdcd1664d 100644 --- a/packages/plugins/headings/src/plugin/HeadingOne.tsx +++ b/packages/plugins/headings/src/plugin/HeadingOne.tsx @@ -6,7 +6,6 @@ import { } from '@yoopta/editor'; import { HeadingOneCommands } from '../commands'; import { HeadingOneElement } from '../types'; -import {defaultHeadingOneLocales} from '../locales'; const HeadingOneRender = ({ extendRender, ...props }: PluginElementRenderProps) => { const { element, HTMLAttributes = {}, attributes, children } = props; @@ -84,7 +83,6 @@ const HeadingOne = new YooptaPlugin>({ }, }, }, - translations: defaultHeadingOneLocales, }); export { HeadingOne }; diff --git a/packages/plugins/headings/src/plugin/HeadingThree.tsx b/packages/plugins/headings/src/plugin/HeadingThree.tsx index c1091599e..b37fab3fc 100644 --- a/packages/plugins/headings/src/plugin/HeadingThree.tsx +++ b/packages/plugins/headings/src/plugin/HeadingThree.tsx @@ -6,7 +6,6 @@ import { } from '@yoopta/editor'; import { HeadingThreeCommands } from '../commands'; import { HeadingThreeElement } from '../types'; -import {defaultHeadingThreeLocales} from '../locales'; const HeadingThreeRender = ({ extendRender, ...props }: PluginElementRenderProps) => { const { element, HTMLAttributes = {}, attributes, children } = props; @@ -92,7 +91,6 @@ const HeadingThree = new YooptaPlugin { const { element, HTMLAttributes = {}, attributes, children } = props; @@ -87,7 +86,6 @@ const HeadingTwo = new YooptaPlugin>({ }, }, }, - translations: defaultHeadingTwoLocales }); export { HeadingTwo }; diff --git a/packages/plugins/image/src/locales/en.ts b/packages/plugins/image/src/locales/en.ts deleted file mode 100644 index 45376749e..000000000 --- a/packages/plugins/image/src/locales/en.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const imageEnLocale = { - title: 'Image', - description: 'Upload from device or insert with link.', -} diff --git a/packages/plugins/image/src/locales/es.ts b/packages/plugins/image/src/locales/es.ts deleted file mode 100644 index 227dec495..000000000 --- a/packages/plugins/image/src/locales/es.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const imageEsLocale = { - title: 'Imagen', - description: 'Inserta imágenes desde un archivo o enlace.', -} - diff --git a/packages/plugins/image/src/locales/index.ts b/packages/plugins/image/src/locales/index.ts deleted file mode 100644 index 43411b283..000000000 --- a/packages/plugins/image/src/locales/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import {imageEnLocale} from './en'; -import {imageEsLocale} from './es'; - -export const defaultLocales = { - en: imageEnLocale, - es: imageEsLocale, -} diff --git a/packages/plugins/image/src/plugin/index.tsx b/packages/plugins/image/src/plugin/index.tsx index 3b65c41ab..bdd1088cc 100644 --- a/packages/plugins/image/src/plugin/index.tsx +++ b/packages/plugins/image/src/plugin/index.tsx @@ -3,7 +3,6 @@ import { ImageCommands } from '../commands'; import { ImageElementMap, ImageElementProps, ImagePluginElements, ImagePluginOptions } from '../types'; import { ImageRender } from '../ui/Image'; import { limitSizes } from '../utils/limitSizes'; -import {defaultLocales} from '../locales'; const ALIGNS_TO_JUSTIFY = { left: 'flex-start', @@ -118,7 +117,6 @@ const Image = new YooptaPlugin({ }, }, }, - translations: defaultLocales, }); export { Image }; diff --git a/packages/plugins/link/src/locales/en.ts b/packages/plugins/link/src/locales/en.ts deleted file mode 100644 index e15593099..000000000 --- a/packages/plugins/link/src/locales/en.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const enLocale = { - title: 'Link', - description: 'Adds clickable hyperlinks to content.', -} diff --git a/packages/plugins/link/src/locales/es.ts b/packages/plugins/link/src/locales/es.ts deleted file mode 100644 index f87123a8d..000000000 --- a/packages/plugins/link/src/locales/es.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const esLocale = { - title: 'Enlace', - description: 'Agrega hipervínculos clicables al contenido.', -} diff --git a/packages/plugins/link/src/locales/index.ts b/packages/plugins/link/src/locales/index.ts deleted file mode 100644 index 1651fca2b..000000000 --- a/packages/plugins/link/src/locales/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import {enLocale} from './en'; -import {esLocale} from './es'; - -export const defaultLocales = { - en: enLocale, - es: esLocale -} diff --git a/packages/plugins/link/src/plugin/index.tsx b/packages/plugins/link/src/plugin/index.tsx index 4eef07f91..23966a85a 100644 --- a/packages/plugins/link/src/plugin/index.tsx +++ b/packages/plugins/link/src/plugin/index.tsx @@ -2,7 +2,6 @@ import { deserializeTextNodes, generateId, serializeTextNodes, YooptaPlugin } fr import { LinkCommands } from '../commands'; import { LinkElementMap, LinkElementProps } from '../types'; import { LinkRender } from '../ui/LinkRender'; -import { defaultLocales } from '../locales'; const Link = new YooptaPlugin({ type: 'LinkPlugin', diff --git a/packages/plugins/lists/src/locales/en.ts b/packages/plugins/lists/src/locales/en.ts deleted file mode 100644 index 36a453e82..000000000 --- a/packages/plugins/lists/src/locales/en.ts +++ /dev/null @@ -1,16 +0,0 @@ -export const bulletedListEnLocale = { - title: 'Bulleted List', - description: 'Creates unordered lists with bullet points.', -} - -export const numberedListEnLocale = { - title: 'Bulleted List', - description: 'Creates ordered lists with numbers.', -} - - -export const todoListEnLocale = { - title: 'Todo List', - description: 'Creates lists with checkable items.', -} - diff --git a/packages/plugins/lists/src/locales/es.ts b/packages/plugins/lists/src/locales/es.ts deleted file mode 100644 index 067022e08..000000000 --- a/packages/plugins/lists/src/locales/es.ts +++ /dev/null @@ -1,14 +0,0 @@ -export const bulletedListEsLocale = { - title: 'Lista de viñetas', - description: 'Crea listas con viñetas.', -} - -export const numberedListEsLocale = { - title: 'Lista enumerada', - description: 'Crea listas ordenadas con números.', -} - -export const todoListEsLocale = { - title: 'Lista de tareas', - description: 'Crea listas con elementos marcables.', -} diff --git a/packages/plugins/lists/src/locales/index.ts b/packages/plugins/lists/src/locales/index.ts deleted file mode 100644 index 31d2ce0ad..000000000 --- a/packages/plugins/lists/src/locales/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -import {bulletedListEnLocale, numberedListEnLocale, todoListEnLocale} from './en'; -import {bulletedListEsLocale, numberedListEsLocale, todoListEsLocale} from './es'; - - -export const defaultBulletedListLocales = { - en: bulletedListEnLocale, - es: bulletedListEsLocale -} - -export const defaultNumberedListLocales = { - en: numberedListEnLocale, - es: numberedListEsLocale -} - -export const defaultTodoListLocales = { - en: todoListEnLocale, - es: todoListEsLocale -} diff --git a/packages/plugins/lists/src/plugin/BulletedList.tsx b/packages/plugins/lists/src/plugin/BulletedList.tsx index e4ef8a597..9b14a4e03 100644 --- a/packages/plugins/lists/src/plugin/BulletedList.tsx +++ b/packages/plugins/lists/src/plugin/BulletedList.tsx @@ -10,7 +10,6 @@ import { BulletedListRender } from '../elements/BulletedList'; import { onKeyDown } from '../events/onKeyDown'; import { ListElementMap } from '../types'; import { deserializeListNodes } from '../utils/deserializeListNodes'; -import {defaultBulletedListLocales} from '../locales'; const BulletedList = new YooptaPlugin>({ type: 'BulletedList', @@ -89,7 +88,6 @@ const BulletedList = new YooptaPlugin>({ }, }, }, - translations: defaultBulletedListLocales }); export { BulletedList }; diff --git a/packages/plugins/lists/src/plugin/NumberedList.tsx b/packages/plugins/lists/src/plugin/NumberedList.tsx index d6867fea1..b5405d6d4 100644 --- a/packages/plugins/lists/src/plugin/NumberedList.tsx +++ b/packages/plugins/lists/src/plugin/NumberedList.tsx @@ -4,7 +4,6 @@ import { NumberedListRender } from '../elements/NumberedList'; import { onKeyDown } from '../events/onKeyDown'; import { ListElementMap } from '../types'; import { deserializeListNodes } from '../utils/deserializeListNodes'; -import { defaultNumberedListLocales } from '../locales'; const NumberedList = new YooptaPlugin>({ type: 'NumberedList', @@ -82,7 +81,6 @@ const NumberedList = new YooptaPlugin>({ }, }, }, - translations: defaultNumberedListLocales }); export { NumberedList }; diff --git a/packages/plugins/lists/src/plugin/TodoList.tsx b/packages/plugins/lists/src/plugin/TodoList.tsx index f98c91c00..d789d46df 100644 --- a/packages/plugins/lists/src/plugin/TodoList.tsx +++ b/packages/plugins/lists/src/plugin/TodoList.tsx @@ -4,7 +4,6 @@ import { TodoListRender } from '../elements/TodoList'; import { onKeyDown } from '../events/onKeyDown'; import { ListElementMap } from '../types'; import { deserializeListNodes } from '../utils/deserializeListNodes'; -import { defaultTodoListLocales } from '../locales'; const TodoList = new YooptaPlugin>({ type: 'TodoList', @@ -88,7 +87,6 @@ const TodoList = new YooptaPlugin>({ }, }, }, - translations: defaultTodoListLocales }); export { TodoList }; diff --git a/packages/plugins/paragraph/src/locales/en.ts b/packages/plugins/paragraph/src/locales/en.ts deleted file mode 100644 index 9916f3403..000000000 --- a/packages/plugins/paragraph/src/locales/en.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const enLocale = { - title: 'Paragraph', - description: 'Adds blocks of text for content.', -} diff --git a/packages/plugins/paragraph/src/locales/es.ts b/packages/plugins/paragraph/src/locales/es.ts deleted file mode 100644 index 61c1e3d10..000000000 --- a/packages/plugins/paragraph/src/locales/es.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const esLocale = { - title: 'Párrafo', - description: 'Agrega bloques de texto.', -} diff --git a/packages/plugins/paragraph/src/locales/index.ts b/packages/plugins/paragraph/src/locales/index.ts deleted file mode 100644 index 1651fca2b..000000000 --- a/packages/plugins/paragraph/src/locales/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import {enLocale} from './en'; -import {esLocale} from './es'; - -export const defaultLocales = { - en: enLocale, - es: esLocale -} diff --git a/packages/plugins/paragraph/src/plugin/index.tsx b/packages/plugins/paragraph/src/plugin/index.tsx index e50e08f03..de5c5e309 100644 --- a/packages/plugins/paragraph/src/plugin/index.tsx +++ b/packages/plugins/paragraph/src/plugin/index.tsx @@ -1,9 +1,7 @@ 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'; -import { defaultLocales } from '../locales'; const Paragraph = new YooptaPlugin({ type: 'Paragraph', diff --git a/packages/plugins/table/src/locales/en.ts b/packages/plugins/table/src/locales/en.ts deleted file mode 100644 index d6897d2f3..000000000 --- a/packages/plugins/table/src/locales/en.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const enLocale = { - title: 'Table', - description: 'Organizes content into rows and columns.', -} diff --git a/packages/plugins/table/src/locales/es.ts b/packages/plugins/table/src/locales/es.ts deleted file mode 100644 index 0ad8236d6..000000000 --- a/packages/plugins/table/src/locales/es.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const esLocale = { - title: 'Tabla', - description: 'Organiza contenido en filas y columnas.', -} diff --git a/packages/plugins/table/src/locales/index.ts b/packages/plugins/table/src/locales/index.ts deleted file mode 100644 index 1651fca2b..000000000 --- a/packages/plugins/table/src/locales/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import {enLocale} from './en'; -import {esLocale} from './es'; - -export const defaultLocales = { - en: enLocale, - es: esLocale -} diff --git a/packages/plugins/table/src/plugin/Table.tsx b/packages/plugins/table/src/plugin/Table.tsx index 47bd8d67b..e764f1a6b 100644 --- a/packages/plugins/table/src/plugin/Table.tsx +++ b/packages/plugins/table/src/plugin/Table.tsx @@ -12,7 +12,6 @@ import { deserializeTable } from '../parsers/html/deserialize'; import { serializeTable } from '../parsers/html/serialize'; import { serializeMarkown } from '../parsers/markdown/serialize'; import { serializeTableToEmail } from '../parsers/email/serialize'; -import { defaultLocales } from '../locales'; const Table = new YooptaPlugin({ type: 'Table', @@ -71,7 +70,6 @@ const Table = new YooptaPlugin({ shortcuts: ['table', '||', '3x3'], }, commands: TableCommands, - translations: defaultLocales }); export { Table }; diff --git a/packages/plugins/video/src/locales/en.ts b/packages/plugins/video/src/locales/en.ts deleted file mode 100644 index 5d859a9e6..000000000 --- a/packages/plugins/video/src/locales/en.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const enLocale = { - title: 'Video', - description: 'Upload from device, insert from Youtube, Vimeo, DailyMotion, Loom, Wistia...', -} diff --git a/packages/plugins/video/src/locales/es.ts b/packages/plugins/video/src/locales/es.ts deleted file mode 100644 index f0698714c..000000000 --- a/packages/plugins/video/src/locales/es.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const esLocale = { - title: 'Vídeo', - description: 'Subir desde el dispositivo, insertar desde YouTube, Vimeo, DailyMotion, Loom, Wistia...', -} diff --git a/packages/plugins/video/src/locales/index.ts b/packages/plugins/video/src/locales/index.ts deleted file mode 100644 index 1651fca2b..000000000 --- a/packages/plugins/video/src/locales/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import {enLocale} from './en'; -import {esLocale} from './es'; - -export const defaultLocales = { - en: enLocale, - es: esLocale -} diff --git a/packages/plugins/video/src/plugin/index.tsx b/packages/plugins/video/src/plugin/index.tsx index 6aa267f53..63505cacd 100644 --- a/packages/plugins/video/src/plugin/index.tsx +++ b/packages/plugins/video/src/plugin/index.tsx @@ -3,7 +3,6 @@ import { VideoCommands } from '../commands'; import { VideoElementMap, VideoPluginOptions } from '../types'; import { VideoRender } from '../ui/Video'; import { limitSizes } from '../utils/limitSizes'; -import {defaultLocales} from '../locales'; const ALIGNS_TO_JUSTIFY = { left: 'flex-start', @@ -217,7 +216,6 @@ const Video = new YooptaPlugin({ }, }, }, - translations: defaultLocales, }); export { Video }; From 76885282a7be1e7098dd60cf64beb8be5d208389 Mon Sep 17 00:00:00 2001 From: Guillermo Loaysa <25121538+gloaysa@users.noreply.github.com> Date: Fri, 31 Jan 2025 17:20:29 +0100 Subject: [PATCH 13/18] feat: emit language-change event on setLanguage (#442) * feat: emit language-change event on setLanguage * fix: make editor i18n agnostic * chore: improve dev example and docs * feat: create I18nYooEditorProvider and use hook in example --- packages/core/editor/src/YooptaEditor.tsx | 5 +- packages/core/editor/src/editor/types.ts | 2 + packages/core/editor/src/index.ts | 1 - .../src/plugins/SlateEditorComponent.tsx | 2 +- packages/core/i18n/README.md | 219 +++++++++++------- .../i18n/src/context/YooptaI18nProvider.tsx | 47 +++- .../i18n/src/extension/withTranslations.ts | 51 ++++ .../i18n/src/extenstion/withTranslations.ts | 55 ----- .../core/i18n/src/hooks/useTranslation.ts | 57 ++++- packages/core/i18n/src/index.ts | 4 +- .../core/i18n/src/types/i18n-yoo-editor.ts | 64 +++++ packages/core/i18n/src/types/index.ts | 11 +- .../i18n/src/types/translation-options.ts | 7 + .../components/FixedToolbar/FixedToolbar.tsx | 39 ++-- packages/development/src/pages/dev/index.tsx | 57 ++--- 15 files changed, 412 insertions(+), 209 deletions(-) create mode 100644 packages/core/i18n/src/extension/withTranslations.ts delete mode 100644 packages/core/i18n/src/extenstion/withTranslations.ts create mode 100644 packages/core/i18n/src/types/i18n-yoo-editor.ts create mode 100644 packages/core/i18n/src/types/translation-options.ts diff --git a/packages/core/editor/src/YooptaEditor.tsx b/packages/core/editor/src/YooptaEditor.tsx index b3484ed0f..d0acbfbb8 100644 --- a/packages/core/editor/src/YooptaEditor.tsx +++ b/packages/core/editor/src/YooptaEditor.tsx @@ -71,9 +71,8 @@ const YooptaEditor = ({ }, [marksProps]); const plugins = useMemo(() => { - return pluginsProps.map((pluginInstance) => { - const plugin = pluginInstance.getPlugin as Plugin>; - return plugin; + return pluginsProps.map((plugin) => { + return plugin.getPlugin as Plugin>; }); }, [pluginsProps]); diff --git a/packages/core/editor/src/editor/types.ts b/packages/core/editor/src/editor/types.ts index 609bca8a8..60beb4fc0 100644 --- a/packages/core/editor/src/editor/types.ts +++ b/packages/core/editor/src/editor/types.ts @@ -84,6 +84,8 @@ export type YooptaEventsMap = { blur: boolean; 'block:copy': YooptaBlockData; 'path-change': YooptaPath; +} & { + [key: string]: any; }; export type BaseCommands = Record any>; diff --git a/packages/core/editor/src/index.ts b/packages/core/editor/src/index.ts index 9b0ef168b..6a247984b 100644 --- a/packages/core/editor/src/index.ts +++ b/packages/core/editor/src/index.ts @@ -7,7 +7,6 @@ export { useYooptaReadOnly, useYooptaPluginOptions, } from './contexts/YooptaContext/YooptaContext'; -export { useTranslation } from './i18n/hooks/useTranslation'; import { YooptaEditor, type YooptaEditorProps, type YooptaOnChangeOptions } from './YooptaEditor'; export { deserializeHTML } from './parsers/deserializeHTML'; export { type EmailTemplateOptions } from './parsers/getEmail'; diff --git a/packages/core/editor/src/plugins/SlateEditorComponent.tsx b/packages/core/editor/src/plugins/SlateEditorComponent.tsx index e87f235fa..07661c0a1 100644 --- a/packages/core/editor/src/plugins/SlateEditorComponent.tsx +++ b/packages/core/editor/src/plugins/SlateEditorComponent.tsx @@ -47,7 +47,7 @@ const SlateEditorComponent = , events, options, extensions: withExtensions, - placeholder = 'editor.placeholder', + placeholder = 'Type / for commands', }: Props) => { const editor = useYooptaEditor(); const block = useBlockData(id); diff --git a/packages/core/i18n/README.md b/packages/core/i18n/README.md index 87bf50d61..7fe27429d 100644 --- a/packages/core/i18n/README.md +++ b/packages/core/i18n/README.md @@ -1,126 +1,167 @@ -# Exports +# @yoopta/i18n -Exports is core package for exporting/importing yoopta content in different formats -The package `@yoopta/exports` supports exporting/importing in the next formats: +This package provides internationalization (i18n) support for **YooptaEditor** by extending the editor with translation capabilities. -- HTML -- Markdown -- Plain text - -### Installation +## 📦 Installation ```bash -yarn add @yoopta/exports +yarn add @yoopta/i18n ``` -### Usage +## 🔹 Exports -HTML exports/imports example +```typescript +import { + withTranslations, + useTranslation, + type I18nYooEditor, + I18nYooEditorProvider, + useI18nYooEditor, +} from '@yoopta/i18n'; +``` -```jsx -import { html } from '@yoopta/exports'; +### **Modules** -const Editor = () => { - const editor = useMemo(() => createYooptaEditor(), []); +- **`withTranslations`** - Function to extend `YooEditor` with i18n support. +- **`I18nYooEditor`** - Extended type for `YooEditor` with i18n properties. +- **`I18nYooEditorProvider`** - Context provider for managing the i18n-enabled editor instance (needed for `useTranslation` and `useI18nYooEditor`). +- - **`useTranslation`** - Hook for language-switching and retrieving translations by keys (requires provider). +- **`useI18nYooEditor`** - Hook for retrieving the `I18nYooEditor` instance from the provider (requires provider). - // from html to @yoopta content - const deserializeHTML = () => { - const htmlString = '

First title

'; - const content = html.deserialize(editor, htmlString); +--- - editor.setEditorValue(content); - }; +## 🛠 Usage - // from @yoopta content to html string - const serializeHTML = () => { - const data = editor.getEditorValue(); - const htmlString = html.serialize(editor, data); - console.log('html string', htmlString); - }; +### **Option 1: Using Only `withTranslations` (No Provider Required)** - return ( -
- - +If you only need to extend the editor with i18n support, you can use `withTranslations()` without the provider. +However, you will need to manually track language changes. - -
- ); +```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: 'ru', + }); +}, []); + +useEffect(() => { + const handleLanguageChange = (lang: string) => { + console.log('Language changed to:', lang); + }; + + editor.on('language-change', handleLanguageChange); + return () => { + editor.off('language-change', handleLanguageChange); + }; +}, [editor]); ``` --- -Markdown exports/imports example +### **Option 2: Using `I18nYooEditorProvider` (Recommended for `useTranslation`)** -```jsx -import { markdown } from '@yoopta/exports'; +If you want to use `useTranslation()` inside your components, wrap the editor in `I18nYooEditorProvider`. -const Editor = () => { - const editor = useMemo(() => createYooptaEditor(), []); +```tsx +import { withTranslations, I18nYooEditorProvider, useTranslation } from '@yoopta/i18n'; +import { createYooptaEditor, YooptaEditor } from '@yoopta/editor'; +import { useMemo, useState, useRef } from 'react'; - // from markdown to @yoopta content - const deserializeMarkdown = () => { - const markdownString = '# First title'; - const value = markdown.deserialize(editor, markdownString); +const TRANSLATION_OPTIONS = { + translations: { + es: esTranslations, + ru: ruTranslations, + cz: czTranslations, + }, + defaultLanguage: 'en', + language: 'en', +}; - editor.setEditorValue(value); - }; - - // from @yoopta content to markdown string - const serializeMarkdown = () => { - const data = editor.getEditorValue(); - const markdownString = markdown.serialize(editor, data); - console.log('markdown string', markdownString); - }; +const TranslationSelector = () => { + const { currentLanguage, setLanguage, languages } = useTranslation(); return ( -
- - - - +
+ Languages +
+ {languages.map((lang) => { + const isCurrent = lang === currentLanguage; + + return ( + + ); + })} +
); }; -``` - -Plain text exports/imports example -```jsx -import { plainText } from '@yoopta/exports'; - -const Editor = () => { - const editor = useMemo(() => createYooptaEditor(), []); - - // from plain text to @yoopta content - const deserializeText = () => { - const textString = '# First title'; - const value = plainText.deserialize(editor, textString); - - editor.setEditorValue(value); - }; +const BasicExample = () => { + const editor = useMemo(() => { + const baseEditor = createYooptaEditor(); + return withTranslations(baseEditor, TRANSLATION_OPTIONS); + }, []); - // from @yoopta content to plain text string - const serializeText = () => { - const data = editor.getEditorValue(); - const textString = plainText.serialize(editor, data); - console.log('plain text string', textString); - }; + const selectionRef = useRef(null); + const [value, setValue] = useState({ + 'block-1': buildBlockData({ id: 'block-1' }), + }); return ( -
- - - - -
+ + + + ); }; ``` -Examples +--- + +## 📜 **I18n API** + +When `withTranslations` is applied to the editor, it extends `YooEditor` with the following additional properties and methods: -- Page - [https://yoopta.dev/examples/withExports](https://yoopta.dev/examples/withExports) - - Example with HTML - [https://yoopta.dev/examples/withExports/html](https://yoopta.dev/examples/withExports/html) - - Example with Markdown - [https://yoopta.dev/examples/withExports/markdown](https://yoopta.dev/examples/withExports/markdown) +```typescript +export type I18nYooEditor = YooEditor & { + translations: Record>; + language: string; + languages: string[]; + defaultLanguage: string; + setLanguage: (lang: string) => void; +}; +``` diff --git a/packages/core/i18n/src/context/YooptaI18nProvider.tsx b/packages/core/i18n/src/context/YooptaI18nProvider.tsx index cb0ff5c3b..51e879dff 100644 --- a/packages/core/i18n/src/context/YooptaI18nProvider.tsx +++ b/packages/core/i18n/src/context/YooptaI18nProvider.tsx @@ -1 +1,46 @@ -export {}; +import { createContext, FunctionComponent, PropsWithChildren, useContext, useRef } from 'react'; +import { createYooptaEditor } from '@yoopta/editor'; +import { I18nYooEditor, TranslationOptions } from '../types'; + +export type I18nYooEditorContext = { + editor: I18nYooEditor; + options: TranslationOptions; +}; + +const DEFAULT_HANDLERS: I18nYooEditorContext = { + editor: createYooptaEditor() as I18nYooEditor, + options: { + language: '', + defaultLanguage: '', + translations: {}, + }, +}; + +export const I18nYooEditorContext = createContext(DEFAULT_HANDLERS); + +const I18nYooEditorProvider: FunctionComponent> = ({ + children, + editor, + options, +}) => { + const contextValueRef = useRef(DEFAULT_HANDLERS); + + contextValueRef.current = { + editor, + options, + }; + + return {children}; +}; + +const useI18nYooEditor = (): I18nYooEditor => { + const context = useContext(I18nYooEditorContext); + + if (!context) { + throw new Error('useI18nYooEditor must be used within a I18nYooEditorProvider'); + } + + return context.editor; +}; + +export { I18nYooEditorProvider, useI18nYooEditor }; diff --git a/packages/core/i18n/src/extension/withTranslations.ts b/packages/core/i18n/src/extension/withTranslations.ts new file mode 100644 index 000000000..ff8af57b1 --- /dev/null +++ b/packages/core/i18n/src/extension/withTranslations.ts @@ -0,0 +1,51 @@ +import { I18nYooEditor, TranslationOptions } from '../types'; +import { YooEditor } from '@yoopta/editor'; + +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 languages = Object.keys(translations); + + i18nEditor.getLabelText = (key) => { + const keyParts = key.split('.'); + + const currentLangValue = getNestedValue(translations[i18nEditor.language], keyParts); + if (typeof currentLangValue === 'string') { + return currentLangValue; + } + + const defaultLangValue = getNestedValue(translations[i18nEditor.defaultLanguage], keyParts); + + if (typeof defaultLangValue === 'string') { + return defaultLangValue; + } + + return getLabelText(key); + }; + + i18nEditor.languages = languages; + + i18nEditor.setLanguage = (lang: string) => { + if (translations[lang]) { + i18nEditor.language = lang; + i18nEditor.emit('language-change', lang); + } + }; + + i18nEditor.translations = translations; + i18nEditor.defaultLanguage = defaultLanguage; + i18nEditor.language = language; + + return i18nEditor; +} diff --git a/packages/core/i18n/src/extenstion/withTranslations.ts b/packages/core/i18n/src/extenstion/withTranslations.ts deleted file mode 100644 index 520919f18..000000000 --- a/packages/core/i18n/src/extenstion/withTranslations.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { I18nYooEditor } from '../types'; - -type TranslationOptions = { - language: string; - defaultLanguage: string; - translations: I18nYooEditor['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: I18nYooEditor, options: TranslationOptions): I18nYooEditor { - const { translations, defaultLanguage, language } = options; - - const { getLabelText } = editor; - const languages = Object.keys(translations); - - editor.getLabelText = (key) => { - const keyParts = key.split('.'); - - const currentLangValue = getNestedValue(translations[editor.language], keyParts); - if (typeof currentLangValue === 'string') { - return currentLangValue; - } - - const defaultLangValue = getNestedValue(translations[editor.defaultLanguage], keyParts); - - if (typeof defaultLangValue === 'string') { - return defaultLangValue; - } - - return getLabelText(key); - }; - - editor.languages = languages; - - editor.setLanguage = (lang: string) => { - if (translations[lang]) { - editor.language = lang; - // editor.emit - } - }; - - editor.translations = translations; - editor.defaultLanguage = defaultLanguage; - editor.language = language; - - return editor; -} diff --git a/packages/core/i18n/src/hooks/useTranslation.ts b/packages/core/i18n/src/hooks/useTranslation.ts index 0cdfe09c7..3a457fadb 100644 --- a/packages/core/i18n/src/hooks/useTranslation.ts +++ b/packages/core/i18n/src/hooks/useTranslation.ts @@ -1,6 +1,55 @@ -import { useYooptaEditor } from '@yoopta/editor'; +import { useI18nYooEditor } from '../context/YooptaI18nProvider'; +import { useCallback, useEffect, useState } from 'react'; -export function useTranslation() { - const editor = useYooptaEditor(); - return { t: editor.getLabelText }; +interface UseTranslationReturn { + /** + * Translates a key into the current language. + * The key format is `namespace.key`, e.g., `editor.blockOptions.delete`. + * If the translation for the key is not found, it returns the key provided. + * + * @param key - The key to translate, formatted as `namespace.key`. + * @returns The translated string or a fallback message. + */ + t: (key: string) => string; + + /** + * The current language set in the editor. + * This value updates reactively when the language is changed. + */ + currentLanguage: string; + + /** + * A list of all supported languages available in the editor. + */ + languages: string[]; + + /** + * Changes the current language to the specified value. + * Emits the event 'language-change'. + * + * @param lang - The language code to set, e.g., 'en' or 'es' or 'fr'... + */ + setLanguage: (lang: string) => void; +} + +export function useTranslation(): UseTranslationReturn { + const editor = useI18nYooEditor(); + const [currentLanguage, setCurrentLanguage] = useState(editor.language); + + const handleLanguageChange = useCallback( + (lang: string) => { + setCurrentLanguage(lang); + }, + [setCurrentLanguage], + ); + + useEffect(() => { + editor.on('language-change', handleLanguageChange); + + return () => { + editor.off('language-change', handleLanguageChange); + }; + }, []); + + return { t: editor.getLabelText, currentLanguage, setLanguage: editor.setLanguage, languages: editor.languages }; } diff --git a/packages/core/i18n/src/index.ts b/packages/core/i18n/src/index.ts index 6d458ef8c..f87455af4 100644 --- a/packages/core/i18n/src/index.ts +++ b/packages/core/i18n/src/index.ts @@ -1,2 +1,4 @@ -export { withTranslations } from './extenstion/withTranslations'; +export { withTranslations } from './extension/withTranslations'; export { useTranslation } from './hooks/useTranslation'; +export { I18nYooEditorProvider } from './context/YooptaI18nProvider'; +export * 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 index 4a3072138..73760d854 100644 --- a/packages/core/i18n/src/types/index.ts +++ b/packages/core/i18n/src/types/index.ts @@ -1,9 +1,2 @@ -import { YooEditor } from '@yoopta/editor'; - -export type I18nYooEditor = YooEditor & { - translations: Record>; - language: string; - languages: string[]; - defaultLanguage: string; - setLanguage: (lang: string) => void; -}; +export * from './i18n-yoo-editor'; +export * from './translation-options'; diff --git a/packages/core/i18n/src/types/translation-options.ts b/packages/core/i18n/src/types/translation-options.ts new file mode 100644 index 000000000..cc39f5d60 --- /dev/null +++ b/packages/core/i18n/src/types/translation-options.ts @@ -0,0 +1,7 @@ +import { I18nYooEditor } from './i18n-yoo-editor'; + +export type TranslationOptions = { + language: string; + defaultLanguage: string; + translations: I18nYooEditor['translations']; +}; diff --git a/packages/development/src/components/FixedToolbar/FixedToolbar.tsx b/packages/development/src/components/FixedToolbar/FixedToolbar.tsx index 4054b7b87..c26ce241d 100644 --- a/packages/development/src/components/FixedToolbar/FixedToolbar.tsx +++ b/packages/development/src/components/FixedToolbar/FixedToolbar.tsx @@ -8,16 +8,16 @@ import { TableCommands } from '@yoopta/table'; import { AccordionCommands } from '@yoopta/accordion'; import { TodoListCommands } from '@yoopta/lists'; import { HeadingOneCommands } from '@yoopta/headings'; -import { Blocks, YooEditor, YooptaPathIndex } from '@yoopta/editor'; -import { useTranslation } from '@yoopta/i18n'; +import { Blocks, YooptaPathIndex } from '@yoopta/editor'; +import { I18nYooEditor, useTranslation } from '@yoopta/i18n'; type Props = { - editor: YooEditor; + editor: I18nYooEditor; DEFAULT_DATA: any; }; export const FixedToolbar = ({ editor, DEFAULT_DATA }: Props) => { - const { t } = useTranslation(); + const { currentLanguage, setLanguage, languages } = useTranslation(); return (
@@ -51,7 +51,7 @@ export const FixedToolbar = ({ editor, DEFAULT_DATA }: Props) => { className="p-2 text-xs shadow-md border-r hover:bg-[#64748b] hover:text-[#fff]" > {/* Insert Image */} - {t('editor.blockOptions.turnInto')} + {editor.getLabelText('editor.blockOptions.turnInto') || 'Turn into'} -
- {editor.languages.map((lang) => { - const isCurrent = lang === editor.language; +
+ Languages +
+ {languages.map((lang) => { + const isCurrent = lang === currentLanguage; - return ( - - ); - })} + return ( + + ); + })} +
@@ -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 && } From 4b562d5a22c24e5e8dbefbf65d593f2cdd1ea92b Mon Sep 17 00:00:00 2001 From: Darginec05 Date: Sat, 1 Feb 2025 17:10:51 +0300 Subject: [PATCH 15/18] image labels are ready --- packages/core/i18n/README.md | 99 +------------------ .../i18n/src/context/YooptaI18nProvider.tsx | 46 --------- .../i18n/src/extension/withTranslations.ts | 1 + .../core/i18n/src/hooks/useTranslation.ts | 58 ----------- packages/core/i18n/src/index.ts | 1 - packages/core/i18n/src/types/index.ts | 1 + .../i18n/src/types/translation-options.ts | 7 -- .../components/FixedToolbar/FixedToolbar.tsx | 17 ++-- packages/development/src/locales/es.json | 22 ++++- packages/development/src/pages/dev/index.tsx | 30 +++--- .../image/src/ui/ImageBlockOptions.tsx | 10 +- .../plugins/image/src/ui/InputAltText.tsx | 14 ++- 12 files changed, 64 insertions(+), 242 deletions(-) delete mode 100644 packages/core/i18n/src/context/YooptaI18nProvider.tsx delete mode 100644 packages/core/i18n/src/hooks/useTranslation.ts delete mode 100644 packages/core/i18n/src/types/translation-options.ts diff --git a/packages/core/i18n/README.md b/packages/core/i18n/README.md index 7fe27429d..d0e09a7e0 100644 --- a/packages/core/i18n/README.md +++ b/packages/core/i18n/README.md @@ -11,31 +11,19 @@ yarn add @yoopta/i18n ## 🔹 Exports ```typescript -import { - withTranslations, - useTranslation, - type I18nYooEditor, - I18nYooEditorProvider, - useI18nYooEditor, -} from '@yoopta/i18n'; +import { withTranslations, type I18nYooEditor } from '@yoopta/i18n'; ``` ### **Modules** - **`withTranslations`** - Function to extend `YooEditor` with i18n support. - **`I18nYooEditor`** - Extended type for `YooEditor` with i18n properties. -- **`I18nYooEditorProvider`** - Context provider for managing the i18n-enabled editor instance (needed for `useTranslation` and `useI18nYooEditor`). -- - **`useTranslation`** - Hook for language-switching and retrieving translations by keys (requires provider). -- **`useI18nYooEditor`** - Hook for retrieving the `I18nYooEditor` instance from the provider (requires provider). --- ## 🛠 Usage -### **Option 1: Using Only `withTranslations` (No Provider Required)** - -If you only need to extend the editor with i18n support, you can use `withTranslations()` without the provider. -However, you will need to manually track language changes. +If you only need to extend the editor with i18n support, you can use `withTranslations()`. ```tsx import { withTranslations } from '@yoopta/i18n'; @@ -57,10 +45,11 @@ const editor = useMemo(() => { return withTranslations(baseEditor, { translations: TRANSLATIONS, defaultLanguage: 'en', - language: 'ru', + language: 'es', }); }, []); +// track language change if you need useEffect(() => { const handleLanguageChange = (lang: string) => { console.log('Language changed to:', lang); @@ -73,85 +62,6 @@ useEffect(() => { }, [editor]); ``` ---- - -### **Option 2: Using `I18nYooEditorProvider` (Recommended for `useTranslation`)** - -If you want to use `useTranslation()` inside your components, wrap the editor in `I18nYooEditorProvider`. - -```tsx -import { withTranslations, I18nYooEditorProvider, useTranslation } from '@yoopta/i18n'; -import { createYooptaEditor, YooptaEditor } from '@yoopta/editor'; -import { useMemo, useState, useRef } from 'react'; - -const TRANSLATION_OPTIONS = { - translations: { - es: esTranslations, - ru: ruTranslations, - cz: czTranslations, - }, - defaultLanguage: 'en', - language: 'en', -}; - -const TranslationSelector = () => { - const { currentLanguage, setLanguage, languages } = useTranslation(); - - return ( -
- Languages -
- {languages.map((lang) => { - const isCurrent = lang === currentLanguage; - - return ( - - ); - })} -
-
- ); -}; - -const BasicExample = () => { - const editor = useMemo(() => { - const baseEditor = createYooptaEditor(); - return withTranslations(baseEditor, TRANSLATION_OPTIONS); - }, []); - - const selectionRef = useRef(null); - const [value, setValue] = useState({ - 'block-1': buildBlockData({ id: 'block-1' }), - }); - - return ( - - - - - ); -}; -``` - ---- - ## 📜 **I18n API** When `withTranslations` is applied to the editor, it extends `YooEditor` with the following additional properties and methods: @@ -163,5 +73,6 @@ export type I18nYooEditor = YooEditor & { languages: string[]; defaultLanguage: string; setLanguage: (lang: string) => void; + t: (keyPath: string) => string; }; ``` diff --git a/packages/core/i18n/src/context/YooptaI18nProvider.tsx b/packages/core/i18n/src/context/YooptaI18nProvider.tsx deleted file mode 100644 index 51e879dff..000000000 --- a/packages/core/i18n/src/context/YooptaI18nProvider.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { createContext, FunctionComponent, PropsWithChildren, useContext, useRef } from 'react'; -import { createYooptaEditor } from '@yoopta/editor'; -import { I18nYooEditor, TranslationOptions } from '../types'; - -export type I18nYooEditorContext = { - editor: I18nYooEditor; - options: TranslationOptions; -}; - -const DEFAULT_HANDLERS: I18nYooEditorContext = { - editor: createYooptaEditor() as I18nYooEditor, - options: { - language: '', - defaultLanguage: '', - translations: {}, - }, -}; - -export const I18nYooEditorContext = createContext(DEFAULT_HANDLERS); - -const I18nYooEditorProvider: FunctionComponent> = ({ - children, - editor, - options, -}) => { - const contextValueRef = useRef(DEFAULT_HANDLERS); - - contextValueRef.current = { - editor, - options, - }; - - return {children}; -}; - -const useI18nYooEditor = (): I18nYooEditor => { - const context = useContext(I18nYooEditorContext); - - if (!context) { - throw new Error('useI18nYooEditor must be used within a I18nYooEditorProvider'); - } - - return context.editor; -}; - -export { I18nYooEditorProvider, useI18nYooEditor }; diff --git a/packages/core/i18n/src/extension/withTranslations.ts b/packages/core/i18n/src/extension/withTranslations.ts index 5cd27edd6..9d537aa2b 100644 --- a/packages/core/i18n/src/extension/withTranslations.ts +++ b/packages/core/i18n/src/extension/withTranslations.ts @@ -54,6 +54,7 @@ export function withTranslations( i18nEditor.translations = translations; i18nEditor.defaultLanguage = defaultLanguage; i18nEditor.language = language; + i18nEditor.t = i18nEditor.getLabelText; return i18nEditor; } diff --git a/packages/core/i18n/src/hooks/useTranslation.ts b/packages/core/i18n/src/hooks/useTranslation.ts deleted file mode 100644 index 19dbeefe5..000000000 --- a/packages/core/i18n/src/hooks/useTranslation.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { useCallback, useEffect, useState } from 'react'; -import { useYooptaEditor } from '@yoopta/editor'; -import { I18nYooEditor } from '../types'; - -interface UseTranslationReturn { - /** - * Translates a key into the current language. - * The key format is `namespace.key`, e.g., `editor.blockOptions.delete`. - * If the translation for the key is not found, it returns the key provided. - * - * @param key - The key to translate, formatted as `namespace.key`. - * @returns The translated string or a fallback message. - */ - t: (key: string) => string; - - /** - * The current language set in the editor. - * This value updates reactively when the language is changed. - */ - currentLanguage: string; - - /** - * A list of all supported languages available in the editor. - */ - languages: string[]; - - /** - * Changes the current language to the specified value. - * Emits the event 'language-change'. - * - * @param lang - The language code to set, e.g., 'en' or 'es' or 'fr'... - */ - setLanguage: (lang: string) => void; -} - -export function useTranslation(): UseTranslationReturn { - const editor = useYooptaEditor() as I18nYooEditor; - const [currentLanguage, setCurrentLanguage] = useState(editor.language); - - console.log('useTranslation', { currentLanguage, setLanguage: editor.setLanguage, languages: editor.languages }); - - const handleLanguageChange = useCallback( - (lang: string) => { - setCurrentLanguage(lang); - }, - [setCurrentLanguage], - ); - - useEffect(() => { - editor.on('language-change', handleLanguageChange); - - return () => { - editor.off('language-change', handleLanguageChange); - }; - }, []); - - return { t: editor.getLabelText, currentLanguage, setLanguage: editor.setLanguage, languages: editor.languages }; -} diff --git a/packages/core/i18n/src/index.ts b/packages/core/i18n/src/index.ts index a66cab83b..6b1b6f55b 100644 --- a/packages/core/i18n/src/index.ts +++ b/packages/core/i18n/src/index.ts @@ -1,3 +1,2 @@ export { withTranslations } from './extension/withTranslations'; -export { useTranslation } from './hooks/useTranslation'; export { I18nYooEditor, Translation } from './types'; diff --git a/packages/core/i18n/src/types/index.ts b/packages/core/i18n/src/types/index.ts index e5764a58c..67deb0e82 100644 --- a/packages/core/i18n/src/types/index.ts +++ b/packages/core/i18n/src/types/index.ts @@ -54,4 +54,5 @@ export type I18nYooEditor = YooEditor & { defaultLanguage: Keys; languages: Keys[]; setLanguage: (lang: Keys) => void; + t: (key: string) => string; }; diff --git a/packages/core/i18n/src/types/translation-options.ts b/packages/core/i18n/src/types/translation-options.ts deleted file mode 100644 index cc39f5d60..000000000 --- a/packages/core/i18n/src/types/translation-options.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { I18nYooEditor } from './i18n-yoo-editor'; - -export type TranslationOptions = { - language: string; - defaultLanguage: string; - translations: I18nYooEditor['translations']; -}; diff --git a/packages/development/src/components/FixedToolbar/FixedToolbar.tsx b/packages/development/src/components/FixedToolbar/FixedToolbar.tsx index aaf4a834c..deaf6dbd9 100644 --- a/packages/development/src/components/FixedToolbar/FixedToolbar.tsx +++ b/packages/development/src/components/FixedToolbar/FixedToolbar.tsx @@ -9,7 +9,7 @@ import { AccordionCommands } from '@yoopta/accordion'; import { TodoListCommands } from '@yoopta/lists'; import { HeadingOneCommands } from '@yoopta/headings'; import { Blocks, YooptaPathIndex } from '@yoopta/editor'; -import { I18nYooEditor, useTranslation } from '@yoopta/i18n'; +import { I18nYooEditor } from '@yoopta/i18n'; type Props = { editor: I18nYooEditor; @@ -17,10 +17,6 @@ type Props = { }; export const FixedToolbar = ({ editor, DEFAULT_DATA }: Props) => { - const { currentLanguage, setLanguage, languages = [] } = useTranslation(); - - console.log({ currentLanguage, setLanguage, languages }); - return (
@@ -53,7 +49,7 @@ export const FixedToolbar = ({ editor, DEFAULT_DATA }: Props) => { className="p-2 text-xs shadow-md border-r hover:bg-[#64748b] hover:text-[#fff]" > {/* Insert Image */} - {editor.getLabelText('editor.blockOptions.turnInto') || 'Turn into'} + {editor.t('editor.blockOptions.turnInto') || 'Turn into'}
Languages
- {languages.map((lang) => { - const isCurrent = lang === currentLanguage; + {editor.languages.map((lang) => { + const isCurrent = lang === editor.language; return ( diff --git a/packages/development/src/locales/es.json b/packages/development/src/locales/es.json index faa252fb3..88b15ef49 100644 --- a/packages/development/src/locales/es.json +++ b/packages/development/src/locales/es.json @@ -1,4 +1,9 @@ { + "toggle": { + "into": { + "blockquote": "Convertir en cita" + } + }, "plugins": { "Paragraph": { "display": { @@ -57,11 +62,26 @@ "description": "Imagen" }, "options": { + "placeholder": { + "tabs": { + "upload": "" + } + }, "fit": { "contain": "Contener", "cover": "Cubrir", "fill": "Rellenar" - } + }, + "alt": { + "title": "Texto alternativo", + "label": "Texto alternativo", + "placeholder": "Editar texto alternativo", + "delete": "Eliminar texto alternativo", + "save": "Guardar" + }, + "replaceImage": "Reemplazar", + "downloadImage": "Download es", + "align": "Aligniore" } }, "Video": { diff --git a/packages/development/src/pages/dev/index.tsx b/packages/development/src/pages/dev/index.tsx index e292357f0..20c05be88 100644 --- a/packages/development/src/pages/dev/index.tsx +++ b/packages/development/src/pages/dev/index.tsx @@ -56,22 +56,20 @@ const BasicExample = () => { return ( <>
- - - - + +
); diff --git a/packages/plugins/image/src/ui/ImageBlockOptions.tsx b/packages/plugins/image/src/ui/ImageBlockOptions.tsx index b7dfc4fd2..9a71684bb 100644 --- a/packages/plugins/image/src/ui/ImageBlockOptions.tsx +++ b/packages/plugins/image/src/ui/ImageBlockOptions.tsx @@ -197,6 +197,7 @@ const ImageBlockOptions = ({ editor, block, props: imageProps }: Props) => { refs={refs} onDelete={onDeleteAltText} onSave={onSaveAltText} + editor={editor} /> )} @@ -207,7 +208,7 @@ const ImageBlockOptions = ({ editor, block, props: imageProps }: Props) => { onClick={() => setIsAltTextOpen(true)} > - Alt text + {editor.getLabelText('plugins.Image.options.alt.title') || 'Alt text'} @@ -230,7 +231,7 @@ const ImageBlockOptions = ({ editor, block, props: imageProps }: Props) => { ) : ( )} - Replace image + {editor.getLabelText('plugins.Image.options.replaceImage') || 'Replace image'} @@ -243,13 +244,14 @@ 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/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'}
From 9c87dbeb153b48b51a2735d1377b4da5c71fedd9 Mon Sep 17 00:00:00 2001 From: Darginec05 Date: Wed, 26 Feb 2025 21:28:30 +0100 Subject: [PATCH 16/18] added translations for image placeholders --- packages/development/src/locales/es.json | 47 ++++++++++++++----- .../plugins/image/src/ui/EmbedUploader.tsx | 6 ++- .../plugins/image/src/ui/FileUploader.tsx | 2 +- .../image/src/ui/ImageBlockOptions.tsx | 4 -- .../plugins/image/src/ui/ImageUploader.tsx | 9 ++-- packages/plugins/image/src/ui/Placeholder.tsx | 9 +++- 6 files changed, 54 insertions(+), 23 deletions(-) diff --git a/packages/development/src/locales/es.json b/packages/development/src/locales/es.json index 88b15ef49..8c7fda29e 100644 --- a/packages/development/src/locales/es.json +++ b/packages/development/src/locales/es.json @@ -1,9 +1,4 @@ { - "toggle": { - "into": { - "blockquote": "Convertir en cita" - } - }, "plugins": { "Paragraph": { "display": { @@ -62,9 +57,19 @@ "description": "Imagen" }, "options": { + "replaceImage": "Reemplazar", + "downloadImage": "Descargar", + "align": "Aligniore", "placeholder": { - "tabs": { - "upload": "" + "title": "Título de la imagen", + "loading": "Cargando", + "upload": { + "label": "Subir imagen" + }, + "external": { + "label": "URL de la imagen", + "placeholder": "Introduce la URL de la imagen", + "button": "Insertar" } }, "fit": { @@ -78,16 +83,36 @@ "placeholder": "Editar texto alternativo", "delete": "Eliminar texto alternativo", "save": "Guardar" - }, - "replaceImage": "Reemplazar", - "downloadImage": "Download es", - "align": "Aligniore" + } } }, "Video": { "display": { "title": "Vídeo", "description": "Vídeo" + }, + "options": { + "replaceVideo": "Reemplazar", + "downloadVideo": "Descargar", + "addPosterImage": "Añadir imagen de portada", + "align": "Aligniore", + "fit": { + "contain": "Contener", + "cover": "Cubrir", + "fill": "Rellenar" + }, + "placeholder": { + "title": "Título del vídeo", + "loading": "Cargando", + "upload": { + "label": "Subir vídeo" + }, + "external": { + "label": "URL del vídeo", + "placeholder": "Introduce la URL del vídeo", + "button": "Insertar" + } + } } }, "File": { 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 9a71684bb..2a23649f6 100644 --- a/packages/plugins/image/src/ui/ImageBlockOptions.tsx +++ b/packages/plugins/image/src/ui/ImageBlockOptions.tsx @@ -153,7 +153,6 @@ const ImageBlockOptions = ({ editor, block, props: imageProps }: Props) => { 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/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} From 40141ca97702c91ed3833f3d8e47807260ce0a26 Mon Sep 17 00:00:00 2001 From: Darginec05 Date: Sat, 1 Mar 2025 19:52:12 +0100 Subject: [PATCH 17/18] media blocks are done with translations --- packages/development/src/locales/es.json | 67 ++++++++++++++++++- .../plugins/code/src/ui/CodeBlockOptions.tsx | 9 +-- .../embed/src/ui/EmbedBlockOptions.tsx | 4 +- .../embed/src/ui/EmbedLinkUploader.tsx | 6 +- .../plugins/embed/src/ui/EmbedUploader.tsx | 7 +- packages/plugins/embed/src/ui/Placeholder.tsx | 7 +- .../plugins/file/src/ui/FileBlockOptions.tsx | 4 +- .../file/src/ui/FilePlaceholderUploader.tsx | 7 +- packages/plugins/file/src/ui/FileUploader.tsx | 2 +- packages/plugins/file/src/ui/Placeholder.tsx | 9 ++- .../src/components/TableColumnOptions.tsx | 15 +++-- .../table/src/components/TableRowOptions.tsx | 10 +-- .../plugins/video/src/ui/EmbedUploader.tsx | 6 +- .../plugins/video/src/ui/FileUploader.tsx | 2 +- packages/plugins/video/src/ui/Placeholder.tsx | 9 ++- .../video/src/ui/VideoBlockOptions.tsx | 19 +++--- .../plugins/video/src/ui/VideoUploader.tsx | 9 +-- 17 files changed, 145 insertions(+), 47 deletions(-) diff --git a/packages/development/src/locales/es.json b/packages/development/src/locales/es.json index 8c7fda29e..347b504f1 100644 --- a/packages/development/src/locales/es.json +++ b/packages/development/src/locales/es.json @@ -94,7 +94,9 @@ "options": { "replaceVideo": "Reemplazar", "downloadVideo": "Descargar", - "addPosterImage": "Añadir imagen de portada", + "addPosterVideo": "Añadir imagen de portada", + "replacePosterVideo": "Reemplazar imagen de portada", + "openVideo": "Abrir imagen", "align": "Aligniore", "fit": { "contain": "Contener", @@ -119,6 +121,47 @@ "display": { "title": "Archivo", "description": "Archivo para descargar" + }, + "options": { + "replaceFile": "Reemplazar", + "downloadFile": "Descargar", + "openFile": "Abrir archivo", + "align": "Aligniore", + "placeholder": { + "title": "Título del archivo", + "loading": "Cargando", + "upload": { + "label": "Subir archivo" + }, + "external": { + "label": "URL del archivo", + "placeholder": "Introduce la URL del archivo", + "button": "Insertar" + } + } + } + }, + "Embed": { + "display": { + "title": "Incrustar", + "description": "Incrustar" + }, + "options": { + "replaceEmbed": "Reemplazar", + "openEmbed": "Abrir incrustado", + "align": "Aligniore", + "placeholder": { + "title": "Título del incrustado", + "loading": "Cargando", + "upload": { + "label": "Subir incrustado" + }, + "external": { + "label": "URL del incrustado", + "placeholder": "Introduce la URL del incrustado", + "button": "Insertar" + } + } } }, "Table": { @@ -128,13 +171,33 @@ }, "options": { "toggleHeaderRow": "Alternar fila de encabezado", - "toggleHeaderColumn": "Alternar columna de encabezado" + "toggleHeaderColumn": "Alternar columna de encabezado", + "rowOptions": { + "insertAbove": "Insertar fila arriba", + "insertBelow": "Insertar fila abajo", + "moveUp": "Mover fila arriba", + "moveDown": "Mover fila abajo", + "delete": "Eliminar fila" + }, + "columnOptions": { + "insertLeft": "Insertar columna a la izquierda", + "insertRight": "Insertar columna a la derecha", + "moveLeft": "Mover columna a la izquierda", + "moveRight": "Mover columna a la derecha", + "delete": "Eliminar columna" + } } }, "Code": { "display": { "title": "Código", "description": "Bloque de código" + }, + "options": { + "language": "Lenguaje", + "theme": "Tema", + "copyCode": "Copiar código", + "copyCodeSuccess": "Código copiado" } }, "Divider": { diff --git a/packages/plugins/code/src/ui/CodeBlockOptions.tsx b/packages/plugins/code/src/ui/CodeBlockOptions.tsx index 5f6472e3a..5af50c64e 100644 --- a/packages/plugins/code/src/ui/CodeBlockOptions.tsx +++ b/packages/plugins/code/src/ui/CodeBlockOptions.tsx @@ -27,7 +27,6 @@ export const CodeBlockOptions = ({ block, editor, element }: Props) => { // We change it directly in the block because this plugin doesn't have Slate instance // because it's a plugin with custom editor editor.updateBlock(block.id, { value: [{ ...element, props: { ...element.props, theme } }] }); - // editor.applyChanges(); }; const onChangeLanguage = (language: string) => { @@ -53,7 +52,9 @@ export const CodeBlockOptions = ({ block, editor, element }: Props) => { @@ -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/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'}
From faf36dbe14a361b35e45cd75585cc70ca0556f1e Mon Sep 17 00:00:00 2001 From: Darginec05 Date: Sat, 12 Apr 2025 18:05:35 +0200 Subject: [PATCH 18/18] working on fixing types --- packages/core/editor/src/editor/index.tsx | 6 +++--- packages/core/editor/src/editor/types.ts | 6 ++++++ packages/core/i18n/src/types/index.ts | 2 +- .../src/components/FixedToolbar/FixedToolbar.tsx | 5 ++--- packages/development/src/pages/dev/index.tsx | 2 +- packages/development/src/yoopta.d.ts | 8 ++++++++ 6 files changed, 21 insertions(+), 8 deletions(-) create mode 100644 packages/development/src/yoopta.d.ts diff --git a/packages/core/editor/src/editor/index.tsx b/packages/core/editor/src/editor/index.tsx index 548107ca6..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'; @@ -37,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: {}, diff --git a/packages/core/editor/src/editor/types.ts b/packages/core/editor/src/editor/types.ts index 8b6bfc4c2..4a27e68f5 100644 --- a/packages/core/editor/src/editor/types.ts +++ b/packages/core/editor/src/editor/types.ts @@ -180,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/i18n/src/types/index.ts b/packages/core/i18n/src/types/index.ts index 67deb0e82..886ef05ec 100644 --- a/packages/core/i18n/src/types/index.ts +++ b/packages/core/i18n/src/types/index.ts @@ -48,7 +48,7 @@ export type Translation

[] = Plugin<{}>[]> = { export type Translations = Record; -export type I18nYooEditor = YooEditor & { +export type I18nYooEditor = { translations: Translations; language: Keys; defaultLanguage: Keys; diff --git a/packages/development/src/components/FixedToolbar/FixedToolbar.tsx b/packages/development/src/components/FixedToolbar/FixedToolbar.tsx index deaf6dbd9..aec9a8e7e 100644 --- a/packages/development/src/components/FixedToolbar/FixedToolbar.tsx +++ b/packages/development/src/components/FixedToolbar/FixedToolbar.tsx @@ -8,11 +8,10 @@ import { TableCommands } from '@yoopta/table'; import { AccordionCommands } from '@yoopta/accordion'; import { TodoListCommands } from '@yoopta/lists'; import { HeadingOneCommands } from '@yoopta/headings'; -import { Blocks, YooptaPathIndex } from '@yoopta/editor'; -import { I18nYooEditor } from '@yoopta/i18n'; +import { Blocks, YooEditor, YooptaPathIndex } from '@yoopta/editor'; type Props = { - editor: I18nYooEditor; + editor: YooEditor; DEFAULT_DATA: any; }; diff --git a/packages/development/src/pages/dev/index.tsx b/packages/development/src/pages/dev/index.tsx index 20c05be88..7b4f2f6f5 100644 --- a/packages/development/src/pages/dev/index.tsx +++ b/packages/development/src/pages/dev/index.tsx @@ -34,7 +34,7 @@ const TRANSLATION_OPTIONS = { }; const BasicExample = () => { - const editor: I18nYooEditor = useMemo(() => { + const editor = useMemo(() => { const baseEditor = createYooptaEditor(); return withTranslations(baseEditor, { translations: TRANSLATIONS, diff --git a/packages/development/src/yoopta.d.ts b/packages/development/src/yoopta.d.ts new file mode 100644 index 000000000..41b4da57c --- /dev/null +++ b/packages/development/src/yoopta.d.ts @@ -0,0 +1,8 @@ +import { YooEditor } from '@yoopta/editor'; +import { I18nYooEditor } from '@yoopta/i18n'; + +declare module '@yoopta/editor' { + interface CustomTypes { + Editor: YooEditor & I18nYooEditor; + } +}