summaryrefslogtreecommitdiff
path: root/src/lib/services/translation.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/services/translation.ts')
-rw-r--r--src/lib/services/translation.ts303
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}`);
+ }
+}