bhashantarh
Version:
Tiny translation wrapper with fallbacks (LibreTranslate -> MyMemory) and detection with franc fallback.
125 lines (101 loc) • 3.28 kB
JavaScript
const axios = require('axios');
const getSupportedLanguages = require('./supportedLanguages');
const { iso3ToIso1 } = require('./supportedLanguages');
const FALLBACK_URL = 'https://libretranslate.de';
// Lazy-load franc-min for CJS
let cachedFranc = null;
async function getFranc() {
if (cachedFranc) return cachedFranc;
try {
const mod = await import('franc-min');
cachedFranc = mod.franc;
return cachedFranc;
} catch (e) {
console.error('Failed to load franc-min:', e.message);
return null;
}
}
// Script-based guess
function guessByScript(text) {
const ranges = {
hi: /[\u0900-\u097F]/,
mr: /[\u0900-\u097F]/,
bn: /[\u0980-\u09FF]/,
gu: /[\u0A80-\u0AFF]/,
ta: /[\u0B80-\u0BFF]/,
te: /[\u0C00-\u0C7F]/,
ml: /[\u0D00-\u0D7F]/,
ar: /[\u0600-\u06FF]/,
ur: /[\u0600-\u06FF]/,
ru: /[\u0400-\u04FF]/,
zh: /[\u4E00-\u9FFF]/,
ja: /[\u3040-\u309F\u30A0-\u30FF]/
};
const priority = ['zh', 'ja', 'ru', 'ar', 'ur', 'bn', 'gu', 'ta', 'te', 'ml', 'hi', 'mr'];
for (const code of priority) {
if (ranges[code]?.test(text)) return code;
}
return null;
}
async function tryLibreDetect(text) {
const res = await axios.post(`${FALLBACK_URL}/detect`, { q: text }, {
headers: { 'Content-Type': 'application/json' },
timeout: 8000
});
if (Array.isArray(res.data) && res.data[0]?.language) {
return res.data[0].language;
}
return 'unknown';
}
function isSupported(code) {
return !!getSupportedLanguages()[code];
}
async function detectLanguage(text) {
const cleaned = (text || '').replace(/[0-9]/g, '').trim();
// 1) Try LibreTranslate.de
try {
const lt = await tryLibreDetect(cleaned);
if (lt && isSupported(lt)) return lt;
} catch (err) {
console.error('LibreTranslate.de detect error:', err.message);
}
// add near the top, below guessByScript()
function guessLatinByCommonWords(text) {
if (!text) return null;
const t = text.toLowerCase();
// Spanish
if (/\bhola\b/.test(t) || /\bamigo(s)?\b/.test(t) || /\b¿?c(ó|o)mo\b/.test(t)) return 'es';
// French
if (/\bbonjour\b/.test(t) || /\bsalut\b/.test(t) || /\bmerci\b/.test(t)) return 'fr';
// Italian
if (/\bciao\b/.test(t) || /\bgrazie\b/.test(t)) return 'it';
// Portuguese
if (/\bol[áa]\b/.test(t) || /\bobrigado(a)?\b/.test(t)) return 'pt';
// German
if (/\bhallo\b/.test(t) || /\bdanke\b/.test(t)) return 'de';
// English
if (/\bhello\b/.test(t) || /\bhi\b/.test(t) || /\bthanks\b/.test(t)) return 'en';
return null;
}
// 2) Script guess
const scriptGuess = guessByScript(cleaned);
if (scriptGuess && isSupported(scriptGuess)) return scriptGuess;
// 2.5) Latin common-words guess (helps short phrases like "Hola amigo")
const latinGuess = guessLatinByCommonWords(cleaned);
if (latinGuess && isSupported(latinGuess)) return latinGuess;
// 3) franc-min
try {
const franc = await getFranc();
if (franc) {
const iso3 = franc(cleaned, { minLength: 1 });
if (iso3 && iso3 !== 'und') {
const iso1 = iso3ToIso1(iso3);
if (iso1 && isSupported(iso1)) return iso1;
}
}
} catch (e) {
console.error('franc detect error:', e.message);
}
return 'unknown';
}
module.exports = detectLanguage;