From 5820191c26de90a1806e4528554b2e3eb2820ec8 Mon Sep 17 00:00:00 2001 From: Huang Xin Date: Thu, 29 May 2025 16:08:01 +0800 Subject: [PATCH] translator: also translate table of contents (#1273) --- .../src/app/reader/components/HeaderBar.tsx | 2 +- .../reader/components/TranslationToggler.tsx | 4 ++- .../app/reader/components/sidebar/TOCView.tsx | 3 +++ .../app/reader/hooks/useTextTranslation.ts | 25 +++++++++++-------- apps/readest-app/src/utils/toc.ts | 6 +++++ 5 files changed, 27 insertions(+), 13 deletions(-) diff --git a/apps/readest-app/src/app/reader/components/HeaderBar.tsx b/apps/readest-app/src/app/reader/components/HeaderBar.tsx index f63ff98f..2b959fa9 100644 --- a/apps/readest-app/src/app/reader/components/HeaderBar.tsx +++ b/apps/readest-app/src/app/reader/components/HeaderBar.tsx @@ -120,6 +120,7 @@ const HeaderBar: React.FC = ({ +
@@ -129,7 +130,6 @@ const HeaderBar: React.FC = ({
- { } disabled={ - !bookData || isSameLang(bookData.book?.primaryLanguage, viewSettings.translateTargetLang!) + !bookData || + bookData.book?.format === 'PDF' || + isSameLang(bookData.book?.primaryLanguage, viewSettings.translateTargetLang!) } onClick={() => setTranslationEnabled(!translationEnabled)} tooltip={translationEnabled ? _('Disable Translation') : _('Enable Translation')} diff --git a/apps/readest-app/src/app/reader/components/sidebar/TOCView.tsx b/apps/readest-app/src/app/reader/components/sidebar/TOCView.tsx index 326e0d43..37fd3bbc 100644 --- a/apps/readest-app/src/app/reader/components/sidebar/TOCView.tsx +++ b/apps/readest-app/src/app/reader/components/sidebar/TOCView.tsx @@ -8,6 +8,7 @@ import { findParentPath } from '@/utils/toc'; import { getContentMd5 } from '@/utils/misc'; import { eventDispatcher } from '@/utils/event'; import { BookProgress } from '@/types/book'; +import { useTextTranslation } from '../../hooks/useTextTranslation'; const createExpanderIcon = (isExpanded: boolean) => { return ( @@ -129,6 +130,8 @@ const TOCView: React.FC<{ const [expandedItems, setExpandedItems] = useState([]); const viewRef = useRef(null); + useTextTranslation(bookKey, viewRef.current); + const expandParents = (toc: TOCItem[], href: string) => { const parentPath = findParentPath(toc, href).map((item) => item.href); setExpandedItems(parentPath.filter(Boolean) as string[]); diff --git a/apps/readest-app/src/app/reader/hooks/useTextTranslation.ts b/apps/readest-app/src/app/reader/hooks/useTextTranslation.ts index 817739fd..7e85bfa4 100644 --- a/apps/readest-app/src/app/reader/hooks/useTextTranslation.ts +++ b/apps/readest-app/src/app/reader/hooks/useTextTranslation.ts @@ -7,7 +7,7 @@ import { walkTextNodes } from '@/utils/walk'; import { localeToLang } from '@/utils/lang'; import { getLocale } from '@/utils/misc'; -export function useTextTranslation(bookKey: string, view: FoliateView | null) { +export function useTextTranslation(bookKey: string, view: FoliateView | HTMLElement | null) { const { getViewSettings } = useReaderStore(); const viewSettings = getViewSettings(bookKey)!; @@ -136,6 +136,9 @@ export function useTextTranslation(bookKey: string, view: FoliateView | null) { blockWrapper.appendChild(inner); wrapper.appendChild(blockWrapper); + if (el.querySelector('.translation-target')) { + return; + } el.appendChild(wrapper); translatedElements.current.push(el); } catch (err) { @@ -176,7 +179,7 @@ export function useTextTranslation(bookKey: string, view: FoliateView | null) { useEffect(() => { if (!view) return; - const onLoad = () => { + const observeTextNodes = () => { const observer = createTranslationObserver(); observerRef.current = observer; const nodes = walkTextNodes(view); @@ -184,18 +187,18 @@ export function useTextTranslation(bookKey: string, view: FoliateView | null) { nodes.forEach((el) => observer.observe(el)); }; - view.addEventListener('load', onLoad); - return () => { - view.removeEventListener('load', onLoad); - observerRef.current?.disconnect(); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [view]); - - useEffect(() => { + if ('renderer' in view) { + view.addEventListener('load', observeTextNodes); + } else { + observeTextNodes(); + } return () => { + if ('renderer' in view) { + view.removeEventListener('load', observeTextNodes); + } observerRef.current?.disconnect(); translatedElements.current = []; }; + // eslint-disable-next-line react-hooks/exhaustive-deps }, [view]); } diff --git a/apps/readest-app/src/utils/toc.ts b/apps/readest-app/src/utils/toc.ts index a2ce6698..636f4a60 100644 --- a/apps/readest-app/src/utils/toc.ts +++ b/apps/readest-app/src/utils/toc.ts @@ -60,6 +60,12 @@ export const updateToc = (bookDoc: BookDoc, items: TOCItem[], sections: SectionI return map; }, {}); updateTocData(bookDoc, items, sectionsMap); + items.sort((a, b) => { + if (a.location && b.location) { + return a.location.current - b.location.current; + } + return 0; + }); }; const updateTocData = (