feat: update translation

This commit is contained in:
reya 2024-01-15 20:01:06 +07:00
parent 3301af5cbb
commit 7744a5e17c
3 changed files with 83 additions and 19 deletions

View File

@ -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 { useStorage } from "@lume/storage";
import * as Switch from "@radix-ui/react-switch"; import * as Switch from "@radix-ui/react-switch";
import { invoke } from "@tauri-apps/api/core"; import { invoke } from "@tauri-apps/api/core";
@ -14,6 +14,7 @@ import { twMerge } from "tailwind-merge";
export function GeneralSettingScreen() { export function GeneralSettingScreen() {
const storage = useStorage(); const storage = useStorage();
const [apiKey, setAPIKey] = useState("");
const [settings, setSettings] = useState({ const [settings, setSettings] = useState({
lowPower: false, lowPower: false,
autoupdate: false, autoupdate: false,
@ -22,6 +23,7 @@ export function GeneralSettingScreen() {
media: true, media: true,
hashtag: true, hashtag: true,
notification: true, notification: true,
translation: false,
appearance: "system", appearance: "system",
}); });
@ -77,6 +79,17 @@ export function GeneralSettingScreen() {
setSettings((prev) => ({ ...prev, notification: !settings.notification })); 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(() => { useEffect(() => {
async function loadSettings() { async function loadSettings() {
const theme = await getCurrent().theme(); const theme = await getCurrent().theme();
@ -121,6 +134,12 @@ export function GeneralSettingScreen() {
...prev, ...prev,
hashtag: !!parseInt(item.value), hashtag: !!parseInt(item.value),
})); }));
if (item.key === "translation")
setSettings((prev) => ({
...prev,
translation: !!parseInt(item.value),
}));
} }
} }
@ -223,6 +242,46 @@ export function GeneralSettingScreen() {
<Switch.Thumb className="block h-6 w-6 translate-x-0.5 rounded-full bg-white transition-transform duration-100 will-change-transform data-[state=checked]:translate-x-[19px]" /> <Switch.Thumb className="block h-6 w-6 translate-x-0.5 rounded-full bg-white transition-transform duration-100 will-change-transform data-[state=checked]:translate-x-[19px]" />
</Switch.Root> </Switch.Root>
</div> </div>
<div className="flex w-full items-center justify-between">
<div className="flex items-center gap-8">
<div className="w-24 shrink-0 text-end text-sm font-semibold">
Translation
</div>
<div className="text-sm">Translate text to your language</div>
</div>
<Switch.Root
checked={settings.translation}
onClick={() => 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"
>
<Switch.Thumb className="block h-6 w-6 translate-x-0.5 rounded-full bg-white transition-transform duration-100 will-change-transform data-[state=checked]:translate-x-[19px]" />
</Switch.Root>
</div>
{settings.translation ? (
<div className="flex w-full items-center gap-8">
<div className="w-24 shrink-0 text-end text-sm font-semibold">
API Key
</div>
<div className="relative w-full">
<input
type="password"
spellCheck={false}
value={apiKey}
onChange={(e) => 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"
/>
<div className="h-9 absolute right-0 top-0 inline-flex items-center justify-center">
<button
type="button"
onClick={saveApi}
className="mr-1 h-7 w-16 text-sm font-medium shrink-0 inline-flex items-center justify-center rounded-md bg-neutral-200 dark:bg-neutral-800 hover:bg-neutral-300 dark:hover:bg-neutral-700"
>
Save
</button>
</div>
</div>
</div>
) : null}
<div className="flex w-full items-start gap-8"> <div className="flex w-full items-start gap-8">
<div className="w-24 shrink-0 text-end text-sm font-semibold"> <div className="w-24 shrink-0 text-end text-sm font-semibold">
Appearance Appearance

View File

@ -89,7 +89,7 @@ export function NoteChild({
); );
} }
if (isError) { if (isError || !data) {
return ( return (
<div className="relative flex gap-3"> <div className="relative flex gap-3">
<div className="relative flex-1 rounded-md bg-neutral-200 px-2 py-2 dark:bg-neutral-800"> <div className="relative flex-1 rounded-md bg-neutral-200 px-2 py-2 dark:bg-neutral-800">

View File

@ -13,10 +13,10 @@ import { NDKKind } from "@nostr-dev-kit/ndk";
import { fetch } from "@tauri-apps/plugin-http"; import { fetch } from "@tauri-apps/plugin-http";
import getUrls from "get-urls"; import getUrls from "get-urls";
import { nanoid } from "nanoid"; import { nanoid } from "nanoid";
import { nip19 } from "nostr-tools"; import { ReactNode, useEffect, useMemo, useState } from "react";
import { ReactNode, useMemo, useState } from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import reactStringReplace from "react-string-replace"; import reactStringReplace from "react-string-replace";
import { toast } from "sonner";
import { Hashtag } from "./mentions/hashtag"; import { Hashtag } from "./mentions/hashtag";
import { MentionNote } from "./mentions/note"; import { MentionNote } from "./mentions/note";
import { MentionUser } from "./mentions/user"; import { MentionUser } from "./mentions/user";
@ -29,17 +29,18 @@ import { useNoteContext } from "./provider";
export function NoteContent({ export function NoteContent({
className, className,
mini = false, mini = false,
isTranslatable = false,
}: { }: {
className?: string; className?: string;
mini?: boolean; mini?: boolean;
isTranslatable?: boolean;
}) { }) {
const storage = useStorage(); const storage = useStorage();
const event = useNoteContext(); const event = useNoteContext();
const [content, setContent] = useState(event.content); const [content, setContent] = useState(event.content);
const [translated, setTranslated] = useState(false); const [translate, setTranslate] = useState({
translatable: true,
translated: false,
});
const richContent = useMemo(() => { const richContent = useMemo(() => {
if (event.kind !== NDKKind.Text) return content; if (event.kind !== NDKKind.Text) return content;
@ -200,24 +201,31 @@ export function NoteContent({
} }
}, [content]); }, [content]);
const translate = async () => { const translateContent = async () => {
try { try {
if (!translate.translatable) return;
const res = await fetch("https://translate.nostr.wine/translate", { const res = await fetch("https://translate.nostr.wine/translate", {
method: "POST", method: "POST",
body: JSON.stringify({ body: JSON.stringify({
q: content, q: event.content,
target: "vi", target: storage.locale.slice(0, 2),
api_key: storage.settings.translateApiKey, api_key: storage.settings.translateApiKey,
}), }),
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
}); });
if (!res.ok)
toast.error(
"Cannot connect to translate service, please try again later.",
);
const data = await res.json(); const data = await res.json();
setContent(data.translatedText); setContent(data.translatedText);
setTranslated(true); setTranslate((state) => ({ ...state, translated: true }));
} catch (e) { } catch (e) {
console.error(event.id, String(e)); console.error("translate api: ", String(e));
} }
}; };
@ -235,14 +243,11 @@ export function NoteContent({
> >
{richContent} {richContent}
</div> </div>
{isTranslatable && storage.settings.translation ? ( {storage.settings.translation && translate.translatable ? (
translated ? ( translate.translated ? (
<button <button
type="button" type="button"
onClick={() => { onClick={() => setContent(event.content)}
setTranslated(false);
setContent(event.content);
}}
className="mt-3 text-sm text-blue-500 hover:text-blue-600 border-none shadow-none focus:outline-none" className="mt-3 text-sm text-blue-500 hover:text-blue-600 border-none shadow-none focus:outline-none"
> >
Show original content Show original content
@ -250,7 +255,7 @@ export function NoteContent({
) : ( ) : (
<button <button
type="button" type="button"
onClick={translate} onClick={translateContent}
className="mt-3 text-sm text-blue-500 hover:text-blue-600 border-none shadow-none focus:outline-none" className="mt-3 text-sm text-blue-500 hover:text-blue-600 border-none shadow-none focus:outline-none"
> >
Translate to {regionNames.of(storage.locale)} Translate to {regionNames.of(storage.locale)}