mirror of
https://activitypub.software/TransFem-org/Sharkey.git
synced 2025-04-13 09:44:40 +00:00
merge: Add LibreTranslate as an option to External Services (!935)
View MR for information: https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/935 Closes #807 Approved-by: dakkar <dakkar@thenautilus.net> Approved-by: Hazelnoot <acomputerdog@gmail.com>
This commit is contained in:
commit
49597e7e08
8 changed files with 194 additions and 65 deletions
18
packages/backend/migration/1741215877000-libetranslate.js
Normal file
18
packages/backend/migration/1741215877000-libetranslate.js
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: marie and other Sharkey contributors
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class Libetranslate1741215877000 {
|
||||||
|
name = 'Libretranslate1741215877000';
|
||||||
|
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" ADD "libreTranslateURL" character varying(1024)`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" ADD "libreTranslateKey" character varying(1024)`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "libreTranslateURL"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "libreTranslateKey"`);
|
||||||
|
}
|
||||||
|
}
|
|
@ -135,7 +135,7 @@ export class MetaEntityService {
|
||||||
enableEmail: instance.enableEmail,
|
enableEmail: instance.enableEmail,
|
||||||
enableServiceWorker: instance.enableServiceWorker,
|
enableServiceWorker: instance.enableServiceWorker,
|
||||||
|
|
||||||
translatorAvailable: instance.deeplAuthKey != null || instance.deeplFreeMode && instance.deeplFreeInstance != null,
|
translatorAvailable: instance.deeplAuthKey != null || instance.libreTranslateURL != null || instance.deeplFreeMode && instance.deeplFreeInstance != null,
|
||||||
|
|
||||||
serverRules: instance.serverRules,
|
serverRules: instance.serverRules,
|
||||||
|
|
||||||
|
|
|
@ -403,6 +403,18 @@ export class MiMeta {
|
||||||
})
|
})
|
||||||
public deeplFreeInstance: string | null;
|
public deeplFreeInstance: string | null;
|
||||||
|
|
||||||
|
@Column('varchar', {
|
||||||
|
length: 1024,
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
public libreTranslateURL: string | null;
|
||||||
|
|
||||||
|
@Column('varchar', {
|
||||||
|
length: 1024,
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
public libreTranslateKey: string | null;
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 1024,
|
length: 1024,
|
||||||
nullable: true,
|
nullable: true,
|
||||||
|
|
|
@ -459,6 +459,14 @@ export const meta = {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
optional: false, nullable: true,
|
optional: false, nullable: true,
|
||||||
},
|
},
|
||||||
|
libreTranslateURL: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: true,
|
||||||
|
},
|
||||||
|
libreTranslateKey: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: true,
|
||||||
|
},
|
||||||
defaultDarkTheme: {
|
defaultDarkTheme: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
optional: false, nullable: true,
|
optional: false, nullable: true,
|
||||||
|
@ -652,7 +660,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
defaultLike: instance.defaultLike,
|
defaultLike: instance.defaultLike,
|
||||||
enableEmail: instance.enableEmail,
|
enableEmail: instance.enableEmail,
|
||||||
enableServiceWorker: instance.enableServiceWorker,
|
enableServiceWorker: instance.enableServiceWorker,
|
||||||
translatorAvailable: instance.deeplAuthKey != null,
|
translatorAvailable: instance.deeplAuthKey != null || instance.libreTranslateURL != null || instance.deeplFreeMode && instance.deeplFreeInstance != null,
|
||||||
cacheRemoteFiles: instance.cacheRemoteFiles,
|
cacheRemoteFiles: instance.cacheRemoteFiles,
|
||||||
cacheRemoteSensitiveFiles: instance.cacheRemoteSensitiveFiles,
|
cacheRemoteSensitiveFiles: instance.cacheRemoteSensitiveFiles,
|
||||||
pinnedUsers: instance.pinnedUsers,
|
pinnedUsers: instance.pinnedUsers,
|
||||||
|
@ -700,6 +708,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
deeplIsPro: instance.deeplIsPro,
|
deeplIsPro: instance.deeplIsPro,
|
||||||
deeplFreeMode: instance.deeplFreeMode,
|
deeplFreeMode: instance.deeplFreeMode,
|
||||||
deeplFreeInstance: instance.deeplFreeInstance,
|
deeplFreeInstance: instance.deeplFreeInstance,
|
||||||
|
libreTranslateURL: instance.libreTranslateURL,
|
||||||
|
libreTranslateKey: instance.libreTranslateKey,
|
||||||
enableIpLogging: instance.enableIpLogging,
|
enableIpLogging: instance.enableIpLogging,
|
||||||
enableActiveEmailValidation: instance.enableActiveEmailValidation,
|
enableActiveEmailValidation: instance.enableActiveEmailValidation,
|
||||||
enableVerifymailApi: instance.enableVerifymailApi,
|
enableVerifymailApi: instance.enableVerifymailApi,
|
||||||
|
|
|
@ -107,6 +107,8 @@ export const paramDef = {
|
||||||
deeplIsPro: { type: 'boolean' },
|
deeplIsPro: { type: 'boolean' },
|
||||||
deeplFreeMode: { type: 'boolean' },
|
deeplFreeMode: { type: 'boolean' },
|
||||||
deeplFreeInstance: { type: 'string', nullable: true },
|
deeplFreeInstance: { type: 'string', nullable: true },
|
||||||
|
libreTranslateURL: { type: 'string', nullable: true },
|
||||||
|
libreTranslateKey: { type: 'string', nullable: true },
|
||||||
enableEmail: { type: 'boolean' },
|
enableEmail: { type: 'boolean' },
|
||||||
email: { type: 'string', nullable: true },
|
email: { type: 'string', nullable: true },
|
||||||
smtpSecure: { type: 'boolean' },
|
smtpSecure: { type: 'boolean' },
|
||||||
|
@ -577,6 +579,22 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ps.libreTranslateURL !== undefined) {
|
||||||
|
if (ps.libreTranslateURL === '') {
|
||||||
|
set.libreTranslateURL = null;
|
||||||
|
} else {
|
||||||
|
set.libreTranslateURL = ps.libreTranslateURL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ps.libreTranslateKey !== undefined) {
|
||||||
|
if (ps.libreTranslateKey === '') {
|
||||||
|
set.libreTranslateKey = null;
|
||||||
|
} else {
|
||||||
|
set.libreTranslateKey = ps.libreTranslateKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (ps.enableIpLogging !== undefined) {
|
if (ps.enableIpLogging !== undefined) {
|
||||||
set.enableIpLogging = ps.enableIpLogging;
|
set.enableIpLogging = ps.enableIpLogging;
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,23 +93,21 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.serverSettings.deeplAuthKey == null && !this.serverSettings.deeplFreeMode) {
|
const canDeeplFree = this.serverSettings.deeplFreeMode && !!this.serverSettings.deeplFreeInstance;
|
||||||
throw new ApiError(meta.errors.unavailable);
|
const canDeepl = !!this.serverSettings.deeplAuthKey || canDeeplFree;
|
||||||
}
|
const canLibre = !!this.serverSettings.libreTranslateURL;
|
||||||
|
if (!canDeepl && !canLibre) throw new ApiError(meta.errors.unavailable);
|
||||||
if (this.serverSettings.deeplFreeMode && !this.serverSettings.deeplFreeInstance) {
|
|
||||||
throw new ApiError(meta.errors.unavailable);
|
|
||||||
}
|
|
||||||
|
|
||||||
let targetLang = ps.targetLang;
|
let targetLang = ps.targetLang;
|
||||||
if (targetLang.includes('-')) targetLang = targetLang.split('-')[0];
|
if (targetLang.includes('-')) targetLang = targetLang.split('-')[0];
|
||||||
|
|
||||||
|
// DeepL/DeepLX handling
|
||||||
|
if (canDeepl) {
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
if (this.serverSettings.deeplAuthKey) params.append('auth_key', this.serverSettings.deeplAuthKey);
|
if (this.serverSettings.deeplAuthKey) params.append('auth_key', this.serverSettings.deeplAuthKey);
|
||||||
params.append('text', note.text);
|
params.append('text', note.text);
|
||||||
params.append('target_lang', targetLang);
|
params.append('target_lang', targetLang);
|
||||||
|
const endpoint = canDeeplFree ? this.serverSettings.deeplFreeInstance as string : this.serverSettings.deeplIsPro ? 'https://api.deepl.com/v2/translate' : 'https://api-free.deepl.com/v2/translate';
|
||||||
const endpoint = this.serverSettings.deeplFreeMode && this.serverSettings.deeplFreeInstance ? this.serverSettings.deeplFreeInstance : this.serverSettings.deeplIsPro ? 'https://api.deepl.com/v2/translate' : 'https://api-free.deepl.com/v2/translate';
|
|
||||||
|
|
||||||
const res = await this.httpRequestService.send(endpoint, {
|
const res = await this.httpRequestService.send(endpoint, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
@ -150,6 +148,42 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
text: json.data,
|
text: json.data,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LibreTranslate handling
|
||||||
|
if (canLibre) {
|
||||||
|
const res = await this.httpRequestService.send(this.serverSettings.libreTranslateURL as string, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Accept: 'application/json, */*',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
q: note.text,
|
||||||
|
source: 'auto',
|
||||||
|
target: targetLang,
|
||||||
|
format: 'text',
|
||||||
|
api_key: this.serverSettings.libreTranslateKey ?? '',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const json = (await res.json()) as {
|
||||||
|
alternatives: string[],
|
||||||
|
detectedLanguage: { [key: string]: string | number },
|
||||||
|
translatedText: string,
|
||||||
|
};
|
||||||
|
|
||||||
|
const languageNames = new Intl.DisplayNames(['en'], {
|
||||||
|
type: 'language',
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
sourceLang: languageNames.of(json.detectedLanguage.language as string),
|
||||||
|
text: json.translatedText,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template>
|
<template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template>
|
||||||
<MkSpacer :contentMax="700" :marginMin="16" :marginMax="32">
|
<MkSpacer :contentMax="700" :marginMin="16" :marginMax="32">
|
||||||
<FormSuspense :p="init">
|
<FormSuspense :p="init">
|
||||||
|
<div class="_gaps_m">
|
||||||
<MkFolder>
|
<MkFolder>
|
||||||
<template #label>DeepL Translation</template>
|
<template #label>DeepL Translation</template>
|
||||||
|
|
||||||
|
@ -32,6 +33,25 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkButton primary @click="save_deepl">Save</MkButton>
|
<MkButton primary @click="save_deepl">Save</MkButton>
|
||||||
</div>
|
</div>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
|
|
||||||
|
<MkFolder>
|
||||||
|
<template #label>LibreTranslate Translation</template>
|
||||||
|
|
||||||
|
<div class="_gaps_m">
|
||||||
|
<MkInput v-model="libreTranslateURL" :placeholder="'example.com/translate'">
|
||||||
|
<template #prefix><i class="ph-globe-simple ph-bold ph-lg"></i></template>
|
||||||
|
<template #label>LibreTranslate URL</template>
|
||||||
|
</MkInput>
|
||||||
|
|
||||||
|
<MkInput v-model="libreTranslateKey">
|
||||||
|
<template #prefix><i class="ti ti-key"></i></template>
|
||||||
|
<template #label>LibreTranslate Api Key</template>
|
||||||
|
</MkInput>
|
||||||
|
|
||||||
|
<MkButton primary @click="save_libre">Save</MkButton>
|
||||||
|
</div>
|
||||||
|
</MkFolder>
|
||||||
|
</div>
|
||||||
</FormSuspense>
|
</FormSuspense>
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
</MkStickyContainer>
|
</MkStickyContainer>
|
||||||
|
@ -51,10 +71,12 @@ import { i18n } from '@/i18n.js';
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
import MkFolder from '@/components/MkFolder.vue';
|
import MkFolder from '@/components/MkFolder.vue';
|
||||||
|
|
||||||
const deeplAuthKey = ref<string>('');
|
const deeplAuthKey = ref<string | null>('');
|
||||||
const deeplIsPro = ref<boolean>(false);
|
const deeplIsPro = ref<boolean>(false);
|
||||||
const deeplFreeMode = ref<boolean>(false);
|
const deeplFreeMode = ref<boolean>(false);
|
||||||
const deeplFreeInstance = ref<string>('');
|
const deeplFreeInstance = ref<string | null>('');
|
||||||
|
const libreTranslateURL = ref<string | null>('');
|
||||||
|
const libreTranslateKey = ref<string | null>('');
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
const meta = await misskeyApi('admin/meta');
|
const meta = await misskeyApi('admin/meta');
|
||||||
|
@ -62,6 +84,8 @@ async function init() {
|
||||||
deeplIsPro.value = meta.deeplIsPro;
|
deeplIsPro.value = meta.deeplIsPro;
|
||||||
deeplFreeMode.value = meta.deeplFreeMode;
|
deeplFreeMode.value = meta.deeplFreeMode;
|
||||||
deeplFreeInstance.value = meta.deeplFreeInstance;
|
deeplFreeInstance.value = meta.deeplFreeInstance;
|
||||||
|
libreTranslateURL.value = meta.libreTranslateURL;
|
||||||
|
libreTranslateKey.value = meta.libreTranslateKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
function save_deepl() {
|
function save_deepl() {
|
||||||
|
@ -75,6 +99,15 @@ function save_deepl() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function save_libre() {
|
||||||
|
os.apiWithDialog('admin/update-meta', {
|
||||||
|
libreTranslateURL: libreTranslateURL.value,
|
||||||
|
libreTranslateKey: libreTranslateKey.value,
|
||||||
|
}).then(() => {
|
||||||
|
fetchInstance(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const headerActions = computed(() => []);
|
const headerActions = computed(() => []);
|
||||||
|
|
||||||
const headerTabs = computed(() => []);
|
const headerTabs = computed(() => []);
|
||||||
|
|
|
@ -8826,6 +8826,8 @@ export type operations = {
|
||||||
deeplIsPro: boolean;
|
deeplIsPro: boolean;
|
||||||
deeplFreeMode: boolean;
|
deeplFreeMode: boolean;
|
||||||
deeplFreeInstance: string | null;
|
deeplFreeInstance: string | null;
|
||||||
|
libreTranslateURL: string | null;
|
||||||
|
libreTranslateKey: string | null;
|
||||||
defaultDarkTheme: string | null;
|
defaultDarkTheme: string | null;
|
||||||
defaultLightTheme: string | null;
|
defaultLightTheme: string | null;
|
||||||
description: string | null;
|
description: string | null;
|
||||||
|
@ -11401,6 +11403,8 @@ export type operations = {
|
||||||
deeplIsPro?: boolean;
|
deeplIsPro?: boolean;
|
||||||
deeplFreeMode?: boolean;
|
deeplFreeMode?: boolean;
|
||||||
deeplFreeInstance?: string | null;
|
deeplFreeInstance?: string | null;
|
||||||
|
libreTranslateURL?: string | null;
|
||||||
|
libreTranslateKey?: string | null;
|
||||||
enableEmail?: boolean;
|
enableEmail?: boolean;
|
||||||
email?: string | null;
|
email?: string | null;
|
||||||
smtpSecure?: boolean;
|
smtpSecure?: boolean;
|
||||||
|
|
Loading…
Add table
Reference in a new issue