Initial upload

This commit is contained in:
Chandu
2026-07-04 19:47:59 -04:00
commit c0e3aace8e
7 changed files with 2311 additions and 0 deletions

437
server.js Normal file
View File

@@ -0,0 +1,437 @@
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
========================================
`);
});