forked from akai/readest
translator: post-process translated text for punctuation spacing (#1245)
This commit is contained in:
@@ -57,18 +57,48 @@ export function useTextTranslation(bookKey: string, view: FoliateView | null) {
|
||||
const createTranslationObserver = () => {
|
||||
return new IntersectionObserver(
|
||||
(entries) => {
|
||||
let beforeIntersectedElement: HTMLElement | null = null;
|
||||
let lastIntersectedElement: HTMLElement | null = null;
|
||||
for (const entry of entries) {
|
||||
if (!entry.isIntersecting) continue;
|
||||
translateElement(entry.target as HTMLElement);
|
||||
if (!entry.isIntersecting) {
|
||||
if (!lastIntersectedElement) {
|
||||
beforeIntersectedElement = entry.target as HTMLElement;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const currentElement = entry.target as HTMLElement;
|
||||
translateElement(currentElement);
|
||||
lastIntersectedElement = currentElement;
|
||||
}
|
||||
if (beforeIntersectedElement) {
|
||||
translateElement(beforeIntersectedElement);
|
||||
}
|
||||
if (lastIntersectedElement) {
|
||||
preTranslateNextElements(lastIntersectedElement, 2);
|
||||
}
|
||||
},
|
||||
{
|
||||
rootMargin: '2560px',
|
||||
threshold: 0.01,
|
||||
rootMargin: '1280px',
|
||||
threshold: 0,
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const preTranslateNextElements = (currentElement: HTMLElement, count: number) => {
|
||||
if (!allTextNodes.current || count <= 0) return;
|
||||
const currentIndex = allTextNodes.current.indexOf(currentElement);
|
||||
if (currentIndex === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextElements = allTextNodes.current.slice(currentIndex + 1, currentIndex + 1 + count);
|
||||
nextElements.forEach((element, index) => {
|
||||
setTimeout(() => {
|
||||
translateElement(element);
|
||||
}, index * 500);
|
||||
});
|
||||
};
|
||||
|
||||
const recreateTranslationObserver = () => {
|
||||
const observer = createTranslationObserver();
|
||||
observerRef.current?.disconnect();
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { useState, useCallback, useEffect } from 'react';
|
||||
import { useAuth } from '@/context/AuthContext';
|
||||
import { getTranslator, getTranslators } from '@/services/translators';
|
||||
import { getFromCache, storeInCache, UseTranslatorOptions } from '@/services/translators';
|
||||
import { getFromCache, storeInCache, polish, UseTranslatorOptions } from '@/services/translators';
|
||||
|
||||
export function useTranslator({
|
||||
provider = 'deepl',
|
||||
sourceLang = 'AUTO',
|
||||
targetLang = 'EN',
|
||||
enablePolishing = true,
|
||||
}: UseTranslatorOptions = {}) {
|
||||
const { token } = useAuth();
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -120,7 +121,7 @@ export function useTranslator({
|
||||
);
|
||||
|
||||
setLoading(false);
|
||||
return results;
|
||||
return enablePolishing ? polish(results, targetLanguage) : results;
|
||||
} catch (err) {
|
||||
console.error('Translation error:', err);
|
||||
setLoading(false);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './types';
|
||||
export * from './cache';
|
||||
export * from './polish';
|
||||
export * from './providers';
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
export type Polisher = (text: string) => string;
|
||||
|
||||
const languagePolishers: Record<string, Polisher> = {
|
||||
// Chinese - fix punctuation spacing
|
||||
zh: (text: string) =>
|
||||
text
|
||||
.replace(/--/g, '⸺')
|
||||
.replace(/\s+([。、!?])/g, '$1')
|
||||
.replace(/([。、!?])\s+/g, '$1'),
|
||||
|
||||
// Spanish - fix punctuation spacing
|
||||
es: (text: string) =>
|
||||
text.replace(/\?([A-ZÁÉÍÓÚÑÜ])/g, '? $1').replace(/\!([A-ZÁÉÍÓÚÑÜ])/g, '! $1'),
|
||||
|
||||
// French - fix punctuation spacing
|
||||
fr: (text: string) => text.replace(/\s+([!?:;])/g, ' $1').replace(/([!?:;])\s+/g, '$1 '),
|
||||
|
||||
// Japanese - fix punctuation spacing
|
||||
ja: (text: string) => text.replace(/\s+([。、!?])/g, '$1').replace(/([。、!?])\s+/g, '$1'),
|
||||
};
|
||||
|
||||
export const basicPolish: Polisher = (text: string) => {
|
||||
return text
|
||||
.replace(/\s+/g, ' ') // Multiple spaces to single space
|
||||
.replace(/\s+([.,!?;:])/g, '$1') // Remove space before punctuation
|
||||
.trim();
|
||||
};
|
||||
|
||||
export function getPolisher(targetLang: string): Polisher {
|
||||
const langCode = targetLang.split('-')[0]!.toLowerCase();
|
||||
const languagePolisher = languagePolishers[langCode];
|
||||
|
||||
if (languagePolisher) {
|
||||
return (text: string) => {
|
||||
const basicPolished = basicPolish(text);
|
||||
const polished = languagePolisher(basicPolished);
|
||||
return polished;
|
||||
};
|
||||
}
|
||||
|
||||
return basicPolish;
|
||||
}
|
||||
|
||||
export function polish(texts: string[], targetLang: string): string[] {
|
||||
const polisher = getPolisher(targetLang);
|
||||
return texts.map(polisher);
|
||||
}
|
||||
@@ -20,4 +20,5 @@ export interface UseTranslatorOptions {
|
||||
provider?: TranslatorName;
|
||||
sourceLang?: string;
|
||||
targetLang?: string;
|
||||
enablePolishing?: boolean;
|
||||
}
|
||||
|
||||
@@ -359,7 +359,7 @@ export const getFootnoteStyles = () => `
|
||||
|
||||
const getTranslationStyles = () => `
|
||||
.translation-block-wrapper {
|
||||
display: inline-block !important;
|
||||
display: block !important;
|
||||
margin: 0.5em 0 !important;
|
||||
}
|
||||
.translation-target.hidden {
|
||||
|
||||
Reference in New Issue
Block a user