import { useState, useEffect, useRef } from "react"; import { ArrowLeftRight, Copy, Check, Share2, Trash } from "lucide-react"; import { Textarea, Button, Loader } from "./theme"; import cn from "./utils/classnames.ts"; import { type LanguageCode, LANGUAGES } from "./constants"; import LanguageSelector from "./components/LanguageSelector"; import Translator from "./ai/Translator.ts"; import { formatTime, formatNumber } from "./utils/format"; import { countWords } from "./utils/countWords.ts"; const MAX_INPUT_LENGTH = 1000; interface TranslateProps { className?: string; translator: Translator; } export default function Translate({ className = "", translator, }: TranslateProps) { // Initialize from URL hash const getInitialState = () => { const hash = window.location.hash.slice(1); // Remove the # character const params = new URLSearchParams(hash); const sourceLang = params.get("sl"); const targetLang = params.get("tl"); const text = params.get("text"); // Validate language codes const isValidLanguage = (code: string | null): code is LanguageCode => { if (!code) return false; return LANGUAGES.some((lang) => lang.code === code); }; return { sourceLanguage: isValidLanguage(sourceLang) ? sourceLang : ("en" as LanguageCode), targetLanguage: isValidLanguage(targetLang) ? targetLang : ("de_DE" as LanguageCode), sourceText: text ? decodeURIComponent(text) : "", }; }; const initialState = getInitialState(); const [sourceText, setSourceText] = useState(initialState.sourceText); const [targetText, setTargetText] = useState(""); const [sourceLanguage, setSourceLanguage] = useState( initialState.sourceLanguage ); const [targetLanguage, setTargetLanguage] = useState( initialState.targetLanguage ); const [translating, setTranslating] = useState(false); const [copied, setCopied] = useState(false); const [shared, setShared] = useState(false); const abortControllerRef = useRef(null); const [translationTime, setTranslationTime] = useState(0); const [translationWords, setTranslationWords] = useState(0); const handleSwapLanguages = () => { // Swap languages setSourceLanguage(targetLanguage); setTargetLanguage(sourceLanguage); // Swap text content setSourceText(targetText); setTargetText(sourceText); }; const handleCopy = async () => { if (!targetText) return; try { await navigator.clipboard.writeText(targetText); setCopied(true); setTimeout(() => setCopied(false), 2000); } catch (error) { console.error("Failed to copy:", error); } }; const handleShare = async () => { try { const currentUrl = window.location.href; await navigator.clipboard.writeText(currentUrl); setShared(true); setTimeout(() => setShared(false), 2000); } catch (error) { console.error("Failed to share:", error); } }; const translate = async ( text: string, sourceLang: LanguageCode, targetLang: LanguageCode ) => { if (!text.trim()) { setTargetText(""); setTranslationTime(0); setTranslationWords(0); return; } const started = performance.now(); if (abortControllerRef.current) { abortControllerRef.current.abort(); } abortControllerRef.current = new AbortController(); const currentController = abortControllerRef.current; setTranslating(true); try { const translation = await translator.translate( text, sourceLang, targetLang ); if (!currentController.signal.aborted) { setTargetText(translation); setTranslationTime(Math.round(performance.now() - started)); setTranslationWords(countWords(text)); } } catch (error) { if (!currentController.signal.aborted) { console.error("Translation error:", error); } } finally { if (!currentController.signal.aborted) { setTranslating(false); } } }; // Update URL hash when languages or text change useEffect(() => { const params = new URLSearchParams(); params.set("sl", sourceLanguage); params.set("tl", targetLanguage); if (sourceText) { params.set("text", encodeURIComponent(sourceText)); } window.location.hash = `#${params.toString()}`; }, [sourceLanguage, targetLanguage, sourceText]); useEffect(() => { const timer = setTimeout(() => { translate(sourceText, sourceLanguage, targetLanguage); }, 500); return () => { clearTimeout(timer); }; }, [sourceText, sourceLanguage, targetLanguage]); return (