mirror of
https://gitlab.com/soapbox-pub/ditto.git
synced 2025-12-06 11:29:46 +00:00
Reorganize translation interfaces/files
This commit is contained in:
parent
874da1baad
commit
d639d9a14d
8 changed files with 71 additions and 76 deletions
|
|
@ -120,6 +120,7 @@ import { indexController } from '@/controllers/site.ts';
|
||||||
import { manifestController } from '@/controllers/manifest.ts';
|
import { manifestController } from '@/controllers/manifest.ts';
|
||||||
import { nodeInfoController, nodeInfoSchemaController } from '@/controllers/well-known/nodeinfo.ts';
|
import { nodeInfoController, nodeInfoSchemaController } from '@/controllers/well-known/nodeinfo.ts';
|
||||||
import { nostrController } from '@/controllers/well-known/nostr.ts';
|
import { nostrController } from '@/controllers/well-known/nostr.ts';
|
||||||
|
import { DittoTranslator } from '@/interfaces/DittoTranslator.ts';
|
||||||
import { auth98Middleware, requireProof, requireRole } from '@/middleware/auth98Middleware.ts';
|
import { auth98Middleware, requireProof, requireRole } from '@/middleware/auth98Middleware.ts';
|
||||||
import { cspMiddleware } from '@/middleware/cspMiddleware.ts';
|
import { cspMiddleware } from '@/middleware/cspMiddleware.ts';
|
||||||
import { metricsMiddleware } from '@/middleware/metricsMiddleware.ts';
|
import { metricsMiddleware } from '@/middleware/metricsMiddleware.ts';
|
||||||
|
|
@ -129,7 +130,6 @@ 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';
|
import { translatorMiddleware } from '@/middleware/translatorMiddleware.ts';
|
||||||
|
|
||||||
interface AppEnv extends HonoEnv {
|
interface AppEnv extends HonoEnv {
|
||||||
|
|
|
||||||
16
src/caches/translationCache.ts
Normal file
16
src/caches/translationCache.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { LanguageCode } from 'iso-639-1';
|
||||||
|
import { LRUCache } from 'lru-cache';
|
||||||
|
|
||||||
|
import { MastodonTranslation } from '@/entities/MastodonTranslation.ts';
|
||||||
|
import { Time } from '@/utils/time.ts';
|
||||||
|
|
||||||
|
/** Entity returned by DittoTranslator and LRUCache */
|
||||||
|
interface DittoTranslation {
|
||||||
|
data: MastodonTranslation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Translations LRU cache. */
|
||||||
|
export const translationCache = new LRUCache<`${LanguageCode}-${string}`, DittoTranslation>({
|
||||||
|
max: 1000,
|
||||||
|
ttl: Time.hours(6),
|
||||||
|
});
|
||||||
|
|
@ -2,10 +2,11 @@ import { LanguageCode } from 'iso-639-1';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { AppController } from '@/app.ts';
|
import { AppController } from '@/app.ts';
|
||||||
import { localeSchema } from '@/schema.ts';
|
import { translationCache } from '@/caches/translationCache.ts';
|
||||||
import { dittoTranslations, dittoTranslationsKey, MastodonTranslation } from '@/translators/translator.ts';
|
import { MastodonTranslation } from '@/entities/MastodonTranslation.ts';
|
||||||
import { parseBody } from '@/utils/api.ts';
|
|
||||||
import { getEvent } from '@/queries.ts';
|
import { getEvent } from '@/queries.ts';
|
||||||
|
import { localeSchema } from '@/schema.ts';
|
||||||
|
import { parseBody } from '@/utils/api.ts';
|
||||||
import { renderStatus } from '@/views/mastodon/statuses.ts';
|
import { renderStatus } from '@/views/mastodon/statuses.ts';
|
||||||
|
|
||||||
const translateSchema = z.object({
|
const translateSchema = z.object({
|
||||||
|
|
@ -45,11 +46,11 @@ const translateController: AppController = async (c) => {
|
||||||
return c.json({ error: 'Bad request.', schema: result.error }, 400);
|
return c.json({ error: 'Bad request.', schema: result.error }, 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
const translatedId = `${lang}-${id}` as dittoTranslationsKey;
|
const cacheKey: `${LanguageCode}-${string}` = `${lang}-${id}`;
|
||||||
const translationCache = dittoTranslations.get(translatedId);
|
const cached = translationCache.get(cacheKey);
|
||||||
|
|
||||||
if (translationCache) {
|
if (cached) {
|
||||||
return c.json(translationCache.data, 200);
|
return c.json(cached.data, 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
const mediaAttachments = status?.media_attachments.map((value) => {
|
const mediaAttachments = status?.media_attachments.map((value) => {
|
||||||
|
|
@ -68,7 +69,7 @@ const translateController: AppController = async (c) => {
|
||||||
media_attachments: [],
|
media_attachments: [],
|
||||||
poll: null,
|
poll: null,
|
||||||
detected_source_language: event.language ?? 'en',
|
detected_source_language: event.language ?? 'en',
|
||||||
provider: translator.getProvider(),
|
provider: translator.provider,
|
||||||
};
|
};
|
||||||
|
|
||||||
if ((status?.poll as MastodonTranslation['poll'])?.options) {
|
if ((status?.poll as MastodonTranslation['poll'])?.options) {
|
||||||
|
|
@ -130,10 +131,10 @@ const translateController: AppController = async (c) => {
|
||||||
|
|
||||||
mastodonTranslation.detected_source_language = data.source_lang;
|
mastodonTranslation.detected_source_language = data.source_lang;
|
||||||
|
|
||||||
dittoTranslations.set(translatedId, { data: mastodonTranslation });
|
translationCache.set(cacheKey, { data: mastodonTranslation });
|
||||||
return c.json(mastodonTranslation, 200);
|
return c.json(mastodonTranslation, 200);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof Error && e.message?.includes('not supported')) {
|
if (e instanceof Error && e.message.includes('not supported')) {
|
||||||
return c.json({ error: `Translation of source language '${event.language}' not supported` }, 422);
|
return c.json({ error: `Translation of source language '${event.language}' not supported` }, 422);
|
||||||
}
|
}
|
||||||
return c.json({ error: 'Service Unavailable' }, 503);
|
return c.json({ error: 'Service Unavailable' }, 503);
|
||||||
|
|
|
||||||
17
src/entities/MastodonTranslation.ts
Normal file
17
src/entities/MastodonTranslation.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { LanguageCode } from 'iso-639-1';
|
||||||
|
|
||||||
|
/** https://docs.joinmastodon.org/entities/Translation/ */
|
||||||
|
export interface MastodonTranslation {
|
||||||
|
/** HTML-encoded translated content of the status. */
|
||||||
|
content: string;
|
||||||
|
/** The translated spoiler warning of the status. */
|
||||||
|
spoiler_text: string;
|
||||||
|
/** The translated media descriptions of the status. */
|
||||||
|
media_attachments: { id: string; description: string }[];
|
||||||
|
/** The translated poll of the status. */
|
||||||
|
poll: { id: string; options: { title: string }[] } | null;
|
||||||
|
//** The language of the source text, as auto-detected by the machine translation provider. */
|
||||||
|
detected_source_language: LanguageCode;
|
||||||
|
/** The service that provided the machine translation. */
|
||||||
|
provider: string;
|
||||||
|
}
|
||||||
18
src/interfaces/DittoTranslator.ts
Normal file
18
src/interfaces/DittoTranslator.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import type { LanguageCode } from 'iso-639-1';
|
||||||
|
|
||||||
|
/** DittoTranslator class, used for status translation. */
|
||||||
|
export interface DittoTranslator {
|
||||||
|
/** Translate the 'content' into 'targetLanguage'. */
|
||||||
|
translate(
|
||||||
|
/** Texts to translate. */
|
||||||
|
texts: string[],
|
||||||
|
/** The language of the source texts. */
|
||||||
|
sourceLanguage: LanguageCode | undefined,
|
||||||
|
/** The texts will be translated into this language. */
|
||||||
|
targetLanguage: LanguageCode,
|
||||||
|
/** Custom options. */
|
||||||
|
opts?: { signal?: AbortSignal },
|
||||||
|
): Promise<{ results: string[]; source_lang: LanguageCode }>;
|
||||||
|
/** Provider name, eg `DeepL.com` */
|
||||||
|
provider: string;
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { LanguageCode } from 'iso-639-1';
|
import { LanguageCode } from 'iso-639-1';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { DittoTranslator, SourceLanguage, TargetLanguage } from '@/translators/translator.ts';
|
import { DittoTranslator } from '@/interfaces/DittoTranslator.ts';
|
||||||
import { languageSchema } from '@/schema.ts';
|
import { languageSchema } from '@/schema.ts';
|
||||||
|
|
||||||
interface DeepLTranslatorOpts {
|
interface DeepLTranslatorOpts {
|
||||||
|
|
@ -28,8 +28,8 @@ export class DeepLTranslator implements DittoTranslator {
|
||||||
|
|
||||||
async translate(
|
async translate(
|
||||||
texts: string[],
|
texts: string[],
|
||||||
source: SourceLanguage | undefined,
|
source: LanguageCode | undefined,
|
||||||
dest: TargetLanguage,
|
dest: LanguageCode,
|
||||||
opts?: { signal?: AbortSignal },
|
opts?: { signal?: AbortSignal },
|
||||||
) {
|
) {
|
||||||
const { translations } = await this.translateMany(texts, source, dest, opts);
|
const { translations } = await this.translateMany(texts, source, dest, opts);
|
||||||
|
|
@ -43,8 +43,8 @@ export class DeepLTranslator implements DittoTranslator {
|
||||||
/** DeepL translate request. */
|
/** DeepL translate request. */
|
||||||
private async translateMany(
|
private async translateMany(
|
||||||
texts: string[],
|
texts: string[],
|
||||||
source: SourceLanguage | undefined,
|
source: LanguageCode | undefined,
|
||||||
targetLanguage: TargetLanguage,
|
targetLanguage: LanguageCode,
|
||||||
opts?: { signal?: AbortSignal },
|
opts?: { signal?: AbortSignal },
|
||||||
) {
|
) {
|
||||||
const body: any = {
|
const body: any = {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { LanguageCode } from 'iso-639-1';
|
import { LanguageCode } from 'iso-639-1';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { DittoTranslator, SourceLanguage, TargetLanguage } from '@/translators/translator.ts';
|
import { DittoTranslator } from '@/interfaces/DittoTranslator.ts';
|
||||||
import { languageSchema } from '@/schema.ts';
|
import { languageSchema } from '@/schema.ts';
|
||||||
|
|
||||||
interface LibreTranslateTranslatorOpts {
|
interface LibreTranslateTranslatorOpts {
|
||||||
|
|
@ -28,8 +28,8 @@ export class LibreTranslateTranslator implements DittoTranslator {
|
||||||
|
|
||||||
async translate(
|
async translate(
|
||||||
texts: string[],
|
texts: string[],
|
||||||
source: SourceLanguage | undefined,
|
source: LanguageCode | undefined,
|
||||||
dest: TargetLanguage,
|
dest: LanguageCode,
|
||||||
opts?: { signal?: AbortSignal },
|
opts?: { signal?: AbortSignal },
|
||||||
) {
|
) {
|
||||||
const translations = await Promise.all(
|
const translations = await Promise.all(
|
||||||
|
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
import { LanguageCode } from 'iso-639-1';
|
|
||||||
import { LRUCache } from 'lru-cache';
|
|
||||||
|
|
||||||
import { Time } from '@/utils/time.ts';
|
|
||||||
|
|
||||||
/** Original language of the post */
|
|
||||||
export type SourceLanguage = LanguageCode;
|
|
||||||
|
|
||||||
/** Content will be translated to this language */
|
|
||||||
export type TargetLanguage = LanguageCode;
|
|
||||||
|
|
||||||
/** Entity returned by DittoTranslator and LRUCache */
|
|
||||||
type DittoTranslation = {
|
|
||||||
data: MastodonTranslation;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type MastodonTranslation = {
|
|
||||||
/** HTML-encoded translated content of the status. */
|
|
||||||
content: string;
|
|
||||||
/** The translated spoiler warning of the status. */
|
|
||||||
spoiler_text: string;
|
|
||||||
/** The translated media descriptions of the status. */
|
|
||||||
media_attachments: { id: string; description: string }[];
|
|
||||||
/** The translated poll of the status. */
|
|
||||||
poll: { id: string; options: { title: string }[] } | null;
|
|
||||||
//** The language of the source text, as auto-detected by the machine translation provider. */
|
|
||||||
detected_source_language: SourceLanguage;
|
|
||||||
/** The service that provided the machine translation. */
|
|
||||||
provider: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
/** DittoTranslator class, used for status translation. */
|
|
||||||
export interface DittoTranslator {
|
|
||||||
/** Translate the 'content' into 'targetLanguage'. */
|
|
||||||
translate(
|
|
||||||
texts: string[],
|
|
||||||
/** The language of the source text/status. */
|
|
||||||
sourceLanguage: SourceLanguage | undefined,
|
|
||||||
/** The status content will be translated into this language. */
|
|
||||||
targetLanguage: TargetLanguage,
|
|
||||||
/** Custom options. */
|
|
||||||
opts?: { signal?: AbortSignal },
|
|
||||||
): Promise<{ results: string[]; source_lang: SourceLanguage }>;
|
|
||||||
/** Provider name, eg `DeepL.com` */
|
|
||||||
provider: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Includes the TARGET language and the status id.
|
|
||||||
* Example: en-390f5b01b49a8ee6e13fe917420c023d889b3da8e983a14c9e84587e43d12c15
|
|
||||||
* The example above means:
|
|
||||||
* I want the status 390f5b01b49a8ee6e13fe917420c023d889b3da8e983a14c9e84587e43d12c15 translated to english (if it exists in the LRUCache). */
|
|
||||||
export type dittoTranslationsKey = `${TargetLanguage}-${string}`;
|
|
||||||
|
|
||||||
export const dittoTranslations = new LRUCache<dittoTranslationsKey, DittoTranslation>({
|
|
||||||
max: 1000,
|
|
||||||
ttl: Time.hours(6),
|
|
||||||
});
|
|
||||||
Loading…
Add table
Reference in a new issue