feat: auto translate

This commit is contained in:
Kieran 2023-11-06 13:32:02 +00:00
parent e0b68ae817
commit 6e349051a2
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
5 changed files with 49 additions and 27 deletions

View File

@ -1,4 +1,4 @@
import { useState } from "react";
import { useEffect, useState } from "react";
import { FormattedMessage, useIntl } from "react-intl";
import { HexKey, Lists, NostrLink, TaggedNostrEvent } from "@snort/system";
import { Menu, MenuItem } from "@szhsin/react-menu";
@ -59,20 +59,27 @@ export function NoteContextMenu({ ev, ...props }: NosteContextMenuProps) {
async function translate() {
const api = new SnortApi();
const targetLang = lang.split("-")[0].toUpperCase();
const result = await api.translate({
text: [ev.content],
target_lang: lang.split("-")[0].toUpperCase(),
target_lang: targetLang,
});
if (typeof props.onTranslated === "function" && result.translations.length > 0) {
props.onTranslated({
text: result.translations[0].text,
fromLanguage: langNames.of(result.translations[0].detected_source_language),
confidence: 1,
} as NoteTranslation);
if ("translations" in result) {
if (typeof props.onTranslated === "function" && result.translations.length > 0 && targetLang != result.translations[0].detected_source_language) {
props.onTranslated({
text: result.translations[0].text,
fromLanguage: langNames.of(result.translations[0].detected_source_language),
confidence: 1,
} as NoteTranslation);
}
}
}
useEffect(() => {
translate();
}, []);
async function copyId() {
const link = NostrLink.fromEvent(ev).encode(CONFIG.eventLinkPrefix);
await navigator.clipboard.writeText(link);

View File

@ -41,6 +41,7 @@ export function NoteInner(props: NoteProps) {
const { pinned, bookmarked } = login;
const { publisher, system } = useEventPublisher();
const [translated, setTranslated] = useState<NoteTranslation>();
const [showTranslation, setShowTranslation] = useState(true);
const { formatMessage } = useIntl();
const totalReactions = reactions.positive.length + reactions.negative.length + reposts.length + zaps.length;
@ -78,10 +79,11 @@ export function NoteInner(props: NoteProps) {
}
const innerContent = useMemo(() => {
const body = ev?.content ?? "";
const body = translated && showTranslation ? translated.text : ev?.content ?? "";
const id = translated && showTranslation ? `${ev.id}-translated` : ev.id;
return (
<Text
id={ev.id}
id={id}
highlighText={props.searchedValue}
content={body}
tags={ev.tags}
@ -91,7 +93,7 @@ export function NoteInner(props: NoteProps) {
disableMediaSpotlight={!(props.options?.showMediaSpotlight ?? true)}
/>
);
}, [ev, props.searchedValue, props.depth, options.showMedia, props.options?.showMediaSpotlight]);
}, [ev, translated, showTranslation, props.searchedValue, props.depth, options.showMedia, props.options?.showMediaSpotlight]);
const transformBody = () => {
if (deletions?.length > 0) {
@ -172,8 +174,8 @@ export function NoteInner(props: NoteProps) {
const replyTo = thread?.replyTo ?? thread?.root;
const replyLink = replyTo
? NostrLink.fromTag(
[replyTo.key, replyTo.value ?? "", replyTo.relay ?? "", replyTo.marker ?? ""].filter(a => a.length > 0),
)
[replyTo.key, replyTo.value ?? "", replyTo.relay ?? "", replyTo.marker ?? ""].filter(a => a.length > 0),
)
: undefined;
const mentions: { pk: string; name: string; link: ReactNode }[] = [];
for (const pk of thread?.pubKeys ?? []) {
@ -243,17 +245,17 @@ export function NoteInner(props: NoteProps) {
if (translated && translated.confidence > 0.5) {
return (
<>
<p className="highlight">
<span className="text-xs font-semibold text-gray-light select-none" onClick={(e) => {
e.stopPropagation();
setShowTranslation(s => !s)
}}>
<FormattedMessage {...messages.TranslatedFrom} values={{ lang: translated.fromLanguage }} />
</p>
<div className="card text">
<div className="text-frag">{translated.text}</div>
</div>
</span>
</>
);
} else if (translated) {
return (
<p className="highlight">
<p className="text-xs font-semibold text-gray-light">
<FormattedMessage {...messages.TranslationFailed} />
</p>
);
@ -298,7 +300,7 @@ export function NoteInner(props: NoteProps) {
{options.showContextMenu && (
<NoteContextMenu
ev={ev}
react={async () => {}}
react={async () => { }}
onTranslated={t => setTranslated(t)}
setShowReactions={setShowReactions}
/>

View File

@ -1,6 +1,7 @@
import { throwIfOffline } from "@snort/shared";
import { EventKind, EventPublisher } from "@snort/system";
import { ApiHost } from "Const";
import { unwrap } from "SnortUtils";
import { SubscriptionType } from "Subscription";
export interface RevenueToday {
@ -117,7 +118,7 @@ export default class SnortApi {
}
translate(tx: TranslationRequest) {
return this.#getJson<TranslationResponse>("api/v1/translate", "POST", tx);
return this.#getJson<TranslationResponse | object>("api/v1/translate", "POST", tx);
}
async #getJsonAuthd<T>(
@ -160,9 +161,9 @@ export default class SnortApi {
});
if (rsp.ok) {
const text = await rsp.text();
if (text.length > 0) {
const obj = JSON.parse(text);
const text = (await rsp.text()) as string | null;
if ((text?.length ?? 0) > 0) {
const obj = JSON.parse(unwrap(text));
if ("error" in obj) {
throw new SubscriptionError(obj.error, obj.code);
}

View File

@ -524,11 +524,11 @@ export function getDisplayNameOrPlaceHolder(user: UserMetadata | undefined, pubk
export function getCountry() {
const tz = Intl.DateTimeFormat().resolvedOptions();
const info = (TZ as Record<string, Array<string>>)[tz.timeZone];
const [,lat, lon] = info[1].split(/[-+]/);
const info = (TZ as Record<string, Array<string> | undefined>)[tz.timeZone];
const [, lat, lon] = info?.[1].split(/[-+]/) ?? ["", "00", "000"];
return {
zone: tz.timeZone,
country: info[0],
country: info?.[0],
lat: Number(lat) / Math.pow(10, lat.length - 2),
lon: Number(lon) / Math.pow(10, lon.length - 3),
};

View File

@ -716,6 +716,18 @@ div.form-col {
color: var(--repost);
}
.text-gray {
color: var(--gray);
}
.text-gray-medium {
color: var(--gray-medium);
}
.text-gray-light {
color: var(--gray-light);
}
.tweet {
display: flex;
align-items: center;