From 914355c6445027c17e20cbb31504ab6a6de64dd3 Mon Sep 17 00:00:00 2001 From: Kaity A Date: Sun, 30 Apr 2023 11:09:51 +1000 Subject: [PATCH] Add Libre Translate support --- .../migration/1682777547198-LibreTranslate.js | 23 ++++++++++ packages/backend/src/config/types.ts | 5 +++ packages/backend/src/models/entities/meta.ts | 12 ++++++ .../api/endpoints/admin/accounts/hosted.ts | 11 +++++ .../src/server/api/endpoints/admin/meta.ts | 5 ++- .../server/api/endpoints/admin/update-meta.ts | 18 ++++++++ .../backend/src/server/api/endpoints/meta.ts | 3 +- .../server/api/endpoints/notes/translate.ts | 43 ++++++++++++++++++- packages/client/src/pages/admin/settings.vue | 34 +++++++++++++++ 9 files changed, 150 insertions(+), 4 deletions(-) create mode 100644 packages/backend/migration/1682777547198-LibreTranslate.js diff --git a/packages/backend/migration/1682777547198-LibreTranslate.js b/packages/backend/migration/1682777547198-LibreTranslate.js new file mode 100644 index 0000000000..dbaf483e6c --- /dev/null +++ b/packages/backend/migration/1682777547198-LibreTranslate.js @@ -0,0 +1,23 @@ +export class LibreTranslate1682777547198 { + name = "LibreTranslate1682777547198"; + + async up(queryRunner) { + await queryRunner.query(` + ALTER TABLE "meta" + ADD "libreTranslateApiUrl" character varying(512) + `); + await queryRunner.query(` + ALTER TABLE "meta" + ADD "libreTranslateApiKey" character varying(128) + `); + } + + async down(queryRunner) { + await queryRunner.query(` + ALTER TABLE "meta" DROP COLUMN "libreTranslateApiKey" + `); + await queryRunner.query(` + ALTER TABLE "meta" DROP COLUMN "libreTranslateApiUrl" + `); + } +} diff --git a/packages/backend/src/config/types.ts b/packages/backend/src/config/types.ts index 4f367debe0..0cd8c02adc 100644 --- a/packages/backend/src/config/types.ts +++ b/packages/backend/src/config/types.ts @@ -89,6 +89,11 @@ export type Source = { authKey?: string; isPro?: boolean; }; + libreTranslate: { + managed?: boolean; + apiUrl?: string; + apiKey?: string; + }; email: { managed?: boolean; address?: string; diff --git a/packages/backend/src/models/entities/meta.ts b/packages/backend/src/models/entities/meta.ts index 26a7c9c193..2f77796c4b 100644 --- a/packages/backend/src/models/entities/meta.ts +++ b/packages/backend/src/models/entities/meta.ts @@ -386,6 +386,18 @@ export class Meta { }) public deeplIsPro: boolean; + @Column('varchar', { + length: 512, + nullable: true, + }) + public libreTranslateApiUrl: string | null; + + @Column('varchar', { + length: 128, + nullable: true, + }) + public libreTranslateApiKey: string | null; + @Column('varchar', { length: 512, nullable: true, diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/hosted.ts b/packages/backend/src/server/api/endpoints/admin/accounts/hosted.ts index 15ad1f9a17..a7b6e95c28 100644 --- a/packages/backend/src/server/api/endpoints/admin/accounts/hosted.ts +++ b/packages/backend/src/server/api/endpoints/admin/accounts/hosted.ts @@ -30,6 +30,17 @@ export default define(meta, paramDef, async (ps, me) => { set.deeplIsPro = config.deepl.isPro; } } + if ( + config.libreTranslate.managed != null && + config.libreTranslate.managed === true + ) { + if (typeof config.libreTranslate.apiUrl === "string") { + set.libreTranslateApiUrl = config.libreTranslate.apiUrl; + } + if (typeof config.libreTranslate.apiKey === "string") { + set.libreTranslateApiKey = config.libreTranslate.apiKey; + } + } if (config.email.managed != null && config.email.managed === true) { set.enableEmail = true; if (typeof config.email.address === "string") { diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index c8c639f504..f0ac57892d 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -512,7 +512,8 @@ export default define(meta, paramDef, async (ps, me) => { enableGithubIntegration: instance.enableGithubIntegration, enableDiscordIntegration: instance.enableDiscordIntegration, enableServiceWorker: instance.enableServiceWorker, - translatorAvailable: instance.deeplAuthKey != null, + translatorAvailable: + instance.deeplAuthKey != null || instance.libreTranslateApiUrl != null, pinnedPages: instance.pinnedPages, pinnedClipId: instance.pinnedClipId, cacheRemoteFiles: instance.cacheRemoteFiles, @@ -564,6 +565,8 @@ export default define(meta, paramDef, async (ps, me) => { objectStorageS3ForcePathStyle: instance.objectStorageS3ForcePathStyle, deeplAuthKey: instance.deeplAuthKey, deeplIsPro: instance.deeplIsPro, + libreTranslateApiUrl: instance.libreTranslateApiUrl, + libreTranslateApiKey: instance.libreTranslateApiKey, enableIpLogging: instance.enableIpLogging, enableActiveEmailValidation: instance.enableActiveEmailValidation, }; diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts index f7e79b64b5..a230007323 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -124,6 +124,8 @@ export const paramDef = { summalyProxy: { type: "string", nullable: true }, deeplAuthKey: { type: "string", nullable: true }, deeplIsPro: { type: "boolean" }, + libreTranslateApiUrl: { type: "string", nullable: true }, + libreTranslateApiKey: { type: "string", nullable: true }, enableTwitterIntegration: { type: "boolean" }, twitterConsumerKey: { type: "string", nullable: true }, twitterConsumerSecret: { type: "string", nullable: true }, @@ -515,6 +517,22 @@ export default define(meta, paramDef, async (ps, me) => { set.deeplIsPro = ps.deeplIsPro; } + if (ps.libreTranslateApiUrl !== undefined) { + if (ps.libreTranslateApiUrl === "") { + set.libreTranslateApiUrl = null; + } else { + set.libreTranslateApiUrl = ps.libreTranslateApiUrl; + } + } + + if (ps.libreTranslateApiKey !== undefined) { + if (ps.libreTranslateApiKey === "") { + set.libreTranslateApiKey = null; + } else { + set.libreTranslateApiKey = ps.libreTranslateApiKey; + } + } + if (ps.enableIpLogging !== undefined) { set.enableIpLogging = ps.enableIpLogging; } diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts index 4dc1c941e3..23989750fc 100644 --- a/packages/backend/src/server/api/endpoints/meta.ts +++ b/packages/backend/src/server/api/endpoints/meta.ts @@ -482,7 +482,8 @@ export default define(meta, paramDef, async (ps, me) => { enableServiceWorker: instance.enableServiceWorker, - translatorAvailable: instance.deeplAuthKey != null, + translatorAvailable: + instance.deeplAuthKey != null || instance.libreTranslateApiUrl != null, defaultReaction: instance.defaultReaction, ...(ps.detail diff --git a/packages/backend/src/server/api/endpoints/notes/translate.ts b/packages/backend/src/server/api/endpoints/notes/translate.ts index c6415ceef2..d86fc12a2e 100644 --- a/packages/backend/src/server/api/endpoints/notes/translate.ts +++ b/packages/backend/src/server/api/endpoints/notes/translate.ts @@ -51,15 +51,54 @@ export default define(meta, paramDef, async (ps, user) => { const instance = await fetchMeta(); - if (instance.deeplAuthKey == null) { + if (instance.deeplAuthKey == null && instance.libreTranslateApiUrl == null) { return 204; // TODO: 良い感じのエラー返す } let targetLang = ps.targetLang; if (targetLang.includes("-")) targetLang = targetLang.split("-")[0]; + if (instance.libreTranslateApiUrl != null) { + const jsonBody = { + q: note.text, + source: "auto", + target: targetLang, + format: "text", + api_key: instance.libreTranslateApiKey ?? "", + }; + + const url = new URL(instance.libreTranslateApiUrl); + if (url.pathname.endsWith("/")) { + url.pathname = url.pathname.slice(0, -1); + } + if (!url.pathname.endsWith("/translate")) { + url.pathname += "/translate"; + } + const res = await fetch(url.toString(), { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(jsonBody), + agent: getAgentByUrl, + }); + + const json = (await res.json()) as { + detectedLanguage?: { + confidence: number; + language: string; + }; + translatedText: string; + }; + + return { + sourceLang: json.detectedLanguage?.language, + text: json.translatedText, + }; + } + const params = new URLSearchParams(); - params.append("auth_key", instance.deeplAuthKey); + params.append("auth_key", instance.deeplAuthKey ?? ""); params.append("text", note.text); params.append("target_lang", targetLang); diff --git a/packages/client/src/pages/admin/settings.vue b/packages/client/src/pages/admin/settings.vue index 5349df805a..feedaff6d5 100644 --- a/packages/client/src/pages/admin/settings.vue +++ b/packages/client/src/pages/admin/settings.vue @@ -371,6 +371,34 @@ + + + + + + + + + + + + + + @@ -422,6 +450,8 @@ let swPublicKey: any = $ref(null); let swPrivateKey: any = $ref(null); let deeplAuthKey: string = $ref(""); let deeplIsPro: boolean = $ref(false); +let libreTranslateApiUrl: string = $ref(""); +let libreTranslateApiKey: string = $ref(""); let defaultReaction: string = $ref(""); let defaultReactionCustom: string = $ref(""); @@ -456,6 +486,8 @@ async function init() { swPrivateKey = meta.swPrivateKey; deeplAuthKey = meta.deeplAuthKey; deeplIsPro = meta.deeplIsPro; + libreTranslateApiUrl = meta.libreTranslateApiUrl; + libreTranslateApiKey = meta.libreTranslateApiKey; defaultReaction = ["⭐", "👍", "❤️"].includes(meta.defaultReaction) ? meta.defaultReaction : "custom"; @@ -498,6 +530,8 @@ function save() { swPrivateKey, deeplAuthKey, deeplIsPro, + libreTranslateApiUrl, + libreTranslateApiKey, defaultReaction, }).then(() => { fetchInstance();