const express = require('express'); const cors = require('cors'); const bodyParser = require('body-parser'); const axios = require('axios'); const path = require('path'); const googleTTS = require('google-tts-api'); require('dotenv').config(); const HUGGINGFACE_API_TOKEN = process.env.HUGGINGFACEHUB_API_TOKEN || process.env.HF_API_TOKEN; const huggingFaceLangMap = { 'en': 'eng', 'es': 'spa', 'fr': 'fra', 'de': 'deu', 'pt': 'por', 'it': 'ita', 'nl': 'nld', 'ru': 'rus', 'ar': 'ara', 'ja': 'jpn', 'ko': 'kor', 'zh': 'zho', 'hi': 'hin', 'ta': 'tam', 'te': 'tel', 'ml': 'mal', 'pa': 'pan', 'kn': 'kan', 'id': 'ind', 'vi': 'vie', 'tr': 'tur' }; const app = express(); const PORT = process.env.PORT || 3000; // Middleware app.use(cors()); app.use(bodyParser.json({ limit: '10mb' })); app.use(bodyParser.urlencoded({ limit: '10mb', extended: true })); app.use(bodyParser.raw({ type: 'audio/*', limit: '10mb' })); app.use(express.static(path.join(__dirname))); // Language code mapping const langMap = { 'en': 'en', 'es': 'es', 'fr': 'fr', 'de': 'de', 'pt': 'pt', 'ja': 'ja', 'ko': 'ko', 'zh': 'zh-CN', 'ta': 'ta', 'te': 'te', 'ml': 'ml', 'hi': 'hi' }; // Translation API endpoint (using free mymemory translation API) const translateViaMyMemory = async (text, sourceLang, targetLang) => { try { const response = await axios.get('https://api.mymemory.translated.net/get', { params: { q: text, langpair: `${sourceLang}|${targetLang}` } }); if (response.data.responseStatus === 200) { return response.data.responseData.translatedText; } throw new Error('Translation failed'); } catch (error) { console.error('MyMemory translation error:', error.message); return null; } }; // Hugging Face translation via M2M100 const translateViaHuggingFace = async (text, sourceLang, targetLang) => { if (!HUGGINGFACE_API_TOKEN) { return null; } const srcCode = huggingFaceLangMap[sourceLang] || 'eng'; const tgtCode = huggingFaceLangMap[targetLang] || 'eng'; try { const response = await axios.post( 'https://api-inference.huggingface.co/models/facebook/m2m100_418M', { inputs: text, parameters: { src_lang: srcCode, tgt_lang: tgtCode } }, { headers: { Authorization: `Bearer ${HUGGINGFACE_API_TOKEN}`, 'Content-Type': 'application/json' }, timeout: 20000 } ); const data = response.data; if (Array.isArray(data) && data[0]?.generated_text) { return data[0].generated_text; } if (data?.generated_text) { return data.generated_text; } if (typeof data === 'string') { return data; } return null; } catch (error) { console.error('Hugging Face translation error:', error.message || error); return null; } }; // Hugging Face text-to-speech const ttsLangMap = { 'en': 'en-US', 'es': 'es-ES', 'fr': 'fr-FR', 'de': 'de-DE', 'pt': 'pt-BR', 'it': 'it-IT', 'nl': 'nl-NL', 'ru': 'ru-RU', 'ar': 'ar-SA', 'ja': 'ja-JP', 'ko': 'ko-KR', 'zh': 'zh-CN', 'hi': 'hi-IN', 'ta': 'ta-IN', 'te': 'te-IN', 'ml': 'ml-IN', 'pa': 'pa-IN', 'kn': 'kn-IN', 'id': 'id-ID', 'vi': 'vi-VN', 'tr': 'tr-TR' }; const speakViaHuggingFace = async (text, targetLang) => { const normalizedLang = (targetLang || 'en').toLowerCase(); if (!HUGGINGFACE_API_TOKEN) { return await speakViaGoogleTts(text, normalizedLang); } const ttsLang = ttsLangMap[normalizedLang] || 'en-US'; const preferredModels = []; if (['ta', 'te', 'ml', 'kn', 'pa', 'hi'].includes(normalizedLang)) { const languageModelMap = { 'ta': 'facebook/mms-tts-ta', 'te': 'facebook/mms-tts-te', 'ml': 'facebook/mms-tts-ml', 'kn': 'facebook/mms-tts-kn', 'pa': 'facebook/mms-tts-pa', 'hi': 'facebook/mms-tts-hi' }; preferredModels.push(languageModelMap[normalizedLang]); } preferredModels.push( 'microsoft/speecht5_tts', 'tts_models/multilingual/multi-dataset/your_tts' ); for (const model of preferredModels) { try { const payload = model.includes('mms-tts') ? { inputs: text } : model.includes('speecht5') ? { inputs: text, parameters: { language: ttsLang, voice: 'alloy' } } : { inputs: text, parameters: { speaker: 'alloy', language: ttsLang } }; const response = await axios.post( `https://api-inference.huggingface.co/models/${model}`, payload, { headers: { Authorization: `Bearer ${HUGGINGFACE_API_TOKEN}`, 'Content-Type': 'application/json', Accept: 'audio/*' }, responseType: 'arraybuffer', timeout: 60000 } ); const contentType = response.headers['content-type'] || 'audio/wav'; if (response.data && contentType.includes('audio')) { return { audio: Buffer.from(response.data).toString('base64'), format: contentType.includes('mpeg') ? 'audio/mpeg' : 'audio/wav' }; } } catch (error) { console.warn(`Hugging Face TTS model ${model} failed:`, error.message || error); } } return await speakViaGoogleTts(text, normalizedLang); }; const speakViaGoogleTts = async (text, targetLang) => { try { const url = googleTTS.getAudioUrl(text, { lang: targetLang, slow: false, host: 'https://translate.google.com', splitPunctuation: '.?' }); const response = await axios.get(url, { responseType: 'arraybuffer', headers: { 'User-Agent': 'Mozilla/5.0 (compatible; GoogleTTS/1.0)' }, timeout: 20000 }); if (response.data) { return { audio: Buffer.from(response.data).toString('base64'), format: 'audio/mpeg' }; } } catch (error) { console.warn('Google TTS fallback failed:', error.message || error); } return null; }; // Alternative: Using LibreTranslate if available locally const translateViaLibreTranslate = async (text, sourceLang, targetLang) => { try { const response = await axios.post('http://localhost:5000/translate', { q: text, source: sourceLang, target: targetLang }, { timeout: 5000 }); return response.data.translatedText; } catch (error) { console.warn('LibreTranslate not available, trying MyMemory...'); return null; } }; // Speech-to-text endpoint using Hugging Face Whisper app.post('/api/transcribe', async (req, res) => { try { const audioBuffer = req.body; if (!audioBuffer || audioBuffer.length === 0) { return res.status(400).json({ error: 'No audio provided' }); } if (!HUGGINGFACE_API_TOKEN) { return res.status(500).json({ error: 'Hugging Face token not configured' }); } const contentType = req.headers['content-type'] || 'application/octet-stream'; const response = await axios.post( 'https://api-inference.huggingface.co/models/openai/whisper-large-v3', audioBuffer, { headers: { Authorization: `Bearer ${HUGGINGFACE_API_TOKEN}`, 'Content-Type': contentType }, timeout: 60000 } ); const data = response.data; const transcript = Array.isArray(data) ? data[0]?.text : data?.text || ''; res.json({ transcript }); } catch (error) { console.error('Speech-to-text error:', error.message || error); res.status(500).json({ error: 'Speech-to-text failed' }); } }); // Translation endpoint app.post('/api/translate', async (req, res) => { try { const { text, sourceLang, targetLang } = req.body; // Validate input if (!text || !sourceLang || !targetLang) { return res.status(400).json({ error: 'Missing required fields' }); } if (text.length > 5000) { return res.status(400).json({ error: 'Text exceeds 5000 character limit' }); } // If source and target languages are the same if (sourceLang === targetLang) { return res.json({ translatedText: text }); } console.log(`Translating "${text.substring(0, 50)}..." from ${sourceLang} to ${targetLang}`); // Try Hugging Face first if the key is configured let translatedText = await translateViaHuggingFace(text, sourceLang, targetLang); // Fallback to LibreTranslate if available locally if (!translatedText) { translatedText = await translateViaLibreTranslate(text, sourceLang, targetLang); } // Fallback to MyMemory if other services are unavailable if (!translatedText) { translatedText = await translateViaMyMemory(text, sourceLang, targetLang); } if (!translatedText) { return res.status(500).json({ error: 'Translation service unavailable', translatedText: text // Fallback to original text }); } res.json({ translatedText }); } catch (error) { console.error('Translation error:', error); res.status(500).json({ error: 'Internal server error' }); } }); app.post('/api/speak', async (req, res) => { try { const { text, targetLang } = req.body; if (!text || !targetLang) { return res.status(400).json({ error: 'Missing required fields' }); } const speechResult = await speakViaHuggingFace(text, targetLang); if (!speechResult?.audio) { return res.status(500).json({ error: 'Speech service unavailable' }); } res.json({ audio: speechResult.audio, format: speechResult.format || 'audio/wav' }); } catch (error) { console.error('Speech endpoint error:', error); res.status(500).json({ error: 'Internal server error' }); } }); // Health check endpoint app.get('/api/health', (req, res) => { res.json({ status: 'healthy', message: 'Translator API is running' }); }); // Get supported languages app.get('/api/languages', (req, res) => { const languages = { 'en': 'English', 'es': 'Spanish', 'fr': 'French', 'de': 'German', 'pt': 'Portuguese', 'it': 'Italian', 'nl': 'Dutch', 'ru': 'Russian', 'ar': 'Arabic', 'ja': 'Japanese', 'ko': 'Korean', 'zh': 'Chinese (Simplified)', 'hi': 'Hindi', 'ta': 'Tamil', 'te': 'Telugu', 'ml': 'Malayalam', 'pa': 'Punjabi', 'kn': 'Kannada', 'id': 'Indonesian', 'vi': 'Vietnamese', 'tr': 'Turkish' }; res.json(languages); }); // Main app page app.get('/', (req, res) => { res.sendFile(path.join(__dirname, 'index.html')); }); // 404 handler app.use((req, res) => { res.status(404).json({ error: 'Not Found' }); }); // Error handler app.use((err, req, res, next) => { console.error('Server error:', err); res.status(500).json({ error: 'Internal Server Error' }); }); // Start server app.listen(PORT, () => { console.log(` ======================================== 🌍 Language Translator Server ======================================== Server running on: http://localhost:${PORT} Available endpoints: - POST /api/translate - Translate text - GET /api/languages - Get supported languages - GET /api/health - Health check ======================================== `); });