UNPKG

6.52 kBJavaScriptView Raw
1const { asyncMapValuesDeep } = require('./util');
2
3const _ = require('lodash');
4const md5 = require('md5');
5const request = require('request-json');
6
7const createRedisClient = require('../util/redis');
8const AsksuiteUtil = require('../asksuite.util')();
9
10const languageMap = {
11 pt_br: 'ptBR',
12 pt: 'ptBR',
13 'pt-br': 'ptBR',
14 en: 'enUS',
15 es: 'es',
16};
17
18const UGLIFY_TOKEN = '_';
19const fullVariableRegex = /^[{]{2,3}\s*[\w\.]+\s*[}]{2,3}$/g;
20const innerNameVariableRegex = /\w+/;
21
22module.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) // old translations
86 .filter(v => _.includes(v.chatTreeIds, customId)) // vinculateds
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};