Feat: 粘贴剪贴板中不同语言的翻译值
This commit is contained in:
parent
8335970e2e
commit
d3f78f40d8
|
|
@ -3,7 +3,7 @@ import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMe
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { ArrowBigDownDash, ArrowBigUpDash, Brackets, CaseSensitive, Copy, Filter, Languages, LocateFixed, MoreVertical, PencilLine, Trash2 } from "lucide-react";
|
import { ArrowBigDownDash, ArrowBigUpDash, Brackets, CaseSensitive, ClipboardPaste, Copy, Filter, Languages, LocateFixed, MoreVertical, PencilLine, Trash2 } from "lucide-react";
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import type React from "react";
|
import type React from "react";
|
||||||
import { TableVirtuoso, type TableVirtuosoHandle } from "react-virtuoso";
|
import { TableVirtuoso, type TableVirtuosoHandle } from "react-virtuoso";
|
||||||
|
|
@ -145,6 +145,46 @@ export default function EditorTable({
|
||||||
toast.success("复制成功");
|
toast.success("复制成功");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handlePasteAllValues = async () => {
|
||||||
|
if (!navigator.clipboard?.readText) {
|
||||||
|
toast.error("当前环境不支持读取剪贴板");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let parsed: unknown;
|
||||||
|
try {
|
||||||
|
const text = await navigator.clipboard.readText();
|
||||||
|
parsed = JSON.parse(text);
|
||||||
|
} catch {
|
||||||
|
toast.error("读取或解析剪贴板失败");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
||||||
|
toast.error("剪贴板内容不是有效的语言值 JSON");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const valuesToPaste: Record<string, string> = {};
|
||||||
|
for (const [lang, value] of Object.entries(parsed)) {
|
||||||
|
if (languages.includes(lang) && typeof value === "string") {
|
||||||
|
valuesToPaste[lang] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const pastedCount = await inlineEdit.updateValuesForPath(entry.path, valuesToPaste);
|
||||||
|
if (pastedCount === 0) {
|
||||||
|
toast.error("剪贴板中没有可粘贴的语言值");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.success(`已粘贴 ${pastedCount} 种语言的值`);
|
||||||
|
} catch {
|
||||||
|
toast.error("粘贴语言值失败");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<td className="sticky left-0 px-3 py-2 bg-white">
|
<td className="sticky left-0 px-3 py-2 bg-white">
|
||||||
|
|
@ -236,6 +276,10 @@ export default function EditorTable({
|
||||||
<Copy />
|
<Copy />
|
||||||
复制所有语言值
|
复制所有语言值
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem onClick={() => { void handlePasteAllValues(); }}>
|
||||||
|
<ClipboardPaste />
|
||||||
|
粘贴语言值
|
||||||
|
</DropdownMenuItem>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onSelect={async () => {
|
onSelect={async () => {
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,31 @@ export function useTranslationInlineEdit(opts: {
|
||||||
return valuesByLang[lang]?.[path] ?? "";
|
return valuesByLang[lang]?.[path] ?? "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function updateValuesForPath(path: string, values: Record<string, string>) {
|
||||||
|
if (!projectId) return 0;
|
||||||
|
const entries = Object.entries(values);
|
||||||
|
if (entries.length === 0) return 0;
|
||||||
|
|
||||||
|
const updates: ValuesByLang = {};
|
||||||
|
|
||||||
|
try {
|
||||||
|
await Promise.all(
|
||||||
|
entries.map(async ([lang, value]) => {
|
||||||
|
const prev = valuesByLang[lang] ?? {};
|
||||||
|
const next = { ...prev, [path]: value };
|
||||||
|
updates[lang] = next;
|
||||||
|
await upsertLanguageTranslations(projectId, lang, next);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
setValuesByLang((old) => ({ ...old, ...updates }));
|
||||||
|
cancelEdit();
|
||||||
|
return entries.length;
|
||||||
|
} catch (e) {
|
||||||
|
onError?.((e as Error)?.message ?? "保存失败");
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function saveEdit() {
|
async function saveEdit() {
|
||||||
if (!projectId || !editingKey) return;
|
if (!projectId || !editingKey) return;
|
||||||
if (savingKey === editingKey) return;
|
if (savingKey === editingKey) return;
|
||||||
|
|
@ -87,6 +112,7 @@ export function useTranslationInlineEdit(opts: {
|
||||||
startEdit,
|
startEdit,
|
||||||
cancelEdit,
|
cancelEdit,
|
||||||
setEditingValue,
|
setEditingValue,
|
||||||
|
updateValuesForPath,
|
||||||
saveEdit,
|
saveEdit,
|
||||||
handleKeyDown,
|
handleKeyDown,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue