diff --git a/app/components/Editor.tsx b/app/components/Editor.tsx index ad14209ce0..e270c01ee2 100644 --- a/app/components/Editor.tsx +++ b/app/components/Editor.tsx @@ -3,6 +3,7 @@ import { observer } from "mobx-react"; import { DOMParser as ProsemirrorDOMParser } from "prosemirror-model"; import { TextSelection } from "prosemirror-state"; import * as React from "react"; +import { useTranslation } from "react-i18next"; import { toast } from "sonner"; import { mergeRefs } from "react-merge-refs"; import type { Optional } from "utility-types"; @@ -16,7 +17,6 @@ import ClickablePadding from "~/components/ClickablePadding"; import ErrorBoundary from "~/components/ErrorBoundary"; import type { Props as EditorProps, Editor as SharedEditor } from "~/editor"; import useCurrentUser from "~/hooks/useCurrentUser"; -import useDictionary from "~/hooks/useDictionary"; import useEditorClickHandlers from "~/hooks/useEditorClickHandlers"; import useEmbeds from "~/hooks/useEmbeds"; import useStores from "~/hooks/useStores"; @@ -28,12 +28,7 @@ const LazyLoadedEditor = lazyWithRetry(() => import("~/editor")); export type Props = Optional< EditorProps, - | "placeholder" - | "defaultValue" - | "onClickLink" - | "embeds" - | "dictionary" - | "extensions" + "placeholder" | "defaultValue" | "onClickLink" | "embeds" | "extensions" > & { embedsDisabled?: boolean; onSynced?: () => Promise; @@ -52,7 +47,7 @@ function Editor(props: Props, ref: React.RefObject | null) { } = props; const { comments } = useStores(); const { shareId } = useShare(); - const dictionary = useDictionary(); + const { t } = useTranslation(); const embeds = useEmbeds(!shareId); const localRef = React.useRef(); const preferences = useCurrentUser({ rejectOnEmpty: false })?.preferences; @@ -95,11 +90,11 @@ function Editor(props: Props, ref: React.RefObject | null) { const handleFileUploadStart = React.useCallback(() => { uploadState.current.timeoutId = setTimeout(() => { uploadState.current.toastId = toast.loading( - dictionary.uploadingWithProgress(0) + t("Uploading… {{ progress }}%", { progress: 0 }) ); }, 2000); onFileUploadStart?.(); - }, [onFileUploadStart, dictionary]); + }, [onFileUploadStart, t]); const handleFileUploadProgress = React.useCallback( (fileId: string, fractionComplete: number) => { @@ -113,12 +108,12 @@ function Editor(props: Props, ref: React.RefObject | null) { // Update toast if visible if (uploadState.current.toastId) { - toast.loading(dictionary.uploadingWithProgress(percent), { + toast.loading(t("Uploading… {{ progress }}%", { progress: percent }), { id: uploadState.current.toastId, }); } }, - [dictionary] + [t] ); const handleFileUploadStop = React.useCallback(() => { @@ -183,7 +178,6 @@ function Editor(props: Props, ref: React.RefObject | null) { onFileUploadStart: handleFileUploadStart, onFileUploadStop: handleFileUploadStop, onFileUploadProgress: handleFileUploadProgress, - dictionary, isAttachment, }); }, @@ -192,7 +186,6 @@ function Editor(props: Props, ref: React.RefObject | null) { handleFileUploadStart, handleFileUploadStop, handleFileUploadProgress, - dictionary, handleUploadFile, ] ); @@ -289,7 +282,6 @@ function Editor(props: Props, ref: React.RefObject | null) { uploadFile={handleUploadFile} embeds={embeds} userPreferences={preferences} - dictionary={dictionary} {...props} onClickLink={handleClickLink} onChange={handleChange} diff --git a/app/editor/components/BlockMenu.tsx b/app/editor/components/BlockMenu.tsx index 6b007cd840..f093098262 100644 --- a/app/editor/components/BlockMenu.tsx +++ b/app/editor/components/BlockMenu.tsx @@ -8,7 +8,6 @@ import type { MenuItem } from "@shared/editor/types"; import { ProsemirrorHelper } from "@shared/utils/ProsemirrorHelper"; import { TextHelper } from "@shared/utils/TextHelper"; import useCurrentUser from "~/hooks/useCurrentUser"; -import useDictionary from "~/hooks/useDictionary"; import useStores from "~/hooks/useStores"; import getMenuItems from "../menus/block"; import { useEditor } from "./EditorContext"; @@ -108,19 +107,19 @@ type Props = Omit & Required>; function BlockMenu(props: Props) { - const dictionary = useDictionary(); + const { t } = useTranslation(); const { elementRef } = useEditor(); const templateMenuItem = useTemplateMenuItem(); const items = useMemo(() => { - const baseItems = getMenuItems(dictionary, elementRef); + const baseItems = getMenuItems(t, elementRef); if (!templateMenuItem) { return baseItems; } return [...baseItems, { name: "separator" } as MenuItem, templateMenuItem]; - }, [dictionary, elementRef, templateMenuItem]); + }, [t, elementRef, templateMenuItem]); const renderMenuItem = useCallback( (item, _index, options) => ( diff --git a/app/editor/components/LinkEditor.tsx b/app/editor/components/LinkEditor.tsx index cbfadd4232..9dc84cdbd8 100644 --- a/app/editor/components/LinkEditor.tsx +++ b/app/editor/components/LinkEditor.tsx @@ -10,6 +10,7 @@ import type { Mark } from "prosemirror-model"; import type { EditorView } from "prosemirror-view"; import * as React from "react"; import { useEffect, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; import styled from "styled-components"; import Icon from "@shared/components/Icon"; import { hideScrollbars, s } from "@shared/styles"; @@ -18,7 +19,6 @@ import DocumentBreadcrumb from "~/components/DocumentBreadcrumb"; import Flex from "~/components/Flex"; import { ResizingHeightContainer } from "~/components/ResizingHeightContainer"; import Scrollable from "~/components/Scrollable"; -import type { Dictionary } from "~/hooks/useDictionary"; import useRequest from "~/hooks/useRequest"; import useStores from "~/hooks/useStores"; import { client } from "~/utils/ApiClient"; @@ -31,7 +31,6 @@ import { useEditor } from "./EditorContext"; type Props = { mark?: Mark; - dictionary: Dictionary; view: EditorView; autoFocus?: boolean; onLinkAdd: () => void; @@ -44,7 +43,6 @@ type Props = { const LinkEditor: React.FC = ({ mark, - dictionary, view, autoFocus, onLinkAdd, @@ -54,6 +52,7 @@ const LinkEditor: React.FC = ({ onClickOutside, onClickBack, }) => { + const { t } = useTranslation(); const getHref = () => sanitizeUrl(mark?.attrs.href) ?? ""; const initialValue = getHref(); const { commands } = useEditor(); @@ -183,21 +182,21 @@ const LinkEditor: React.FC = ({ const isInternal = isInternalUrl(query); const actions = [ { - tooltip: isInternal ? dictionary.goToLink : dictionary.openLink, + tooltip: isInternal ? t("Go to link") : t("Open link"), icon: isInternal ? : , visible: true, disabled: !query, handler: openLink, }, { - tooltip: dictionary.removeLink, + tooltip: t("Remove link"), icon: , visible: view.editable, disabled: false, handler: removeLink, }, { - tooltip: dictionary.formattingControls, + tooltip: t("Formatting controls"), icon: , visible: view.editable, disabled: false, @@ -211,7 +210,7 @@ const LinkEditor: React.FC = ({ void; onLinkRemove: () => void; @@ -25,12 +24,12 @@ type Props = { export function MediaLinkEditor({ node, view, - dictionary, onLinkUpdate, onLinkRemove, onEscape, onClickOutside, }: Props) { + const { t } = useTranslation(); const url = (node?.attrs.href ?? node?.attrs.src) as string; const [localUrl, setLocalUrl] = useState(url); const wrapperRef = useRef(null); @@ -111,12 +110,12 @@ export function MediaLinkEditor({ setLocalUrl(e.target.value)} onKeyDown={handleKeyDown} readOnly={!view.editable} /> - + @@ -124,9 +123,7 @@ export function MediaLinkEditor({ {view.editable && ( diff --git a/app/editor/components/SelectionToolbar.tsx b/app/editor/components/SelectionToolbar.tsx index c566c2a128..7a25587329 100644 --- a/app/editor/components/SelectionToolbar.tsx +++ b/app/editor/components/SelectionToolbar.tsx @@ -2,6 +2,7 @@ import type { EditorState, Selection } from "prosemirror-state"; import Suggestion from "~/editor/extensions/Suggestion"; import { NodeSelection, TextSelection } from "prosemirror-state"; import * as React from "react"; +import { useTranslation } from "react-i18next"; import filterExcessSeparators from "@shared/editor/lib/filterExcessSeparators"; import { getMarkRange, @@ -17,7 +18,6 @@ import { } from "@shared/editor/queries/table"; import type { MenuItem } from "@shared/editor/types"; import useBoolean from "~/hooks/useBoolean"; -import useDictionary from "~/hooks/useDictionary"; import useEventListener from "~/hooks/useEventListener"; import useMobile from "~/hooks/useMobile"; import getAttachmentMenuItems from "../menus/attachment"; @@ -82,7 +82,7 @@ enum Toolbar { export function SelectionToolbar(props: Props) { const { readOnly = false } = props; const { view, extensions, commands } = useEditor(); - const dictionary = useDictionary(); + const { t } = useTranslation(); const menuRef = React.useRef(null); const isMobile = useMobile(); const isActive = props.isActive || isMobile; @@ -244,32 +244,32 @@ export function SelectionToolbar(props: Props) { isCodeSelection && (selection.empty || selection instanceof NodeSelection) ) { - items = getCodeMenuItems(state, readOnly, dictionary); + items = getCodeMenuItems(state, readOnly, t); align = "end"; } else if (isTableSelected(state)) { - items = getTableMenuItems(state, readOnly, dictionary); + items = getTableMenuItems(state, readOnly, t); } else if (colIndex !== undefined) { - items = getTableColMenuItems(state, readOnly, dictionary, { + items = getTableColMenuItems(state, readOnly, t, { index: colIndex, rtl, }); } else if (rowIndex !== undefined) { - items = getTableRowMenuItems(state, readOnly, dictionary, { + items = getTableRowMenuItems(state, readOnly, t, { index: rowIndex, }); } else if (isImageSelection) { - items = getImageMenuItems(state, readOnly, dictionary); + items = getImageMenuItems(state, readOnly, t); } else if (isAttachmentSelection) { - items = getAttachmentMenuItems(state, readOnly, dictionary); + items = getAttachmentMenuItems(state, readOnly, t); } else if (isDividerSelection) { - items = getDividerMenuItems(state, readOnly, dictionary); + items = getDividerMenuItems(state, readOnly, t); } else if (readOnly) { - items = getReadOnlyMenuItems(state, !!canUpdate, dictionary); + items = getReadOnlyMenuItems(state, !!canUpdate, t); } else if (isNoticeSelection && selection.empty) { - items = getNoticeMenuItems(state, readOnly, dictionary); + items = getNoticeMenuItems(state, readOnly, t); align = "end"; } else { - items = getFormattingMenuItems(state, isTemplate, dictionary); + items = getFormattingMenuItems(state, isTemplate, t); } // Some extensions may be disabled, remove corresponding items @@ -332,7 +332,6 @@ export function SelectionToolbar(props: Props) { {activeToolbar === Toolbar.Link ? ( setActiveToolbar(null)} onLinkRemove={() => setActiveToolbar(null)} onEscape={() => setActiveToolbar(Toolbar.Menu)} diff --git a/app/editor/components/SuggestionsMenu.tsx b/app/editor/components/SuggestionsMenu.tsx index 93f180ef97..3860074e82 100644 --- a/app/editor/components/SuggestionsMenu.tsx +++ b/app/editor/components/SuggestionsMenu.tsx @@ -27,7 +27,6 @@ import { } from "~/components/primitives/Popover"; import { MouseSafeArea } from "~/components/MouseSafeArea"; import Scrollable from "~/components/Scrollable"; -import useDictionary from "~/hooks/useDictionary"; import useMobile from "~/hooks/useMobile"; import Logger from "~/utils/Logger"; import { useEditor } from "./EditorContext"; @@ -63,7 +62,6 @@ export type Props = { function SuggestionsMenu(props: Props) { const { view, commands, props: editorProps } = useEditor(); - const dictionary = useDictionary(); const { t } = useTranslation(); const isMobile = useMobile(); const pointerRef = React.useRef<{ clientX: number; clientY: number }>({ @@ -301,7 +299,7 @@ function SuggestionsMenu(props: Props) { const matches = "matcher" in insertItem && insertItem.matcher(href); if (!matches) { - toast.error(dictionary.embedInvalidLink); + toast.error(t("Sorry, that link won’t work for this embed type")); return; } @@ -390,7 +388,6 @@ function SuggestionsMenu(props: Props) { onFileUploadStart, onFileUploadStop, onFileUploadProgress, - dictionary, isAttachment: inputRef.current?.accept === "*", attrs, }); @@ -901,7 +898,7 @@ function SuggestionsMenu(props: Props) { })} {items.length === 0 && ( - {dictionary.noResults} + {t("No results")} )} @@ -925,8 +922,10 @@ function SuggestionsMenu(props: Props) { "placeholder" in insertItem && !!insertItem.placeholder ? insertItem.placeholder : insertItem.title - ? dictionary.pasteLinkWithTitle(insertItem.title) - : dictionary.pasteLink + ? t("Paste a {{service}} link…", { + service: insertItem.title, + }) + : `${t("Paste a link")}…` } onKeyDown={handleLinkInputKeydown} onPaste={handleLinkInputPaste} @@ -975,8 +974,10 @@ function SuggestionsMenu(props: Props) { "placeholder" in insertItem && !!insertItem.placeholder ? insertItem.placeholder : insertItem.title - ? dictionary.pasteLinkWithTitle(insertItem.title) - : dictionary.pasteLink + ? t("Paste a {{service}} link…", { + service: insertItem.title, + }) + : `${t("Paste a link")}…` } onKeyDown={handleLinkInputKeydown} onPaste={handleLinkInputPaste} diff --git a/app/editor/extensions/BlockMenu.tsx b/app/editor/extensions/BlockMenu.tsx index 59f3561c5d..bc3fedaca7 100644 --- a/app/editor/extensions/BlockMenu.tsx +++ b/app/editor/extensions/BlockMenu.tsx @@ -1,3 +1,4 @@ +import { t } from "i18next"; import { action } from "mobx"; import { PlusIcon } from "outline-icons"; import { Plugin } from "prosemirror-state"; @@ -6,20 +7,10 @@ import ReactDOM from "react-dom"; import type { WidgetProps } from "@shared/editor/lib/Extension"; import { PlaceholderPlugin } from "@shared/editor/plugins/PlaceholderPlugin"; import { findParentNode } from "@shared/editor/queries/findParentNode"; -import type { Dictionary } from "~/hooks/useDictionary"; -import type { SuggestionOptions } from "~/editor/extensions/Suggestion"; import Suggestion from "~/editor/extensions/Suggestion"; import BlockMenu from "../components/BlockMenu"; -/** - * Options for the BlockMenu extension. - */ -type BlockMenuOptions = SuggestionOptions & { - /** A dictionary of translated strings used in the editor. */ - dictionary: Dictionary; -}; - -export default class BlockMenuExtension extends Suggestion { +export default class BlockMenuExtension extends Suggestion { get defaultOptions() { return { trigger: "/", @@ -90,14 +81,14 @@ export default class BlockMenuExtension extends Suggestion { !!textContent && node.childCount === 0 && node.textContent === "", - text: this.options.dictionary.newLineEmpty, + text: `${t("Type '/' to insert")}…`, }, { condition: ({ node, $start, state }) => $start.depth === 1 && state.selection.$from.pos === $start.pos + node.content.size && node.textContent === "/", - text: ` ${this.options.dictionary.newLineWithSlash}`, + text: ` ${t("Keep typing to filter")}…`, }, ]), ]; diff --git a/app/editor/index.tsx b/app/editor/index.tsx index abb547f3c9..b7bb4f6635 100644 --- a/app/editor/index.tsx +++ b/app/editor/index.tsx @@ -46,7 +46,6 @@ import EventEmitter from "@shared/utils/events"; import type Document from "~/models/Document"; import Flex from "~/components/Flex"; import { PortalContext } from "~/components/Portal"; -import type { Dictionary } from "~/hooks/useDictionary"; import type { Properties } from "~/types"; import Logger from "~/utils/Logger"; import ComponentView from "./components/ComponentView"; @@ -89,8 +88,6 @@ export type Props = { canUpdate?: boolean; /** If the editor should still allow commenting when it is readOnly */ canComment?: boolean; - /** A dictionary of translated strings used in the editor */ - dictionary: Dictionary; /** The reading direction of the text content, if known */ dir?: "rtl" | "ltr"; /** If the editor should vertically grow to fill available space */ diff --git a/app/editor/menus/attachment.tsx b/app/editor/menus/attachment.tsx index fc54cbc738..359e40171b 100644 --- a/app/editor/menus/attachment.tsx +++ b/app/editor/menus/attachment.tsx @@ -1,13 +1,13 @@ +import type { TFunction } from "i18next"; import { TrashIcon, DownloadIcon, ReplaceIcon, PDFIcon } from "outline-icons"; import type { EditorState } from "prosemirror-state"; import type { MenuItem } from "@shared/editor/types"; -import type { Dictionary } from "~/hooks/useDictionary"; import { isNodeActive } from "@shared/editor/queries/isNodeActive"; export default function attachmentMenuItems( state: EditorState, readOnly: boolean, - dictionary: Dictionary + t: TFunction ): MenuItem[] { if (readOnly) { return []; @@ -24,17 +24,17 @@ export default function attachmentMenuItems( return [ { name: "replaceAttachment", - tooltip: dictionary.replaceAttachment, + tooltip: t("Replace file"), icon: , }, { name: "deleteAttachment", - tooltip: dictionary.deleteAttachment, + tooltip: t("Delete file"), icon: , }, { name: "toggleAttachmentPreview", - tooltip: dictionary.previewAttachment, + tooltip: t("Show preview"), icon: , active: isAttachmentWithPreview, visible: isPdfAttachment(state), @@ -44,7 +44,7 @@ export default function attachmentMenuItems( }, { name: "dimensions", - tooltip: dictionary.dimensions, + tooltip: `${t("Width")} × ${t("Height")}`, visible: isAttachmentWithPreview(state), skipIcon: true, }, @@ -53,7 +53,7 @@ export default function attachmentMenuItems( }, { name: "downloadAttachment", - label: dictionary.download, + label: t("Download"), icon: , visible: !!fetch, }, diff --git a/app/editor/menus/block.tsx b/app/editor/menus/block.tsx index 98aa7405e0..0ed9f5bea6 100644 --- a/app/editor/menus/block.tsx +++ b/app/editor/menus/block.tsx @@ -26,10 +26,10 @@ import { } from "outline-icons"; import * as React from "react"; import styled from "styled-components"; +import type { TFunction } from "i18next"; import Image from "@shared/editor/components/Img"; import type { MenuItem } from "@shared/editor/types"; import { metaDisplay } from "@shared/utils/keyboard"; -import type { Dictionary } from "~/hooks/useDictionary"; import Desktop from "~/utils/Desktop"; const Img = styled(Image)` @@ -42,7 +42,7 @@ const Img = styled(Image)` `; export default function blockMenuItems( - dictionary: Dictionary, + t: TFunction, documentRef: React.RefObject ): MenuItem[] { const documentWidth = documentRef.current?.clientWidth ?? 0; @@ -50,7 +50,7 @@ export default function blockMenuItems( const items = [ { name: "heading", - title: dictionary.h1, + title: t("Big heading"), keywords: "h1 heading1 title", icon: , shortcut: "^ ⇧ 1", @@ -58,7 +58,7 @@ export default function blockMenuItems( }, { name: "heading", - title: dictionary.h2, + title: t("Medium heading"), keywords: "h2 heading2", icon: , shortcut: "^ ⇧ 2", @@ -66,7 +66,7 @@ export default function blockMenuItems( }, { name: "heading", - title: dictionary.h3, + title: t("Small heading"), keywords: "h3 heading3", icon: , shortcut: "^ ⇧ 3", @@ -74,7 +74,7 @@ export default function blockMenuItems( }, { name: "heading", - title: dictionary.h4, + title: t("Extra small heading"), keywords: "h4 heading4", icon: , shortcut: "^ ⇧ 4", @@ -85,20 +85,20 @@ export default function blockMenuItems( }, { name: "checkbox_list", - title: dictionary.checkboxList, + title: t("Todo list"), icon: , keywords: "checklist checkbox task", shortcut: "^ ⇧ 7", }, { name: "bullet_list", - title: dictionary.bulletList, + title: t("Bulleted list"), icon: , shortcut: "^ ⇧ 8", }, { name: "ordered_list", - title: dictionary.orderedList, + title: t("Ordered list"), icon: , shortcut: "^ ⇧ 9", }, @@ -107,19 +107,19 @@ export default function blockMenuItems( }, { name: "image", - title: dictionary.image, + title: t("Image"), icon: , keywords: "picture photo", }, { name: "video", - title: dictionary.video, + title: t("Video"), icon: , keywords: "mov avi upload player", }, { name: "attachment", - title: dictionary.pdf, + title: t("Embed PDF"), icon: , keywords: "pdf upload attach", attrs: { @@ -131,13 +131,13 @@ export default function blockMenuItems( }, { name: "attachment", - title: dictionary.file, + title: t("File attachment"), icon: , keywords: "file upload attach", }, { name: "table", - title: dictionary.table, + title: t("Table"), icon: , attrs: { rowsCount: 3, @@ -147,59 +147,59 @@ export default function blockMenuItems( }, { name: "blockquote", - title: dictionary.quote, + title: t("Quote"), icon: , keywords: "blockquote pullquote", shortcut: `${metaDisplay} ]`, }, { name: "code_block", - title: dictionary.codeBlock, + title: t("Code block"), icon: , shortcut: "^ ⇧ c", keywords: "script", }, { name: "math_block", - title: dictionary.mathBlock, + title: t("Math block (LaTeX)"), icon: , keywords: "math katex latex", }, { name: "container_toggle", - title: dictionary.toggleBlock, + title: t("Toggle block"), icon: , keywords: "toggle collapsible collapse fold", }, { name: "hr", - title: dictionary.hr, + title: t("Divider"), icon: , shortcut: `${metaDisplay} _`, keywords: "horizontal rule break line", }, { name: "hr", - title: dictionary.pageBreak, + title: t("Page break"), icon: , keywords: "page print break line", attrs: { markup: "***" }, }, { name: "date", - title: dictionary.insertDate, + title: t("Current date"), keywords: "clock today", icon: , }, { name: "time", - title: dictionary.insertTime, + title: t("Current time"), keywords: "clock now", icon: , }, { name: "datetime", - title: dictionary.insertDateTime, + title: t("Current date and time"), keywords: "clock today date", icon: , }, @@ -208,28 +208,28 @@ export default function blockMenuItems( }, { name: "container_notice", - title: dictionary.infoNotice, + title: t("Info notice"), icon: , keywords: "notice card information", attrs: { style: "info" }, }, { name: "container_notice", - title: dictionary.successNotice, + title: t("Success notice"), icon: , keywords: "notice card success", attrs: { style: "success" }, }, { name: "container_notice", - title: dictionary.warningNotice, + title: t("Warning notice"), icon: , keywords: "notice card error", attrs: { style: "warning" }, }, { name: "container_notice", - title: dictionary.tipNotice, + title: t("Tip notice"), icon: , keywords: "notice card suggestion", attrs: { style: "tip" }, diff --git a/app/editor/menus/code.tsx b/app/editor/menus/code.tsx index f3d3826542..bc123fb81b 100644 --- a/app/editor/menus/code.tsx +++ b/app/editor/menus/code.tsx @@ -12,14 +12,14 @@ import { getLabelForLanguage, } from "@shared/editor/lib/code"; import { isMermaid } from "@shared/editor/lib/isCode"; +import type { TFunction } from "i18next"; import type { MenuItem } from "@shared/editor/types"; -import type { Dictionary } from "~/hooks/useDictionary"; import { metaDisplay } from "@shared/utils/keyboard"; export default function codeMenuItems( state: EditorState, readOnly: boolean | undefined, - dictionary: Dictionary + t: TFunction ): MenuItem[] { const node = state.selection instanceof NodeSelection @@ -59,7 +59,7 @@ export default function codeMenuItems( label: readOnly ? getLabelForLanguage(node.attrs.language ?? "none") : undefined, - tooltip: dictionary.copy, + tooltip: t("Copy"), }, { name: "separator", @@ -67,7 +67,7 @@ export default function codeMenuItems( { name: "edit_mermaid", icon: , - tooltip: dictionary.editDiagram, + tooltip: t("Edit diagram"), shortcut: `${metaDisplay} Enter`, visible: isMermaid(node) && !isEditingMermaid && !readOnly, }, @@ -77,7 +77,7 @@ export default function codeMenuItems( { name: "toggleCodeBlockWrap", icon: , - tooltip: dictionary.wrapText, + tooltip: t("Wrap text"), active: () => node.attrs.wrap, visible: !readOnly && (!isMermaid(node) || isEditingMermaid), }, diff --git a/app/editor/menus/divider.tsx b/app/editor/menus/divider.tsx index 83036f1a7d..be3cff8ea5 100644 --- a/app/editor/menus/divider.tsx +++ b/app/editor/menus/divider.tsx @@ -1,13 +1,13 @@ +import type { TFunction } from "i18next"; import { PageBreakIcon, HorizontalRuleIcon } from "outline-icons"; import type { EditorState } from "prosemirror-state"; import { isNodeActive } from "@shared/editor/queries/isNodeActive"; import type { MenuItem } from "@shared/editor/types"; -import type { Dictionary } from "~/hooks/useDictionary"; export default function dividerMenuItems( state: EditorState, readOnly: boolean, - dictionary: Dictionary + t: TFunction ): MenuItem[] { if (readOnly) { return []; @@ -17,14 +17,14 @@ export default function dividerMenuItems( return [ { name: "hr", - tooltip: dictionary.hr, + tooltip: t("Divider"), attrs: { markup: "---" }, active: isNodeActive(schema.nodes.hr, { markup: "---" }), icon: , }, { name: "hr", - tooltip: dictionary.pageBreak, + tooltip: t("Page break"), attrs: { markup: "***" }, active: isNodeActive(schema.nodes.hr, { markup: "***" }), icon: , diff --git a/app/editor/menus/formatting.tsx b/app/editor/menus/formatting.tsx index 38fa33534d..c8ad424f45 100644 --- a/app/editor/menus/formatting.tsx +++ b/app/editor/menus/formatting.tsx @@ -35,8 +35,8 @@ import { isMarkActive } from "@shared/editor/queries/isMarkActive"; import { isNodeActive } from "@shared/editor/queries/isNodeActive"; import type { MenuItem } from "@shared/editor/types"; import { metaDisplay } from "@shared/utils/keyboard"; +import type { TFunction } from "i18next"; import CircleIcon from "~/components/Icons/CircleIcon"; -import type { Dictionary } from "~/hooks/useDictionary"; import { isMobile as isMobileDevice, isTouchDevice, @@ -57,7 +57,7 @@ import { DottedCircleIcon } from "~/components/Icons/DottedCircleIcon"; export default function formattingMenuItems( state: EditorState, isTemplate: boolean, - dictionary: Dictionary + t: TFunction ): MenuItem[] { const { schema } = state; const isCode = isInCode(state); @@ -86,7 +86,7 @@ export default function formattingMenuItems( return [ { name: "placeholder", - tooltip: dictionary.placeholder, + tooltip: t("Placeholder"), icon: , active: isMarkActive(schema.marks.placeholder), visible: isTemplate && (!isMobile || !isEmpty), @@ -97,7 +97,7 @@ export default function formattingMenuItems( }, { name: "strong", - tooltip: dictionary.strong, + tooltip: t("Bold"), shortcut: `${metaDisplay}+B`, icon: , active: isMarkActive(schema.marks.strong), @@ -105,7 +105,7 @@ export default function formattingMenuItems( }, { name: "em", - tooltip: dictionary.em, + tooltip: t("Italic"), shortcut: `${metaDisplay}+I`, icon: , active: isMarkActive(schema.marks.em), @@ -113,14 +113,14 @@ export default function formattingMenuItems( }, { name: "strikethrough", - tooltip: dictionary.strikethrough, + tooltip: t("Strikethrough"), shortcut: `${metaDisplay}+D`, icon: , active: isMarkActive(schema.marks.strikethrough), visible: !isCodeBlock && (!isMobile || !isEmpty), }, { - tooltip: dictionary.background, + tooltip: t("Background color"), icon: getColorSetForSelectedCells(state.selection).size > 1 ? ( @@ -147,7 +147,7 @@ export default function formattingMenuItems( return [ { name: "toggleCellSelectionBackgroundAndCollapseSelection", - label: dictionary.none, + label: t("None"), icon: , active: () => (cellSelectionHasBackground ? false : true), attrs: { color: null }, @@ -215,7 +215,7 @@ export default function formattingMenuItems( }, }, { - tooltip: dictionary.mark, + tooltip: t("Highlight"), shortcut: `${metaDisplay}+⇧+H`, icon: highlight ? ( , active: () => false, attrs: { color: highlight.mark.attrs.color }, @@ -309,7 +309,7 @@ export default function formattingMenuItems( }, { name: "code_inline", - tooltip: dictionary.codeInline, + tooltip: t("Code"), shortcut: `${metaDisplay}+E`, icon: , active: isMarkActive(schema.marks.code_inline), @@ -321,7 +321,7 @@ export default function formattingMenuItems( }, { name: "heading", - tooltip: dictionary.heading, + tooltip: t("Heading"), shortcut: `⇧+Ctrl+1`, icon: , active: isNodeActive(schema.nodes.heading, { level: 1 }), @@ -330,7 +330,7 @@ export default function formattingMenuItems( }, { name: "heading", - tooltip: dictionary.subheading, + tooltip: t("Subheading"), shortcut: `⇧+Ctrl+2`, icon: , active: isNodeActive(schema.nodes.heading, { level: 2 }), @@ -339,7 +339,7 @@ export default function formattingMenuItems( }, { name: "heading", - tooltip: dictionary.subheading, + tooltip: t("Subheading"), shortcut: `⇧+Ctrl+3`, icon: , active: isNodeActive(schema.nodes.heading, { level: 3 }), @@ -348,7 +348,7 @@ export default function formattingMenuItems( }, { name: "blockquote", - tooltip: dictionary.quote, + tooltip: t("Quote"), shortcut: `${metaDisplay}+]`, icon: , active: isNodeActive(schema.nodes.blockquote), @@ -360,20 +360,20 @@ export default function formattingMenuItems( }, { name: "mergeCells", - tooltip: dictionary.mergeCells, + tooltip: t("Merge cells"), icon: , visible: isMultipleCellSelection(state), }, { name: "splitCell", - tooltip: dictionary.splitCell, + tooltip: t("Split cell"), icon: , visible: isMergedCellSelection(state), }, { name: "container_toggle", icon: , - tooltip: dictionary.toggleBlock, + tooltip: t("Toggle block"), active: isNodeActive(schema.nodes.container_toggle), attrs: { id: uuidv4() }, visible: !isCodeBlock && (!isMobile || isEmpty), @@ -383,7 +383,7 @@ export default function formattingMenuItems( }, { name: "checkbox_list", - tooltip: dictionary.checkboxList, + tooltip: t("Todo list"), shortcut: `⇧+Ctrl+7`, icon: , keywords: "checklist checkbox task", @@ -392,7 +392,7 @@ export default function formattingMenuItems( }, { name: "bullet_list", - tooltip: dictionary.bulletList, + tooltip: t("Bulleted list"), shortcut: `⇧+Ctrl+8`, icon: , active: isNodeActive(schema.nodes.bullet_list), @@ -400,7 +400,7 @@ export default function formattingMenuItems( }, { name: "ordered_list", - tooltip: dictionary.orderedList, + tooltip: t("Ordered list"), shortcut: `⇧+Ctrl+9`, icon: , active: isNodeActive(schema.nodes.ordered_list), @@ -408,28 +408,28 @@ export default function formattingMenuItems( }, { name: "outdentList", - tooltip: dictionary.outdent, + tooltip: t("Outdent"), shortcut: `⇧+Tab`, icon: , visible: isTouch && isList, }, { name: "indentList", - tooltip: dictionary.indent, + tooltip: t("Indent"), shortcut: `Tab`, icon: , visible: isTouch && isList, }, { name: "outdentCheckboxList", - tooltip: dictionary.outdent, + tooltip: t("Outdent"), shortcut: `⇧+Tab`, icon: , visible: isTouch && isInList(state, { types: ["checkbox_list"] }), }, { name: "indentCheckboxList", - tooltip: dictionary.indent, + tooltip: t("Indent"), shortcut: `Tab`, icon: , visible: isTouch && isInList(state, { types: ["checkbox_list"] }), @@ -440,7 +440,7 @@ export default function formattingMenuItems( }, { name: "addLink", - tooltip: dictionary.createLink, + tooltip: t("Create link"), shortcut: `${metaDisplay}+K`, icon: , attrs: { href: "" }, @@ -449,10 +449,10 @@ export default function formattingMenuItems( }, { name: "comment", - tooltip: dictionary.comment, + tooltip: t("Comment"), shortcut: `${metaDisplay}+⌥+M`, icon: , - label: isCodeBlock ? dictionary.comment : undefined, + label: isCodeBlock ? t("Comment") : undefined, active: isMarkActive( schema.marks.comment, { resolved: false }, @@ -467,7 +467,7 @@ export default function formattingMenuItems( { name: "copyToClipboard", icon: , - tooltip: dictionary.copy, + tooltip: t("Copy"), shortcut: `${metaDisplay}+C`, visible: isCode && !isCodeBlock && (!isMobile || !isEmpty), }, diff --git a/app/editor/menus/image.tsx b/app/editor/menus/image.tsx index 993fae04d8..8b78571346 100644 --- a/app/editor/menus/image.tsx +++ b/app/editor/menus/image.tsx @@ -12,8 +12,8 @@ import { } from "outline-icons"; import type { EditorState } from "prosemirror-state"; import { isNodeActive } from "@shared/editor/queries/isNodeActive"; +import type { TFunction } from "i18next"; import type { MenuItem } from "@shared/editor/types"; -import type { Dictionary } from "~/hooks/useDictionary"; import { metaDisplay } from "@shared/utils/keyboard"; import { ImageSource } from "@shared/editor/lib/FileHelper"; import Desktop from "~/utils/Desktop"; @@ -22,7 +22,7 @@ import { isMarkActive } from "@shared/editor/queries/isMarkActive"; export default function imageMenuItems( state: EditorState, readOnly: boolean, - dictionary: Dictionary + t: TFunction ): MenuItem[] { if (readOnly) { return []; @@ -48,14 +48,14 @@ export default function imageMenuItems( return [ { name: "alignLeft", - tooltip: dictionary.alignLeft, + tooltip: t("Align left"), icon: , active: isLeftAligned, visible: !isEmptyDiagram(state), }, { name: "alignCenter", - tooltip: dictionary.alignCenter, + tooltip: t("Align center"), icon: , active: (state) => isNodeActive(schema.nodes.image)(state) && @@ -66,14 +66,14 @@ export default function imageMenuItems( }, { name: "alignRight", - tooltip: dictionary.alignRight, + tooltip: t("Align right"), icon: , active: isRightAligned, visible: !isEmptyDiagram(state), }, { name: "alignFullWidth", - tooltip: dictionary.alignFullWidth, + tooltip: t("Full width"), icon: , active: isFullWidthAligned, visible: !isEmptyDiagram(state), @@ -83,7 +83,7 @@ export default function imageMenuItems( }, { name: "dimensions", - tooltip: dictionary.dimensions, + tooltip: `${t("Width")} × ${t("Height")}`, visible: !isFullWidthAligned(state) && !isEmptyDiagram(state), skipIcon: true, }, @@ -92,34 +92,34 @@ export default function imageMenuItems( }, { name: "editDiagram", - tooltip: dictionary.editDiagram, + tooltip: t("Edit diagram"), icon: , visible: isDiagram(state) && !Desktop.isElectron(), }, { name: "downloadImage", - tooltip: dictionary.downloadImage, + tooltip: t("Download image"), icon: , visible: !!fetch && !isEmptyDiagram(state), }, { - tooltip: dictionary.replaceImage, + tooltip: t("Replace image"), icon: , visible: !isDiagram(state), children: [ { name: "replaceImage", - label: dictionary.uploadImage, + label: t("Upload an image"), }, { name: "editImageUrl", - label: dictionary.editImageUrl, + label: t("Edit image URL"), }, ], }, { name: "deleteImage", - tooltip: dictionary.deleteImage, + tooltip: t("Delete image"), icon: , }, { @@ -127,14 +127,14 @@ export default function imageMenuItems( }, { name: "linkOnImage", - tooltip: dictionary.createLink, + tooltip: t("Create link"), shortcut: `${metaDisplay}+K`, active: isMarkActive(schema.marks.link), icon: , }, { name: "commentOnImage", - tooltip: dictionary.comment, + tooltip: t("Comment"), shortcut: `${metaDisplay}+⌥+M`, icon: , }, diff --git a/app/editor/menus/notice.tsx b/app/editor/menus/notice.tsx index 26d3384fc7..75d52dd1bd 100644 --- a/app/editor/menus/notice.tsx +++ b/app/editor/menus/notice.tsx @@ -1,3 +1,4 @@ +import type { TFunction } from "i18next"; import { DoneIcon, ExpandedIcon, @@ -8,21 +9,20 @@ import { import type { EditorState } from "prosemirror-state"; import { NoticeTypes } from "@shared/editor/nodes/Notice"; import type { MenuItem } from "@shared/editor/types"; -import type { Dictionary } from "~/hooks/useDictionary"; export default function noticeMenuItems( state: EditorState, readOnly: boolean | undefined, - dictionary: Dictionary + t: TFunction ): MenuItem[] { const node = state.selection.$from.node(-1); const currentStyle = node?.attrs.style as NoticeTypes; const mapping = { - [NoticeTypes.Info]: dictionary.infoNotice, - [NoticeTypes.Warning]: dictionary.warningNotice, - [NoticeTypes.Success]: dictionary.successNotice, - [NoticeTypes.Tip]: dictionary.tipNotice, + [NoticeTypes.Info]: t("Info notice"), + [NoticeTypes.Warning]: t("Warning notice"), + [NoticeTypes.Success]: t("Success notice"), + [NoticeTypes.Tip]: t("Tip notice"), }; return [ @@ -35,25 +35,25 @@ export default function noticeMenuItems( { name: NoticeTypes.Info, icon: , - label: dictionary.infoNotice, + label: t("Info notice"), active: () => currentStyle === NoticeTypes.Info, }, { name: NoticeTypes.Success, icon: , - label: dictionary.successNotice, + label: t("Success notice"), active: () => currentStyle === NoticeTypes.Success, }, { name: NoticeTypes.Warning, icon: , - label: dictionary.warningNotice, + label: t("Warning notice"), active: () => currentStyle === NoticeTypes.Warning, }, { name: NoticeTypes.Tip, icon: , - label: dictionary.tipNotice, + label: t("Tip notice"), active: () => currentStyle === NoticeTypes.Tip, }, ], diff --git a/app/editor/menus/readOnly.tsx b/app/editor/menus/readOnly.tsx index 567eb60d2b..c29142f666 100644 --- a/app/editor/menus/readOnly.tsx +++ b/app/editor/menus/readOnly.tsx @@ -1,13 +1,13 @@ +import type { TFunction } from "i18next"; import { CommentIcon } from "outline-icons"; import type { EditorState } from "prosemirror-state"; import { isMarkActive } from "@shared/editor/queries/isMarkActive"; import type { MenuItem } from "@shared/editor/types"; -import type { Dictionary } from "~/hooks/useDictionary"; export default function readOnlyMenuItems( state: EditorState, canUpdate: boolean, - dictionary: Dictionary + t: TFunction ): MenuItem[] { const { schema } = state; const isEmpty = state.selection.empty; @@ -16,8 +16,8 @@ export default function readOnlyMenuItems( { visible: canUpdate && !isEmpty, name: "comment", - tooltip: dictionary.comment, - label: dictionary.comment, + tooltip: t("Comment"), + label: t("Comment"), icon: , active: isMarkActive(schema.marks.comment), }, diff --git a/app/editor/menus/table.tsx b/app/editor/menus/table.tsx index 84de1d8b88..9cd4e2bcde 100644 --- a/app/editor/menus/table.tsx +++ b/app/editor/menus/table.tsx @@ -6,14 +6,14 @@ import { } from "outline-icons"; import type { EditorState } from "prosemirror-state"; import { isNodeActive } from "@shared/editor/queries/isNodeActive"; +import type { TFunction } from "i18next"; import type { MenuItem } from "@shared/editor/types"; import { TableLayout } from "@shared/editor/types"; -import type { Dictionary } from "~/hooks/useDictionary"; export default function tableMenuItems( state: EditorState, readOnly: boolean, - dictionary: Dictionary + t: TFunction ): MenuItem[] { if (readOnly) { return []; @@ -27,16 +27,14 @@ export default function tableMenuItems( return [ { name: "setTableAttr", - tooltip: isFullWidth - ? dictionary.alignDefaultWidth - : dictionary.alignFullWidth, + tooltip: isFullWidth ? t("Default width") : t("Full width"), icon: , attrs: isFullWidth ? { layout: null } : { layout: TableLayout.fullWidth }, active: () => isFullWidth, }, { name: "distributeColumns", - tooltip: dictionary.distributeColumns, + tooltip: t("Distribute columns"), icon: , }, { @@ -44,7 +42,7 @@ export default function tableMenuItems( }, { name: "deleteTable", - tooltip: dictionary.deleteTable, + tooltip: t("Delete table"), icon: , }, { @@ -52,7 +50,7 @@ export default function tableMenuItems( }, { name: "exportTable", - tooltip: dictionary.exportAsCSV, + tooltip: t("Export as CSV"), label: "CSV", attrs: { format: "csv", fileName: `${window.document.title}.csv` }, icon: , diff --git a/app/editor/menus/tableCol.tsx b/app/editor/menus/tableCol.tsx index f01629e750..1180466c0c 100644 --- a/app/editor/menus/tableCol.tsx +++ b/app/editor/menus/tableCol.tsx @@ -24,8 +24,8 @@ import { isMultipleCellSelection, tableHasRowspan, } from "@shared/editor/queries/table"; +import type { TFunction } from "i18next"; import type { MenuItem, NodeAttrMark } from "@shared/editor/types"; -import type { Dictionary } from "~/hooks/useDictionary"; import { ArrowLeftIcon, ArrowRightIcon } from "~/components/Icons/ArrowIcon"; import CircleIcon from "~/components/Icons/CircleIcon"; import CellBackgroundColorPicker from "../components/CellBackgroundColorPicker"; @@ -58,7 +58,7 @@ function getColumnColors(state: EditorState, colIndex: number): Set { export default function tableColMenuItems( state: EditorState, readOnly: boolean, - dictionary: Dictionary, + t: TFunction, options: { index: number; rtl: boolean; @@ -89,7 +89,7 @@ export default function tableColMenuItems( return [ { name: "setColumnAttr", - tooltip: dictionary.alignLeft, + tooltip: t("Align left"), icon: , attrs: { index, alignment: "left" }, active: isNodeActive(schema.nodes.th, { @@ -100,7 +100,7 @@ export default function tableColMenuItems( }, { name: "setColumnAttr", - tooltip: dictionary.alignCenter, + tooltip: t("Align center"), icon: , attrs: { index, alignment: "center" }, active: isNodeActive(schema.nodes.th, { @@ -111,7 +111,7 @@ export default function tableColMenuItems( }, { name: "setColumnAttr", - tooltip: dictionary.alignRight, + tooltip: t("Align right"), icon: , attrs: { index, alignment: "right" }, active: isNodeActive(schema.nodes.th, { @@ -125,14 +125,14 @@ export default function tableColMenuItems( }, { name: "sortTable", - tooltip: dictionary.sortAsc, + tooltip: t("Sort ascending"), attrs: { index, direction: "asc" }, icon: , disabled: tableHasRowspan(state), }, { name: "sortTable", - tooltip: dictionary.sortDesc, + tooltip: t("Sort descending"), attrs: { index, direction: "desc" }, icon: , disabled: tableHasRowspan(state), @@ -141,7 +141,7 @@ export default function tableColMenuItems( name: "separator", }, { - tooltip: dictionary.background, + tooltip: t("Background color"), icon: colColors.size > 1 ? ( @@ -154,7 +154,7 @@ export default function tableColMenuItems( ...[ { name: "toggleColumnBackgroundAndCollapseSelection", - label: dictionary.none, + label: t("None"), icon: , active: () => (hasBackground ? false : true), attrs: { color: null }, @@ -203,32 +203,32 @@ export default function tableColMenuItems( children: [ { name: "toggleHeaderColumn", - label: dictionary.toggleHeader, + label: t("Toggle header"), icon: , visible: index === 0, }, { name: rtl ? "addColumnAfter" : "addColumnBefore", - label: rtl ? dictionary.addColumnAfter : dictionary.addColumnBefore, + label: rtl ? t("Insert after") : t("Insert before"), icon: , attrs: { index }, }, { name: rtl ? "addColumnBefore" : "addColumnAfter", - label: rtl ? dictionary.addColumnBefore : dictionary.addColumnAfter, + label: rtl ? t("Insert before") : t("Insert after"), icon: , attrs: { index }, }, { name: "moveTableColumn", - label: dictionary.moveColumnLeft, + label: t("Move left"), icon: , attrs: { from: index, to: index - 1 }, visible: index > 0, }, { name: "moveTableColumn", - label: dictionary.moveColumnRight, + label: t("Move right"), icon: , attrs: { from: index, to: index + 1 }, visible: index < tableMap.map.width - 1, @@ -238,20 +238,20 @@ export default function tableColMenuItems( }, { name: "mergeCells", - label: dictionary.mergeCells, + label: t("Merge cells"), icon: , visible: isMultipleCellSelection(state), }, { name: "splitCell", - label: dictionary.splitCell, + label: t("Split cell"), icon: , visible: isMergedCellSelection(state), }, { name: "distributeColumns", visible: selectedCols.length > 1, - label: dictionary.distributeColumns, + label: t("Distribute columns"), icon: , }, { @@ -260,7 +260,7 @@ export default function tableColMenuItems( { name: "deleteColumn", dangerous: true, - label: dictionary.deleteColumn, + label: t("Delete"), icon: , }, ], diff --git a/app/editor/menus/tableRow.tsx b/app/editor/menus/tableRow.tsx index 3bc9bfe377..c0e8542840 100644 --- a/app/editor/menus/tableRow.tsx +++ b/app/editor/menus/tableRow.tsx @@ -15,8 +15,8 @@ import { isMergedCellSelection, isMultipleCellSelection, } from "@shared/editor/queries/table"; +import type { TFunction } from "i18next"; import type { MenuItem, NodeAttrMark } from "@shared/editor/types"; -import type { Dictionary } from "~/hooks/useDictionary"; import { ArrowDownIcon, ArrowUpIcon } from "~/components/Icons/ArrowIcon"; import CircleIcon from "~/components/Icons/CircleIcon"; import CellBackgroundColorPicker from "../components/CellBackgroundColorPicker"; @@ -49,7 +49,7 @@ function getRowColors(state: EditorState, rowIndex: number): Set { export default function tableRowMenuItems( state: EditorState, readOnly: boolean, - dictionary: Dictionary, + t: TFunction, options: { index: number; } @@ -77,7 +77,7 @@ export default function tableRowMenuItems( return [ { - tooltip: dictionary.background, + tooltip: t("Background color"), icon: rowColors.size > 1 ? ( @@ -90,7 +90,7 @@ export default function tableRowMenuItems( ...[ { name: "toggleRowBackgroundAndCollapseSelection", - label: dictionary.none, + label: t("None"), icon: , active: () => (hasBackground ? false : true), attrs: { color: null }, @@ -139,32 +139,32 @@ export default function tableRowMenuItems( children: [ { name: "toggleHeaderRow", - label: dictionary.toggleHeader, + label: t("Toggle header"), icon: , visible: index === 0, }, { name: "addRowBefore", - label: dictionary.addRowBefore, + label: t("Insert before"), icon: , attrs: { index }, }, { name: "addRowAfter", - label: dictionary.addRowAfter, + label: t("Insert after"), icon: , attrs: { index }, }, { name: "moveTableRow", - label: dictionary.moveRowUp, + label: t("Move up"), icon: , attrs: { from: index, to: index - 1 }, visible: index > 0, }, { name: "moveTableRow", - label: dictionary.moveRowDown, + label: t("Move down"), icon: , attrs: { from: index, to: index + 1 }, visible: index < tableMap.map.height - 1, @@ -174,13 +174,13 @@ export default function tableRowMenuItems( }, { name: "mergeCells", - label: dictionary.mergeCells, + label: t("Merge cells"), icon: , visible: isMultipleCellSelection(state), }, { name: "splitCell", - label: dictionary.splitCell, + label: t("Split cell"), icon: , visible: isMergedCellSelection(state), }, @@ -189,7 +189,7 @@ export default function tableRowMenuItems( }, { name: "deleteRow", - label: dictionary.deleteRow, + label: t("Delete"), dangerous: true, icon: , }, diff --git a/app/hooks/useDictionary.ts b/app/hooks/useDictionary.ts deleted file mode 100644 index 649e74e96d..0000000000 --- a/app/hooks/useDictionary.ts +++ /dev/null @@ -1,135 +0,0 @@ -import { useMemo } from "react"; -import { useTranslation } from "react-i18next"; - -/** - * Hook that provides a dictionary of translated UI strings. - * - * @returns An object containing all translated UI strings used throughout the application - */ -export default function useDictionary() { - const { t } = useTranslation(); - - return useMemo( - () => ({ - addColumnAfter: t("Insert after"), - addColumnBefore: t("Insert before"), - moveRowUp: t("Move up"), - moveRowDown: t("Move down"), - moveColumnLeft: t("Move left"), - moveColumnRight: t("Move right"), - addRowAfter: t("Insert after"), - addRowBefore: t("Insert before"), - alignCenter: t("Align center"), - alignLeft: t("Align left"), - alignRight: t("Align right"), - alignDefaultWidth: t("Default width"), - alignFullWidth: t("Full width"), - bulletList: t("Bulleted list"), - checkboxList: t("Todo list"), - showCompleted: (count: number) => - t("Show {{ count }} completed", { count }), - hideCompleted: t("Hide completed"), - codeBlock: t("Code block"), - codeCopied: t("Copied to clipboard"), - codeInline: t("Code"), - comment: t("Comment"), - copy: t("Copy"), - createLink: t("Create link"), - editDiagram: t("Edit diagram"), - editImageUrl: t("Edit image URL"), - createLinkError: t("Sorry, an error occurred creating the link"), - createNewDoc: t("Create a new doc"), - createNewChildDoc: t("Create a new child doc"), - deleteColumn: t("Delete"), - deleteRow: t("Delete"), - deleteTable: t("Delete table"), - deleteAttachment: t("Delete file"), - previewAttachment: t("Show preview"), - dimensions: `${t("Width")} × ${t("Height")}`, - download: t("Download"), - downloadAttachment: t("Download file"), - replaceAttachment: t("Replace file"), - deleteImage: t("Delete image"), - downloadImage: t("Download image"), - replaceImage: t("Replace image"), - em: t("Italic"), - embedInvalidLink: t("Sorry, that link won’t work for this embed type"), - file: t("File attachment"), - pdf: t("Embed PDF"), - enterLink: `${t("Enter a link")}…`, - h1: t("Big heading"), - h2: t("Medium heading"), - h3: t("Small heading"), - h4: t("Extra small heading"), - heading: t("Heading"), - hr: t("Divider"), - image: t("Image"), - fileUploadError: t("Sorry, an error occurred uploading the file"), - uploadingWithProgress: (progress: number) => - t("Uploading… {{ progress }}%", { progress }), - imageCaptionPlaceholder: t("Write a caption"), - info: t("Info"), - infoNotice: t("Info notice"), - link: t("Link"), - linkCopied: t("Link copied to clipboard"), - mark: t("Highlight"), - background: t("Background color"), - newLineEmpty: `${t("Type '/' to insert")}…`, - newLineWithSlash: `${t("Keep typing to filter")}…`, - noResults: t("No results"), - openLink: t("Open link"), - goToLink: t("Go to link"), - openLinkError: t("Sorry, that type of link is not supported"), - orderedList: t("Ordered list"), - pageBreak: t("Page break"), - pasteLink: `${t("Paste a link")}…`, - pasteLinkWithTitle: (service: string) => - t("Paste a {{service}} link…", { - service, - }), - placeholder: t("Placeholder"), - quote: t("Quote"), - removeLink: t("Remove link"), - searchOrPasteLink: `${t("Search or paste a link")}…`, - strikethrough: t("Strikethrough"), - strong: t("Bold"), - subheading: t("Subheading"), - sortAsc: t("Sort ascending"), - sortDesc: t("Sort descending"), - table: t("Table"), - exportAsCSV: t("Export as CSV"), - toggleHeader: t("Toggle header"), - mathInline: t("Math inline (LaTeX)"), - mathBlock: t("Math block (LaTeX)"), - mergeCells: t("Merge cells"), - splitCell: t("Split cell"), - tip: t("Tip"), - tipNotice: t("Tip notice"), - warning: t("Warning"), - warningNotice: t("Warning notice"), - success: t("Success"), - successNotice: t("Success notice"), - insertDate: t("Current date"), - insertTime: t("Current time"), - insertDateTime: t("Current date and time"), - indent: t("Indent"), - outdent: t("Outdent"), - video: t("Video"), - untitled: t("Untitled"), - none: t("None"), - toggleBlock: t("Toggle block"), - emptyToggleBlockHead: `${t("Add title")}…`, - emptyToggleBlockBody: `${t("Add content")}…`, - deleteEmbed: t("Delete embed"), - uploadImage: t("Upload an image"), - formattingControls: t("Formatting controls"), - distributeColumns: t("Distribute columns"), - wrapText: t("Wrap text"), - collapseCode: t("Collapse"), - expandCode: t("Expand"), - }), - [t] - ); -} - -export type Dictionary = ReturnType; diff --git a/shared/editor/commands/insertFiles.ts b/shared/editor/commands/insertFiles.ts index 615c553fc5..df335cf3b4 100644 --- a/shared/editor/commands/insertFiles.ts +++ b/shared/editor/commands/insertFiles.ts @@ -1,16 +1,14 @@ import * as Sentry from "@sentry/react"; +import { t } from "i18next"; import { v4 as uuidv4 } from "uuid"; import type { EditorView } from "prosemirror-view"; import { toast } from "sonner"; -import type { Dictionary } from "~/hooks/useDictionary"; import FileHelper from "../lib/FileHelper"; import uploadPlaceholderPlugin, { findPlaceholder, } from "../lib/uploadPlaceholder"; export type Options = { - /** Dictionary object containing translation strings */ - dictionary: Dictionary; /** Set to true to force images and videos to become file attachments */ isAttachment?: boolean; /** Set to true to replace any existing image at the users selection */ @@ -55,7 +53,6 @@ const insertFiles = async function ( options: Options ) { const { - dictionary, uploadFile, onFileUploadStart, onFileUploadStop, @@ -179,7 +176,7 @@ const insertFiles = async function ( to || from, schema.nodes.video.create({ src, - title: upload.file.name ?? dictionary.untitled, + title: upload.file.name ?? t("Untitled"), ...upload.dimensions, ...options.attrs, }) @@ -201,7 +198,7 @@ const insertFiles = async function ( to || from, schema.nodes.attachment.create({ href: src, - title: upload.file.name ?? dictionary.untitled, + title: upload.file.name ?? t("Untitled"), size: upload.file.size, contentType: upload.file.type, preview: false, @@ -229,7 +226,9 @@ const insertFiles = async function ( }) ); - toast.error(error.message || dictionary.fileUploadError); + toast.error( + error.message || t("Sorry, an error occurred uploading the file") + ); }) .finally(() => { complete++; diff --git a/shared/editor/commands/link.ts b/shared/editor/commands/link.ts index 14f4e58c6a..954d3eb6cb 100644 --- a/shared/editor/commands/link.ts +++ b/shared/editor/commands/link.ts @@ -1,3 +1,4 @@ +import { t } from "i18next"; import { chainCommands, toggleMark } from "prosemirror-commands"; import type { Attrs } from "prosemirror-model"; import type { Command } from "prosemirror-state"; @@ -58,8 +59,7 @@ const openLinkTextSelection = | MouseEvent | React.MouseEvent ) => void) - | undefined, - dictionary: { openLinkError: string } + | undefined ): Command => (state) => { if (!(state.selection instanceof TextSelection)) { @@ -72,7 +72,7 @@ const openLinkTextSelection = const event = new KeyboardEvent("keydown", { metaKey: false }); onClickLink(sanitizeUrl(range.mark.attrs.href) ?? "", event); } catch (_err) { - toast.error(dictionary.openLinkError); + toast.error(t("Sorry, that type of link is not supported")); } return true; } @@ -89,8 +89,7 @@ const openLinkNodeSelection = | MouseEvent | React.MouseEvent ) => void) - | undefined, - dictionary: { openLinkError: string } + | undefined ): Command => (state) => { if (!(state.selection instanceof NodeSelection)) { @@ -111,7 +110,7 @@ const openLinkNodeSelection = const event = new KeyboardEvent("keydown", { metaKey: false }); onClickLink(sanitizeUrl(linkMark.attrs.href) ?? "", event); } catch (_err) { - toast.error(dictionary.openLinkError); + toast.error(t("Sorry, that type of link is not supported")); } return true; }; @@ -260,12 +259,11 @@ export const openLink = ( url: string, event?: KeyboardEvent | MouseEvent | React.MouseEvent ) => void) - | undefined, - dictionary: { openLinkError: string } + | undefined ): Command => chainCommands( - openLinkTextSelection(onClickLink, dictionary), - openLinkNodeSelection(onClickLink, dictionary) + openLinkTextSelection(onClickLink), + openLinkNodeSelection(onClickLink) ); export const updateLink = (attrs: Attrs): Command => diff --git a/shared/editor/extensions/HexColorPreview.ts b/shared/editor/extensions/HexColorPreview.ts index df6b10fcd8..740ba7195a 100644 --- a/shared/editor/extensions/HexColorPreview.ts +++ b/shared/editor/extensions/HexColorPreview.ts @@ -1,4 +1,5 @@ import copy from "copy-to-clipboard"; +import { t } from "i18next"; import type { EditorState } from "prosemirror-state"; import { Plugin, PluginKey } from "prosemirror-state"; import { Decoration, DecorationSet } from "prosemirror-view"; @@ -110,7 +111,7 @@ export default class HexColorPreview extends Extension { event.preventDefault(); event.stopPropagation(); copy(color); - toast.message(this.editor.props.dictionary.codeCopied); + toast.message(t("Copied to clipboard")); }); return swatch; diff --git a/shared/editor/extensions/Mermaid.ts b/shared/editor/extensions/Mermaid.ts index af93e10365..b255d0a4b1 100644 --- a/shared/editor/extensions/Mermaid.ts +++ b/shared/editor/extensions/Mermaid.ts @@ -1,3 +1,4 @@ +import { t } from "i18next"; import last from "lodash/last"; import sortBy from "lodash/sortBy"; import { v4 as uuidv4 } from "uuid"; @@ -363,7 +364,7 @@ export default function Mermaid({ isDark: boolean; editor: Editor; }) { - const { onClickLink, dictionary } = editor.props; + const { onClickLink } = editor.props; return new Plugin({ key: pluginKey, @@ -591,7 +592,7 @@ export default function Mermaid({ onClickLink(sanitizeUrl(href) ?? ""); } } catch (_err) { - toast.error(dictionary.openLinkError); + toast.error(t("Sorry, that type of link is not supported")); } } diff --git a/shared/editor/marks/Link.tsx b/shared/editor/marks/Link.tsx index dbe46099f1..5a6cb5bf18 100644 --- a/shared/editor/marks/Link.tsx +++ b/shared/editor/marks/Link.tsx @@ -1,3 +1,4 @@ +import { t } from "i18next"; import type { Token } from "markdown-it"; import { InputRule } from "prosemirror-inputrules"; import type { MarkdownSerializerState } from "prosemirror-markdown"; @@ -12,7 +13,6 @@ import type { Command, EditorState } from "prosemirror-state"; import { Plugin, TextSelection } from "prosemirror-state"; import type { EditorView } from "prosemirror-view"; import { toast } from "sonner"; -import type { Dictionary } from "~/hooks/useDictionary"; import { isUrl, sanitizeUrl } from "../../utils/urls"; import { getMarkRange } from "../queries/getMarkRange"; import Mark from "./Mark"; @@ -58,8 +58,6 @@ function isPlainURL( * Options for the Link mark. */ type LinkOptions = { - /** A dictionary of translated strings used in the editor. */ - dictionary: Dictionary; /** Callback invoked when the user clicks any link in the document. */ onClickLink?: ( href: string, @@ -128,7 +126,7 @@ export default class Link extends Mark { keys(): Record { return { - "Mod-Enter": openLink(this.options.onClickLink, this.options.dictionary), + "Mod-Enter": openLink(this.options.onClickLink), }; } @@ -137,8 +135,7 @@ export default class Link extends Mark { link: (attrs: Attrs) => toggleLink(attrs), addLink, updateLink, - openLink: (): Command => - openLink(this.options.onClickLink, this.options.dictionary), + openLink: (): Command => openLink(this.options.onClickLink), removeLink, }; } @@ -229,7 +226,7 @@ export default class Link extends Mark { this.options.onClickLink(sanitized, event); } } catch (_err) { - toast.error(this.options.dictionary.openLinkError); + toast.error(t("Sorry, that type of link is not supported")); } return true; diff --git a/shared/editor/nodes/Attachment.tsx b/shared/editor/nodes/Attachment.tsx index ec7923397f..91e8d91c2b 100644 --- a/shared/editor/nodes/Attachment.tsx +++ b/shared/editor/nodes/Attachment.tsx @@ -9,7 +9,6 @@ import type { Command } from "prosemirror-state"; import { NodeSelection } from "prosemirror-state"; import { Trans } from "react-i18next"; import type { Primitive } from "utility-types"; -import type { Dictionary } from "~/hooks/useDictionary"; import { bytesToHumanReadable, getEventFiles } from "../../utils/files"; import { sanitizeUrl } from "../../utils/urls"; import insertFiles from "../commands/insertFiles"; @@ -22,15 +21,7 @@ import type { ComponentProps } from "../types"; import Node from "./Node"; import PdfViewer from "../components/PDF"; -/** - * Options for the Attachment node. - */ -type AttachmentOptions = { - /** A dictionary of translated strings used in the editor. */ - dictionary: Dictionary; -}; - -export default class Attachment extends Node { +export default class Attachment extends Node { get name() { return "attachment"; } @@ -216,7 +207,6 @@ export default class Attachment extends Node { onFileUploadStart, onFileUploadStop, onFileUploadProgress, - dictionary: this.options.dictionary, replaceExisting: true, attrs: { preview: node.attrs.preview, diff --git a/shared/editor/nodes/CheckboxList.ts b/shared/editor/nodes/CheckboxList.ts index a9c1e7cf59..ff07f18add 100644 --- a/shared/editor/nodes/CheckboxList.ts +++ b/shared/editor/nodes/CheckboxList.ts @@ -36,7 +36,6 @@ export default class CheckboxList extends Node { get plugins() { const userIdentifier = this.editor.props.userId; - const dictionary = this.editor.props.dictionary; // Plugin to auto-assign IDs to checkbox lists const assignIdsPlugin = new Plugin({ @@ -67,13 +66,7 @@ export default class CheckboxList extends Node { props: { nodeViews: { [this.name]: (node, view, getPos) => - new CheckboxListView( - node, - view, - getPos, - userIdentifier || "", - dictionary - ), + new CheckboxListView(node, view, getPos, userIdentifier || ""), }, }, }); diff --git a/shared/editor/nodes/CheckboxListView.ts b/shared/editor/nodes/CheckboxListView.ts index 2989e88975..c7a7930bfa 100644 --- a/shared/editor/nodes/CheckboxListView.ts +++ b/shared/editor/nodes/CheckboxListView.ts @@ -1,6 +1,6 @@ +import { t } from "i18next"; import type { Node as ProsemirrorNode } from "prosemirror-model"; import type { EditorView, NodeView } from "prosemirror-view"; -import type { Dictionary } from "../../../app/hooks/useDictionary"; import { isBrowser } from "../../utils/browser"; import Storage from "../../utils/Storage"; import { EditorStyleHelper } from "../styles/EditorStyleHelper"; @@ -16,19 +16,16 @@ export class CheckboxListView implements NodeView { private toggleControl: HTMLButtonElement; private node: ProsemirrorNode; private userIdentifier: string; - private dictionary: Dictionary; private isNested: boolean; constructor( node: ProsemirrorNode, _view: EditorView, _getPos: () => number | undefined, - userIdentifier: string, - dictionary: Dictionary + userIdentifier: string ) { this.node = node; this.userIdentifier = userIdentifier; - this.dictionary = dictionary; // Detect if this is a nested checkbox list (inside a checkbox_item) const pos = _getPos(); @@ -118,8 +115,8 @@ export class CheckboxListView implements NodeView { } else { this.toggleControl.style.display = "inline-block"; this.toggleControl.textContent = shouldCollapse - ? this.dictionary.showCompleted(completedItemsCount) - : this.dictionary.hideCompleted; + ? t("Show {{ count }} completed", { count: completedItemsCount }) + : t("Hide completed"); if (shouldCollapse) { this.dom.classList.add(EditorStyleHelper.checklistCompletedHidden); diff --git a/shared/editor/nodes/CodeFence.ts b/shared/editor/nodes/CodeFence.ts index 3cbd1df549..06bb8eab98 100644 --- a/shared/editor/nodes/CodeFence.ts +++ b/shared/editor/nodes/CodeFence.ts @@ -1,4 +1,5 @@ import copy from "copy-to-clipboard"; +import { t } from "i18next"; import type { Token } from "markdown-it"; import { textblockTypeInputRule } from "prosemirror-inputrules"; import type { @@ -17,7 +18,6 @@ import { import { Decoration, DecorationSet, type EditorView } from "prosemirror-view"; import { toast } from "sonner"; import type { Primitive } from "utility-types"; -import type { Dictionary } from "~/hooks/useDictionary"; import type { UserPreferences } from "../../types"; import { isBrowser, isMac } from "../../utils/browser"; import backspaceToParagraph from "../commands/backspaceToParagraph"; @@ -149,8 +149,6 @@ function buildCollapseState( * Options for the CodeFence node. */ type CodeFenceOptions = { - /** A dictionary of translated strings used in the editor. */ - dictionary: Dictionary; /** Display preferences for the logged in user, if any. */ userPreferences?: UserPreferences | null; }; @@ -317,7 +315,7 @@ export default class CodeFence extends Node { if (codeBlock) { copy(codeBlock.node.textContent); - toast.message(this.options.dictionary.codeCopied); + toast.message(t("Copied to clipboard")); return true; } @@ -338,7 +336,7 @@ export default class CodeFence extends Node { dispatch?.(tr); copy(tr.doc.textBetween(state.selection.from, state.selection.to)); - toast.message(this.options.dictionary.codeCopied); + toast.message(t("Copied to clipboard")); return true; } @@ -380,19 +378,11 @@ export default class CodeFence extends Node { /** Plugins for collapsible code block behavior. */ private collapsePlugins(): Plugin[] { const collapseKey = CodeFence.collapseKey; - const options = this.options; const build = ( doc: ProsemirrorNode, tall: Set, collapsed: Set - ) => - buildCollapseState( - doc, - tall, - collapsed, - options.dictionary?.expandCode ?? "", - options.dictionary?.collapseCode ?? "" - ); + ) => buildCollapseState(doc, tall, collapsed, t("Expand"), t("Collapse")); return [ // Main collapse plugin: manages state and decorations diff --git a/shared/editor/nodes/Heading.ts b/shared/editor/nodes/Heading.ts index 55b0881bf6..149e50ab85 100644 --- a/shared/editor/nodes/Heading.ts +++ b/shared/editor/nodes/Heading.ts @@ -1,4 +1,5 @@ import copy from "copy-to-clipboard"; +import { t } from "i18next"; import type Token from "markdown-it/lib/token.mjs"; import { textblockTypeInputRule } from "prosemirror-inputrules"; import type { @@ -13,7 +14,6 @@ import { Decoration, DecorationSet } from "prosemirror-view"; import { toast } from "sonner"; import type { Primitive } from "utility-types"; import { isSafari } from "../../utils/browser"; -import type { Dictionary } from "~/hooks/useDictionary"; import Storage from "../../utils/Storage"; import backspaceToParagraph from "../commands/backspaceToParagraph"; import splitHeading from "../commands/splitHeading"; @@ -39,8 +39,6 @@ type HeadingOptions = { levels: number[]; /** Offset added to the rendered heading level (e.g. 1 renders an `h2` for level 1). */ offset?: number; - /** A dictionary of translated strings used in the editor. */ - dictionary: Dictionary; }; export default class Heading extends Node { @@ -187,7 +185,7 @@ export default class Heading extends Node { .replace("/edit", ""); copy(normalizedUrl + hash); - toast.message(this.options.dictionary.linkCopied); + toast.message(t("Link copied to clipboard")); }; keys({ type, schema }: { type: NodeType; schema: Schema }) { diff --git a/shared/editor/nodes/Image.tsx b/shared/editor/nodes/Image.tsx index 6a0a4e2a0d..1abbbbf291 100644 --- a/shared/editor/nodes/Image.tsx +++ b/shared/editor/nodes/Image.tsx @@ -1,3 +1,4 @@ +import { t } from "i18next"; import type { Token } from "markdown-it"; import { InputRule } from "prosemirror-inputrules"; import type { @@ -402,7 +403,7 @@ export default class Image extends SimpleImage { onBlur={this.handleCaptionBlur(props)} onKeyDown={this.handleCaptionKeyDown(props)} isSelected={props.isSelected} - placeholder={this.options.dictionary.imageCaptionPlaceholder} + placeholder={t("Write a caption")} > {props.node.attrs.alt} diff --git a/shared/editor/nodes/SimpleImage.tsx b/shared/editor/nodes/SimpleImage.tsx index b78481e7eb..07d12e2571 100644 --- a/shared/editor/nodes/SimpleImage.tsx +++ b/shared/editor/nodes/SimpleImage.tsx @@ -184,7 +184,6 @@ export default class SimpleImage extends Node { onFileUploadStart, onFileUploadStop, onFileUploadProgress, - dictionary: this.options.dictionary, replaceExisting: true, attrs: { width: node.attrs.width, diff --git a/shared/editor/nodes/ToggleBlock.ts b/shared/editor/nodes/ToggleBlock.ts index be59c398c6..cc902934f4 100644 --- a/shared/editor/nodes/ToggleBlock.ts +++ b/shared/editor/nodes/ToggleBlock.ts @@ -1,3 +1,4 @@ +import { t } from "i18next"; import { chainCommands, newlineInCode } from "prosemirror-commands"; import { wrappingInputRule } from "prosemirror-inputrules"; import type { ParseSpec } from "prosemirror-markdown"; @@ -12,7 +13,6 @@ import { Plugin, PluginKey, TextSelection } from "prosemirror-state"; import { findWrapping } from "prosemirror-transform"; import { Decoration, DecorationSet } from "prosemirror-view"; import { v4 } from "uuid"; -import type { Dictionary } from "~/hooks/useDictionary"; import Storage from "../../utils/Storage"; import { deleteSelectionPreservingBody, @@ -65,15 +65,7 @@ export const toggleEventPluginKey = new PluginKey("toggleBlockEvent"); /** Build the localStorage key used to persist a toggle block's fold state. */ export const toggleStorageKey = (id: string) => `toggle:${id}`; -/** - * Options for the ToggleBlock node. - */ -type ToggleBlockOptions = { - /** A dictionary of translated strings used in the editor. */ - dictionary?: Dictionary; -}; - -export default class ToggleBlock extends Node { +export default class ToggleBlock extends Node { get name() { return "container_toggle"; } @@ -349,7 +341,7 @@ export default class ToggleBlock extends Node { parent.type.name === "container_toggle" && $start.index($start.depth - 1) === 0 && node.textContent === "", - text: this.options.dictionary?.emptyToggleBlockHead ?? "", + text: `${t("Add title")}…`, }, { condition: ({ parent, $start, state }) => @@ -359,7 +351,7 @@ export default class ToggleBlock extends Node { ToggleBlock.isBodyEmpty(parent) && (state.selection.$from.pos < $start.pos || state.selection.$from.pos > $start.end($start.depth - 1)), - text: this.options.dictionary?.emptyToggleBlockBody ?? "", + text: `${t("Add content")}…`, }, { condition: ({ node, parent, $start, state }) => @@ -368,7 +360,7 @@ export default class ToggleBlock extends Node { node.isTextblock && node.textContent === "" && (state.selection as TextSelection).$cursor?.pos === $start.pos, - text: this.options.dictionary?.newLineEmpty ?? "", + text: `${t("Type '/' to insert")}…`, }, ]), ]; diff --git a/shared/editor/nodes/Video.tsx b/shared/editor/nodes/Video.tsx index f5e9179381..d4f9571636 100644 --- a/shared/editor/nodes/Video.tsx +++ b/shared/editor/nodes/Video.tsx @@ -1,3 +1,4 @@ +import { t } from "i18next"; import type { Token } from "markdown-it"; import type { NodeSpec, @@ -7,7 +8,6 @@ import type { import { NodeSelection, TextSelection } from "prosemirror-state"; import * as React from "react"; import type { Primitive } from "utility-types"; -import type { Dictionary } from "~/hooks/useDictionary"; import { sanitizeUrl } from "../../utils/urls"; import toggleWrap from "../commands/toggleWrap"; import Caption from "../components/Caption"; @@ -17,15 +17,7 @@ import attachmentsRule from "../rules/links"; import type { ComponentProps } from "../types"; import Node from "./Node"; -/** - * Options for the Video node. - */ -type VideoOptions = { - /** A dictionary of translated strings used in the editor. */ - dictionary: Dictionary; -}; - -export default class Video extends Node { +export default class Video extends Node { get name() { return "video"; } @@ -178,7 +170,7 @@ export default class Video extends Node { onBlur={this.handleCaptionBlur(props)} onKeyDown={this.handleCaptionKeyDown(props)} isSelected={props.isSelected} - placeholder={this.options.dictionary.imageCaptionPlaceholder} + placeholder={t("Write a caption")} > {props.node.attrs.title} diff --git a/shared/i18n/locales/en_US/translation.json b/shared/i18n/locales/en_US/translation.json index 3c0781bdb5..8d810c0fa6 100644 --- a/shared/i18n/locales/en_US/translation.json +++ b/shared/i18n/locales/en_US/translation.json @@ -298,6 +298,7 @@ "Currently editing": "Currently editing", "Currently viewing": "Currently viewing", "Viewed {{ timeAgo }}": "Viewed {{ timeAgo }}", + "Uploading… {{ progress }}%": "Uploading… {{ progress }}%", "File type not supported. Please use PNG, JPG, GIF, or WebP.": "File type not supported. Please use PNG, JPG, GIF, or WebP.", "File size too large. Maximum size is {{ size }}.": "File size too large. Maximum size is {{ size }}.", "Click or drag to replace": "Click or drag to replace", @@ -554,10 +555,18 @@ "Replacement": "Replacement", "Replace": "Replace", "Replace all": "Replace all", + "Go to link": "Go to link", + "Open link": "Open link", + "Remove link": "Remove link", + "Formatting controls": "Formatting controls", + "Search or paste a link": "Search or paste a link", "Image width": "Image width", "Width": "Width", "Image height": "Image height", "Height": "Height", + "Paste a link": "Paste a link", + "Delete embed": "Delete embed", + "Delete image": "Delete image", "Profile picture": "Profile picture", "Create a new doc": "Create a new doc", "Create a nested doc": "Create a nested doc", @@ -567,106 +576,78 @@ "Mention": "Mention", "Embed": "Embed", "Not supported": "Not supported", + "Sorry, that link won’t work for this embed type": "Sorry, that link won’t work for this embed type", "Upload file": "Upload file", + "Paste a {{service}} link…": "Paste a {{service}} link…", "More options": "More options", - "Rename": "Rename", - "Insert after": "Insert after", - "Insert before": "Insert before", - "Move up": "Move up", - "Move down": "Move down", - "Move left": "Move left", - "Move right": "Move right", - "Align center": "Align center", - "Align left": "Align left", - "Align right": "Align right", - "Default width": "Default width", - "Full width": "Full width", - "Bulleted list": "Bulleted list", - "Todo list": "Task list", - "Show {{ count }} completed": "Show {{ count }} completed", - "Show {{ count }} completed_plural": "Show {{ count }} completed", - "Hide completed": "Hide completed", - "Code block": "Code block", - "Copied to clipboard": "Copied to clipboard", - "Code": "Code", - "Comment": "Comment", - "Create link": "Create link", - "Edit image URL": "Edit image URL", - "Sorry, an error occurred creating the link": "Sorry, an error occurred creating the link", - "Create a new child doc": "Create a new child doc", - "Delete table": "Delete table", + "Type '/' to insert": "Type '/' to insert", + "Keep typing to filter": "Keep typing to filter", + "Replace file": "Replace file", "Delete file": "Delete file", "Show preview": "Show preview", - "Download file": "Download file", - "Replace file": "Replace file", - "Delete image": "Delete image", - "Download image": "Download image", - "Replace image": "Replace image", - "Italic": "Italic", - "Sorry, that link won’t work for this embed type": "Sorry, that link won’t work for this embed type", - "File attachment": "File attachment", - "Embed PDF": "Embed PDF", - "Enter a link": "Enter a link", "Big heading": "Big heading", "Medium heading": "Medium heading", "Small heading": "Small heading", "Extra small heading": "Extra small heading", - "Heading": "Heading", - "Divider": "Divider", - "Image": "Image", - "Sorry, an error occurred uploading the file": "Sorry, an error occurred uploading the file", - "Uploading… {{ progress }}%": "Uploading… {{ progress }}%", - "Write a caption": "Write a caption", - "Info": "Info", - "Info notice": "Info notice", - "Link": "Link", - "Highlight": "Highlight", - "Background color": "Background color", - "Type '/' to insert": "Type '/' to insert", - "Keep typing to filter": "Keep typing to filter", - "Open link": "Open link", - "Go to link": "Go to link", - "Sorry, that type of link is not supported": "Sorry, that type of link is not supported", + "Todo list": "Task list", + "Bulleted list": "Bulleted list", "Ordered list": "Ordered list", - "Page break": "Page break", - "Paste a link": "Paste a link", - "Paste a {{service}} link…": "Paste a {{service}} link…", - "Placeholder": "Placeholder", - "Quote": "Quote", - "Remove link": "Remove link", - "Search or paste a link": "Search or paste a link", - "Strikethrough": "Strikethrough", - "Bold": "Bold", - "Subheading": "Subheading", - "Sort ascending": "Sort ascending", - "Sort descending": "Sort descending", + "Image": "Image", + "Video": "Video", + "Embed PDF": "Embed PDF", + "File attachment": "File attachment", "Table": "Table", - "Export as CSV": "Export as CSV", - "Toggle header": "Toggle header", - "Math inline (LaTeX)": "Math inline (LaTeX)", + "Quote": "Quote", + "Code block": "Code block", "Math block (LaTeX)": "Math block (LaTeX)", - "Merge cells": "Merge cells", - "Split cell": "Split cell", - "Tip": "Tip", - "Tip notice": "Tip notice", - "Warning": "Warning", - "Warning notice": "Warning notice", - "Success": "Success", - "Success notice": "Success notice", + "Toggle block": "Toggle block", + "Divider": "Divider", + "Page break": "Page break", "Current date": "Current date", "Current time": "Current time", "Current date and time": "Current date and time", - "Indent": "Indent", - "Outdent": "Outdent", - "Video": "Video", - "None": "None", - "Toggle block": "Toggle block", - "Add title": "Add title", - "Add content": "Add content", - "Delete embed": "Delete embed", - "Formatting controls": "Formatting controls", - "Distribute columns": "Distribute columns", + "Info notice": "Info notice", + "Success notice": "Success notice", + "Warning notice": "Warning notice", + "Tip notice": "Tip notice", "Wrap text": "Wrap text", + "Placeholder": "Placeholder", + "Bold": "Bold", + "Italic": "Italic", + "Strikethrough": "Strikethrough", + "Background color": "Background color", + "None": "None", + "Highlight": "Highlight", + "Code": "Code", + "Heading": "Heading", + "Subheading": "Subheading", + "Merge cells": "Merge cells", + "Split cell": "Split cell", + "Outdent": "Outdent", + "Indent": "Indent", + "Create link": "Create link", + "Comment": "Comment", + "Align left": "Align left", + "Align center": "Align center", + "Align right": "Align right", + "Full width": "Full width", + "Download image": "Download image", + "Replace image": "Replace image", + "Edit image URL": "Edit image URL", + "Default width": "Default width", + "Distribute columns": "Distribute columns", + "Delete table": "Delete table", + "Export as CSV": "Export as CSV", + "Sort ascending": "Sort ascending", + "Sort descending": "Sort descending", + "Toggle header": "Toggle header", + "Insert after": "Insert after", + "Insert before": "Insert before", + "Move left": "Move left", + "Move right": "Move right", + "Move up": "Move up", + "Move down": "Move down", + "Rename": "Rename", "Delete Emoji": "Delete Emoji", "Emoji deleted": "Emoji deleted", "I'm sure – Delete": "I'm sure – Delete", @@ -876,6 +857,7 @@ "Learn more": "Learn more", "Backlinks": "Backlinks", "Clear search highlight": "Clear search highlight", + "Warning": "Warning", "This document is large which may affect performance": "This document is large which may affect performance", "Are you sure about that? Deleting the {{ documentTitle }} document will delete all of its history.": "Are you sure about that? Deleting the {{ documentTitle }} document will delete all of its history.", "Are you sure about that? Deleting the {{ documentTitle }} document will delete all of its history and {{ any }} nested document.": "Are you sure about that? Deleting the {{ documentTitle }} document will delete all of its history and {{ any }} nested document.", @@ -943,6 +925,7 @@ "Large header": "Large header", "Medium header": "Medium header", "Small header": "Small header", + "Link": "Link", "Underline": "Underline", "Undo": "Undo", "Redo": "Redo", @@ -1099,6 +1082,7 @@ "Credentials": "Credentials", "OAuth client ID": "OAuth client ID", "The public identifier for this app": "The public identifier for this app", + "Copied to clipboard": "Copied to clipboard", "OAuth client secret": "OAuth client secret", "Store this value securely, do not expose it publicly": "Store this value securely, do not expose it publicly", "Where users are redirected after authorizing this app": "Where users are redirected after authorizing this app", @@ -1757,10 +1741,18 @@ "{{ user }} updated {{ timeAgo }}": "{{ user }} updated {{ timeAgo }}", "You created {{ timeAgo }}": "You created {{ timeAgo }}", "{{ user }} created {{ timeAgo }}": "{{ user }} created {{ timeAgo }}", + "Sorry, an error occurred uploading the file": "Sorry, an error occurred uploading the file", + "Sorry, that type of link is not supported": "Sorry, that type of link is not supported", "Caption": "Caption", "Empty diagram": "Empty diagram", "Double click to edit": "Double click to edit", "Open": "Open", "Loading": "Loading", - "Error loading data": "Error loading data" + "Error loading data": "Error loading data", + "Show {{ count }} completed": "Show {{ count }} completed", + "Show {{ count }} completed_plural": "Show {{ count }} completed", + "Hide completed": "Hide completed", + "Write a caption": "Write a caption", + "Add title": "Add title", + "Add content": "Add content" }