From 3e50a1387b6c56a34215303beafbc8b2511991b5 Mon Sep 17 00:00:00 2001 From: Hoang Pham Date: Wed, 2 Oct 2024 19:34:31 +0700 Subject: [PATCH 1/3] support translation Signed-off-by: Hoang Pham --- src/App.tsx | 68 ++++++++++++++++++++++++---------- src/hooks/useExcalidrawLang.ts | 35 +++++++++++++++++ 2 files changed, 83 insertions(+), 20 deletions(-) create mode 100644 src/hooks/useExcalidrawLang.ts diff --git a/src/App.tsx b/src/App.tsx index ff8d05b..a23ea82 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -28,14 +28,21 @@ import Embeddable from './Embeddable' import type { ResolvablePromise } from '@excalidraw/excalidraw/types/utils' import type { NonDeletedExcalidrawElement } from '@excalidraw/excalidraw/types/element/types' import { getLinkWithPicker } from '@nextcloud/vue/dist/Components/NcRichText.js' +import { useExcalidrawLang } from './hooks/useExcalidrawLang' + interface WhiteboardAppProps { - fileId: number; - fileName: string; - isEmbedded: boolean; - publicSharingToken: string | null; + fileId: number + fileName: string + isEmbedded: boolean + publicSharingToken: string | null } -export default function App({ fileId, isEmbedded, fileName, publicSharingToken }: WhiteboardAppProps) { +export default function App({ + fileId, + isEmbedded, + fileName, + publicSharingToken, +}: WhiteboardAppProps) { const fileNameWithoutExtension = fileName.split('.').slice(0, -1).join('.') const [viewModeEnabled] = useState(isEmbedded) @@ -44,13 +51,21 @@ export default function App({ fileId, isEmbedded, fileName, publicSharingToken } const isDarkMode = () => { const ncThemes = document.body.dataset?.themes - return (window.matchMedia('(prefers-color-scheme: dark)').matches && ncThemes?.indexOf('light') === -1) + return ( + (window.matchMedia('(prefers-color-scheme: dark)').matches + && ncThemes?.indexOf('light') === -1) || ncThemes?.indexOf('dark') > -1 + ) } const [theme, setTheme] = useState(isDarkMode() ? 'dark' : 'light') + const lang = useExcalidrawLang() + + console.log('lang', lang) + useEffect(() => { - const themeChangeListener = () => setTheme(isDarkMode() ? 'dark' : 'light') + const themeChangeListener = () => + setTheme(isDarkMode() ? 'dark' : 'light') const mq = window.matchMedia('(prefers-color-scheme: dark)') mq.addEventListener('change', themeChangeListener) return () => { @@ -69,26 +84,29 @@ export default function App({ fileId, isEmbedded, fileName, publicSharingToken } } const initialStatePromiseRef = useRef<{ - promise: ResolvablePromise; + promise: ResolvablePromise }>({ promise: null! }) if (!initialStatePromiseRef.current.promise) { initialStatePromiseRef.current.promise = resolvablePromise() } - const [ - excalidrawAPI, - setExcalidrawAPI, - ] = useState(null) + const [excalidrawAPI, setExcalidrawAPI] + = useState(null) const [collab, setCollab] = useState(null) - if (excalidrawAPI && !collab) setCollab(new Collab(excalidrawAPI, fileId, publicSharingToken)) + if (excalidrawAPI && !collab) { setCollab(new Collab(excalidrawAPI, fileId, publicSharingToken)) } if (collab && !collab.portal.socket) collab.startCollab() useEffect(() => { - const extraTools = document.getElementsByClassName('App-toolbar__extra-tools-trigger')[0] + const extraTools = document.getElementsByClassName( + 'App-toolbar__extra-tools-trigger', + )[0] const smartPick = document.createElement('label') smartPick.classList.add(...['ToolIcon', 'Shape']) if (extraTools) { - extraTools.parentNode?.insertBefore(smartPick, extraTools.previousSibling) + extraTools.parentNode?.insertBefore( + smartPick, + extraTools.previousSibling, + ) const root = createRoot(smartPick) root.render(renderSmartPicker()) } @@ -130,7 +148,7 @@ export default function App({ fileId, isEmbedded, fileName, publicSharingToken } ( element: NonDeletedExcalidrawElement, event: CustomEvent<{ - nativeEvent: MouseEvent | React.PointerEvent; + nativeEvent: MouseEvent | React.PointerEvent }>, ) => { const link = element.link! @@ -151,11 +169,16 @@ export default function App({ fileId, isEmbedded, fileName, publicSharingToken } const addWebEmbed = (link: string) => { let cords: { x: any; y: any } if (excalidrawAPI) { - cords = viewportCoordsToSceneCoords({ clientX: 100, clientY: 100 }, excalidrawAPI.getAppState()) + cords = viewportCoordsToSceneCoords( + { clientX: 100, clientY: 100 }, + excalidrawAPI.getAppState(), + ) } else { cords = { x: 0, y: 0 } } - const elements = excalidrawAPI?.getSceneElementsIncludingDeleted().slice() + const elements = excalidrawAPI + ?.getSceneElementsIncludingDeleted() + .slice() elements?.push({ link, id: (Math.random() + 1).toString(36).substring(7), @@ -204,7 +227,12 @@ export default function App({ fileId, isEmbedded, fileName, publicSharingToken } const renderSmartPicker = () => { return ( - ) @@ -234,7 +262,7 @@ export default function App({ fileId, isEmbedded, fileName, publicSharingToken } }, }} onLinkOpen={onLinkOpen} - > + langCode={lang}> {renderMenu()} diff --git a/src/hooks/useExcalidrawLang.ts b/src/hooks/useExcalidrawLang.ts new file mode 100644 index 0000000..394a1c4 --- /dev/null +++ b/src/hooks/useExcalidrawLang.ts @@ -0,0 +1,35 @@ +import { useState, useEffect } from 'react' +import { getLanguage } from '@nextcloud/l10n' +import { languages } from '@excalidraw/excalidraw' + +function mapNextcloudToExcalidrawLang(nextcloudLang: string): string { + const lowerNextcloudLang = nextcloudLang.toLowerCase() + + const exactMatch = languages.find( + (lang) => + lang.code.toLowerCase() === lowerNextcloudLang + || lang.code.toLowerCase().replace('-', '_') === lowerNextcloudLang, + ) + if (exactMatch) return exactMatch.code + + const partialMatch = languages.find((lang) => + lang.code.toLowerCase().startsWith(lowerNextcloudLang.slice(0, 1)), + ) + + if (partialMatch) return partialMatch.code + + return 'en' +} + +export function useExcalidrawLang() { + const [lang, setLang] = useState( + mapNextcloudToExcalidrawLang(getLanguage()), + ) + + useEffect(() => { + const nextcloudLang = getLanguage() + setLang(mapNextcloudToExcalidrawLang(nextcloudLang)) + }, []) + + return lang +} From df8f4418399768afe09de558765a80fe59a24002 Mon Sep 17 00:00:00 2001 From: Hoang Pham Date: Wed, 2 Oct 2024 19:39:33 +0700 Subject: [PATCH 2/3] support translation Signed-off-by: Hoang Pham --- src/hooks/useExcalidrawLang.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/hooks/useExcalidrawLang.ts b/src/hooks/useExcalidrawLang.ts index 394a1c4..31f61aa 100644 --- a/src/hooks/useExcalidrawLang.ts +++ b/src/hooks/useExcalidrawLang.ts @@ -1,3 +1,8 @@ +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + import { useState, useEffect } from 'react' import { getLanguage } from '@nextcloud/l10n' import { languages } from '@excalidraw/excalidraw' From 5cf3b9cb6d70217f3403ae75df719e414121eb70 Mon Sep 17 00:00:00 2001 From: Hoang Pham Date: Mon, 7 Oct 2024 18:40:20 +0700 Subject: [PATCH 3/3] fix lang mapper Signed-off-by: Hoang Pham --- src/App.tsx | 2 -- src/hooks/useExcalidrawLang.ts | 29 ++++++++++++++++------------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index a23ea82..4044cf0 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -61,8 +61,6 @@ export default function App({ const lang = useExcalidrawLang() - console.log('lang', lang) - useEffect(() => { const themeChangeListener = () => setTheme(isDarkMode() ? 'dark' : 'light') diff --git a/src/hooks/useExcalidrawLang.ts b/src/hooks/useExcalidrawLang.ts index 31f61aa..4dd375f 100644 --- a/src/hooks/useExcalidrawLang.ts +++ b/src/hooks/useExcalidrawLang.ts @@ -7,29 +7,32 @@ import { useState, useEffect } from 'react' import { getLanguage } from '@nextcloud/l10n' import { languages } from '@excalidraw/excalidraw' +const languageMap = new Map(languages.map(lang => [lang.code.toLowerCase(), lang.code])) + function mapNextcloudToExcalidrawLang(nextcloudLang: string): string { const lowerNextcloudLang = nextcloudLang.toLowerCase() - const exactMatch = languages.find( - (lang) => - lang.code.toLowerCase() === lowerNextcloudLang - || lang.code.toLowerCase().replace('-', '_') === lowerNextcloudLang, - ) - if (exactMatch) return exactMatch.code + if (languageMap.has(lowerNextcloudLang)) { + return languageMap.get(lowerNextcloudLang)! + } - const partialMatch = languages.find((lang) => - lang.code.toLowerCase().startsWith(lowerNextcloudLang.slice(0, 1)), - ) + const hyphenatedLang = lowerNextcloudLang.replace('_', '-') + if (languageMap.has(hyphenatedLang)) { + return languageMap.get(hyphenatedLang)! + } - if (partialMatch) return partialMatch.code + for (const [excalidrawLang, originalCode] of languageMap) { + if (excalidrawLang.startsWith(lowerNextcloudLang) + || lowerNextcloudLang.startsWith(excalidrawLang.split('-')[0])) { + return originalCode + } + } return 'en' } export function useExcalidrawLang() { - const [lang, setLang] = useState( - mapNextcloudToExcalidrawLang(getLanguage()), - ) + const [lang, setLang] = useState(() => mapNextcloudToExcalidrawLang(getLanguage())) useEffect(() => { const nextcloudLang = getLanguage()