mirror of
https://github.com/luminous-devs/lume.git
synced 2024-09-30 00:41:00 +00:00
feat: update translation
This commit is contained in:
parent
3301af5cbb
commit
7744a5e17c
@ -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
|
||||||
|
@ -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">
|
||||||
|
@ -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)}
|
||||||
|
Loading…
Reference in New Issue
Block a user