@@ -177,18 +200,25 @@ const LangPanel: React.FC = ({ bookKey, onRegisterReset
type='checkbox'
className='toggle'
checked={showTranslateSource}
- disabled={!translationEnabled}
onChange={() => setShowTranslateSource(!showTranslateSource)}
/>
+
+ {_('TTS Text')}
+
+
+
{_('Translation Service')}
@@ -198,7 +228,6 @@ const LangPanel: React.FC
= ({ bookKey, onRegisterReset
value={getCurrentTargetLangOption().value}
onChange={handleSelectTargetLang}
options={getLangOptions(TRANSLATOR_LANGS)}
- disabled={!translationEnabled}
/>
diff --git a/apps/readest-app/src/services/constants.ts b/apps/readest-app/src/services/constants.ts
index e7747504..4fc758c2 100644
--- a/apps/readest-app/src/services/constants.ts
+++ b/apps/readest-app/src/services/constants.ts
@@ -226,6 +226,7 @@ export const DEFAULT_TRANSLATOR_CONFIG: TranslatorConfig = {
translationProvider: 'deepl',
translateTargetLang: '',
showTranslateSource: true,
+ ttsReadAloudText: 'both',
};
export const DEFAULT_SCREEN_CONFIG: ScreenConfig = {
diff --git a/apps/readest-app/src/services/tts/TTSController.ts b/apps/readest-app/src/services/tts/TTSController.ts
index c227d4c8..1a50533b 100644
--- a/apps/readest-app/src/services/tts/TTSController.ts
+++ b/apps/readest-app/src/services/tts/TTSController.ts
@@ -1,6 +1,6 @@
import { FoliateView } from '@/types/view';
import { AppService } from '@/types/system';
-import { parseSSMLMarks } from '@/utils/ssml';
+import { filterSSMLWithLang, parseSSMLMarks } from '@/utils/ssml';
import { Overlayer } from 'foliate-js/overlayer.js';
import { TTSGranularity, TTSHighlightOptions, TTSMark, TTSVoice } from './types';
import { createRejectFilter } from '@/utils/node';
@@ -39,6 +39,7 @@ export class TTSController extends EventTarget {
ttsWebVoices: TTSVoice[] = [];
ttsEdgeVoices: TTSVoice[] = [];
ttsNativeVoices: TTSVoice[] = [];
+ ttsTargetLang: string = '';
constructor(appService: AppService | null, view: FoliateView) {
super();
@@ -141,6 +142,9 @@ export class TTSController extends EventTarget {
.replace(/\*/g, ' ')
.replace(/·/g, ' ');
+ if (this.ttsTargetLang) {
+ ssml = filterSSMLWithLang(ssml, this.ttsTargetLang);
+ }
return ssml;
}
@@ -362,6 +366,10 @@ export class TTSController extends EventTarget {
return this.ttsClient.getSpeakingLang();
}
+ setTargetLang(lang: string) {
+ this.ttsTargetLang = lang;
+ }
+
dispatchSpeakMark(mark?: TTSMark) {
this.dispatchEvent(new CustomEvent('tts-speak-mark', { detail: mark || { text: '' } }));
if (mark) {
diff --git a/apps/readest-app/src/types/book.ts b/apps/readest-app/src/types/book.ts
index 9fdfba11..08fa9dbb 100644
--- a/apps/readest-app/src/types/book.ts
+++ b/apps/readest-app/src/types/book.ts
@@ -182,6 +182,7 @@ export interface TranslatorConfig {
translationProvider: string;
translateTargetLang: string;
showTranslateSource: boolean;
+ ttsReadAloudText: string;
}
export interface ScreenConfig {
diff --git a/apps/readest-app/src/utils/reload.ts b/apps/readest-app/src/utils/reload.ts
index ad2faf5b..32634531 100644
--- a/apps/readest-app/src/utils/reload.ts
+++ b/apps/readest-app/src/utils/reload.ts
@@ -1,7 +1,5 @@
import { RELOAD_BEFORE_SAVED_TIMEOUT_MS } from '@/services/constants';
-import { eventDispatcher } from './event';
export const saveAndReload = async () => {
- await eventDispatcher.dispatch('beforereload');
setTimeout(() => window.location.reload(), RELOAD_BEFORE_SAVED_TIMEOUT_MS);
};
diff --git a/apps/readest-app/src/utils/ssml.ts b/apps/readest-app/src/utils/ssml.ts
index a9ac74ff..f98b5639 100644
--- a/apps/readest-app/src/utils/ssml.ts
+++ b/apps/readest-app/src/utils/ssml.ts
@@ -116,3 +116,59 @@ export const findSSMLMark = (charIndex: number, marks: TTSMark[]) => {
return result;
};
+
+export const filterSSMLWithLang = (
+ ssml: string,
+ targetLang: string,
+ primaryLang?: string,
+): string => {
+ const mainLang = parseSSMLLang(ssml, primaryLang);
+
+ // Normalize target language
+ const normalizedTarget = code6392to6391(targetLang.toLowerCase()) || targetLang.toLowerCase();
+
+ // Check if target matches main language
+ if (isSameLang(normalizedTarget, mainLang)) {
+ // Remove all