diff --git a/apps/desktop/src/routes/settings/general.tsx b/apps/desktop/src/routes/settings/general.tsx index 4ba3e3f3c..0a8e43ef1 100644 --- a/apps/desktop/src/routes/settings/general.tsx +++ b/apps/desktop/src/routes/settings/general.tsx @@ -1,4 +1,4 @@ -import { DarkIcon, LightIcon, SystemModeIcon } from "@lume/icons"; +import { CheckIcon, DarkIcon, LightIcon, SystemModeIcon } from "@lume/icons"; import { useStorage } from "@lume/storage"; import * as Switch from "@radix-ui/react-switch"; import { invoke } from "@tauri-apps/api/core"; @@ -14,6 +14,7 @@ import { twMerge } from "tailwind-merge"; export function GeneralSettingScreen() { const storage = useStorage(); + const [apiKey, setAPIKey] = useState(""); const [settings, setSettings] = useState({ lowPower: false, autoupdate: false, @@ -22,6 +23,7 @@ export function GeneralSettingScreen() { media: true, hashtag: true, notification: true, + translation: false, appearance: "system", }); @@ -77,6 +79,17 @@ export function GeneralSettingScreen() { setSettings((prev) => ({ ...prev, notification: !settings.notification })); }; + const toggleTranslation = async () => { + await storage.createSetting("translation", String(+!settings.translation)); + storage.settings.translation = !settings.translation; + // update state + setSettings((prev) => ({ ...prev, translation: !settings.translation })); + }; + + const saveApi = async () => { + await storage.createSetting("translateApiKey", apiKey); + }; + useEffect(() => { async function loadSettings() { const theme = await getCurrent().theme(); @@ -121,6 +134,12 @@ export function GeneralSettingScreen() { ...prev, hashtag: !!parseInt(item.value), })); + + if (item.key === "translation") + setSettings((prev) => ({ + ...prev, + translation: !!parseInt(item.value), + })); } } @@ -223,6 +242,46 @@ export function GeneralSettingScreen() { +
+
+
+ Translation +
+
Translate text to your language
+
+ toggleTranslation()} + className="relative h-7 w-12 cursor-default rounded-full bg-neutral-200 outline-none data-[state=checked]:bg-blue-500 dark:bg-neutral-800" + > + + +
+ {settings.translation ? ( +
+
+ API Key +
+
+ setAPIKey(e.target.value)} + className="w-full border-transparent outline-none focus:outline-none focus:ring-0 focus:border-none h-9 rounded-lg ring-0 placeholder:text-neutral-600 bg-neutral-100 dark:bg-neutral-900" + /> +
+ +
+
+
+ ) : null}
Appearance diff --git a/packages/ark/src/components/note/child.tsx b/packages/ark/src/components/note/child.tsx index 41327a38d..a6daf1178 100644 --- a/packages/ark/src/components/note/child.tsx +++ b/packages/ark/src/components/note/child.tsx @@ -89,7 +89,7 @@ export function NoteChild({ ); } - if (isError) { + if (isError || !data) { return (
diff --git a/packages/ark/src/components/note/content.tsx b/packages/ark/src/components/note/content.tsx index 0f5e0b36f..6b774fc97 100644 --- a/packages/ark/src/components/note/content.tsx +++ b/packages/ark/src/components/note/content.tsx @@ -13,10 +13,10 @@ import { NDKKind } from "@nostr-dev-kit/ndk"; import { fetch } from "@tauri-apps/plugin-http"; import getUrls from "get-urls"; import { nanoid } from "nanoid"; -import { nip19 } from "nostr-tools"; -import { ReactNode, useMemo, useState } from "react"; +import { ReactNode, useEffect, useMemo, useState } from "react"; import { Link } from "react-router-dom"; import reactStringReplace from "react-string-replace"; +import { toast } from "sonner"; import { Hashtag } from "./mentions/hashtag"; import { MentionNote } from "./mentions/note"; import { MentionUser } from "./mentions/user"; @@ -29,17 +29,18 @@ import { useNoteContext } from "./provider"; export function NoteContent({ className, mini = false, - isTranslatable = false, }: { className?: string; mini?: boolean; - isTranslatable?: boolean; }) { const storage = useStorage(); const event = useNoteContext(); const [content, setContent] = useState(event.content); - const [translated, setTranslated] = useState(false); + const [translate, setTranslate] = useState({ + translatable: true, + translated: false, + }); const richContent = useMemo(() => { if (event.kind !== NDKKind.Text) return content; @@ -200,24 +201,31 @@ export function NoteContent({ } }, [content]); - const translate = async () => { + const translateContent = async () => { try { + if (!translate.translatable) return; + const res = await fetch("https://translate.nostr.wine/translate", { method: "POST", body: JSON.stringify({ - q: content, - target: "vi", + q: event.content, + target: storage.locale.slice(0, 2), api_key: storage.settings.translateApiKey, }), headers: { "Content-Type": "application/json" }, }); + if (!res.ok) + toast.error( + "Cannot connect to translate service, please try again later.", + ); + const data = await res.json(); setContent(data.translatedText); - setTranslated(true); + setTranslate((state) => ({ ...state, translated: true })); } catch (e) { - console.error(event.id, String(e)); + console.error("translate api: ", String(e)); } }; @@ -235,14 +243,11 @@ export function NoteContent({ > {richContent}
- {isTranslatable && storage.settings.translation ? ( - translated ? ( + {storage.settings.translation && translate.translatable ? ( + translate.translated ? (