Feat: 新增一键读取按钮,快速读取文件内容而不是一个一个再次重新导入
This commit is contained in:
parent
cd4dd441a4
commit
ff1cb22475
|
|
@ -0,0 +1,106 @@
|
||||||
|
import { useState } from "react";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { useFileConnections } from "@/store/file-connection";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { upsertLanguageTranslations } from "@/lib/db";
|
||||||
|
import { flattenValues } from "@/lib/i18n-structure";
|
||||||
|
import { RefreshCw } from "lucide-react";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
projectId: string;
|
||||||
|
onSynced: () => void | Promise<void>;
|
||||||
|
disabled?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
async function ensureReadPermission(handle: FileSystemFileHandle): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
// @ts-expect-error - queryPermission/requestPermission exist in supporting browsers
|
||||||
|
const q = await handle.queryPermission?.({ mode: "read" });
|
||||||
|
if (q === "granted") return true;
|
||||||
|
// @ts-expect-error - requestPermission exist in supporting browsers
|
||||||
|
const r = await handle.requestPermission?.({ mode: "read" });
|
||||||
|
return r === "granted";
|
||||||
|
} catch {
|
||||||
|
try {
|
||||||
|
// Fallback: try to read once; if it throws, we treat as no permission
|
||||||
|
const f = await handle.getFile();
|
||||||
|
// Touch the file object so TS doesn't complain about unused variable
|
||||||
|
if (!f) return false;
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SyncFromFilesButton({ projectId, onSynced, disabled }: Props) {
|
||||||
|
const [syncing, setSyncing] = useState(false);
|
||||||
|
const connSnap = useFileConnections(projectId);
|
||||||
|
|
||||||
|
const hasConnections = Object.keys(connSnap.connections).length > 0;
|
||||||
|
|
||||||
|
async function handleSync() {
|
||||||
|
if (!projectId) return;
|
||||||
|
if (!hasConnections) {
|
||||||
|
toast.info("没有已连接的语言");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setSyncing(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const entries = Object.entries(connSnap.connections);
|
||||||
|
const results = await Promise.allSettled(
|
||||||
|
entries.map(async ([lang, conn]) => {
|
||||||
|
const canRead = await ensureReadPermission(conn.handle);
|
||||||
|
if (!canRead) throw new Error(`${lang}: 无读取权限`);
|
||||||
|
|
||||||
|
const file = await conn.handle.getFile();
|
||||||
|
const text = await file.text();
|
||||||
|
|
||||||
|
let json: unknown;
|
||||||
|
try {
|
||||||
|
json = JSON.parse(text);
|
||||||
|
} catch {
|
||||||
|
throw new Error(`${lang}: JSON 解析失败`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const values = flattenValues(json);
|
||||||
|
await upsertLanguageTranslations(projectId, lang, values);
|
||||||
|
return lang;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const failed: string[] = [];
|
||||||
|
let success = 0;
|
||||||
|
|
||||||
|
for (const r of results) {
|
||||||
|
if (r.status === "fulfilled") success += 1;
|
||||||
|
else failed.push((r.reason as Error)?.message || "未知语言");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failed.length === 0) {
|
||||||
|
toast.success(`同步完成(${success})`);
|
||||||
|
} else if (success === 0) {
|
||||||
|
toast.error(`全部失败(${failed.length}):${failed.join(",")}`);
|
||||||
|
} else {
|
||||||
|
toast.warning(`部分成功(成功 ${success},失败 ${failed.length}):${failed.join(",")}`);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setSyncing(false);
|
||||||
|
await onSynced?.();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={handleSync}
|
||||||
|
disabled={syncing || disabled || !hasConnections}
|
||||||
|
title={!hasConnections ? "暂无已连接的语言" : "读取已连接文件并导入翻译"}
|
||||||
|
>
|
||||||
|
<RefreshCw />
|
||||||
|
{syncing ? "读取中..." : "一键读取"}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -41,6 +41,7 @@ import { Textarea } from "@/components/ui/textarea";
|
||||||
import { clearAllConnections, useFileConnections, writeLanguageToConnectedFile } from "@/store/file-connection";
|
import { clearAllConnections, useFileConnections, writeLanguageToConnectedFile } from "@/store/file-connection";
|
||||||
import { generateLanguageJson } from "@/lib/utils";
|
import { generateLanguageJson } from "@/lib/utils";
|
||||||
import { HeaderConnectionIndicator } from "@/components/biz/header-connection-indicator";
|
import { HeaderConnectionIndicator } from "@/components/biz/header-connection-indicator";
|
||||||
|
import { SyncFromFilesButton } from "@/components/biz/sync-from-files-button";
|
||||||
|
|
||||||
export default function Editor() {
|
export default function Editor() {
|
||||||
const { id: projectId } = useParams();
|
const { id: projectId } = useParams();
|
||||||
|
|
@ -494,6 +495,12 @@ export default function Editor() {
|
||||||
导出
|
导出
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
{projectId && (
|
||||||
|
<SyncFromFilesButton
|
||||||
|
projectId={projectId ?? ""}
|
||||||
|
onSynced={refresh}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{projectId && (
|
{projectId && (
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue