UNPKG

asksuite-core

Version:
294 lines (245 loc) 7.66 kB
const { asyncMapValuesDeep, removePreventTranslationMarkers } = require('./util'); const _ = require('lodash'); const md5 = require('md5'); const request = require('request-json'); const createRedisClient = require('../util/redis'); const AsksuiteUtil = require('../asksuite.util')(); const { URL } = require('url'); const languageMap = { pt_br: 'ptBR', pt: 'ptBR', 'pt-br': 'ptBR', en: 'enUS', es: 'es', }; module.exports = (configuration) => { const redis = createRedisClient(configuration.redis); const client = request.createClient(configuration.url); const getTranslation = async (text, languageFrom, languageTo, customId, options) => { if (languageTo === languageFrom) return text; let path = `translate/${languageTo}/${languageFrom}`; if (customId) { path += `/${customId}`; } const optionsKeys = _.keys(options); if (!_.isEmpty(optionsKeys)) { const queryString = _.map(optionsKeys, (key) => `${key}=${_.get(options, key)}`).join('&'); path += `?${queryString}`; } const translateResponse = await executeRequestAndRetry(client, path, [text], 1, 5); return translateResponse.body[0]; }; const executeRequestAndRetry = (client, path, body, attempt, maxAttempts) => { return client.post(path, body).catch(async (error) => { if (attempt > 1) { console.log('Waiting to retry: ', attempt); await sleep(3000); } if (attempt <= maxAttempts) { console.log('retry translate', { path, body }, error); return executeRequestAndRetry(client, path, body, ++attempt, maxAttempts); } console.log('[translate-object.executeRequestAndRetry]Error: ', error); throw error; }); }; const sleep = async (ms) => { return new Promise(resolve => setTimeout(resolve, ms)); } const getCachedTranslationFromRedis = async (key, redisCache) => { if (redisCache && redisCache[key]) { return redisCache[key]; } const cached = await redis.getValue(key); if (redisCache) { redisCache[key] = cached; } return cached; }; const getCachedTranslation = async ( hash, languageFormatIsoFrom, languageFormatIsoTo, languageFormattedTo, customId, options, redisCache, ) => { const path = `translations.${languageFormatIsoTo}`; const customTranslation = await getCachedTranslationFromRedis( `${customId}:${hash}`, redisCache, ); if (shouldOverWrite(customTranslation, languageFormatIsoTo)) return null; if ( customTranslation && _.get(customTranslation, path) != null && _.includes(customTranslation.chatTreeIds, customId) ) { return _.get(customTranslation, path); } const globalTranslationByLanguage = await getCachedTranslationFromRedis( `${languageFormatIsoFrom}:${hash}`, redisCache, ); if ( shouldOverWrite( globalTranslationByLanguage, languageFormatIsoTo, ) ) return null; if ( globalTranslationByLanguage && _.get(globalTranslationByLanguage, path) != null && _.includes(globalTranslationByLanguage.chatTreeIds, customId) ) { return _.get(globalTranslationByLanguage, path); } const globalTranslation = await getCachedTranslationFromRedis(hash, redisCache); if (shouldOverWrite(globalTranslation, languageFormatIsoTo)) return null; if ( globalTranslation && _.get(globalTranslation, path) != null && _.includes(globalTranslation.chatTreeIds, customId) ) { return _.get(globalTranslation, path); } const languageProperty = _.get(languageMap, languageFormattedTo); return _.chain([customTranslation, globalTranslationByLanguage, globalTranslation]) .compact() .filter((v) => v.translations == null) // old translations .filter((v) => _.includes(v.chatTreeIds, customId)) // vinculateds .map((v) => _.get(v, languageProperty)) .compact() .head() .value(); }; const normalizeLanguage = (lang, languages) => { if (languages) { const l = AsksuiteUtil.resolveLanguage(lang, languages, 'formatIso'); if (l) { return l; } } return lang.toLowerCase().replace('-', '_').split('_')[0]; }; const stringIsAValidUrl = (text) => { try { const textWithoutMarkers = removePreventTranslationMarkers(text); return new URL(textWithoutMarkers) && textWithoutMarkers.startsWith('http'); } catch (err) { return false; } }; const stringIsAValidUuid = (text) => { return /^[0-9a-f]{8}-[0-9a-f]{4}-[4][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(text); }; const notNeedsTranslate = ( text, attribute, removedAttributes, languageFrom, languageTo, languages, ) => { const regexRemovedAttributes = _.filter(removedAttributes, _.isRegExp); const notNeedsByAllRegexRemovedAttributes = _.some(regexRemovedAttributes, (regex) => new RegExp(regex).test(text), ); if (notNeedsByAllRegexRemovedAttributes) { return notNeedsByAllRegexRemovedAttributes; } const notNeeds = _.includes(removedAttributes, attribute) || !_.isString(text) || _.isEmpty(text); if (notNeeds) { return notNeeds; } if (stringIsAValidUrl(text) || stringIsAValidUuid(text)) { return true; } return normalizeLanguage(languageFrom, languages) === normalizeLanguage(languageTo, languages); }; const translate = async ( text, attribute, removedAttributes, languageFrom, languageTo, customId, options, languages, redisCache, ) => { if (notNeedsTranslate(text, attribute, removedAttributes, languageFrom, languageTo, languages)) return removePreventTranslationMarkers(text); const hash = md5(text); const languageFormatIsoTo = normalizeLanguage(languageTo, languages); const languageFormatIsoFrom = normalizeLanguage(languageFrom, languages); const languageFormattedTo = normalizeLanguage(languageTo, null); const cachedTranslation = await getCachedTranslation( hash, languageFormatIsoFrom, languageFormatIsoTo, languageFormattedTo, customId, options, redisCache, ); if (cachedTranslation != null) { return removePreventTranslationMarkers(cachedTranslation || text); } const translation = await getTranslation(text, languageFrom, languageTo, customId, options); return removePreventTranslationMarkers(translation || text); }; const translateObject = ( object, languageFrom, languageTo, removedAttributes, customId, options, languages, ignoreBranches, ignorePaths, redisCache, ) => { const contextTranslate = (text, attribute) => translate( text, attribute, removedAttributes, languageFrom, languageTo, customId, options, languages, redisCache, ); const translationDelay = 0; const translationConcurrency = 10; return asyncMapValuesDeep( object, contextTranslate, ignoreBranches, ignorePaths, translationDelay, translationConcurrency, ); }; const shouldOverWrite = (translation, IsoLanguage) => { if (translation) { if ( !translation?.extraInfo || !translation?.extraInfo?.[IsoLanguage] || translation?.extraInfo?.[IsoLanguage]?.provider === 'google_translate' ) { return true; } } return false; }; return translateObject; };