Merge pull request 'Add LibreTranslate support' (#224) from libretranslate into main
Reviewed-on: FoundKeyGang/FoundKey#224 Changelog: Added
This commit is contained in:
commit
512351746f
11 changed files with 301 additions and 60 deletions
|
@ -849,6 +849,8 @@ misskeyUpdated: "FoundKey has been updated!"
|
||||||
whatIsNew: "Show changes"
|
whatIsNew: "Show changes"
|
||||||
translate: "Translate"
|
translate: "Translate"
|
||||||
translatedFrom: "Translated from {x}"
|
translatedFrom: "Translated from {x}"
|
||||||
|
translationSettings: "Translation Settings"
|
||||||
|
translationService: "Translation Service"
|
||||||
accountDeletionInProgress: "Account deletion is currently in progress."
|
accountDeletionInProgress: "Account deletion is currently in progress."
|
||||||
usernameInfo: "A name that identifies your account from others on this server. You\
|
usernameInfo: "A name that identifies your account from others on this server. You\
|
||||||
\ can use the alphabet (a~z, A~Z), digits (0~9) or underscores (_). Usernames cannot\
|
\ can use the alphabet (a~z, A~Z), digits (0~9) or underscores (_). Usernames cannot\
|
||||||
|
@ -1525,3 +1527,10 @@ _services:
|
||||||
_github:
|
_github:
|
||||||
connected: "GitHub: @{login} connected to FoundKey: @{userName}!"
|
connected: "GitHub: @{login} connected to FoundKey: @{userName}!"
|
||||||
disconnected: "GitHub linkage has been removed."
|
disconnected: "GitHub linkage has been removed."
|
||||||
|
_translationService:
|
||||||
|
_deepl:
|
||||||
|
authKey: "DeepL Auth Key"
|
||||||
|
pro: "Pro Account"
|
||||||
|
_libreTranslate:
|
||||||
|
endpoint: "LibreTranslate API Endpoint"
|
||||||
|
authKey: "LibreTranslate Auth Key (optional)"
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
export class addLibretranslate1668661888188 {
|
||||||
|
name = 'addLibretranslate1668661888188'
|
||||||
|
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`CREATE TYPE "public"."meta_translationservice_enum" AS ENUM('deepl', 'libretranslate')`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" ADD "translationService" "public"."meta_translationservice_enum"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" ADD "libreTranslateAuthKey" character varying(128)`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" ADD "libreTranslateEndpoint" character varying(2048)`);
|
||||||
|
// Set translationService to 'deepl' if auth key is already set
|
||||||
|
await queryRunner.query(`UPDATE "meta" SET "translationService"='deepl' WHERE "deeplAuthKey" IS NOT NULL`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "libreTranslateEndpoint"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "libreTranslateAuthKey"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "translationService"`);
|
||||||
|
await queryRunner.query(`DROP TYPE "public"."meta_translationservice_enum"`);
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,11 @@ import { id } from '../id.js';
|
||||||
import { User } from './user.js';
|
import { User } from './user.js';
|
||||||
import { Clip } from './clip.js';
|
import { Clip } from './clip.js';
|
||||||
|
|
||||||
|
export enum TranslationService {
|
||||||
|
DeepL = 'deepl',
|
||||||
|
LibreTranslate = 'libretranslate',
|
||||||
|
}
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class Meta {
|
export class Meta {
|
||||||
@PrimaryColumn({
|
@PrimaryColumn({
|
||||||
|
@ -299,6 +304,12 @@ export class Meta {
|
||||||
})
|
})
|
||||||
public discordClientSecret: string | null;
|
public discordClientSecret: string | null;
|
||||||
|
|
||||||
|
@Column('enum', {
|
||||||
|
enum: TranslationService,
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
public translationService: TranslationService | null;
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 128,
|
length: 128,
|
||||||
nullable: true,
|
nullable: true,
|
||||||
|
@ -310,6 +321,18 @@ export class Meta {
|
||||||
})
|
})
|
||||||
public deeplIsPro: boolean;
|
public deeplIsPro: boolean;
|
||||||
|
|
||||||
|
@Column('varchar', {
|
||||||
|
length: 128,
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
public libreTranslateAuthKey: string | null;
|
||||||
|
|
||||||
|
@Column('varchar', {
|
||||||
|
length: 2048,
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
public libreTranslateEndpoint: string | null;
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 512,
|
length: 512,
|
||||||
nullable: true,
|
nullable: true,
|
||||||
|
|
12
packages/backend/src/server/api/common/translator.ts
Normal file
12
packages/backend/src/server/api/common/translator.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import { Meta, TranslationService } from '@/models/entities/meta.js';
|
||||||
|
|
||||||
|
export function translatorAvailable(instance: Meta): boolean {
|
||||||
|
switch (instance.translationService) {
|
||||||
|
case TranslationService.DeepL:
|
||||||
|
return instance.deeplAuthKey != null;
|
||||||
|
case TranslationService.LibreTranslate:
|
||||||
|
return instance.libreTranslateEndpoint != null;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,7 @@
|
||||||
import config from '@/config/index.js';
|
import config from '@/config/index.js';
|
||||||
import { fetchMeta } from '@/misc/fetch-meta.js';
|
import { fetchMeta } from '@/misc/fetch-meta.js';
|
||||||
|
import { TranslationService } from '@/models/entities/meta.js';
|
||||||
|
import { translatorAvailable } from '../../common/translator.js';
|
||||||
import define from '../../define.js';
|
import define from '../../define.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
|
@ -269,6 +271,27 @@ export const meta = {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
optional: true, nullable: false,
|
optional: true, nullable: false,
|
||||||
},
|
},
|
||||||
|
translatorService: {
|
||||||
|
type: 'string',
|
||||||
|
enum: [null, ...Object.values(TranslationService)],
|
||||||
|
optional: false, nullable: true,
|
||||||
|
},
|
||||||
|
deeplAuthKey: {
|
||||||
|
type: 'string',
|
||||||
|
optional: true, nullable: true,
|
||||||
|
},
|
||||||
|
deeplIsPro: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: true, nullable: false,
|
||||||
|
},
|
||||||
|
libreTranslateEndpoint: {
|
||||||
|
type: 'string',
|
||||||
|
optional: true, nullable: true,
|
||||||
|
},
|
||||||
|
libreTranslateAuthKey: {
|
||||||
|
type: 'string',
|
||||||
|
optional: true, nullable: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
@ -317,7 +340,6 @@ export default define(meta, paramDef, async (ps, me) => {
|
||||||
enableGithubIntegration: instance.enableGithubIntegration,
|
enableGithubIntegration: instance.enableGithubIntegration,
|
||||||
enableDiscordIntegration: instance.enableDiscordIntegration,
|
enableDiscordIntegration: instance.enableDiscordIntegration,
|
||||||
enableServiceWorker: instance.enableServiceWorker,
|
enableServiceWorker: instance.enableServiceWorker,
|
||||||
translatorAvailable: instance.deeplAuthKey != null,
|
|
||||||
pinnedPages: instance.pinnedPages,
|
pinnedPages: instance.pinnedPages,
|
||||||
pinnedClipId: instance.pinnedClipId,
|
pinnedClipId: instance.pinnedClipId,
|
||||||
cacheRemoteFiles: instance.cacheRemoteFiles,
|
cacheRemoteFiles: instance.cacheRemoteFiles,
|
||||||
|
@ -356,7 +378,12 @@ export default define(meta, paramDef, async (ps, me) => {
|
||||||
objectStorageUseProxy: instance.objectStorageUseProxy,
|
objectStorageUseProxy: instance.objectStorageUseProxy,
|
||||||
objectStorageSetPublicRead: instance.objectStorageSetPublicRead,
|
objectStorageSetPublicRead: instance.objectStorageSetPublicRead,
|
||||||
objectStorageS3ForcePathStyle: instance.objectStorageS3ForcePathStyle,
|
objectStorageS3ForcePathStyle: instance.objectStorageS3ForcePathStyle,
|
||||||
|
|
||||||
|
translatorAvailable: translatorAvailable(instance),
|
||||||
|
translationService: instance.translationService,
|
||||||
deeplAuthKey: instance.deeplAuthKey,
|
deeplAuthKey: instance.deeplAuthKey,
|
||||||
deeplIsPro: instance.deeplIsPro,
|
deeplIsPro: instance.deeplIsPro,
|
||||||
|
libreTranslateEndpoint: instance.libreTranslateEndpoint,
|
||||||
|
libreTranslateAuthKey: instance.libreTranslateAuthKey,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { insertModerationLog } from '@/services/insert-moderation-log.js';
|
import { insertModerationLog } from '@/services/insert-moderation-log.js';
|
||||||
import { fetchMeta, setMeta } from '@/misc/fetch-meta.js';
|
import { fetchMeta, setMeta } from '@/misc/fetch-meta.js';
|
||||||
|
import { TranslationService } from '@/models/entities/meta.js';
|
||||||
import define from '../../define.js';
|
import define from '../../define.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
|
@ -55,8 +56,11 @@ export const paramDef = {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
} },
|
} },
|
||||||
summalyProxy: { type: 'string', nullable: true },
|
summalyProxy: { type: 'string', nullable: true },
|
||||||
|
translationService: { type: 'string', nullable: true, enum: [null, ...Object.values(TranslationService)] },
|
||||||
deeplAuthKey: { type: 'string', nullable: true },
|
deeplAuthKey: { type: 'string', nullable: true },
|
||||||
deeplIsPro: { type: 'boolean' },
|
deeplIsPro: { type: 'boolean' },
|
||||||
|
libreTranslateAuthKey: { type: 'string', nullable: true },
|
||||||
|
libreTranslateEndpoint: { type: 'string', nullable: true },
|
||||||
enableTwitterIntegration: { type: 'boolean' },
|
enableTwitterIntegration: { type: 'boolean' },
|
||||||
twitterConsumerKey: { type: 'string', nullable: true },
|
twitterConsumerKey: { type: 'string', nullable: true },
|
||||||
twitterConsumerSecret: { type: 'string', nullable: true },
|
twitterConsumerSecret: { type: 'string', nullable: true },
|
||||||
|
@ -362,6 +366,10 @@ export default define(meta, paramDef, async (ps, me) => {
|
||||||
set.objectStorageS3ForcePathStyle = ps.objectStorageS3ForcePathStyle;
|
set.objectStorageS3ForcePathStyle = ps.objectStorageS3ForcePathStyle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ps.translationService !== undefined) {
|
||||||
|
set.translationService = ps.translationService;
|
||||||
|
}
|
||||||
|
|
||||||
if (ps.deeplAuthKey !== undefined) {
|
if (ps.deeplAuthKey !== undefined) {
|
||||||
if (ps.deeplAuthKey === '') {
|
if (ps.deeplAuthKey === '') {
|
||||||
set.deeplAuthKey = null;
|
set.deeplAuthKey = null;
|
||||||
|
@ -374,6 +382,22 @@ export default define(meta, paramDef, async (ps, me) => {
|
||||||
set.deeplIsPro = ps.deeplIsPro;
|
set.deeplIsPro = ps.deeplIsPro;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ps.libreTranslateEndpoint !== undefined) {
|
||||||
|
if (ps.libreTranslateEndpoint === '') {
|
||||||
|
set.libreTranslateEndpoint = null;
|
||||||
|
} else {
|
||||||
|
set.libreTranslateEndpoint = ps.libreTranslateEndpoint;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ps.libreTranslateAuthKey !== undefined) {
|
||||||
|
if (ps.libreTranslateAuthKey === '') {
|
||||||
|
set.libreTranslateAuthKey = null;
|
||||||
|
} else {
|
||||||
|
set.libreTranslateAuthKey = ps.libreTranslateAuthKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const meta = await fetchMeta();
|
const meta = await fetchMeta();
|
||||||
await setMeta({
|
await setMeta({
|
||||||
...meta,
|
...meta,
|
||||||
|
|
|
@ -3,6 +3,7 @@ import config from '@/config/index.js';
|
||||||
import { fetchMeta } from '@/misc/fetch-meta.js';
|
import { fetchMeta } from '@/misc/fetch-meta.js';
|
||||||
import { Emojis, Users } from '@/models/index.js';
|
import { Emojis, Users } from '@/models/index.js';
|
||||||
import define from '../define.js';
|
import define from '../define.js';
|
||||||
|
import { translatorAvailable } from '../common/translator.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['meta'],
|
tags: ['meta'],
|
||||||
|
@ -322,7 +323,7 @@ export default define(meta, paramDef, async (ps, me) => {
|
||||||
|
|
||||||
enableServiceWorker: instance.enableServiceWorker,
|
enableServiceWorker: instance.enableServiceWorker,
|
||||||
|
|
||||||
translatorAvailable: instance.deeplAuthKey != null,
|
translatorAvailable: translatorAvailable(instance),
|
||||||
|
|
||||||
pinnedPages: instance.pinnedPages,
|
pinnedPages: instance.pinnedPages,
|
||||||
pinnedClipId: instance.pinnedClipId,
|
pinnedClipId: instance.pinnedClipId,
|
||||||
|
|
|
@ -4,6 +4,7 @@ import config from '@/config/index.js';
|
||||||
import { getAgentByUrl } from '@/misc/fetch.js';
|
import { getAgentByUrl } from '@/misc/fetch.js';
|
||||||
import { fetchMeta } from '@/misc/fetch-meta.js';
|
import { fetchMeta } from '@/misc/fetch-meta.js';
|
||||||
import { Notes } from '@/models/index.js';
|
import { Notes } from '@/models/index.js';
|
||||||
|
import { TranslationService } from '@/models/entities/meta.js';
|
||||||
import { ApiError } from '../../error.js';
|
import { ApiError } from '../../error.js';
|
||||||
import { getNote } from '../../common/getters.js';
|
import { getNote } from '../../common/getters.js';
|
||||||
import define from '../../define.js';
|
import define from '../../define.js';
|
||||||
|
@ -113,50 +114,101 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||||
if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError('NO_SUCH_NOTE');
|
if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError('NO_SUCH_NOTE');
|
||||||
throw err;
|
throw err;
|
||||||
});
|
});
|
||||||
|
const instance = await fetchMeta();
|
||||||
|
|
||||||
if (note.text == null) {
|
if (instance.translationService == null) {
|
||||||
return 204;
|
return 204;
|
||||||
}
|
}
|
||||||
|
|
||||||
const instance = await fetchMeta();
|
type Translation = {
|
||||||
|
sourceLang: string;
|
||||||
|
text: string;
|
||||||
|
};
|
||||||
|
|
||||||
if (instance.deeplAuthKey == null) {
|
async function translateDeepL(): Promise<Translation | number> {
|
||||||
return 204; // TODO: 良い感じのエラー返す
|
if (note.text == null || instance.deeplAuthKey == null) {
|
||||||
|
return 204; // TODO: Return a better error
|
||||||
|
}
|
||||||
|
|
||||||
|
const sourceLang = ps.sourceLang;
|
||||||
|
const targetLang = ps.targetLang;
|
||||||
|
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('auth_key', instance.deeplAuthKey);
|
||||||
|
params.append('text', note.text);
|
||||||
|
params.append('target_lang', targetLang);
|
||||||
|
if (sourceLang) params.append('source_lang', sourceLang);
|
||||||
|
|
||||||
|
const endpoint = instance.deeplIsPro ? 'https://api.deepl.com/v2/translate' : 'https://api-free.deepl.com/v2/translate';
|
||||||
|
|
||||||
|
const res = await fetch(endpoint, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
'User-Agent': config.userAgent,
|
||||||
|
Accept: 'application/json, */*',
|
||||||
|
},
|
||||||
|
body: params,
|
||||||
|
// TODO
|
||||||
|
//timeout: 10000,
|
||||||
|
agent: getAgentByUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function translateLibreTranslate(): Promise<Translation | number> {
|
||||||
|
if (note.text == null || instance.libreTranslateEndpoint == null) {
|
||||||
|
return 204;
|
||||||
|
}
|
||||||
|
|
||||||
|
// LibteTranslate only understands 2-letter codes
|
||||||
|
const source = ps.sourceLang?.toLowerCase().split('-', 1)[0] ?? 'auto';
|
||||||
|
const target = ps.targetLang.toLowerCase().split('-', 1)[0];
|
||||||
|
const api_key = instance.libreTranslateAuthKey ?? undefined;
|
||||||
|
const endpoint = instance.libreTranslateEndpoint;
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
q: note.text,
|
||||||
|
source,
|
||||||
|
target,
|
||||||
|
api_key,
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = await fetch(endpoint, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(params),
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const json = (await res.json()) as {
|
||||||
|
detectedLanguage?: {
|
||||||
|
confidence: number;
|
||||||
|
language: string;
|
||||||
|
};
|
||||||
|
translatedText: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
sourceLang: (json.detectedLanguage?.language ?? source).toUpperCase(),
|
||||||
|
text: json.translatedText,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const sourceLang = ps.sourceLang;
|
switch (instance.translationService) {
|
||||||
const targetLang = ps.targetLang;
|
case TranslationService.DeepL:
|
||||||
|
return await translateDeepL();
|
||||||
const params = new URLSearchParams();
|
case TranslationService.LibreTranslate:
|
||||||
params.append('auth_key', instance.deeplAuthKey);
|
return await translateLibreTranslate();
|
||||||
params.append('text', note.text);
|
}
|
||||||
params.append('target_lang', targetLang);
|
|
||||||
if (sourceLang) params.append('source_lang', sourceLang);
|
|
||||||
|
|
||||||
const endpoint = instance.deeplIsPro ? 'https://api.deepl.com/v2/translate' : 'https://api-free.deepl.com/v2/translate';
|
|
||||||
|
|
||||||
const res = await fetch(endpoint, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
|
||||||
'User-Agent': config.userAgent,
|
|
||||||
Accept: 'application/json, */*',
|
|
||||||
},
|
|
||||||
body: params,
|
|
||||||
// TODO
|
|
||||||
//timeout: 10000,
|
|
||||||
agent: getAgentByUrl,
|
|
||||||
});
|
|
||||||
|
|
||||||
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,
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -172,6 +172,11 @@ const menuDef = $computed(() => [{
|
||||||
text: i18n.ts.proxyAccount,
|
text: i18n.ts.proxyAccount,
|
||||||
to: '/admin/proxy-account',
|
to: '/admin/proxy-account',
|
||||||
active: props.initialPage === 'proxy-account',
|
active: props.initialPage === 'proxy-account',
|
||||||
|
}, {
|
||||||
|
icon: 'fas fa-language',
|
||||||
|
text: i18n.ts.translationSettings,
|
||||||
|
to: '/admin/translation-settings',
|
||||||
|
active: props.initialPage === 'translation-settings',
|
||||||
}],
|
}],
|
||||||
}, {
|
}, {
|
||||||
title: i18n.ts.info,
|
title: i18n.ts.info,
|
||||||
|
@ -202,6 +207,7 @@ const component = $computed(() => {
|
||||||
case 'integrations': return defineAsyncComponent(() => import('./integrations.vue'));
|
case 'integrations': return defineAsyncComponent(() => import('./integrations.vue'));
|
||||||
case 'instance-block': return defineAsyncComponent(() => import('./instance-block.vue'));
|
case 'instance-block': return defineAsyncComponent(() => import('./instance-block.vue'));
|
||||||
case 'proxy-account': return defineAsyncComponent(() => import('./proxy-account.vue'));
|
case 'proxy-account': return defineAsyncComponent(() => import('./proxy-account.vue'));
|
||||||
|
case 'translation-settings': return defineAsyncComponent(() => import('./translation-settings.vue'));
|
||||||
default: return null;
|
default: return null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -128,18 +128,6 @@
|
||||||
</FormInput>
|
</FormInput>
|
||||||
</template>
|
</template>
|
||||||
</FormSection>
|
</FormSection>
|
||||||
|
|
||||||
<FormSection>
|
|
||||||
<template #label>DeepL Translation</template>
|
|
||||||
|
|
||||||
<FormInput v-model="deeplAuthKey" class="_formBlock">
|
|
||||||
<template #prefix><i class="fas fa-key"></i></template>
|
|
||||||
<template #label>DeepL Auth Key</template>
|
|
||||||
</FormInput>
|
|
||||||
<FormSwitch v-model="deeplIsPro" class="_formBlock">
|
|
||||||
<template #label>Pro account</template>
|
|
||||||
</FormSwitch>
|
|
||||||
</FormSection>
|
|
||||||
</div>
|
</div>
|
||||||
</FormSuspense>
|
</FormSuspense>
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
|
@ -182,8 +170,6 @@ let emailRequiredForSignup: boolean = $ref(false);
|
||||||
let enableServiceWorker: boolean = $ref(false);
|
let enableServiceWorker: boolean = $ref(false);
|
||||||
let swPublicKey: any = $ref(null);
|
let swPublicKey: any = $ref(null);
|
||||||
let swPrivateKey: any = $ref(null);
|
let swPrivateKey: any = $ref(null);
|
||||||
let deeplAuthKey: string = $ref('');
|
|
||||||
let deeplIsPro: boolean = $ref(false);
|
|
||||||
|
|
||||||
async function init(): Promise<void> {
|
async function init(): Promise<void> {
|
||||||
const meta = await os.api('admin/meta');
|
const meta = await os.api('admin/meta');
|
||||||
|
@ -209,11 +195,9 @@ async function init(): Promise<void> {
|
||||||
enableServiceWorker = meta.enableServiceWorker;
|
enableServiceWorker = meta.enableServiceWorker;
|
||||||
swPublicKey = meta.swPublickey;
|
swPublicKey = meta.swPublickey;
|
||||||
swPrivateKey = meta.swPrivateKey;
|
swPrivateKey = meta.swPrivateKey;
|
||||||
deeplAuthKey = meta.deeplAuthKey;
|
|
||||||
deeplIsPro = meta.deeplIsPro;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function save() {
|
function save(): void {
|
||||||
os.apiWithDialog('admin/update-meta', {
|
os.apiWithDialog('admin/update-meta', {
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
|
@ -237,8 +221,6 @@ function save() {
|
||||||
enableServiceWorker,
|
enableServiceWorker,
|
||||||
swPublicKey,
|
swPublicKey,
|
||||||
swPrivateKey,
|
swPrivateKey,
|
||||||
deeplAuthKey,
|
|
||||||
deeplIsPro,
|
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
fetchInstance();
|
fetchInstance();
|
||||||
});
|
});
|
||||||
|
|
86
packages/client/src/pages/admin/translation-settings.vue
Normal file
86
packages/client/src/pages/admin/translation-settings.vue
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
<template>
|
||||||
|
<MkStickyContainer>
|
||||||
|
<template #header><MkPageHeader :actions="headerActions"/></template>
|
||||||
|
<MkSpacer :content-max="700" :margin-min="16" :margin-max="32">
|
||||||
|
<FormSuspense :p="init">
|
||||||
|
<div class="_formRoot">
|
||||||
|
<FormSelect v-model="translationService" class="_formBlock">
|
||||||
|
<template #label>{{ i18n.ts.translationService }}</template>
|
||||||
|
<option value="none">{{ i18n.ts.none }}</option>
|
||||||
|
<option value="deepl">DeepL</option>
|
||||||
|
<option value="libretranslate">LibreTranslate</option>
|
||||||
|
</FormSelect>
|
||||||
|
|
||||||
|
<template v-if="translationService === 'deepl'">
|
||||||
|
<FormSwitch v-model="deeplIsPro" class="_formBlock">
|
||||||
|
<template #label>{{ i18n.ts._translationService._deepl.pro }}</template>
|
||||||
|
</FormSwitch>
|
||||||
|
<FormInput v-model="deeplAuthKey" class="_formBlock">
|
||||||
|
<template #prefix><i class="fas fa-key"></i></template>
|
||||||
|
<template #label>{{ i18n.ts._translationService._deepl.authKey }}</template>
|
||||||
|
</FormInput>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="translationService === 'libretranslate'">
|
||||||
|
<FormInput v-model="libreTranslateEndpoint" class="_formBlock">
|
||||||
|
<template #label>{{ i18n.ts._translationService._libreTranslate.endpoint }}</template>
|
||||||
|
</FormInput>
|
||||||
|
<FormInput v-model="libreTranslateAuthKey" class="_formBlock">
|
||||||
|
<template #prefix><i class="fas fa-key"></i></template>
|
||||||
|
<template #label>{{ i18n.ts._translationService._libreTranslate.authKey }}</template>
|
||||||
|
</FormInput>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</FormSuspense>
|
||||||
|
</MkSpacer>
|
||||||
|
</MkStickyContainer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import FormInput from '@/components/form/input.vue';
|
||||||
|
import FormSelect from '@/components/form/select.vue';
|
||||||
|
import FormSuspense from '@/components/form/suspense.vue';
|
||||||
|
import FormSwitch from '@/components/form/switch.vue';
|
||||||
|
import * as os from '@/os';
|
||||||
|
import { i18n } from '@/i18n';
|
||||||
|
import { fetchInstance } from '@/instance';
|
||||||
|
import { definePageMetadata } from '@/scripts/page-metadata';
|
||||||
|
|
||||||
|
let translationService: string = $ref('none');
|
||||||
|
let deeplIsPro: boolean = $ref(false);
|
||||||
|
let deeplAuthKey: string = $ref('');
|
||||||
|
let libreTranslateEndpoint: string = $ref('');
|
||||||
|
let libreTranslateAuthKey: string = $ref('');
|
||||||
|
|
||||||
|
async function init(): Promise<void> {
|
||||||
|
const meta = await os.api('admin/meta');
|
||||||
|
translationService = meta.translationService ?? 'none';
|
||||||
|
deeplIsPro = meta.deeplIsPro;
|
||||||
|
deeplAuthKey = meta.deeplAuthKey;
|
||||||
|
libreTranslateEndpoint = meta.libreTranslateEndpoint;
|
||||||
|
libreTranslateAuthKey = meta.libreTranslateAuthKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
function save(): void {
|
||||||
|
os.apiWithDialog('admin/update-meta', {
|
||||||
|
translationService: translationService === 'none' ? null : translationService,
|
||||||
|
deeplAuthKey,
|
||||||
|
deeplIsPro,
|
||||||
|
libreTranslateEndpoint,
|
||||||
|
libreTranslateAuthKey,
|
||||||
|
}).then(() => {
|
||||||
|
fetchInstance();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const headerActions = $computed(() => [{
|
||||||
|
asFullButton: true,
|
||||||
|
icon: 'fas fa-check',
|
||||||
|
text: i18n.ts.save,
|
||||||
|
handler: save,
|
||||||
|
}]);
|
||||||
|
|
||||||
|
definePageMetadata({
|
||||||
|
title: i18n.ts.translationSettings,
|
||||||
|
icon: 'fas fa-language',
|
||||||
|
});
|
||||||
|
</script>
|
Loading…
Reference in a new issue