From c7a583c53a5205f4a3ebeb3535b11f97e5f4fde5 Mon Sep 17 00:00:00 2001 From: Huang Xin Date: Fri, 30 May 2025 15:08:45 +0800 Subject: [PATCH] translator: lazy load translation observer (#1282) --- .../app/reader/hooks/useTextTranslation.ts | 32 ++++++++++--------- apps/readest-app/src/services/appService.ts | 2 +- .../src/services/tts/TTSController.ts | 1 + apps/readest-app/src/utils/toc.ts | 11 ++++--- 4 files changed, 25 insertions(+), 21 deletions(-) diff --git a/apps/readest-app/src/app/reader/hooks/useTextTranslation.ts b/apps/readest-app/src/app/reader/hooks/useTextTranslation.ts index 7e85bfa4..6cb0cafb 100644 --- a/apps/readest-app/src/app/reader/hooks/useTextTranslation.ts +++ b/apps/readest-app/src/app/reader/hooks/useTextTranslation.ts @@ -9,11 +9,11 @@ import { getLocale } from '@/utils/misc'; export function useTextTranslation(bookKey: string, view: FoliateView | HTMLElement | null) { const { getViewSettings } = useReaderStore(); - const viewSettings = getViewSettings(bookKey)!; + const viewSettings = getViewSettings(bookKey); - const enabled = useRef(viewSettings.translationEnabled); - const [provider, setProvider] = useState(viewSettings.translationProvider); - const [targetLang, setTargetLang] = useState(viewSettings.translateTargetLang); + const enabled = useRef(viewSettings?.translationEnabled); + const [provider, setProvider] = useState(viewSettings?.translationProvider); + const [targetLang, setTargetLang] = useState(viewSettings?.translateTargetLang); const { translate } = useTranslator({ provider, @@ -42,6 +42,16 @@ export function useTextTranslation(bookKey: string, view: FoliateView | HTMLElem translateRef.current = translate; }, [translate]); + const observeTextNodes = () => { + if (!view || !enabled.current) return; + const observer = createTranslationObserver(); + observerRef.current = observer; + const nodes = walkTextNodes(view); + console.log('Observing text nodes for translation:', nodes.length); + allTextNodes.current = nodes; + nodes.forEach((el) => observer.observe(el)); + }; + const updateTranslation = () => { translatedElements.current.forEach((element) => { const translationTargets = element.querySelectorAll('.translation-target'); @@ -49,7 +59,7 @@ export function useTextTranslation(bookKey: string, view: FoliateView | HTMLElem }); translatedElements.current = []; - if (viewSettings.translationEnabled && view) { + if (viewSettings?.translationEnabled && view) { recreateTranslationObserver(); } }; @@ -168,7 +178,7 @@ export function useTextTranslation(bookKey: string, view: FoliateView | HTMLElem if (enabledChanged) { toggleTranslationVisibility(viewSettings.translationEnabled); if (enabled.current) { - recreateTranslationObserver(); + observeTextNodes(); } } else if (providerChanged || targetLangChanged) { updateTranslation(); @@ -177,15 +187,7 @@ export function useTextTranslation(bookKey: string, view: FoliateView | HTMLElem }, [bookKey, viewSettings, provider, targetLang]); useEffect(() => { - if (!view) return; - - const observeTextNodes = () => { - const observer = createTranslationObserver(); - observerRef.current = observer; - const nodes = walkTextNodes(view); - allTextNodes.current = nodes; - nodes.forEach((el) => observer.observe(el)); - }; + if (!view || !enabled.current) return; if ('renderer' in view) { view.addEventListener('load', observeTextNodes); diff --git a/apps/readest-app/src/services/appService.ts b/apps/readest-app/src/services/appService.ts index 78674aec..8b0a7ec9 100644 --- a/apps/readest-app/src/services/appService.ts +++ b/apps/readest-app/src/services/appService.ts @@ -401,7 +401,7 @@ export abstract class BaseAppService implements AppService { } } catch (error) { // don't throw error here since some books may not have cover images at all - console.log('Failed to download cover file:', error); + console.log(`Failed to download cover file for book: '${book.title}'`, error); } if (needDownBook) { diff --git a/apps/readest-app/src/services/tts/TTSController.ts b/apps/readest-app/src/services/tts/TTSController.ts index a6300fea..625ca5b6 100644 --- a/apps/readest-app/src/services/tts/TTSController.ts +++ b/apps/readest-app/src/services/tts/TTSController.ts @@ -86,6 +86,7 @@ export class TTSController extends EventTarget { .replace(/[–—]/g, ',') .replace('', ' ') .replace(/\.{3,}/g, ' ') + .replace(/……/g, ' ') .replace(/·/g, ' '); return ssml; diff --git a/apps/readest-app/src/utils/toc.ts b/apps/readest-app/src/utils/toc.ts index 636f4a60..5837135a 100644 --- a/apps/readest-app/src/utils/toc.ts +++ b/apps/readest-app/src/utils/toc.ts @@ -59,7 +59,7 @@ export const updateToc = (bookDoc: BookDoc, items: TOCItem[], sections: SectionI map[section.id] = section; return map; }, {}); - updateTocData(bookDoc, items, sectionsMap); + updateTocData(bookDoc, items, sections, sectionsMap); items.sort((a, b) => { if (a.location && b.location) { return a.location.current - b.location.current; @@ -71,25 +71,26 @@ export const updateToc = (bookDoc: BookDoc, items: TOCItem[], sections: SectionI const updateTocData = ( bookDoc: BookDoc, items: TOCItem[], - sections: { [id: string]: SectionItem }, + sections: SectionItem[], + sectionsMap: { [id: string]: SectionItem }, index = 0, ): number => { items.forEach((item) => { item.id ??= index++; if (item.href) { const id = bookDoc.splitTOCHref(item.href)[0]!; - const section = sections[id]; + const section = sectionsMap[id]; if (section) { item.cfi = section.cfi; // Add location only when toc item is at the same level as the section // otherwise the location will not be accurate - if (id === item.href) { + if (id === item.href || items.length <= sections.length) { item.location = section.location; } } } if (item.subitems) { - index = updateTocData(bookDoc, item.subitems, sections, index); + index = updateTocData(bookDoc, item.subitems, sections, sectionsMap, index); } }); return index;