asksuite-core
Version:
294 lines (245 loc) • 7.66 kB
JavaScript
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;
};