Skip to content

Commit

Permalink
Merge pull request #551
Browse files Browse the repository at this point in the history
* Libretranslate

*  pnpm run build-cherrypick-js-with-types

* fix type(bool => string)

* fix nullable

* fix polls-translate

* fix
  • Loading branch information
penginn-net authored Jan 1, 2025
1 parent 2fa0c6a commit 6e4b1de
Show file tree
Hide file tree
Showing 11 changed files with 194 additions and 0 deletions.
6 changes: 6 additions & 0 deletions compose.local-db.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,9 @@ services:
# volumes:
# - ./meili_data:/meili_data

# translator:
# restart: always
# image: libretranslate/libretranslate:latest
# healthcheck:
# test: ['CMD-SHELL', './venv/bin/python scripts/healthcheck.py']

6 changes: 6 additions & 0 deletions compose_example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,12 @@ services:
# volumes:
# - ./meili_data:/meili_data

# translator:
# restart: always
# image: libretranslate/libretranslate:latest
# healthcheck:
# test: ['CMD-SHELL', './venv/bin/python scripts/healthcheck.py']

networks:
internal_network:
internal: true
Expand Down
18 changes: 18 additions & 0 deletions packages/backend/migration/1734793052000-LibreTranslate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/

export class LibreTranslate1734793052000 {
name = 'LibreTranslate1734793052000'

async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" ADD "libreTranslateEndPoint" character varying(1024)`);
await queryRunner.query(`ALTER TABLE "meta" ADD "libreTranslateApiKey" character varying(1024)`);
}

async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "libreTranslateEndPoint"`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "libreTranslateApiKey"`);
}
}
12 changes: 12 additions & 0 deletions packages/backend/src/models/Meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,18 @@ export class MiMeta {
})
public ctav3Location: string | null;

@Column('varchar', {
length: 1024,
nullable: true,
})
public libreTranslateEndPoint: string | null;

@Column('varchar', {
length: 1024,
nullable: true,
})
public libreTranslateApiKey: string | null;

@Column('varchar', {
length: 1024,
nullable: true,
Expand Down
10 changes: 10 additions & 0 deletions packages/backend/src/server/api/endpoints/admin/meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,14 @@ export const meta = {
type: 'boolean',
optional: false, nullable: false,
},
libreTranslateEndPoint: {
type: 'string',
optional: false, nullable: true,
},
libreTranslateApiKey: {
type: 'string',
optional: false, nullable: true,
},
defaultDarkTheme: {
type: 'string',
optional: false, nullable: true,
Expand Down Expand Up @@ -763,6 +771,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
remoteObjectStorageS3ForcePathStyle: instance.remoteObjectStorageS3ForcePathStyle,
deeplAuthKey: instance.deeplAuthKey,
deeplIsPro: instance.deeplIsPro,
libreTranslateEndPoint: instance.libreTranslateEndPoint,
libreTranslateApiKey: instance.libreTranslateApiKey,
ctav3SaKey: instance.ctav3SaKey,
ctav3ProjectId: instance.ctav3ProjectId,
ctav3Location: instance.ctav3Location,
Expand Down
10 changes: 10 additions & 0 deletions packages/backend/src/server/api/endpoints/admin/update-meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ export const paramDef = {
ctav3Location: { type: 'string', nullable: true },
ctav3Model: { type: 'string', nullable: true },
ctav3Glossary: { type: 'string', nullable: true },
libreTranslateEndPoint: { type: 'string', nullable: true },
libreTranslateApiKey: { type: 'string', nullable: true },
enableEmail: { type: 'boolean' },
email: { type: 'string', nullable: true },
smtpSecure: { type: 'boolean' },
Expand Down Expand Up @@ -657,6 +659,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
set.ctav3Glossary = ps.ctav3Glossary;
}

if (ps.libreTranslateEndPoint !== undefined) {
set.libreTranslateEndPoint = ps.libreTranslateEndPoint;
}

if (ps.libreTranslateApiKey !== undefined) {
set.libreTranslateApiKey = ps.libreTranslateApiKey;
}

if (ps.enableIpLogging !== undefined) {
set.enableIpLogging = ps.enableIpLogging;
}
Expand Down
42 changes: 42 additions & 0 deletions packages/backend/src/server/api/endpoints/notes/polls/translate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
'deepl',
'google_no_api',
'ctav3',
'Libretranslate',
];

if (this.serverSettings.translatorType == null || !translatorServices.includes(this.serverSettings.translatorType)) {
Expand Down Expand Up @@ -119,6 +120,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
else if (this.serverSettings.ctav3ProjectId == null) return Promise.resolve(204);
else if (this.serverSettings.ctav3Location == null) return Promise.resolve(204);
translationResult = await this.apiCloudTranslationAdvanced(poll.choices, targetLang, this.serverSettings.ctav3SaKey, this.serverSettings.ctav3ProjectId, this.serverSettings.ctav3Location, this.serverSettings.ctav3Model, this.serverSettings.ctav3Glossary, this.serverSettings.translatorType);
} else if (this.serverSettings.translatorType === 'Libretranslate') {
const endPoint = this.serverSettings.libreTranslateEndPoint;
if (endPoint === null) throw new Error('libreTranslateEndPoint is null');
translationResult = await this.translateLibretranslate(poll.choices, targetLang, endPoint, this.serverSettings.libreTranslateApiKey);
} else {
throw new Error('Unsupported translator type');
}
Expand Down Expand Up @@ -217,4 +222,41 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
translator: provider,
};
}

private async translateLibretranslate(texts: string[], targetLang: string, endpoint: string, apiKey:string | null ) {
const translations = [];
const target = targetLang.split('-')[0];
for (const text of texts) {
const res = await this.httpRequestService.send(endpoint + '/translate', {
method: 'POST',
body: JSON.stringify({
q: text,
source: 'auto',
format: 'text',
target: target,
...(apiKey ? { api_key: apiKey } : { }),
}),
headers: { 'Content-Type': 'application/json' },
});

const json = (await res.json()) as {
translatedText: string,
detectedLanguage: {
confidence: number,
language: string,
}
error: string,
};
translations.push({
translatedText: json.translatedText || '',
sourceLang: json.detectedLanguage.language || '',
});
}

return {
sourceLang: translations[0]?.sourceLang || '',
text: translations.map(choice => choice.translatedText),
translator: 'Libretranslate',
};
}
}
33 changes: 33 additions & 0 deletions packages/backend/src/server/api/endpoints/notes/translate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
'deepl',
'google_no_api',
'ctav3',
'Libretranslate',
];

if (this.serverSettings.translatorType == null || !translatorServices.includes(this.serverSettings.translatorType)) {
Expand Down Expand Up @@ -131,6 +132,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
else if (this.serverSettings.ctav3ProjectId == null) return Promise.resolve(204);
else if (this.serverSettings.ctav3Location == null) return Promise.resolve(204);
translationResult = await this.apiCloudTranslationAdvanced((note.cw ? note.cw + '\n' : '') + note.text, targetLang, this.serverSettings.ctav3SaKey, this.serverSettings.ctav3ProjectId, this.serverSettings.ctav3Location, this.serverSettings.ctav3Model, this.serverSettings.ctav3Glossary, this.serverSettings.translatorType);
} else if (this.serverSettings.translatorType === 'Libretranslate') {
const endPoint = this.serverSettings.libreTranslateEndPoint;
if (endPoint === null) throw new Error('libreTranslateEndPoint is null');
translationResult = await this.translateLibretranslate((note.cw ? note.cw + '\n' : '') + note.text, targetLang, endPoint, this.serverSettings.libreTranslateApiKey);
} else {
throw new Error('Unsupported translator type');
}
Expand Down Expand Up @@ -221,4 +226,32 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
translator: provider,
};
}
private async translateLibretranslate(text: string, targetLang: string, endpoint: string, apiKey:string | null ) {
const res = await this.httpRequestService.send(endpoint + '/translate', {
method: 'POST',
body: JSON.stringify({
q: text,
source: 'auto',
format: 'text',
target: targetLang.split('-')[0],
...(apiKey ? { api_key: apiKey } : { }),
}),
headers: { 'Content-Type': 'application/json' },
});

const json = (await res.json()) as {
translatedText: string,
detectedLanguage: {
confidence: number,
language: string,
}
error: string,
};

return {
sourceLang: json.detectedLanguage.language,
text: json.translatedText,
translator: 'Libretranslate',
};
}
}
34 changes: 34 additions & 0 deletions packages/backend/src/server/api/endpoints/users/translate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
'deepl',
'google_no_api',
'ctav3',
'Libretranslate',
];

if (this.serverSettings.translatorType == null || !translatorServices.includes(this.serverSettings.translatorType)) {
Expand Down Expand Up @@ -120,6 +121,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
else if (this.serverSettings.ctav3ProjectId == null) return Promise.resolve(204);
else if (this.serverSettings.ctav3Location == null) return Promise.resolve(204);
translationResult = await this.apiCloudTranslationAdvanced(target.description, targetLang, this.serverSettings.ctav3SaKey, this.serverSettings.ctav3ProjectId, this.serverSettings.ctav3Location, this.serverSettings.ctav3Model, this.serverSettings.ctav3Glossary, this.serverSettings.translatorType);
} else if (this.serverSettings.translatorType === 'Libretranslate') {
const endPoint = this.serverSettings.libreTranslateEndPoint;
if (endPoint === null) throw new Error('libreTranslateEndPoint is null');
translationResult = await this.translateLibretranslate(target.description, targetLang, endPoint, this.serverSettings.libreTranslateApiKey);
} else {
throw new Error('Unsupported translator type');
}
Expand Down Expand Up @@ -210,4 +215,33 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
translator: provider,
};
}

private async translateLibretranslate(text: string, targetLang: string, endpoint: string, apiKey:string | null ) {
const res = await this.httpRequestService.send(endpoint + '/translate', {
method: 'POST',
body: JSON.stringify({
q: text,
source: 'auto',
format: 'text',
target: targetLang.split('-')[0],
...(apiKey ? { api_key: apiKey } : { }),
}),
headers: { 'Content-Type': 'application/json' },
});

const json = (await res.json()) as {
translatedText: string,
detectedLanguage: {
confidence: number,
language: string,
}
error: string,
};

return {
sourceLang: json.detectedLanguage.language,
text: json.translatedText,
translator: 'Libretranslate',
};
}
}
4 changes: 4 additions & 0 deletions packages/cherrypick-js/src/autogen/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5622,6 +5622,8 @@ export type operations = {
backgroundImageUrl: string | null;
deeplAuthKey: string | null;
deeplIsPro: boolean;
libreTranslateEndPoint: string | null;
libreTranslateApiKey: string | null;
defaultDarkTheme: string | null;
defaultLightTheme: string | null;
description: string | null;
Expand Down Expand Up @@ -10401,6 +10403,8 @@ export type operations = {
ctav3Location?: string | null;
ctav3Model?: string | null;
ctav3Glossary?: string | null;
libreTranslateEndPoint?: string | null;
libreTranslateApiKey?: string | null;
enableEmail?: boolean;
email?: string | null;
smtpSecure?: boolean;
Expand Down
19 changes: 19 additions & 0 deletions packages/frontend/src/pages/admin/external-services.vue
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<option value="deepl">DeepL</option>
<option value="google_no_api">Google Translate(without API)</option>
<option value="ctav3">Cloud Translation - Advanced(v3)</option>
<option value="Libretranslate">Libretranslate</option>
</MkRadios>

<template v-if="provider === 'deepl'">
Expand Down Expand Up @@ -64,6 +65,18 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkInput>
</template>

<template v-else-if="provider === 'Libretranslate'">
<div class="_gaps_m">
<MkInput v-model="libreTranslateEndPoint">
<template #prefix><i class="ti ti-server"></i></template>
<template #label>Api Endpoint</template>
</MkInput>
<MkInput v-model="libreTranslateApiKey">
<template #prefix><i class="ti ti-key"></i></template>
<template #label>ApiKey</template>
</MkInput>
</div>
</template>
<MkButton primary rounded @click="save_deepl"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton>
</div>
</MkFolder>
Expand Down Expand Up @@ -95,6 +108,8 @@ const ctav3ProjectId = ref<string>('');
const ctav3Location = ref<string>('');
const ctav3Model = ref<string>('');
const ctav3Glossary = ref<string>('');
const libreTranslateEndPoint = ref<string>('');
const libreTranslateApiKey = ref<string>('');

async function init() {
const meta = await misskeyApi('admin/meta');
Expand All @@ -106,6 +121,8 @@ async function init() {
ctav3Location.value = meta.ctav3Location;
ctav3Model.value = meta.ctav3Model;
ctav3Glossary.value = meta.ctav3Glossary;
libreTranslateEndPoint.value = meta.libreTranslateEndPoint;
libreTranslateApiKey.value = meta.libreTranslateApiKey;
}

function save_deepl() {
Expand All @@ -118,6 +135,8 @@ function save_deepl() {
ctav3Location: ctav3Location.value,
ctav3Model: ctav3Model.value,
ctav3Glossary: ctav3Glossary.value,
libreTranslateEndPoint: libreTranslateEndPoint.value,
libreTranslateApiKey: libreTranslateApiKey.value,
}).then(() => {
fetchInstance(true);
});
Expand Down

0 comments on commit 6e4b1de

Please sign in to comment.