mirror of
https://github.com/artiemis/artemis.js.git
synced 2026-02-14 10:21:54 +00:00
add google translate fallback
This commit is contained in:
parent
6d10dc2a00
commit
ea40e13891
1
.gitignore
vendored
1
.gitignore
vendored
@ -175,3 +175,4 @@ dist
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
data/temp/*
|
data/temp/*
|
||||||
|
src/scripts/sandbox.ts
|
||||||
|
|||||||
251
data/gtrans-langcodes.json
Normal file
251
data/gtrans-langcodes.json
Normal file
@ -0,0 +1,251 @@
|
|||||||
|
[
|
||||||
|
"aa",
|
||||||
|
"ab",
|
||||||
|
"ace",
|
||||||
|
"ach",
|
||||||
|
"af",
|
||||||
|
"ak",
|
||||||
|
"alz",
|
||||||
|
"am",
|
||||||
|
"ar",
|
||||||
|
"as",
|
||||||
|
"av",
|
||||||
|
"awa",
|
||||||
|
"ay",
|
||||||
|
"az",
|
||||||
|
"ba",
|
||||||
|
"bal",
|
||||||
|
"ban",
|
||||||
|
"bbc",
|
||||||
|
"bci",
|
||||||
|
"be",
|
||||||
|
"bem",
|
||||||
|
"ber",
|
||||||
|
"ber-Latn",
|
||||||
|
"bew",
|
||||||
|
"bg",
|
||||||
|
"bho",
|
||||||
|
"bik",
|
||||||
|
"bm",
|
||||||
|
"bm-Nkoo",
|
||||||
|
"bn",
|
||||||
|
"bo",
|
||||||
|
"br",
|
||||||
|
"bs",
|
||||||
|
"bts",
|
||||||
|
"btx",
|
||||||
|
"bua",
|
||||||
|
"ca",
|
||||||
|
"ce",
|
||||||
|
"ceb",
|
||||||
|
"cgg",
|
||||||
|
"ch",
|
||||||
|
"chk",
|
||||||
|
"chm",
|
||||||
|
"ckb",
|
||||||
|
"cnh",
|
||||||
|
"co",
|
||||||
|
"crh",
|
||||||
|
"crh-Latn",
|
||||||
|
"crs",
|
||||||
|
"cs",
|
||||||
|
"cv",
|
||||||
|
"cy",
|
||||||
|
"da",
|
||||||
|
"de",
|
||||||
|
"din",
|
||||||
|
"doi",
|
||||||
|
"dov",
|
||||||
|
"dv",
|
||||||
|
"dyu",
|
||||||
|
"dz",
|
||||||
|
"ee",
|
||||||
|
"el",
|
||||||
|
"en",
|
||||||
|
"eo",
|
||||||
|
"es",
|
||||||
|
"et",
|
||||||
|
"eu",
|
||||||
|
"fa",
|
||||||
|
"fa-AF",
|
||||||
|
"ff",
|
||||||
|
"fi",
|
||||||
|
"fj",
|
||||||
|
"fo",
|
||||||
|
"fon",
|
||||||
|
"fr",
|
||||||
|
"fr-CA",
|
||||||
|
"fur",
|
||||||
|
"fy",
|
||||||
|
"ga",
|
||||||
|
"gaa",
|
||||||
|
"gd",
|
||||||
|
"gl",
|
||||||
|
"gn",
|
||||||
|
"gom",
|
||||||
|
"gu",
|
||||||
|
"gv",
|
||||||
|
"ha",
|
||||||
|
"haw",
|
||||||
|
"hi",
|
||||||
|
"hil",
|
||||||
|
"hmn",
|
||||||
|
"hr",
|
||||||
|
"hrx",
|
||||||
|
"ht",
|
||||||
|
"hu",
|
||||||
|
"hy",
|
||||||
|
"iba",
|
||||||
|
"id",
|
||||||
|
"ig",
|
||||||
|
"ilo",
|
||||||
|
"is",
|
||||||
|
"it",
|
||||||
|
"iu",
|
||||||
|
"iu-Latn",
|
||||||
|
"iw",
|
||||||
|
"ja",
|
||||||
|
"jam",
|
||||||
|
"jw",
|
||||||
|
"ka",
|
||||||
|
"kac",
|
||||||
|
"kek",
|
||||||
|
"kg",
|
||||||
|
"kha",
|
||||||
|
"kk",
|
||||||
|
"kl",
|
||||||
|
"km",
|
||||||
|
"kn",
|
||||||
|
"ko",
|
||||||
|
"kr",
|
||||||
|
"kri",
|
||||||
|
"ktu",
|
||||||
|
"ku",
|
||||||
|
"kv",
|
||||||
|
"ky",
|
||||||
|
"la",
|
||||||
|
"lb",
|
||||||
|
"lg",
|
||||||
|
"li",
|
||||||
|
"lij",
|
||||||
|
"lmo",
|
||||||
|
"ln",
|
||||||
|
"lo",
|
||||||
|
"lt",
|
||||||
|
"ltg",
|
||||||
|
"lua",
|
||||||
|
"luo",
|
||||||
|
"lus",
|
||||||
|
"lv",
|
||||||
|
"mad",
|
||||||
|
"mai",
|
||||||
|
"mak",
|
||||||
|
"mam",
|
||||||
|
"mfe",
|
||||||
|
"mg",
|
||||||
|
"mh",
|
||||||
|
"mi",
|
||||||
|
"min",
|
||||||
|
"mk",
|
||||||
|
"ml",
|
||||||
|
"mn",
|
||||||
|
"mni-Mtei",
|
||||||
|
"mr",
|
||||||
|
"ms",
|
||||||
|
"ms-Arab",
|
||||||
|
"mt",
|
||||||
|
"mwr",
|
||||||
|
"my",
|
||||||
|
"ndc-ZW",
|
||||||
|
"ne",
|
||||||
|
"new",
|
||||||
|
"nhe",
|
||||||
|
"nl",
|
||||||
|
"no",
|
||||||
|
"nr",
|
||||||
|
"nso",
|
||||||
|
"nus",
|
||||||
|
"ny",
|
||||||
|
"oc",
|
||||||
|
"om",
|
||||||
|
"or",
|
||||||
|
"os",
|
||||||
|
"pa",
|
||||||
|
"pa-Arab",
|
||||||
|
"pag",
|
||||||
|
"pam",
|
||||||
|
"pap",
|
||||||
|
"pl",
|
||||||
|
"ps",
|
||||||
|
"pt",
|
||||||
|
"pt-PT",
|
||||||
|
"qu",
|
||||||
|
"rn",
|
||||||
|
"ro",
|
||||||
|
"rom",
|
||||||
|
"ru",
|
||||||
|
"rw",
|
||||||
|
"sa",
|
||||||
|
"sah",
|
||||||
|
"sat",
|
||||||
|
"sat-Latn",
|
||||||
|
"scn",
|
||||||
|
"sd",
|
||||||
|
"se",
|
||||||
|
"sg",
|
||||||
|
"shn",
|
||||||
|
"si",
|
||||||
|
"sk",
|
||||||
|
"sl",
|
||||||
|
"sm",
|
||||||
|
"sn",
|
||||||
|
"so",
|
||||||
|
"sq",
|
||||||
|
"sr",
|
||||||
|
"ss",
|
||||||
|
"st",
|
||||||
|
"su",
|
||||||
|
"sus",
|
||||||
|
"sv",
|
||||||
|
"sw",
|
||||||
|
"szl",
|
||||||
|
"ta",
|
||||||
|
"tcy",
|
||||||
|
"te",
|
||||||
|
"tet",
|
||||||
|
"tg",
|
||||||
|
"th",
|
||||||
|
"ti",
|
||||||
|
"tiv",
|
||||||
|
"tk",
|
||||||
|
"tl",
|
||||||
|
"tn",
|
||||||
|
"to",
|
||||||
|
"tpi",
|
||||||
|
"tr",
|
||||||
|
"trp",
|
||||||
|
"ts",
|
||||||
|
"tt",
|
||||||
|
"tum",
|
||||||
|
"ty",
|
||||||
|
"tyv",
|
||||||
|
"udm",
|
||||||
|
"ug",
|
||||||
|
"uk",
|
||||||
|
"ur",
|
||||||
|
"uz",
|
||||||
|
"ve",
|
||||||
|
"vec",
|
||||||
|
"vi",
|
||||||
|
"war",
|
||||||
|
"wo",
|
||||||
|
"xh",
|
||||||
|
"yi",
|
||||||
|
"yo",
|
||||||
|
"yua",
|
||||||
|
"yue",
|
||||||
|
"zap",
|
||||||
|
"zh-CN",
|
||||||
|
"zh-TW",
|
||||||
|
"zu"
|
||||||
|
]
|
||||||
@ -9,13 +9,14 @@ import { defineCommand } from "..";
|
|||||||
import {
|
import {
|
||||||
getSourceLanguages,
|
getSourceLanguages,
|
||||||
getTargetLanguages,
|
getTargetLanguages,
|
||||||
isSourceLanguageSupported,
|
isSourceLanguage,
|
||||||
isTargetLanguageSupported,
|
isTargetLanguage,
|
||||||
translate,
|
translate as translateDeepl,
|
||||||
} from "../../utils/deepl";
|
} from "../../utils/deepl";
|
||||||
import { abort } from "../../utils/error";
|
import { abort } from "../../utils/error";
|
||||||
import type { OCRResult } from "../../types/ocr";
|
import type { OCRResult } from "../../types/ocr";
|
||||||
import { capitalize, languageCodeToName } from "../../utils/functions";
|
import { capitalize, languageCodeToName } from "../../utils/functions";
|
||||||
|
import { translate as translateGoogle } from "../../utils/gtrans";
|
||||||
|
|
||||||
export async function translateAutocompleteImpl(
|
export async function translateAutocompleteImpl(
|
||||||
interaction: AutocompleteInteraction
|
interaction: AutocompleteInteraction
|
||||||
@ -44,15 +45,18 @@ export async function translateImpl(
|
|||||||
ocrModel?: OCRResult["model"],
|
ocrModel?: OCRResult["model"],
|
||||||
imageUrl?: string
|
imageUrl?: string
|
||||||
): Promise<InteractionEditReplyOptions> {
|
): Promise<InteractionEditReplyOptions> {
|
||||||
const {
|
let { translatedText, detectedSourceLang, model } = await translateDeepl(
|
||||||
text: translatedText,
|
|
||||||
detectedSourceLang,
|
|
||||||
billedCharacters,
|
|
||||||
} = await translate({
|
|
||||||
text,
|
text,
|
||||||
source,
|
source,
|
||||||
target,
|
target
|
||||||
});
|
).catch(() => translateGoogle(text, "auto", "en"));
|
||||||
|
|
||||||
|
if (translatedText.trim() === text.trim() && model === "deepl") {
|
||||||
|
const result = await translateGoogle(text, "auto", "en");
|
||||||
|
translatedText = result.translatedText;
|
||||||
|
detectedSourceLang = result.detectedSourceLang;
|
||||||
|
model = result.model;
|
||||||
|
}
|
||||||
|
|
||||||
const displaySource = languageCodeToName(detectedSourceLang);
|
const displaySource = languageCodeToName(detectedSourceLang);
|
||||||
const displayTarget = languageCodeToName(target);
|
const displayTarget = languageCodeToName(target);
|
||||||
@ -78,20 +82,20 @@ export async function translateImpl(
|
|||||||
{
|
{
|
||||||
title: `From ${displaySource} to ${displayTarget}`,
|
title: `From ${displaySource} to ${displayTarget}`,
|
||||||
description: translatedText,
|
description: translatedText,
|
||||||
color: 0x0f2b46,
|
color: model === "deepl" ? 0x0f2b46 : 0x4285f4,
|
||||||
...(imageUrl ? { image: { url: imageUrl } } : {}),
|
...(imageUrl ? { image: { url: imageUrl } } : {}),
|
||||||
author: {
|
author: {
|
||||||
name: "DeepL",
|
name: model === "deepl" ? "DeepL" : "Google Translate",
|
||||||
icon_url: "https://www.google.com/s2/favicons?domain=deepl.com&sz=64",
|
icon_url: `https://www.google.com/s2/favicons?domain=${model}.com&sz=64`,
|
||||||
},
|
},
|
||||||
|
...(ocrModel
|
||||||
|
? {
|
||||||
footer: {
|
footer: {
|
||||||
text: ocrModel
|
text: `OCR: ${capitalize(ocrModel)}`,
|
||||||
? `OCR: ${capitalize(ocrModel)}`
|
icon_url: `https://www.google.com/s2/favicons?domain=${ocrModel}.com&sz=64`,
|
||||||
: `Billed characters: ${billedCharacters}`,
|
|
||||||
icon_url: ocrModel
|
|
||||||
? `https://www.google.com/s2/favicons?domain=${ocrModel}.com&sz=64`
|
|
||||||
: undefined,
|
|
||||||
},
|
},
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
@ -100,7 +104,9 @@ export async function translateImpl(
|
|||||||
export default defineCommand({
|
export default defineCommand({
|
||||||
data: new SlashCommandBuilder()
|
data: new SlashCommandBuilder()
|
||||||
.setName("translate")
|
.setName("translate")
|
||||||
.setDescription("Translates text using DeepL")
|
.setDescription(
|
||||||
|
"Translates text using DeepL or Google Translate as fallback"
|
||||||
|
)
|
||||||
.addStringOption((option) =>
|
.addStringOption((option) =>
|
||||||
option
|
option
|
||||||
.setName("text")
|
.setName("text")
|
||||||
@ -129,10 +135,10 @@ export default defineCommand({
|
|||||||
|
|
||||||
await interaction.deferReply();
|
await interaction.deferReply();
|
||||||
|
|
||||||
if (source && !(await isSourceLanguageSupported(source))) {
|
if (source && !(await isSourceLanguage(source))) {
|
||||||
abort("Source language not supported");
|
abort("Source language not supported");
|
||||||
}
|
}
|
||||||
if (target && !(await isTargetLanguageSupported(target))) {
|
if (target && !(await isTargetLanguage(target))) {
|
||||||
abort("Target language not supported");
|
abort("Target language not supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -97,7 +97,7 @@ export async function ocrImpl(url: string) {
|
|||||||
export default defineCommand({
|
export default defineCommand({
|
||||||
data: new SlashCommandBuilder()
|
data: new SlashCommandBuilder()
|
||||||
.setName("ocr")
|
.setName("ocr")
|
||||||
.setDescription("OCR an image using Yandex")
|
.setDescription("OCR an image using Google Lens or Yandex as fallback")
|
||||||
.addAttachmentOption((option) =>
|
.addAttachmentOption((option) =>
|
||||||
option.setName("image").setDescription("The image to OCR")
|
option.setName("image").setDescription("The image to OCR")
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,10 +1,7 @@
|
|||||||
import { SlashCommandBuilder } from "discord.js";
|
import { SlashCommandBuilder } from "discord.js";
|
||||||
import { defineCommand } from "..";
|
import { defineCommand } from "..";
|
||||||
import { abort } from "../../utils/error";
|
import { abort } from "../../utils/error";
|
||||||
import {
|
import { isSourceLanguage, isTargetLanguage } from "../../utils/deepl";
|
||||||
isSourceLanguageSupported,
|
|
||||||
isTargetLanguageSupported,
|
|
||||||
} from "../../utils/deepl";
|
|
||||||
import {
|
import {
|
||||||
translateAutocompleteImpl,
|
translateAutocompleteImpl,
|
||||||
translateImpl,
|
translateImpl,
|
||||||
@ -16,7 +13,7 @@ export default defineCommand({
|
|||||||
data: new SlashCommandBuilder()
|
data: new SlashCommandBuilder()
|
||||||
.setName("ocrtranslate")
|
.setName("ocrtranslate")
|
||||||
.setDescription(
|
.setDescription(
|
||||||
"OCR an image using Yandex and translate the result using DeepL"
|
"OCR an image using Google Lens or Yandex and translate the result using DeepL or Google Translate"
|
||||||
)
|
)
|
||||||
.addAttachmentOption((option) =>
|
.addAttachmentOption((option) =>
|
||||||
option.setName("image").setDescription("The image to OCR")
|
option.setName("image").setDescription("The image to OCR")
|
||||||
@ -50,10 +47,10 @@ export default defineCommand({
|
|||||||
|
|
||||||
await interaction.deferReply();
|
await interaction.deferReply();
|
||||||
|
|
||||||
if (source && !(await isSourceLanguageSupported(source))) {
|
if (source && !(await isSourceLanguage(source))) {
|
||||||
abort("Source language not supported");
|
abort("Source language not supported");
|
||||||
}
|
}
|
||||||
if (target && !(await isTargetLanguageSupported(target))) {
|
if (target && !(await isTargetLanguage(target))) {
|
||||||
abort("Target language not supported");
|
abort("Target language not supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
5
src/types/translate.ts
Normal file
5
src/types/translate.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export type TranslateResult = {
|
||||||
|
translatedText: string;
|
||||||
|
detectedSourceLang: string;
|
||||||
|
model: "deepl" | "google";
|
||||||
|
};
|
||||||
@ -5,38 +5,34 @@ import {
|
|||||||
} from "deepl-node";
|
} from "deepl-node";
|
||||||
import { env } from "../env";
|
import { env } from "../env";
|
||||||
import { lazy } from "./functions";
|
import { lazy } from "./functions";
|
||||||
|
import type { TranslateResult } from "../types/translate";
|
||||||
|
|
||||||
const translator = new Translator(env.DEEPL_API_KEY);
|
const translator = new Translator(env.DEEPL_API_KEY);
|
||||||
export const getSourceLanguages = lazy(() => translator.getSourceLanguages());
|
export const getSourceLanguages = lazy(() => translator.getSourceLanguages());
|
||||||
export const getTargetLanguages = lazy(() => translator.getTargetLanguages());
|
export const getTargetLanguages = lazy(() => translator.getTargetLanguages());
|
||||||
|
|
||||||
type TranslateOptions = {
|
export async function translate(
|
||||||
text: string;
|
text: string,
|
||||||
source?: string | null;
|
source: string | null = null,
|
||||||
target?: string;
|
target = "en-US"
|
||||||
};
|
): Promise<TranslateResult> {
|
||||||
|
const result = await translator.translateText(
|
||||||
export async function translate({
|
|
||||||
text,
|
text,
|
||||||
source = null,
|
source as SourceLanguageCode | null,
|
||||||
target = "en-US",
|
|
||||||
}: TranslateOptions) {
|
|
||||||
return translator.translateText(
|
|
||||||
text,
|
|
||||||
source as SourceLanguageCode,
|
|
||||||
target as TargetLanguageCode
|
target as TargetLanguageCode
|
||||||
);
|
);
|
||||||
|
return {
|
||||||
|
translatedText: result.text,
|
||||||
|
detectedSourceLang: result.detectedSourceLang,
|
||||||
|
model: "deepl",
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getUsage() {
|
export async function getUsage() {
|
||||||
return translator.getUsage();
|
return translator.getUsage();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getLanguages() {
|
export async function isSourceLanguage(code: string) {
|
||||||
return (await getSourceLanguages()).concat(await getTargetLanguages());
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function isSourceLanguageSupported(code: string) {
|
|
||||||
const sourceLanguages = await getSourceLanguages();
|
const sourceLanguages = await getSourceLanguages();
|
||||||
return (
|
return (
|
||||||
sourceLanguages.find((l) => l.code.toLowerCase() === code.toLowerCase()) !==
|
sourceLanguages.find((l) => l.code.toLowerCase() === code.toLowerCase()) !==
|
||||||
@ -44,7 +40,7 @@ export async function isSourceLanguageSupported(code: string) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function isTargetLanguageSupported(code: string) {
|
export async function isTargetLanguage(code: string) {
|
||||||
const targetLanguages = await getTargetLanguages();
|
const targetLanguages = await getTargetLanguages();
|
||||||
return (
|
return (
|
||||||
targetLanguages.find((l) => l.code.toLowerCase() === code.toLowerCase()) !==
|
targetLanguages.find((l) => l.code.toLowerCase() === code.toLowerCase()) !==
|
||||||
|
|||||||
60
src/utils/gtrans.ts
Normal file
60
src/utils/gtrans.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import ky from "ky";
|
||||||
|
import { languageCodeToName, lazy } from "./functions";
|
||||||
|
import { readFileSync } from "node:fs";
|
||||||
|
import type { TranslateResult } from "../types/translate";
|
||||||
|
|
||||||
|
type TranslationResponse = {
|
||||||
|
src: string;
|
||||||
|
sentences: {
|
||||||
|
trans: string;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const client = ky.create({
|
||||||
|
prefixUrl: "https://translate.googleapis.com/translate_a",
|
||||||
|
});
|
||||||
|
|
||||||
|
const languageCodes = lazy(
|
||||||
|
() =>
|
||||||
|
JSON.parse(readFileSync("data/gtrans-langcodes.json", "utf8")) as string[]
|
||||||
|
);
|
||||||
|
|
||||||
|
export const getLanguages = lazy(() =>
|
||||||
|
languageCodes().map((code) => ({
|
||||||
|
code,
|
||||||
|
name: languageCodeToName(code),
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
|
export function isLanguage(code: string) {
|
||||||
|
return languageCodes().includes(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function translate(
|
||||||
|
text: string,
|
||||||
|
source = "auto",
|
||||||
|
target = "en"
|
||||||
|
): Promise<TranslateResult> {
|
||||||
|
const res = await client.get("single", {
|
||||||
|
searchParams: {
|
||||||
|
sl: source,
|
||||||
|
tl: target,
|
||||||
|
q: text,
|
||||||
|
client: "gtx",
|
||||||
|
dt: "t",
|
||||||
|
dj: "1",
|
||||||
|
source: "input",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { sentences, src } = await res.json<TranslationResponse>();
|
||||||
|
|
||||||
|
return {
|
||||||
|
translatedText: sentences
|
||||||
|
.map((s) => s?.trans)
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(""),
|
||||||
|
detectedSourceLang: src,
|
||||||
|
model: "google",
|
||||||
|
};
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user