Experiment using browser's built in translation API
Spec: https://webmachinelearning.github.io/translation-api/ API may change in the future
This commit is contained in:
parent
23e9d034e9
commit
945d2ac206
5 changed files with 395 additions and 240 deletions
|
@ -28,6 +28,7 @@ import Menu2 from '../components/menu2';
|
|||
import supportedLanguages from '../data/status-supported-languages';
|
||||
import urlRegex from '../data/url-regex';
|
||||
import { api } from '../utils/api';
|
||||
import { langDetector } from '../utils/browser-translator';
|
||||
import db from '../utils/db';
|
||||
import emojifyText from '../utils/emojify-text';
|
||||
import i18nDuration from '../utils/i18n-duration';
|
||||
|
@ -1875,6 +1876,12 @@ const getCustomEmojis = pmem(_getCustomEmojis, {
|
|||
});
|
||||
|
||||
const detectLangs = async (text) => {
|
||||
if (langDetector) {
|
||||
const langs = await langDetector.detect(text);
|
||||
if (langs?.length) {
|
||||
return langs.slice(0, 2).map((lang) => lang.detectedLanguage);
|
||||
}
|
||||
}
|
||||
const { detectAll } = await import('tinyld/light');
|
||||
const langs = detectAll(text);
|
||||
if (langs?.length) {
|
||||
|
|
|
@ -41,6 +41,7 @@ import Modal from '../components/modal';
|
|||
import NameText from '../components/name-text';
|
||||
import Poll from '../components/poll';
|
||||
import { api } from '../utils/api';
|
||||
import { langDetector } from '../utils/browser-translator';
|
||||
import emojifyText from '../utils/emojify-text';
|
||||
import enhanceContent from '../utils/enhance-content';
|
||||
import FilterContext from '../utils/filter-context';
|
||||
|
@ -253,7 +254,6 @@ const SIZE_CLASS = {
|
|||
};
|
||||
|
||||
const detectLang = pmem(async (text) => {
|
||||
const { detectAll } = await import('tinyld/light');
|
||||
text = text?.trim();
|
||||
|
||||
// Ref: https://github.com/komodojp/tinyld/blob/develop/docs/benchmark.md
|
||||
|
@ -261,7 +261,29 @@ const detectLang = pmem(async (text) => {
|
|||
if (text?.length > 500) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (langDetector) {
|
||||
const langs = await langDetector.detect(text);
|
||||
console.groupCollapsed(
|
||||
'💬 DETECTLANG BROWSER',
|
||||
langs.slice(0, 3).map((l) => l.detectedLanguage),
|
||||
);
|
||||
console.log(text, langs.slice(0, 3));
|
||||
console.groupEnd();
|
||||
const lang = langs[0];
|
||||
if (lang?.detectedLanguage && lang?.confidence > 0.5) {
|
||||
return lang.detectedLanguage;
|
||||
}
|
||||
}
|
||||
|
||||
const { detectAll } = await import('tinyld/light');
|
||||
const langs = detectAll(text);
|
||||
console.groupCollapsed(
|
||||
'💬 DETECTLANG TINYLD',
|
||||
langs.slice(0, 3).map((l) => l.lang),
|
||||
);
|
||||
console.log(text, langs.slice(0, 3));
|
||||
console.groupEnd();
|
||||
const lang = langs[0];
|
||||
if (lang?.lang && lang?.accuracy > 0.5) {
|
||||
// If > 50% accurate, use it
|
||||
|
|
|
@ -6,6 +6,10 @@ import pThrottle from 'p-throttle';
|
|||
import { useEffect, useRef, useState } from 'preact/hooks';
|
||||
|
||||
import sourceLanguages from '../data/lingva-source-languages';
|
||||
import {
|
||||
translate as browserTranslate,
|
||||
supportsBrowserTranslator,
|
||||
} from '../utils/browser-translator';
|
||||
import getTranslateTargetLanguage from '../utils/get-translate-target-language';
|
||||
import localeCode2Text from '../utils/localeCode2Text';
|
||||
import pmem from '../utils/pmem';
|
||||
|
@ -95,7 +99,22 @@ function TranslationBlock({
|
|||
const apiSourceLang = useRef('auto');
|
||||
|
||||
if (!onTranslate) {
|
||||
onTranslate = mini ? throttledLingvaTranslate : lingvaTranslate;
|
||||
// onTranslate = supportsBrowserTranslator
|
||||
// ? browserTranslate
|
||||
// : mini
|
||||
// ? throttledLingvaTranslate
|
||||
// : lingvaTranslate;
|
||||
onTranslate = async (...args) => {
|
||||
if (supportsBrowserTranslator) {
|
||||
const result = await browserTranslate(...args);
|
||||
if (result && !result.error) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return mini
|
||||
? await throttledLingvaTranslate(...args)
|
||||
: await lingvaTranslate(...args);
|
||||
};
|
||||
}
|
||||
|
||||
const translate = async () => {
|
||||
|
|
476
src/locales/en.po
generated
476
src/locales/en.po
generated
File diff suppressed because it is too large
Load diff
107
src/utils/browser-translator.js
Normal file
107
src/utils/browser-translator.js
Normal file
|
@ -0,0 +1,107 @@
|
|||
export const supportsBrowserTranslator =
|
||||
'ai' in self && 'translator' in self.ai;
|
||||
|
||||
// https://developer.chrome.com/docs/ai/language-detection
|
||||
export let langDetector;
|
||||
if (supportsBrowserTranslator) {
|
||||
try {
|
||||
const languageDetectorCapabilities =
|
||||
await self.ai.languageDetector.capabilities();
|
||||
const canDetect = languageDetectorCapabilities.capabilities;
|
||||
if (canDetect === 'no') {
|
||||
// The language detector isn't usable.
|
||||
// return;
|
||||
}
|
||||
if (canDetect === 'readily') {
|
||||
// The language detector can immediately be used.
|
||||
langDetector = await self.ai.languageDetector.create();
|
||||
} else {
|
||||
// The language detector can be used after model download.
|
||||
langDetector = await self.ai.languageDetector.create({
|
||||
monitor(m) {
|
||||
m.addEventListener('downloadprogress', (e) => {
|
||||
console.log(
|
||||
`Detector: Downloaded ${e.loaded} of ${e.total} bytes.`,
|
||||
);
|
||||
});
|
||||
},
|
||||
});
|
||||
await langDetector.ready;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
// https://developer.chrome.com/docs/ai/translator-api
|
||||
export const translate = async (text, source, target) => {
|
||||
let detectedSourceLanguage;
|
||||
const originalSource = source;
|
||||
if (source === 'auto') {
|
||||
try {
|
||||
const results = await langDetector.detect(text);
|
||||
source = results[0].detectedLanguage;
|
||||
detectedSourceLanguage = source;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return {
|
||||
error: e,
|
||||
};
|
||||
}
|
||||
}
|
||||
console.groupCollapsed(
|
||||
'💬 BROWSER TRANSLATE',
|
||||
originalSource,
|
||||
detectedSourceLanguage,
|
||||
target,
|
||||
);
|
||||
console.log(text);
|
||||
try {
|
||||
const translatorCapabilities = await self.ai.translator.capabilities();
|
||||
const canTranslate = translatorCapabilities.languagePairAvailable(
|
||||
source,
|
||||
target,
|
||||
);
|
||||
if (canTranslate === 'no') {
|
||||
console.groupEnd();
|
||||
return {
|
||||
error: `Unsupported language pair: ${source} -> ${target}`,
|
||||
};
|
||||
}
|
||||
let translator;
|
||||
if (canTranslate === 'readily') {
|
||||
translator = await self.ai.translator.create({
|
||||
sourceLanguage: source,
|
||||
targetLanguage: target,
|
||||
});
|
||||
} else {
|
||||
translator = await self.ai.translator.create({
|
||||
sourceLanguage: source,
|
||||
targetLanguage: target,
|
||||
monitor(m) {
|
||||
m.addEventListener('downloadprogress', (e) => {
|
||||
console.log(
|
||||
`Translate ${source} -> ${target}: Downloaded ${e.loaded} of ${e.total} bytes.`,
|
||||
);
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const content = await translator.translate(text);
|
||||
console.log(content);
|
||||
console.groupEnd();
|
||||
|
||||
return {
|
||||
content,
|
||||
detectedSourceLanguage,
|
||||
provider: 'browser',
|
||||
};
|
||||
} catch (e) {
|
||||
console.groupEnd();
|
||||
console.error(e);
|
||||
return {
|
||||
error: e,
|
||||
};
|
||||
}
|
||||
};
|
Loading…
Add table
Reference in a new issue