mirror of
https://github.com/artiemis/artemis.js.git
synced 2026-02-14 02:11:55 +00:00
add google translate fallback
This commit is contained in:
parent
6d10dc2a00
commit
ea40e13891
3
.gitignore
vendored
3
.gitignore
vendored
@ -174,4 +174,5 @@ dist
|
||||
# Finder (MacOS) folder config
|
||||
.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 {
|
||||
getSourceLanguages,
|
||||
getTargetLanguages,
|
||||
isSourceLanguageSupported,
|
||||
isTargetLanguageSupported,
|
||||
translate,
|
||||
isSourceLanguage,
|
||||
isTargetLanguage,
|
||||
translate as translateDeepl,
|
||||
} from "../../utils/deepl";
|
||||
import { abort } from "../../utils/error";
|
||||
import type { OCRResult } from "../../types/ocr";
|
||||
import { capitalize, languageCodeToName } from "../../utils/functions";
|
||||
import { translate as translateGoogle } from "../../utils/gtrans";
|
||||
|
||||
export async function translateAutocompleteImpl(
|
||||
interaction: AutocompleteInteraction
|
||||
@ -44,15 +45,18 @@ export async function translateImpl(
|
||||
ocrModel?: OCRResult["model"],
|
||||
imageUrl?: string
|
||||
): Promise<InteractionEditReplyOptions> {
|
||||
const {
|
||||
text: translatedText,
|
||||
detectedSourceLang,
|
||||
billedCharacters,
|
||||
} = await translate({
|
||||
let { translatedText, detectedSourceLang, model } = await translateDeepl(
|
||||
text,
|
||||
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 displayTarget = languageCodeToName(target);
|
||||
@ -78,20 +82,20 @@ export async function translateImpl(
|
||||
{
|
||||
title: `From ${displaySource} to ${displayTarget}`,
|
||||
description: translatedText,
|
||||
color: 0x0f2b46,
|
||||
color: model === "deepl" ? 0x0f2b46 : 0x4285f4,
|
||||
...(imageUrl ? { image: { url: imageUrl } } : {}),
|
||||
author: {
|
||||
name: "DeepL",
|
||||
icon_url: "https://www.google.com/s2/favicons?domain=deepl.com&sz=64",
|
||||
},
|
||||
footer: {
|
||||
text: ocrModel
|
||||
? `OCR: ${capitalize(ocrModel)}`
|
||||
: `Billed characters: ${billedCharacters}`,
|
||||
icon_url: ocrModel
|
||||
? `https://www.google.com/s2/favicons?domain=${ocrModel}.com&sz=64`
|
||||
: undefined,
|
||||
name: model === "deepl" ? "DeepL" : "Google Translate",
|
||||
icon_url: `https://www.google.com/s2/favicons?domain=${model}.com&sz=64`,
|
||||
},
|
||||
...(ocrModel
|
||||
? {
|
||||
footer: {
|
||||
text: `OCR: ${capitalize(ocrModel)}`,
|
||||
icon_url: `https://www.google.com/s2/favicons?domain=${ocrModel}.com&sz=64`,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
],
|
||||
};
|
||||
@ -100,7 +104,9 @@ export async function translateImpl(
|
||||
export default defineCommand({
|
||||
data: new SlashCommandBuilder()
|
||||
.setName("translate")
|
||||
.setDescription("Translates text using DeepL")
|
||||
.setDescription(
|
||||
"Translates text using DeepL or Google Translate as fallback"
|
||||
)
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName("text")
|
||||
@ -129,10 +135,10 @@ export default defineCommand({
|
||||
|
||||
await interaction.deferReply();
|
||||
|
||||
if (source && !(await isSourceLanguageSupported(source))) {
|
||||
if (source && !(await isSourceLanguage(source))) {
|
||||
abort("Source language not supported");
|
||||
}
|
||||
if (target && !(await isTargetLanguageSupported(target))) {
|
||||
if (target && !(await isTargetLanguage(target))) {
|
||||
abort("Target language not supported");
|
||||
}
|
||||
|
||||
|
||||
@ -97,7 +97,7 @@ export async function ocrImpl(url: string) {
|
||||
export default defineCommand({
|
||||
data: new SlashCommandBuilder()
|
||||
.setName("ocr")
|
||||
.setDescription("OCR an image using Yandex")
|
||||
.setDescription("OCR an image using Google Lens or Yandex as fallback")
|
||||
.addAttachmentOption((option) =>
|
||||
option.setName("image").setDescription("The image to OCR")
|
||||
)
|
||||
|
||||
@ -1,10 +1,7 @@
|
||||
import { SlashCommandBuilder } from "discord.js";
|
||||
import { defineCommand } from "..";
|
||||
import { abort } from "../../utils/error";
|
||||
import {
|
||||
isSourceLanguageSupported,
|
||||
isTargetLanguageSupported,
|
||||
} from "../../utils/deepl";
|
||||
import { isSourceLanguage, isTargetLanguage } from "../../utils/deepl";
|
||||
import {
|
||||
translateAutocompleteImpl,
|
||||
translateImpl,
|
||||
@ -16,7 +13,7 @@ export default defineCommand({
|
||||
data: new SlashCommandBuilder()
|
||||
.setName("ocrtranslate")
|
||||
.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) =>
|
||||
option.setName("image").setDescription("The image to OCR")
|
||||
@ -50,10 +47,10 @@ export default defineCommand({
|
||||
|
||||
await interaction.deferReply();
|
||||
|
||||
if (source && !(await isSourceLanguageSupported(source))) {
|
||||
if (source && !(await isSourceLanguage(source))) {
|
||||
abort("Source language not supported");
|
||||
}
|
||||
if (target && !(await isTargetLanguageSupported(target))) {
|
||||
if (target && !(await isTargetLanguage(target))) {
|
||||
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";
|
||||
import { env } from "../env";
|
||||
import { lazy } from "./functions";
|
||||
import type { TranslateResult } from "../types/translate";
|
||||
|
||||
const translator = new Translator(env.DEEPL_API_KEY);
|
||||
export const getSourceLanguages = lazy(() => translator.getSourceLanguages());
|
||||
export const getTargetLanguages = lazy(() => translator.getTargetLanguages());
|
||||
|
||||
type TranslateOptions = {
|
||||
text: string;
|
||||
source?: string | null;
|
||||
target?: string;
|
||||
};
|
||||
|
||||
export async function translate({
|
||||
text,
|
||||
source = null,
|
||||
target = "en-US",
|
||||
}: TranslateOptions) {
|
||||
return translator.translateText(
|
||||
export async function translate(
|
||||
text: string,
|
||||
source: string | null = null,
|
||||
target = "en-US"
|
||||
): Promise<TranslateResult> {
|
||||
const result = await translator.translateText(
|
||||
text,
|
||||
source as SourceLanguageCode,
|
||||
source as SourceLanguageCode | null,
|
||||
target as TargetLanguageCode
|
||||
);
|
||||
return {
|
||||
translatedText: result.text,
|
||||
detectedSourceLang: result.detectedSourceLang,
|
||||
model: "deepl",
|
||||
};
|
||||
}
|
||||
|
||||
export async function getUsage() {
|
||||
return translator.getUsage();
|
||||
}
|
||||
|
||||
export async function getLanguages() {
|
||||
return (await getSourceLanguages()).concat(await getTargetLanguages());
|
||||
}
|
||||
|
||||
export async function isSourceLanguageSupported(code: string) {
|
||||
export async function isSourceLanguage(code: string) {
|
||||
const sourceLanguages = await getSourceLanguages();
|
||||
return (
|
||||
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();
|
||||
return (
|
||||
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