diff --git a/packages/app/src/Element/Event/NoteContextMenu.tsx b/packages/app/src/Element/Event/NoteContextMenu.tsx index be908fa4..8dbf124c 100644 --- a/packages/app/src/Element/Event/NoteContextMenu.tsx +++ b/packages/app/src/Element/Event/NoteContextMenu.tsx @@ -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); diff --git a/packages/app/src/Element/Event/NoteInner.tsx b/packages/app/src/Element/Event/NoteInner.tsx index 3311cc57..d21f3112 100644 --- a/packages/app/src/Element/Event/NoteInner.tsx +++ b/packages/app/src/Element/Event/NoteInner.tsx @@ -41,6 +41,7 @@ export function NoteInner(props: NoteProps) { const { pinned, bookmarked } = login; const { publisher, system } = useEventPublisher(); const [translated, setTranslated] = useState(); + 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 ( ); - }, [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 ( <> -

+ { + e.stopPropagation(); + setShowTranslation(s => !s) + }}> -

-
-
{translated.text}
-
+ ); } else if (translated) { return ( -

+

); @@ -298,7 +300,7 @@ export function NoteInner(props: NoteProps) { {options.showContextMenu && ( {}} + react={async () => { }} onTranslated={t => setTranslated(t)} setShowReactions={setShowReactions} /> diff --git a/packages/app/src/External/SnortApi.ts b/packages/app/src/External/SnortApi.ts index 04ad5335..39d517c2 100644 --- a/packages/app/src/External/SnortApi.ts +++ b/packages/app/src/External/SnortApi.ts @@ -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("api/v1/translate", "POST", tx); + return this.#getJson("api/v1/translate", "POST", tx); } async #getJsonAuthd( @@ -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); } diff --git a/packages/app/src/SnortUtils/index.ts b/packages/app/src/SnortUtils/index.ts index 9283940c..c2e50114 100644 --- a/packages/app/src/SnortUtils/index.ts +++ b/packages/app/src/SnortUtils/index.ts @@ -524,11 +524,11 @@ export function getDisplayNameOrPlaceHolder(user: UserMetadata | undefined, pubk export function getCountry() { const tz = Intl.DateTimeFormat().resolvedOptions(); - const info = (TZ as Record>)[tz.timeZone]; - const [,lat, lon] = info[1].split(/[-+]/); + const info = (TZ as Record | 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), }; diff --git a/packages/app/src/index.css b/packages/app/src/index.css index b17028ff..6f7a6376 100644 --- a/packages/app/src/index.css +++ b/packages/app/src/index.css @@ -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;