feat: Added Yandex Translator (#1652)

This commit is contained in:
Yurii Kostyukov
2025-07-21 18:13:13 +03:00
committed by GitHub
parent 6d465a40fb
commit 9d3fc07865
5 changed files with 85 additions and 1 deletions
+1
View File
@@ -57,6 +57,7 @@
| **File Association and Open With** | Quickly open files in Readest in your file browser with one-click. | ✅ |
| **Sync across Platforms** | Synchronize book files, reading progress, notes, and bookmarks across all supported platforms. | ✅ |
| **Translate with DeepL** | From a single sentence to the entire book—translate instantly with DeepL. | ✅ |
| **Translate with Yandex** | Instantly translate text or books using Yandex Translate. | ✅ |
| **Text-to-Speech (TTS) Support** | Enjoy smooth, multilingual narration—even within a single book. | ✅ |
| **Library Management** | Organize, sort, and manage your entire ebook library. | ✅ |
| **Code Syntax Highlighting** | Read software manuals with rich coloring of code examples. | ✅ |
@@ -68,6 +68,9 @@
{
"url": "https://edge.microsoft.com"
},
{
"url": "https://translate.toil.cc"
},
{
"url": "https://*.microsofttranslator.com"
},
+1 -1
View File
@@ -15,7 +15,7 @@
"security": {
"csp": {
"default-src": "'self' 'unsafe-inline' blob: data: customprotocol: asset: http://asset.localhost ipc: http://ipc.localhost",
"connect-src": "'self' blob: data: asset: http://asset.localhost ipc: http://ipc.localhost https://*.sentry.io https://*.posthog.com https://*.deepl.com https://*.wikipedia.org https://*.wiktionary.org https://*.supabase.co https://*.readest.com wss://speech.platform.bing.com https://*.cloudflarestorage.com https://translate.googleapis.com https://*.microsofttranslator.com https://edge.microsoft.com https://*.googleusercontent.com",
"connect-src": "'self' blob: data: asset: http://asset.localhost ipc: http://ipc.localhost https://*.sentry.io https://*.posthog.com https://*.deepl.com https://*.wikipedia.org https://*.wiktionary.org https://*.supabase.co https://*.readest.com wss://speech.platform.bing.com https://*.cloudflarestorage.com https://translate.googleapis.com https://translate.toil.cc https://*.microsofttranslator.com https://edge.microsoft.com https://*.googleusercontent.com",
"img-src": "'self' blob: data: asset: http://asset.localhost https://*",
"style-src": "'self' 'unsafe-inline' blob: asset: http://asset.localhost https://cdn.jsdelivr.net https://fonts.googleapis.com https://ik.imagekit.io",
"font-src": "'self' blob: data: asset: http://asset.localhost tauri: https://db.onlinewebfonts.com https://cdn.jsdelivr.net https://fonts.gstatic.com https://ik.imagekit.io",
@@ -2,6 +2,7 @@ import { TranslationProvider } from '../types';
import { deeplProvider } from './deepl';
import { azureProvider } from './azure';
import { googleProvider } from './google';
import { yandexProvider } from './yandex';
function createTranslator<T extends string>(
name: T,
@@ -18,11 +19,13 @@ function createTranslator<T extends string>(
const deeplTranslator = createTranslator('deepl', deeplProvider);
const azureTranslator = createTranslator('azure', azureProvider);
const googleTranslator = createTranslator('google', googleProvider);
const yandexTranslator = createTranslator('yandex', yandexProvider);
const availableTranslators = [
deeplTranslator,
azureTranslator,
googleTranslator,
yandexTranslator,
// Add more translators here
];
@@ -0,0 +1,77 @@
import { stubTranslation as _ } from '@/utils/misc';
import { fetch as tauriFetch } from '@tauri-apps/plugin-http';
import { isTauriAppPlatform } from '@/services/environment';
import { normalizeToShortLang } from '@/utils/lang';
import { TranslationProvider } from '../types';
/**
* Based on https://translate.toil.cc/v2/docs API specification
*/
async function translateSingleTextForService(
text: string,
lang: string,
service: string,
): Promise<string[]> {
const fetchImpl = isTauriAppPlatform() ? tauriFetch : window.fetch;
const url = 'https://translate.toil.cc/v2/translate/';
const request = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
lang: lang,
service: service,
text: text,
})
};
const response = await fetchImpl(url, request);
if (!response.ok) {
const response_json = JSON.stringify(await response.json());
throw new Error(`${service} failed with status ${response.status}\n${text.length}\n${JSON.stringify(request)}\n${response_json}`);
}
const data = await response.json();
if (
data &&
Array.isArray(data.translations)
) {
return data.translations;
} else {
// fallback: return original texts if translation failed
return [text];
}
};
export const yandexProvider: TranslationProvider = {
name: 'yandex',
label: _('Yandex Translate'),
authRequired: false,
translate: async (texts: string[], sourceLang: string, targetLang: string): Promise<string[]> => {
if (!texts.length) return [];
/**
Possible options:
- yandexcloud: often returns 500: {"error":"The text couldn't be translated, because Forbidden"}
- yandexgpt: often better than others
- yandextranslate
- yandexbrowser
*/
const service = "yandexgpt";
// Yandex does not accept "auto" language
const source_lang = sourceLang == "AUTO" ? "en" : normalizeToShortLang(sourceLang).toLowerCase();
const target_lang = normalizeToShortLang(targetLang).toLowerCase();
const lang = `${source_lang}-${target_lang}`;
const responses = await Promise.all(texts.map(async text => {
return await translateSingleTextForService(text, lang, service)
}));
const translatedTexts = responses.flat();
return translatedTexts;
}
};