mirror of
https://gitlab.com/soapbox-pub/ditto.git
synced 2025-12-06 11:29:46 +00:00
feat: create translateController - /api/v1/statuses/:id/translate
This commit is contained in:
parent
8e58b1a7d4
commit
b369b2171d
2 changed files with 95 additions and 0 deletions
|
|
@ -109,6 +109,7 @@ import {
|
||||||
trendingStatusesController,
|
trendingStatusesController,
|
||||||
trendingTagsController,
|
trendingTagsController,
|
||||||
} from '@/controllers/api/trends.ts';
|
} from '@/controllers/api/trends.ts';
|
||||||
|
import { translateController } from '@/controllers/api/translate.ts';
|
||||||
import { errorHandler } from '@/controllers/error.ts';
|
import { errorHandler } from '@/controllers/error.ts';
|
||||||
import { frontendController } from '@/controllers/frontend.ts';
|
import { frontendController } from '@/controllers/frontend.ts';
|
||||||
import { metricsController } from '@/controllers/metrics.ts';
|
import { metricsController } from '@/controllers/metrics.ts';
|
||||||
|
|
@ -126,6 +127,8 @@ import { requireSigner } from '@/middleware/requireSigner.ts';
|
||||||
import { signerMiddleware } from '@/middleware/signerMiddleware.ts';
|
import { signerMiddleware } from '@/middleware/signerMiddleware.ts';
|
||||||
import { storeMiddleware } from '@/middleware/storeMiddleware.ts';
|
import { storeMiddleware } from '@/middleware/storeMiddleware.ts';
|
||||||
import { uploaderMiddleware } from '@/middleware/uploaderMiddleware.ts';
|
import { uploaderMiddleware } from '@/middleware/uploaderMiddleware.ts';
|
||||||
|
import { DittoTranslator } from '@/translators/translator.ts';
|
||||||
|
import { translatorMiddleware } from '@/middleware/translatorMiddleware.ts';
|
||||||
|
|
||||||
interface AppEnv extends HonoEnv {
|
interface AppEnv extends HonoEnv {
|
||||||
Variables: {
|
Variables: {
|
||||||
|
|
@ -141,6 +144,8 @@ interface AppEnv extends HonoEnv {
|
||||||
pagination: { since?: number; until?: number; limit: number };
|
pagination: { since?: number; until?: number; limit: number };
|
||||||
/** Normalized list pagination params. */
|
/** Normalized list pagination params. */
|
||||||
listPagination: { offset: number; limit: number };
|
listPagination: { offset: number; limit: number };
|
||||||
|
/** Translation service. */
|
||||||
|
translator?: DittoTranslator;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -220,6 +225,7 @@ app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/bookmark', requireSigner, bookmarkC
|
||||||
app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/unbookmark', requireSigner, unbookmarkController);
|
app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/unbookmark', requireSigner, unbookmarkController);
|
||||||
app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/pin', requireSigner, pinController);
|
app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/pin', requireSigner, pinController);
|
||||||
app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/unpin', requireSigner, unpinController);
|
app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/unpin', requireSigner, unpinController);
|
||||||
|
app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/translate', requireSigner, translatorMiddleware, translateController);
|
||||||
app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/reblog', requireSigner, reblogStatusController);
|
app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/reblog', requireSigner, reblogStatusController);
|
||||||
app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/unreblog', requireSigner, unreblogStatusController);
|
app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/unreblog', requireSigner, unreblogStatusController);
|
||||||
app.post('/api/v1/statuses', requireSigner, createStatusController);
|
app.post('/api/v1/statuses', requireSigner, createStatusController);
|
||||||
|
|
|
||||||
89
src/controllers/api/translate.ts
Normal file
89
src/controllers/api/translate.ts
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
import { LanguageCode } from 'iso-639-1';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import { AppController } from '@/app.ts';
|
||||||
|
import { languageSchema } from '@/schema.ts';
|
||||||
|
import { Storages } from '@/storages.ts';
|
||||||
|
import { dittoTranslations, dittoTranslationsKey } from '@/translators/translator.ts';
|
||||||
|
import { parseBody } from '@/utils/api.ts';
|
||||||
|
import { getEvent } from '@/queries.ts';
|
||||||
|
import { renderStatus } from '@/views/mastodon/statuses.ts';
|
||||||
|
|
||||||
|
const translateSchema = z.object({
|
||||||
|
//lang: languageSchema, // Correct property name, as stated by Mastodon docs
|
||||||
|
target_language: languageSchema, // Property name soapbox sends
|
||||||
|
});
|
||||||
|
|
||||||
|
const translateController: AppController = async (c) => {
|
||||||
|
const result = translateSchema.safeParse(await parseBody(c.req.raw));
|
||||||
|
const { signal } = c.req.raw;
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
return c.json({ error: 'Bad request.', schema: result.error }, 422);
|
||||||
|
}
|
||||||
|
|
||||||
|
const translator = c.get('translator');
|
||||||
|
if (!translator) {
|
||||||
|
return c.json({ error: 'No translator configured.' }, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { target_language } = result.data;
|
||||||
|
const targetLang = target_language;
|
||||||
|
const id = c.req.param('id');
|
||||||
|
|
||||||
|
const event = await getEvent(id, { signal });
|
||||||
|
if (!event) {
|
||||||
|
return c.json({ error: 'Record not found' }, 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
const viewerPubkey = await c.get('signer')?.getPublicKey();
|
||||||
|
|
||||||
|
const kysely = await Storages.kysely();
|
||||||
|
|
||||||
|
let sourceLang = (await kysely
|
||||||
|
.selectFrom('nostr_events')
|
||||||
|
.select('language').where('id', '=', id)
|
||||||
|
.limit(1)
|
||||||
|
.execute())[0]?.language as LanguageCode | undefined;
|
||||||
|
if (!sourceLang) {
|
||||||
|
sourceLang = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetLang.toLowerCase() === sourceLang?.toLowerCase()) {
|
||||||
|
return c.json({ error: 'Source and target languages are the same. No translation needed.' }, 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
const status = await renderStatus(event, { viewerPubkey });
|
||||||
|
|
||||||
|
const translatedId = `${target_language}-${id}` as dittoTranslationsKey;
|
||||||
|
const translationCache = dittoTranslations.get(translatedId);
|
||||||
|
|
||||||
|
if (translationCache) {
|
||||||
|
return c.json(translationCache.data, 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
const mediaAttachments = status?.media_attachments.map((value) => {
|
||||||
|
return {
|
||||||
|
id: value.id,
|
||||||
|
description: value.description ?? '',
|
||||||
|
};
|
||||||
|
}) ?? [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const translation = await translator.translate(
|
||||||
|
status?.content ?? '',
|
||||||
|
status?.spoiler_text ?? '',
|
||||||
|
mediaAttachments,
|
||||||
|
null,
|
||||||
|
sourceLang,
|
||||||
|
targetLang,
|
||||||
|
{ signal },
|
||||||
|
);
|
||||||
|
dittoTranslations.set(translatedId, translation);
|
||||||
|
return c.json(translation.data, 200);
|
||||||
|
} catch (_) {
|
||||||
|
return c.json({ error: 'Service Unavailable' }, 503);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export { translateController };
|
||||||
Loading…
Add table
Reference in a new issue