import { FC, useState, useCallback, useRef } from "react"; import { Box, Button, VStack, HStack, Text, Input, Alert, AlertIcon, Select, Badge, Icon, useColorModeValue } from "@chakra-ui/react"; import { FiUpload, FiDownload, FiFile } from "react-icons/fi"; import { languageList, LangCode } from "lingva-scraper"; import dynamic from "next/dynamic"; import type { ColumnSelection, SheetColumnInfo } from "./ColumnSelector"; const ColumnSelector = dynamic(() => import("./ColumnSelector"), { ssr: false }); const SUPPORTED_TYPES = [".pdf", ".docx", ".xlsx", ".xls", ".csv"]; const TABULAR_TYPES = [".xlsx", ".xls", ".csv"]; type Stage = "idle" | "columns" | "translating" | "done" | "error"; const DocumentTranslator: FC = () => { const [file, setFile] = useState(null); const [target, setTarget] = useState("en"); const [stage, setStage] = useState("idle"); const [progress, setProgress] = useState(0); const [errorMsg, setErrorMsg] = useState(""); const [sheetColumns, setSheetColumns] = useState([]); const [columnSelections, setColumnSelections] = useState([]); const [downloadUrl, setDownloadUrl] = useState(null); const [downloadName, setDownloadName] = useState(""); const fileInputRef = useRef(null); const borderColor = useColorModeValue("gray.200", "gray.600"); const dropBg = useColorModeValue("gray.50", "gray.700"); const isTabular = file ? TABULAR_TYPES.some(e => file.name.toLowerCase().endsWith(e)) : false; const handleFile = useCallback(async (f: File) => { setFile(f); setStage("idle"); setErrorMsg(""); setDownloadUrl(null); setSheetColumns([]); const ext = "." + f.name.split(".").pop()?.toLowerCase(); if (!SUPPORTED_TYPES.includes(ext)) { setErrorMsg(`Unsupported file type. Supported: ${SUPPORTED_TYPES.join(", ")}`); setStage("error"); return; } if (TABULAR_TYPES.includes(ext)) { // Fetch column info const fd = new FormData(); fd.append("file", f); fd.append("action", "getColumns"); try { const res = await fetch("/api/translate/document", { method: "POST", body: fd }); if (res.ok) { const data = await res.json(); setSheetColumns(data.columns ?? []); setStage("columns"); } else { const e = await res.json(); setErrorMsg(e.error ?? "Failed to read columns"); setStage("error"); } } catch { setErrorMsg("Network error reading file columns"); setStage("error"); } } }, []); const onDrop = useCallback((e: React.DragEvent) => { e.preventDefault(); const f = e.dataTransfer.files[0]; if (f) handleFile(f); }, [handleFile]); const translate = useCallback(async () => { if (!file) return; setStage("translating"); setProgress(10); setErrorMsg(""); const fd = new FormData(); fd.append("file", file); fd.append("targetLanguage", target); fd.append("action", "translate"); if (columnSelections.length > 0) { fd.append("columnSelections", JSON.stringify(columnSelections)); } try { setProgress(30); const res = await fetch("/api/translate/document", { method: "POST", body: fd }); setProgress(90); if (!res.ok) { const e = await res.json().catch(() => ({ error: "Translation failed" })); setErrorMsg(e.error ?? "Translation failed"); setStage("error"); return; } const blob = await res.blob(); const ext = "." + file.name.split(".").pop()!; const outName = file.name.replace(ext, `_${target}${ext}`); const url = URL.createObjectURL(blob); setDownloadUrl(url); setDownloadName(outName); setProgress(100); setStage("done"); } catch (err) { setErrorMsg("Network error during translation"); setStage("error"); } }, [file, target, columnSelections]); const reset = () => { setFile(null); setStage("idle"); setErrorMsg(""); setDownloadUrl(null); setSheetColumns([]); if (fileInputRef.current) fileInputRef.current.value = ""; }; const targetLangs = Object.entries(languageList.target) as [LangCode<"target">, string][]; return ( {/* Drop Zone */} e.preventDefault()} onClick={() => fileInputRef.current?.click()} _hover={{ borderColor: "lingva.400" }} transition="border-color 0.2s" > ) => { const f = e.target.files?.[0]; if (f) handleFile(f); }} /> {file ? ( {file.name} {(file.size / 1024 / 1024).toFixed(2)} MB ) : ( Drop file here or click to upload Supported: PDF, Word (.docx), Excel (.xlsx, .xls), CSV )} {/* Target language + controls */} {file && stage !== "error" && ( )} {/* Column selector for Excel/CSV */} {stage === "columns" && sheetColumns.length > 0 && ( )} {/* Progress */} {stage === "translating" && ( Translating… this may take a moment for large documents. )} {/* Download */} {stage === "done" && downloadUrl && ( Translation complete! )} {/* Error */} {stage === "error" && errorMsg && ( {errorMsg} )} Document translation requires Replicate AI to be enabled in the admin settings.
PDF formatting is best-effort; Excel and Word formatting is fully preserved.
); }; export default DocumentTranslator;