import { Cache } from "react-native-cache"; import { LIBRETRANSLATE_BASE_URL } from "@/constants/api"; import AsyncStorage from "@react-native-async-storage/async-storage"; import { Settings } from "../lib/settings"; type language_t = string; const cache = new Cache({ namespace: "translation_terrace", policy: { maxEntries: 50000, // if unspecified, it can have unlimited entries stdTTL: 0, // the standard ttl as number in seconds, default: 0 (unlimited) }, backend: AsyncStorage, }); export type language_matrix_entry = { code: string; name: string; targets: string[]; }; export type language_matrix = { [key: string]: language_matrix_entry; }; export async function fetchWithTimeout( url: string, options: RequestInit, timeout = 5000 ): Promise { return Promise.race([ fetch(url, options), new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), timeout) ), ]); } export class LanguageServer { constructor(public baseUrl: string) {} async fetchLanguages(timeout = 500): Promise { let data = {}; const res = await fetchWithTimeout( this.baseUrl + "/languages", { headers: { "Content-Type": "application/json", }, }, timeout ); try { data = await res.json(); } catch (e) { throw new Error(`Parsing data from ${await res.text()}: ${e}`); } try { return Object.fromEntries( Object.values(data as language_matrix_entry[]).map( (obj: language_matrix_entry) => { return [obj["code"], obj]; } ) ); } catch (e) { throw new Error( `Can't extract values from data: ${JSON.stringify(data)}` ); } } static async getDefault() { const settings = await Settings.getDefault(); return new LanguageServer( (await settings.getLibretranslateBaseUrl()) || LIBRETRANSLATE_BASE_URL ); } } export class Translator { constructor( public source: language_t, public defaultTarget: string = "en", private _languageServer: LanguageServer ) {} get languageServer() { return this._languageServer; } async translate(text: string, target: string | undefined = undefined) { const url = this._languageServer.baseUrl + `/translate`; console.log(url); const postData = { method: "POST", body: JSON.stringify({ q: text, source: this.source, target: target || this.defaultTarget, format: "text", alternatives: 3, api_key: "", }), headers: { "Content-Type": "application/json" }, }; console.debug("Requesting %s with %o", url, postData); const res = await fetch(url, postData); const data = await res.json(); if (res.status === 200) { console.log(data); return data.translatedText; } else { console.error(data); } } static async getDefault(defaultTarget: string | undefined = undefined) { const settings = await Settings.getDefault(); const source = await settings.getHostLanguage(); return new Translator( source, defaultTarget, await LanguageServer.getDefault() ); } } export class CachedTranslator extends Translator { async translate(text: string, target: string | undefined = undefined) { const targetKey = target || this.defaultTarget; // console.debug(`Translating from ${this.source} -> ${targetKey}`) const key1 = `${this.source}::${targetKey}::${text}`; const tr1 = await cache.get(key1); if (tr1) return tr1; const tr2 = await super.translate(text, target); const key2 = `${this.source}::${targetKey}::${text}`; await cache.set(key2, tr2); return tr2; } static async getDefault(defaultTarget: string | undefined = undefined) { const settings = await Settings.getDefault(); const source = await settings.getHostLanguage() || "en"; return new CachedTranslator( source, defaultTarget, await LanguageServer.getDefault() ); } }