Files
kiss-translator/src/libs/builtinAI.js

169 lines
4.7 KiB
JavaScript
Raw Normal View History

2025-10-04 21:25:54 +08:00
import { kissLog, logger } from "./log";
/**
* Chrome 浏览器内置翻译
*/
class ChromeTranslator {
#translatorMap = new Map();
#detectorPromise = null;
constructor(options = {}) {
this.onProgress = options.onProgress || this.#defaultProgressHandler;
}
#defaultProgressHandler(type, progress) {
kissLog(`Downloading ${type} model: ${progress}%`);
}
#getDetectorPromise() {
if (!this.#detectorPromise) {
this.#detectorPromise = (async () => {
try {
const availability = await LanguageDetector.availability();
if (availability === "unavailable") {
throw new Error("LanguageDetector unavailable");
}
return await LanguageDetector.create({
monitor: (m) => this._monitorProgress(m, "detector"),
});
} catch (error) {
this.#detectorPromise = null;
throw error;
}
})();
}
return this.#detectorPromise;
}
#createTranslator(sourceLanguage, targetLanguage) {
const key = `${sourceLanguage}_${targetLanguage}`;
if (this.#translatorMap.has(key)) {
return this.#translatorMap.get(key);
}
const translatorPromise = (async () => {
try {
const avail = await Translator.availability({
sourceLanguage,
targetLanguage,
});
if (avail === "unavailable") {
throw new Error(
`Translator ${sourceLanguage}_${targetLanguage} unavailable`
);
}
const translator = await Translator.create({
sourceLanguage,
targetLanguage,
monitor: (m) => this._monitorProgress(m, `translator (${key})`),
});
this.#translatorMap.set(key, translator);
return translator;
} catch (error) {
this.#translatorMap.delete(key);
throw error;
}
})();
this.#translatorMap.set(key, translatorPromise);
return translatorPromise;
}
_monitorProgress(monitorable, type) {
monitorable.addEventListener("downloadprogress", (e) => {
const progress = e.total > 0 ? Math.round((e.loaded / e.total) * 100) : 0;
this.onProgress(type, progress);
});
}
async detectLanguage(text, confidenceThreshold = 0.4) {
if (!text) {
return ["", "Input text cannot be empty."];
}
try {
const detector = await this.#getDetectorPromise();
const results = await detector.detect(text);
if (!results || results.length === 0) {
return ["", "No language could be detected."];
}
const { detectedLanguage, confidence } = results[0];
if (confidence < confidenceThreshold) {
return [
"",
`Confidence of test results (${detectedLanguage} ${confidence.toFixed(
2
)}) below the set threshold ${confidenceThreshold}`,
];
}
return [detectedLanguage, ""];
} catch (error) {
kissLog("detectLanguage", error, `(${text})`);
return ["", error.message];
}
}
async translateText(text, targetLanguage, sourceLanguage = "auto") {
if (!text || !targetLanguage || typeof text !== "string") {
return ["", sourceLanguage, "Input text cannot be empty."];
}
try {
let finalSourceLanguage = sourceLanguage;
if (sourceLanguage === "auto") {
const [detectedLanguage, detectionError] =
await this.detectLanguage(text);
if (detectionError || !detectedLanguage) {
const reason =
detectionError || "Unable to determine source language.";
return [
"",
finalSourceLanguage,
`Automatic detection of source language failed: ${reason}`,
];
}
finalSourceLanguage = detectedLanguage;
}
if (finalSourceLanguage === targetLanguage) {
return ["", finalSourceLanguage, "Same lang"];
}
const translator = await this.#createTranslator(
finalSourceLanguage,
targetLanguage
);
const translatedText = await translator.translate(text);
return [translatedText, finalSourceLanguage, ""];
} catch (error) {
kissLog("translateText", error, `(${text})`);
if (
error &&
error.message &&
error.message.includes("Other generic failures occurred")
) {
2025-10-10 13:49:04 +08:00
logger.info("Generic failure detected, resetting translator cache.");
2025-10-04 21:25:54 +08:00
this.#translatorMap.clear();
}
return ["", sourceLanguage, error.message];
}
}
}
const chromeTranslator = new ChromeTranslator();
export const chromeDetect = (args) =>
chromeTranslator.detectLanguage(args.text);
export const chromeTranslate = (args) =>
chromeTranslator.translateText(args.text, args.to, args.from);