diff options
Diffstat (limited to 'src/lib/services/translation.ts')
-rw-r--r-- | src/lib/services/translation.ts | 303 |
1 files changed, 303 insertions, 0 deletions
diff --git a/src/lib/services/translation.ts b/src/lib/services/translation.ts new file mode 100644 index 0000000..f75bac7 --- /dev/null +++ b/src/lib/services/translation.ts @@ -0,0 +1,303 @@ +import { z } from "zod"; +import type { Language, TranslationService, AsyncRes } from "../types"; +import { AiTranslator } from "./aitranslation"; + +const JSON_HEADER = { "Content-Type": "application/json" }; +export class GoogleTranslate implements TranslationService { + endpoint = "https://translate.googleapis.com/language/translate/v2"; + constructor(private apiKey: string) { + if (!apiKey) throw new Error("Google Translate API key is required"); + } + + async call(path: string, body?: any) { + try { + const authH = { + "X-goog-api-key": this.apiKey, + }; + const opts = body + ? { + method: "POST", + headers: { ...authH, ...JSON_HEADER }, + body: JSON.stringify(body), + } + : { headers: authH }; + const response = await fetch(this.endpoint + path, opts); + if (!response.ok) { + const errorMessage = response.statusText; + throw new Error( + `Google Translate API error (${response.status}): ${errorMessage}`, + ); + } + const data = await response.json(); + return data; + } catch (e) { + throw new Error(`${e}`); + } + } + async translate( + text: string, + sourceLang: string, + targetLang: string, + ): AsyncRes<string> { + try { + const body = { + q: text, + source: sourceLang === "auto" ? undefined : sourceLang, + target: targetLang, + format: "text", + }; + const data = await this.call("", body); + + console.log("google translate res", data); + + if (!data.data?.translations?.[0]?.translatedText) { + return { error: "Invalid response format from Google Translate API" }; + } + + return { ok: data.data.translations[0].translatedText }; + } catch (error) { + return { error: "Failed to connect to Google Translate API `${error}`" }; + } + } + + async getSupportedLanguages() { + try { + const res = await this.call("/languages"); + const languageNames = new Intl.DisplayNames(["en"], { type: "language" }); + // lnguages are ISO 639 or BCP-47 + const set = new Set<string>(); + const ret: Language[] = []; + for (let ll of res.data.languages) { + const l: { language: string } = ll; + const code = l.language; + const name = languageNames.of(code); + if (!name) continue; + if (!set.has(name)) ret.push({ code, name }); + set.add(name); + } + return { ok: ret }; + } catch (e) { + return { error: `${e}` }; + } + } +} + +export class MicrosoftTranslator implements TranslationService { + endpoint = "https://api.cognitive.microsofttranslator.com"; + + constructor(private apiKey: string) { + if (!apiKey) throw new Error("Microsoft Translator API key is required"); + } + + async translate( + text: string, + sourceLang: string, + targetLang: string, + ): AsyncRes<string> { + const url = "https://api.cognitive.microsofttranslator.com"; + // documents + // https://sortug.cognitiveservices.azure.com/ + // + try { + const res = await this.call( + `/translate?api-version=3.0&from=${sourceLang === "auto" ? "" : sourceLang}&to=${targetLang}`, + [{ text }], + ); + + if (!res[0]?.translations?.[0]?.text) { + throw new Error( + "Invalid response format from Microsoft Translator API", + ); + } + + return { ok: res[0].translations[0].text }; + } catch (error) { + return { error: "Failed to connect to Microsoft Translator API" }; + } + } + + async getSupportedLanguages() { + try { + const res = await this.call(`/languages?api-version=3.0`); + return { + ok: Object.entries(res.translation).map(([code, l]: any) => ({ + code, + name: l.name, + nativeName: l.nativeName, + })), + }; + } catch (e) { + return { error: `${e}` }; + } + } + async dictionaryLookup(text: string, from: string, to: string) { + const res = await this.call( + `/Dictionary/Lookup?api-version=3.0&from=${from}&to=${to}`, + ); + console.log({ res }); + return res; + } + async pinyin() { + try { + const res = await this.call(`/languages?api-version=3.0`); + // return Object.entries(res.transliteration).map(([code, l]: any) => { + // return { code, ...l }; + // }); + return { ok: res.transliteration }; + } catch (e) { + return { error: `${e}` }; + } + } + async transliterate( + text: string[], + language: string, + from: string, + to: string, + ) { + const body = text.map((t) => ({ Text: t })); + const url = `/transliterate?api-version=3.0&language=${language}&fromScript=${from}&toScript=${to}`; + console.log({ url, body }); + try { + const res = await this.call(url, body); + return { ok: res[0].text }; + } catch (e) { + return { error: `${e}` }; + } + } + async call(path: string, body?: any) { + const authH = { + "Ocp-Apim-Subscription-Key": this.apiKey, + "Ocp-Apim-Subscription-Region": "southeastasia", + // "X-ClientTraceId": uuidv4().toString(), + // Authorization: `Bearer ${this.apiKey}`, + }; + console.log({ authH }); + const opts = body + ? { + method: "POST", + headers: { ...authH, ...JSON_HEADER }, + body: JSON.stringify(body), + } + : { headers: authH }; + const res = await fetch(this.endpoint + path, opts); + console.log({ res }); + if (!res.ok) { + const errorMessage = res.statusText; + throw new Error( + `Microsoft Translator API error (${res.status}): ${errorMessage}`, + ); + } + const j = await res.json(); + return j; + } +} + +export class DeepLTranslator implements TranslationService { + // https://developers.deepl.com/docs/api-reference/client-libraries + // endpoint = "https://api.deepl.com/v2"; + endpoint = "https://api-free.deepl.com/v2"; + constructor(private apiKey: string) { + if (!apiKey) throw new Error("DeepL API key is required"); + } + + async call(path: string, body?: any) { + try { + const authH = { + Authorization: `DeepL-Auth-Key ${this.apiKey}`, + }; + const opts = body + ? { + method: "POST", + headers: { ...authH, ...JSON_HEADER }, + body: JSON.stringify(body), + } + : { headers: authH }; + const response = await fetch(this.endpoint + path, opts); + + const data = await response.json(); + + if (!response.ok) { + const errorMessage = data.message || response.statusText; + throw new Error( + `DeepL API error (${response.status}): ${errorMessage}`, + ); + } + return data; + } catch (error) { + if (error instanceof Error) { + throw error; + } + throw new Error("Failed to connect to DeepL API"); + } + } + async translate( + text: string, + sourceLang: string, + targetLang: string, + context?: string, + formality?: "default" | "more" | "less" | "prefer_more" | "prefer_less", + ): AsyncRes<string> { + try { + const data = await this.call("/translate", { + text: [text], + target_lang: targetLang, + source_lang: sourceLang, + context, + formality, + model_type: "prefer_quality_optimized", + }); + if (!data.translations?.[0]?.text) { + throw new Error("Invalid response format from DeepL API"); + } + + return { ok: data.translations[0].text }; + } catch (error) { + return { error: "Failed to connect to DeepL API" }; + } + } + + async getSupportedLanguages() { + try { + const data = await this.call("/languages"); + return { + ok: data.map((l: { language: string; name: string }) => ({ + code: l.language.toLowerCase(), + name: l.name, + })), + }; + } catch (e) { + return { error: `${e}` }; + } + } +} +// Factory function to create translation service based on provider +export function createTranslationService(provider: string): TranslationService { + const envSchema = z.object({ + GOOGLE_TRANSLATE_API_KEY: z.string(), + AZURE_TRANSLATE_API_KEY: z.string(), + DEEPL_API_KEY: z.string(), + }); + + const env = envSchema.parse(process.env); + + switch (provider) { + case "google": + return new GoogleTranslate(env.GOOGLE_TRANSLATE_API_KEY); + case "microsoft": + return new MicrosoftTranslator(env.AZURE_TRANSLATE_API_KEY); + case "deepl": + return new DeepLTranslator(env.DEEPL_API_KEY); + case "deepseek": + return new AiTranslator({ name: provider }); + case "grok": + return new AiTranslator({ name: provider }); + case "claude": + return new AiTranslator({ name: provider }); + case "gemini": + return new AiTranslator({ name: provider }); + case "chatgpt": + return new AiTranslator({ name: provider }); + default: + throw new Error(`Unsupported translation provider: ${provider}`); + } +} |