asksuite-core
Version:
265 lines (220 loc) • 6.75 kB
JavaScript
const { asyncMapValuesDeep } = 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',
};
const UGLIFY_TOKEN = '_';
const fullVariableRegex = /^[{]{2,3}\s*[\w\.]+\s*[}]{2,3}$/g;
const innerNameVariableRegex = /\w+/;
module.exports = configuration => {
const redis = createRedisClient(configuration.redis);
const getTranslation = async (text, languageFrom, languageTo, customId, options) => {
if (languageTo === languageFrom) return text;
const client = request.createClient(configuration.url);
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 client.post(path, [text]);
return translateResponse.body[0];
};
const getCachedTranslation = async (
hash,
languageFormatIsoFrom,
languageFormatIsoTo,
languageFormattedTo,
customId,
) => {
const path = `translations.${languageFormatIsoTo}`;
const customTranslation = await redis.getValue(`${customId}:${hash}`);
if (
customTranslation &&
_.get(customTranslation, path) != null &&
_.includes(customTranslation.chatTreeIds, customId)
) {
return _.get(customTranslation, path);
}
const globalTranslationByLanguage = await redis.getValue(`${languageFormatIsoFrom}:${hash}`);
if (
globalTranslationByLanguage &&
_.get(globalTranslationByLanguage, path) != null &&
_.includes(globalTranslationByLanguage.chatTreeIds, customId)
) {
return _.get(globalTranslationByLanguage, path);
}
const globalTranslation = await redis.getValue(hash);
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 = s => {
try {
new URL(s);
return true;
} catch (err) {
return false;
}
};
const notNeedsTranslate = (
text,
attribute,
removedAttributes,
languageFrom,
languageTo,
languages,
) => {
const regexRemovedAttributes = _.filter(removedAttributes, _.isRegExp);
const notNeedsByAllRegexRemovedAttributes = _.some(regexRemovedAttributes, regex =>
regex.test(text),
);
if (notNeedsByAllRegexRemovedAttributes) {
return notNeedsByAllRegexRemovedAttributes;
}
const notNeeds =
_.includes(removedAttributes, attribute) || !_.isString(text) || _.isEmpty(text);
if (notNeeds) {
return notNeeds;
}
if (stringIsAValidUrl(text)) {
return true;
}
return normalizeLanguage(languageFrom, languages) === normalizeLanguage(languageTo, languages);
};
const protectMustacheVariablesTranslation = text => {
try {
const variablesNames = extractVariables(text);
for (const name of variablesNames) {
text = text.replace(name, variableNameUglify(name));
}
} catch (e) {
console.log(e);
}
return text;
};
const variableNameUglify = name => {
if (name) {
return `${UGLIFY_TOKEN}${name.split('').join(UGLIFY_TOKEN)}`;
}
return null;
};
const unprotectMustacheVariablesTranslation = text => {
try {
const variablesNames = extractVariables(text);
for (const name of variablesNames) {
text = text.replace(name, variableNamePrettify(name));
}
} catch (e) {
console.log(e);
}
return text;
};
const variableNamePrettify = name => {
const auxToken = '####';
if (name) {
name = name.replace(new RegExp(`[${UGLIFY_TOKEN}]{3}`, 'g'), auxToken);
return name.replace(new RegExp(`${UGLIFY_TOKEN}`, 'g'), '').replace(auxToken, UGLIFY_TOKEN);
}
return null;
};
const extractVariables = text => {
const variables = text.match(fullVariableRegex);
if (variables && variables.length) {
return variables.map(item => item.match(innerNameVariableRegex)[0]);
}
return [];
};
const translate = async (
text,
attribute,
removedAttributes,
languageFrom,
languageTo,
customId,
options,
languages,
) => {
if (notNeedsTranslate(text, attribute, removedAttributes, languageFrom, languageTo, languages))
return 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,
);
if (cachedTranslation != null) {
return cachedTranslation || text;
}
text = protectMustacheVariablesTranslation(text);
const translation = await getTranslation(text, languageFrom, languageTo, customId, options);
text = unprotectMustacheVariablesTranslation(translation || text);
return text;
};
const translateObject = (
object,
languageFrom,
languageTo,
removedAttributes,
customId,
options,
languages,
ignoreBranches,
) => {
const contextTranslate = (text, attribute) =>
translate(
text,
attribute,
removedAttributes,
languageFrom,
languageTo,
customId,
options,
languages,
);
return asyncMapValuesDeep(object, contextTranslate, ignoreBranches);
};
return translateObject;
};