mirror of
https://gitlab.com/soapbox-pub/ditto.git
synced 2025-12-06 03:19:46 +00:00
feat: create LibreTranslateTranslator class that implements DittoTranslator
This commit is contained in:
parent
321d26b470
commit
c23ddb7d84
1 changed files with 147 additions and 0 deletions
147
src/translators/LibreTranslateTranslator.ts
Normal file
147
src/translators/LibreTranslateTranslator.ts
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
import { z } from 'zod';
|
||||
|
||||
import {
|
||||
DittoTranslator,
|
||||
MastodonTranslation,
|
||||
Provider,
|
||||
SourceLanguage,
|
||||
TargetLanguage,
|
||||
} from '@/translators/translator.ts';
|
||||
|
||||
interface LibreTranslateTranslatorOpts {
|
||||
/** Libretranslate endpoint to use. Default: 'https://libretranslate.com' */
|
||||
endpoint?: string;
|
||||
/** Libretranslate API key. */
|
||||
apiKey: string;
|
||||
/** Custom fetch implementation. */
|
||||
fetch?: typeof fetch;
|
||||
}
|
||||
|
||||
export class LibreTranslateTranslator implements DittoTranslator {
|
||||
private readonly endpoint: string;
|
||||
private readonly apiKey: string;
|
||||
private readonly fetch: typeof fetch;
|
||||
private readonly provider: Provider;
|
||||
|
||||
constructor(opts: LibreTranslateTranslatorOpts) {
|
||||
this.endpoint = opts.endpoint ?? 'https://libretranslate.com';
|
||||
this.fetch = opts.fetch ?? globalThis.fetch;
|
||||
this.provider = 'libretranslate.com';
|
||||
this.apiKey = opts.apiKey;
|
||||
}
|
||||
|
||||
async translate(
|
||||
contentHTMLencoded: string,
|
||||
spoilerText: string,
|
||||
mediaAttachments: { id: string; description: string }[],
|
||||
poll: { id: string; options: { title: string }[] } | null,
|
||||
sourceLanguage: SourceLanguage | undefined,
|
||||
targetLanguage: TargetLanguage,
|
||||
opts?: { signal?: AbortSignal },
|
||||
) {
|
||||
const mastodonTranslation: MastodonTranslation = {
|
||||
content: '',
|
||||
spoiler_text: '',
|
||||
media_attachments: [],
|
||||
poll: null,
|
||||
detected_source_language: 'en',
|
||||
provider: this.provider,
|
||||
};
|
||||
|
||||
const translatedContent = await this.makeRequest(contentHTMLencoded, sourceLanguage, targetLanguage, 'html', {
|
||||
signal: opts?.signal,
|
||||
});
|
||||
mastodonTranslation.content = translatedContent;
|
||||
|
||||
if (spoilerText.length) {
|
||||
const translatedSpoilerText = await this.makeRequest(spoilerText, sourceLanguage, targetLanguage, 'text', {
|
||||
signal: opts?.signal,
|
||||
});
|
||||
mastodonTranslation.spoiler_text = translatedSpoilerText;
|
||||
}
|
||||
|
||||
if (mediaAttachments) {
|
||||
for (const media of mediaAttachments) {
|
||||
const translatedDescription = await this.makeRequest(
|
||||
media.description,
|
||||
sourceLanguage,
|
||||
targetLanguage,
|
||||
'text',
|
||||
{
|
||||
signal: opts?.signal,
|
||||
},
|
||||
);
|
||||
mastodonTranslation.media_attachments.push({
|
||||
id: media.id,
|
||||
description: translatedDescription,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (poll) {
|
||||
mastodonTranslation.poll = {
|
||||
id: poll.id,
|
||||
options: [],
|
||||
};
|
||||
|
||||
for (const option of poll.options) {
|
||||
const translatedTitle = await this.makeRequest(
|
||||
option.title,
|
||||
sourceLanguage,
|
||||
targetLanguage,
|
||||
'text',
|
||||
{
|
||||
signal: opts?.signal,
|
||||
},
|
||||
);
|
||||
mastodonTranslation.poll.options.push({
|
||||
title: translatedTitle,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
data: mastodonTranslation,
|
||||
};
|
||||
}
|
||||
|
||||
private async makeRequest(
|
||||
q: string,
|
||||
sourceLanguage: string | undefined,
|
||||
targetLanguage: string,
|
||||
format: 'html' | 'text',
|
||||
opts?: { signal?: AbortSignal },
|
||||
): Promise<string> {
|
||||
const body = {
|
||||
q,
|
||||
source: sourceLanguage?.toLowerCase() ?? 'auto',
|
||||
target: targetLanguage.toLowerCase(),
|
||||
format,
|
||||
api_key: this.apiKey,
|
||||
};
|
||||
|
||||
const headers = new Headers();
|
||||
headers.append('Content-Type', 'application/json');
|
||||
|
||||
const request = new Request(this.endpoint + '/translate', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(body),
|
||||
headers,
|
||||
signal: opts?.signal,
|
||||
});
|
||||
|
||||
const response = await this.fetch(request);
|
||||
const json = await response.json();
|
||||
const data = LibreTranslateTranslator.schema().parse(json).translatedText;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/** Libretranslate response schema.
|
||||
* https://libretranslate.com/docs/#/translate/post_translate */
|
||||
private static schema() {
|
||||
return z.object({
|
||||
translatedText: z.string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue