// Google Translate wrappers used when a bot is configured to fan a message // out into multiple languages. Splitting out into its own module keeps the // google-translate API key (env-only) and the per-language plural-request // dance in one place — index.js doesn't need to know about either. // // createTranslator({ languages, apiKey, fetchWithRateLimit, logger }) // returns { buildTranslations(message) }. Callers hand in the same // fetchWithRateLimit that lib/webex.js uses so the whole app has a single // 429 retry policy. export function createTranslator(deps) { var languages = Array.isArray(deps.languages) ? deps.languages : []; var apiKey = deps.apiKey || ''; var fetchWithRateLimit = deps.fetchWithRateLimit; var logger = deps.logger || function () {}; // POST to Google's translate v2 REST endpoint for one (message, target // language, format) triple. Resolves to a normalized record so the // fan-out caller can index by language name. function translateMessage(message, language, format, messageType) { return new Promise(function (resolve, reject) { var params = { q: message, target: language.key, format: format, source: 'en', model: 'nmt', key: apiKey }; var translateUrl = 'https://translation.googleapis.com/language/translate/v2?' + new URLSearchParams(params); fetchWithRateLimit(translateUrl, { method: 'POST' }) .then(response => response.json()) .then(result => { resolve({ name: language.name, key: language.key, format: format, messageType: messageType, translatedText: result.data.translations[0].translatedText }); }) .catch(reject); }); } // Fans `message` out across every configured language in text + // markdown + html variants. Returns an object shaped like: // { english: {text, markdown, html}, "": {...}, ... } // Failed translations are silently dropped (partial success is better // than the whole send blowing up when one language server is down). function buildTranslations(message) { return new Promise(function (resolve) { logger('buildTranslations', 'Started'); var translationPromises = []; var translations = { english: { text: message.text, markdown: message.markdown, html: message.html } }; for (var language of languages) { translationPromises.push(translateMessage(message.text, language, 'text', 'text')); translationPromises.push(translateMessage(message.markdown, language, 'text', 'markdown')); translationPromises.push(translateMessage(message.html, language, 'html', 'html')); } Promise.allSettled(translationPromises).then(function (results) { for (var translation of results) { if (translation.status === 'fulfilled') { var name = translation.value.name; if (!translations[name]) translations[name] = {}; translations[name][translation.value.messageType] = translation.value.translatedText; } } logger('buildTranslations', 'Completed'); resolve(translations); }); }); } return { buildTranslations, translateMessage }; }