1 | const { asyncMapValuesDeep } = require('./util');
|
2 |
|
3 | const _ = require('lodash');
|
4 | const md5 = require('md5');
|
5 | const request = require('request-json');
|
6 |
|
7 | const createRedisClient = require('../util/redis');
|
8 | const AsksuiteUtil = require('../asksuite.util')();
|
9 |
|
10 | const languageMap = {
|
11 | pt_br: 'ptBR',
|
12 | pt: 'ptBR',
|
13 | 'pt-br': 'ptBR',
|
14 | en: 'enUS',
|
15 | es: 'es',
|
16 | };
|
17 |
|
18 | const UGLIFY_TOKEN = '_';
|
19 | const fullVariableRegex = /^[{]{2,3}\s*[\w\.]+\s*[}]{2,3}$/g;
|
20 | const innerNameVariableRegex = /\w+/;
|
21 |
|
22 | module.exports = configuration => {
|
23 | const redis = createRedisClient(configuration.redis);
|
24 |
|
25 | const getTranslation = async (text, languageFrom, languageTo, customId, options) => {
|
26 | if (languageTo === languageFrom) return text;
|
27 |
|
28 | const client = request.createClient(configuration.url);
|
29 |
|
30 | let path = `translate/${languageTo}/${languageFrom}`;
|
31 | if (customId) {
|
32 | path += `/${customId}`;
|
33 | }
|
34 |
|
35 | const optionsKeys = _.keys(options);
|
36 |
|
37 | if (!_.isEmpty(optionsKeys)) {
|
38 | const queryString = _.map(optionsKeys, key => `${key}=${_.get(options, key)}`).join('&');
|
39 | path += `?${queryString}`;
|
40 | }
|
41 |
|
42 | const translateResponse = await client.post(path, [text]);
|
43 | return translateResponse.body[0];
|
44 | };
|
45 |
|
46 | const getCachedTranslation = async (
|
47 | hash,
|
48 | languageFormatIsoFrom,
|
49 | languageFormatIsoTo,
|
50 | languageFormattedTo,
|
51 | customId,
|
52 | ) => {
|
53 | const path = `translations.${languageFormatIsoTo}`;
|
54 |
|
55 | const customTranslation = await redis.getValue(`${customId}:${hash}`);
|
56 | if (
|
57 | customTranslation &&
|
58 | _.get(customTranslation, path) != null &&
|
59 | _.includes(customTranslation.chatTreeIds, customId)
|
60 | ) {
|
61 | return _.get(customTranslation, path);
|
62 | }
|
63 |
|
64 | const globalTranslationByLanguage = await redis.getValue(`${languageFormatIsoFrom}:${hash}`);
|
65 | if (
|
66 | globalTranslationByLanguage &&
|
67 | _.get(globalTranslationByLanguage, path) != null &&
|
68 | _.includes(globalTranslationByLanguage.chatTreeIds, customId)
|
69 | ) {
|
70 | return _.get(globalTranslationByLanguage, path);
|
71 | }
|
72 |
|
73 | const globalTranslation = await redis.getValue(hash);
|
74 | if (
|
75 | globalTranslation &&
|
76 | _.get(globalTranslation, path) != null &&
|
77 | _.includes(globalTranslation.chatTreeIds, customId)
|
78 | ) {
|
79 | return _.get(globalTranslation, path);
|
80 | }
|
81 |
|
82 | const languageProperty = _.get(languageMap, languageFormattedTo);
|
83 | return _.chain([customTranslation, globalTranslationByLanguage, globalTranslation])
|
84 | .compact()
|
85 | .filter(v => v.translations == null)
|
86 | .filter(v => _.includes(v.chatTreeIds, customId))
|
87 | .map(v => _.get(v, languageProperty))
|
88 | .compact()
|
89 | .head()
|
90 | .value();
|
91 | };
|
92 |
|
93 | const normalizeLanguage = (lang, languages) => {
|
94 | if (languages) {
|
95 | const l = AsksuiteUtil.resolveLanguage(lang, languages, 'formatIso');
|
96 | if (l) {
|
97 | return l;
|
98 | }
|
99 | }
|
100 | return lang
|
101 | .toLowerCase()
|
102 | .replace('-', '_')
|
103 | .split('_')[0];
|
104 | };
|
105 |
|
106 | const notNeedsTranslate = (
|
107 | text,
|
108 | attribute,
|
109 | removedAttributes,
|
110 | languageFrom,
|
111 | languageTo,
|
112 | languages,
|
113 | ) => {
|
114 | const regexRemovedAttributes = _.filter(removedAttributes, _.isRegExp);
|
115 | const notNeedsByAllRegexRemovedAttributes = _.some(regexRemovedAttributes, regex =>
|
116 | regex.test(text),
|
117 | );
|
118 |
|
119 | if (notNeedsByAllRegexRemovedAttributes) {
|
120 | return notNeedsByAllRegexRemovedAttributes;
|
121 | }
|
122 |
|
123 | const notNeeds =
|
124 | _.includes(removedAttributes, attribute) || !_.isString(text) || _.isEmpty(text);
|
125 | if (notNeeds) {
|
126 | return notNeeds;
|
127 | }
|
128 |
|
129 | return normalizeLanguage(languageFrom, languages) === normalizeLanguage(languageTo, languages);
|
130 | };
|
131 |
|
132 | const protectMustacheVariablesTranslation = text => {
|
133 | try {
|
134 | const variablesNames = extractVariables(text);
|
135 |
|
136 | for (const name of variablesNames) {
|
137 | text = text.replace(name, variableNameUglify(name));
|
138 | }
|
139 | } catch (e) {
|
140 | console.log(e);
|
141 | }
|
142 |
|
143 | return text;
|
144 | };
|
145 |
|
146 | const variableNameUglify = name => {
|
147 | if (name) {
|
148 | return `${UGLIFY_TOKEN}${name.split('').join(UGLIFY_TOKEN)}`;
|
149 | }
|
150 |
|
151 | return null;
|
152 | };
|
153 |
|
154 | const unprotectMustacheVariablesTranslation = text => {
|
155 | try {
|
156 | const variablesNames = extractVariables(text);
|
157 | for (const name of variablesNames) {
|
158 | text = text.replace(name, variableNamePrettify(name));
|
159 | }
|
160 | } catch (e) {
|
161 | console.log(e);
|
162 | }
|
163 |
|
164 | return text;
|
165 | };
|
166 |
|
167 | const variableNamePrettify = name => {
|
168 | const auxToken = '####';
|
169 | if (name) {
|
170 | name = name.replace(new RegExp(`[${UGLIFY_TOKEN}]{3}`, 'g'), auxToken);
|
171 |
|
172 | return name.replace(new RegExp(`${UGLIFY_TOKEN}`, 'g'), '').replace(auxToken, UGLIFY_TOKEN);
|
173 | }
|
174 |
|
175 | return null;
|
176 | };
|
177 |
|
178 | const extractVariables = text => {
|
179 | const variables = text.match(fullVariableRegex);
|
180 | if (variables && variables.length) {
|
181 | return variables.map(item => item.match(innerNameVariableRegex)[0]);
|
182 | }
|
183 | return [];
|
184 | };
|
185 |
|
186 | const translate = async (
|
187 | text,
|
188 | attribute,
|
189 | removedAttributes,
|
190 | languageFrom,
|
191 | languageTo,
|
192 | customId,
|
193 | options,
|
194 | languages,
|
195 | ) => {
|
196 | if (notNeedsTranslate(text, attribute, removedAttributes, languageFrom, languageTo, languages))
|
197 | return text;
|
198 |
|
199 | const hash = md5(text);
|
200 | const languageFormatIsoTo = normalizeLanguage(languageTo, languages);
|
201 | const languageFormatIsoFrom = normalizeLanguage(languageFrom, languages);
|
202 | const languageFormattedTo = normalizeLanguage(languageTo, null);
|
203 |
|
204 | const cachedTranslation = await getCachedTranslation(
|
205 | hash,
|
206 | languageFormatIsoFrom,
|
207 | languageFormatIsoTo,
|
208 | languageFormattedTo,
|
209 | customId,
|
210 | );
|
211 |
|
212 | if (cachedTranslation != null) {
|
213 | return cachedTranslation || text;
|
214 | }
|
215 |
|
216 | text = protectMustacheVariablesTranslation(text);
|
217 |
|
218 | const translation = await getTranslation(text, languageFrom, languageTo, customId, options);
|
219 |
|
220 | text = unprotectMustacheVariablesTranslation(text);
|
221 |
|
222 | return translation || text;
|
223 | };
|
224 |
|
225 | const translateObject = (
|
226 | object,
|
227 | languageFrom,
|
228 | languageTo,
|
229 | removedAttributes,
|
230 | customId,
|
231 | options,
|
232 | languages,
|
233 | ignoreBranches,
|
234 | ) => {
|
235 | const contextTranslate = (text, attribute) =>
|
236 | translate(
|
237 | text,
|
238 | attribute,
|
239 | removedAttributes,
|
240 | languageFrom,
|
241 | languageTo,
|
242 | customId,
|
243 | options,
|
244 | languages,
|
245 | );
|
246 | return asyncMapValuesDeep(object, contextTranslate, ignoreBranches);
|
247 | };
|
248 |
|
249 | return translateObject;
|
250 | };
|