diff --git a/packages/backend/migration/1706232992000-deeplx.js b/packages/backend/migration/1706232992000-deeplx.js new file mode 100644 index 0000000000..5c763dbf8b --- /dev/null +++ b/packages/backend/migration/1706232992000-deeplx.js @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class Deeplx1706232992000 { + name = 'Deeplx1706232992000'; + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ADD "deeplFreeMode" boolean NOT NULL DEFAULT false`); + await queryRunner.query(`ALTER TABLE "meta" ADD "deeplFreeInstance" character varying(1024)`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "deeplFreeMode"`); + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "deeplFreeInstance"`); + } +} diff --git a/packages/backend/src/models/Meta.ts b/packages/backend/src/models/Meta.ts index 6d5c4b3746..9629012c74 100644 --- a/packages/backend/src/models/Meta.ts +++ b/packages/backend/src/models/Meta.ts @@ -353,6 +353,17 @@ export class MiMeta { }) public deeplIsPro: boolean; + @Column('boolean', { + default: false, + }) + public deeplFreeMode: boolean; + + @Column('varchar', { + length: 1024, + nullable: true, + }) + public deeplFreeInstance: string | null; + @Column('varchar', { length: 1024, nullable: true, diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index 9fe997f889..c6edd6c9a1 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -395,6 +395,14 @@ export const meta = { type: 'boolean', optional: false, nullable: false, }, + deeplFreeMode: { + type: 'boolean', + optional: false, nullable: false, + }, + deeplFreeInstance: { + type: 'string', + optional: false, nullable: true, + }, defaultDarkTheme: { type: 'string', optional: false, nullable: true, @@ -576,6 +584,8 @@ export default class extends Endpoint { // eslint- objectStorageS3ForcePathStyle: instance.objectStorageS3ForcePathStyle, deeplAuthKey: instance.deeplAuthKey, deeplIsPro: instance.deeplIsPro, + deeplFreeMode: instance.deeplFreeMode, + deeplFreeInstance: instance.deeplFreeInstance, enableIpLogging: instance.enableIpLogging, enableActiveEmailValidation: instance.enableActiveEmailValidation, enableVerifymailApi: instance.enableVerifymailApi, 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 786a628d60..8c0d2f8876 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -91,6 +91,8 @@ export const paramDef = { summalyProxy: { type: 'string', nullable: true }, deeplAuthKey: { type: 'string', nullable: true }, deeplIsPro: { type: 'boolean' }, + deeplFreeMode: { type: 'boolean' }, + deeplFreeInstance: { type: 'string', nullable: true }, enableEmail: { type: 'boolean' }, email: { type: 'string', nullable: true }, smtpSecure: { type: 'boolean' }, @@ -479,6 +481,18 @@ export default class extends Endpoint { // eslint- set.deeplIsPro = ps.deeplIsPro; } + if (ps.deeplFreeMode !== undefined) { + set.deeplFreeMode = ps.deeplFreeMode; + } + + if (ps.deeplFreeInstance !== undefined) { + if (ps.deeplFreeInstance === '') { + set.deeplFreeInstance = null; + } else { + set.deeplFreeInstance = ps.deeplFreeInstance; + } + } + 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 af779aa850..8367536ad9 100644 --- a/packages/backend/src/server/api/endpoints/meta.ts +++ b/packages/backend/src/server/api/endpoints/meta.ts @@ -411,7 +411,7 @@ export default class extends Endpoint { // eslint- enableEmail: instance.enableEmail, enableServiceWorker: instance.enableServiceWorker, - translatorAvailable: instance.deeplAuthKey != null, + translatorAvailable: instance.deeplAuthKey != null || instance.deeplFreeMode && instance.deeplFreeInstance, serverRules: instance.serverRules, diff --git a/packages/backend/src/server/api/endpoints/notes/translate.ts b/packages/backend/src/server/api/endpoints/notes/translate.ts index 698c37b616..2afa515f9d 100644 --- a/packages/backend/src/server/api/endpoints/notes/translate.ts +++ b/packages/backend/src/server/api/endpoints/notes/translate.ts @@ -81,19 +81,23 @@ export default class extends Endpoint { // eslint- const instance = await this.metaService.fetch(); - if (instance.deeplAuthKey == null) { + if (instance.deeplAuthKey == null && !instance.deeplFreeMode) { return 204; // TODO: 良い感じのエラー返す } + if (instance.deeplFreeMode && !instance.deeplFreeInstance) { + return 204; + } + let targetLang = ps.targetLang; if (targetLang.includes('-')) targetLang = targetLang.split('-')[0]; const params = new URLSearchParams(); - params.append('auth_key', instance.deeplAuthKey); + if (instance.deeplAuthKey) params.append('auth_key', instance.deeplAuthKey); params.append('text', note.text); params.append('target_lang', targetLang); - const endpoint = instance.deeplIsPro ? 'https://api.deepl.com/v2/translate' : 'https://api-free.deepl.com/v2/translate'; + const endpoint = instance.deeplFreeMode && instance.deeplFreeInstance ? `https://${instance.deeplFreeInstance}` : instance.deeplIsPro ? 'https://api.deepl.com/v2/translate' : 'https://api-free.deepl.com/v2/translate'; const res = await this.httpRequestService.send(endpoint, { method: 'POST', @@ -103,18 +107,37 @@ export default class extends Endpoint { // eslint- }, body: params.toString(), }); + if (instance.deeplAuthKey) { + const json = (await res.json()) as { + translations: { + detected_source_language: string; + text: string; + }[]; + }; - const json = (await res.json()) as { - translations: { - detected_source_language: string; - text: string; - }[]; - }; + return { + sourceLang: json.translations[0].detected_source_language, + text: json.translations[0].text, + }; + } else { + const json = (await res.json()) as { + code: number, + message: string, + data: string, + source_lang: string, + target_lang: string, + alternatives: string[], + }; - return { - sourceLang: json.translations[0].detected_source_language, - text: json.translations[0].text, - }; + const languageNames = new Intl.DisplayNames(['en'], { + type: 'language', + }); + + return { + sourceLang: languageNames.of(json.source_lang), + text: json.data, + }; + } }); } } diff --git a/packages/frontend/src/pages/admin/external-services.vue b/packages/frontend/src/pages/admin/external-services.vue index 1a67a668de..9994b4641f 100644 --- a/packages/frontend/src/pages/admin/external-services.vue +++ b/packages/frontend/src/pages/admin/external-services.vue @@ -19,6 +19,13 @@ SPDX-License-Identifier: AGPL-3.0-only + + + + + + + @@ -49,17 +56,23 @@ import { definePageMetadata } from '@/scripts/page-metadata.js'; const deeplAuthKey = ref(''); const deeplIsPro = ref(false); +const deeplFreeMode = ref(false); +const deeplFreeInstance = ref(''); async function init() { const meta = await misskeyApi('admin/meta'); deeplAuthKey.value = meta.deeplAuthKey; deeplIsPro.value = meta.deeplIsPro; + deeplFreeMode.value = meta.deeplFreeMode; + deeplFreeInstance.value = meta.deeplFreeInstance; } function save() { os.apiWithDialog('admin/update-meta', { deeplAuthKey: deeplAuthKey.value, deeplIsPro: deeplIsPro.value, + deeplFreeMode: deeplFreeMode.value, + deeplFreeInstance: deeplFreeInstance.value, }).then(() => { fetchInstance(); });