diff --git a/src/components/biz/editor/table.tsx b/src/components/biz/editor/table.tsx index 90a9bea..8813792 100644 --- a/src/components/biz/editor/table.tsx +++ b/src/components/biz/editor/table.tsx @@ -3,7 +3,7 @@ import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMe import { Input } from "@/components/ui/input"; import { Checkbox } from "@/components/ui/checkbox"; 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 type React from "react"; import { TableVirtuoso, type TableVirtuosoHandle } from "react-virtuoso"; @@ -145,6 +145,46 @@ export default function EditorTable({ 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 = {}; + 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 ( <> @@ -236,6 +276,10 @@ export default function EditorTable({ 复制所有语言值 + { void handlePasteAllValues(); }}> + + 粘贴语言值 + { diff --git a/src/hooks/biz/use-translation-inline-edit.ts b/src/hooks/biz/use-translation-inline-edit.ts index d8b67a0..eb09c8d 100644 --- a/src/hooks/biz/use-translation-inline-edit.ts +++ b/src/hooks/biz/use-translation-inline-edit.ts @@ -45,6 +45,31 @@ export function useTranslationInlineEdit(opts: { return valuesByLang[lang]?.[path] ?? ""; } + async function updateValuesForPath(path: string, values: Record) { + 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() { if (!projectId || !editingKey) return; if (savingKey === editingKey) return; @@ -87,6 +112,7 @@ export function useTranslationInlineEdit(opts: { startEdit, cancelEdit, setEditingValue, + updateValuesForPath, saveEdit, handleKeyDown, } as const;