'use client'; import { useState, useCallback, useRef } from 'react'; interface TranslationState { translated: string; isLoading: boolean; error: string | null; isTranslated: boolean; } interface TranslationsMap { [key: string]: TranslationState; } /** * Hook for translating text on demand using MyMemory free API * - 1000 translations/day free (anonymous) * - 10000/day with free email registration * - No API key required for basic usage */ export function useTranslation(targetLang: string = 'en') { const [translations, setTranslations] = useState({}); const translationsRef = useRef({}); // Keep ref in sync with state translationsRef.current = translations; const translate = useCallback(async (text: string, id: string, sourceLang: string = 'auto') => { const current = translationsRef.current[id]; // If already translated, toggle back to original if (current?.isTranslated) { setTranslations(prev => ({ ...prev, [id]: { ...prev[id], isTranslated: false } })); return; } // If we have a cached translation, just show it if (current?.translated) { setTranslations(prev => ({ ...prev, [id]: { ...prev[id], isTranslated: true } })); return; } // Start loading setTranslations(prev => ({ ...prev, [id]: { translated: '', isLoading: true, error: null, isTranslated: false } })); try { // Use MyMemory free translation API // Docs: https://mymemory.translated.net/doc/spec.php // Note: 500 character limit per query const truncatedText = text.length > 500 ? text.substring(0, 497) + '...' : text; const langPair = sourceLang === 'auto' ? `autodetect|${targetLang}` : `${sourceLang}|${targetLang}`; const url = `https://api.mymemory.translated.net/get?q=${encodeURIComponent(truncatedText)}&langpair=${langPair}`; const response = await fetch(url); if (!response.ok) { throw new Error('Translation failed'); } const data = await response.json(); // Handle "same language" error - text is already in target language if (data.responseStatus !== 200) { const errorMsg = data.responseDetails || ''; if (errorMsg.includes('DISTINCT LANGUAGES') || errorMsg.includes('same language')) { // Text is already in target language, just show original setTranslations(prev => ({ ...prev, [id]: { translated: text, isLoading: false, error: null, isTranslated: true } })); return; } throw new Error(errorMsg || 'Translation failed'); } const translatedText = data.responseData.translatedText; setTranslations(prev => ({ ...prev, [id]: { translated: translatedText, isLoading: false, error: null, isTranslated: true } })); } catch (error) { console.error('[Translation] Error:', error); setTranslations(prev => ({ ...prev, [id]: { translated: '', isLoading: false, error: error instanceof Error ? error.message : 'Translation failed', isTranslated: false } })); } }, [targetLang]); const getState = useCallback((id: string): TranslationState => { return translations[id] || { translated: '', isLoading: false, error: null, isTranslated: false }; }, [translations]); return { translate, getState }; }