-
Notifications
You must be signed in to change notification settings - Fork 399
/
Copy pathexecute-translation.js
243 lines (207 loc) · 7.53 KB
/
execute-translation.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
/**
* Script executes the translation of the localization keys for the provided languages (config/supported.localization.json) with the usage of the Azure Cognitive services.
* In case the language localization file doesn't exist, it is going to be created.
* Only languages supported by Azure Cognitive services are supported.
*
* English is used as a main language. The translation is executed for the keys which are the same as in English or are missing.
*/
const path = require('path');
const fs = require('fs');
const _ = require('lodash');
// Cognitive services
const request = require('request-promise');
const uuidv4 = require('uuid');
// Replace with process.env.subscriptionKey to get an access to Azure Cognitive services
const subscriptionKey = process.env.SUBSCRIPTION_KEY;
const endpoint = "https://api.cognitive.microsofttranslator.com";
const locHelper = require('./export-localization');
// Load configuration for supported languages
const languagesConfiguration = require('../config/supported.localization.json');
/**
* Obtain auth token for the global cognitive services endpoint from region (WestEurope)
*/
let authToken = null;
async function getAuthToken() {
try {
// Cache AuthToken
if (authToken) {
return authToken;
}
const options = {
method: 'POST',
url: 'https://api.cognitive.microsoft.com/sts/v1.0/issueToken',
headers: {
'Ocp-Apim-Subscription-Key': subscriptionKey,
'Content-type': 'application/x-www-form-urlencoded',
'Content-length': 0
}
}
const token = await request(options);
if (!token || token.length < 0) {
throw new Error("Somethig went wrong when obtaining Auth token!");
}
// Cache Auth token
authToken = token;
return token;
}
catch (err) {
console.error(`[Exception]: Cannot obtain Auth token. Err=${err}`)
return null;
}
}
/**
* Function executes the translation using cognitive services.
*/
async function executeTranslation(lang, inputObj) {
try {
const authToken = await getAuthToken();
let options = {
method: 'POST',
baseUrl: endpoint,
url: 'translate',
qs: {
'api-version': '3.0',
'to': [`${lang}`]
},
headers: {
'Authorization': `Bearer ${authToken}`,
'Content-type': 'application/json',
'X-ClientTraceId': uuidv4().toString()
},
body: inputObj,
json: true,
};
const response = await request(options);
if (!response || response.length < 0) {
throw new Error("Somethig went wrong when obtaining translated text!");
}
// Go through all the results
const translations = response.map((item) => {
// Every translation is in a separate 1-element array -> make it flat
return item.translations[0];
})
return translations;
} catch (err) {
console.error(`[Exception]: Cannot execute translation for lang - ${lang}. Err=${err}`)
return null;
}
}
function compareTranslationKeys(srcObj, dstObj) {
// Extract all the keys and set them in alpabetyical order
let dstKeys = Object.keys(dstObj);
let toTranslate = [];
// Array<string>
try {
dstKeys.forEach((locKey) => {
if (typeof srcObj[locKey] !== "string") {
// In case we have nested translation objects
toTranslate = toTranslate.concat(compareTranslationKeys(srcObj[locKey], dstObj[locKey]));
} else if (srcObj[locKey] === dstObj[locKey]) {
// In case the english value is the same as localized one, add it to translate
toTranslate.push(srcObj[locKey]);
}
});
} catch (error) {
console.log(error.message);
}
return toTranslate;
}
let currentTranslationIndex = 0;
function injectTranslatedKeys(srcObj, dstObj, translatedValues) {
const srcKeys = Object.keys(srcObj);
srcKeys.forEach((locKey) => {
if (typeof srcObj[locKey] !== "string") {
dstObj[locKey] = injectTranslatedKeys(srcObj[locKey], dstObj[locKey], translatedValues);
} else if (srcObj[locKey] === dstObj[locKey]) {
const translatedKey = translatedValues[currentTranslationIndex++];
dstObj[locKey] = translatedKey ? translatedKey : dstObj[locKey];
}
});
return dstObj;
}
function prepareTranslationRequestMsg(wordsToTranslate) {
// Execute translation for every 50 words in batch
const result = [];
const chunk = 50;
for (let i = 0; i < wordsToTranslate.length; i += chunk) {
const slicedWords = wordsToTranslate.slice(i, i + chunk);
const slicedMessages = [];
// do whatever
slicedWords.forEach((word) => {
slicedMessages.push({
//'text': encodeURI(word)
'text': word
});
});
result.push(slicedMessages);
}
return result;
}
function extranctTranslatedKeys(translatedMsgs) {
// Flatten the result to retrieve original structure of the keys array
const translatedKeys = [];
translatedMsgs.forEach((keys) => {
keys.forEach((translationMsg) => {
// There is often a replacement of '||' to ' | | ' during the translation
// Replace ' | | ' to '||'
const translationResult = translationMsg.text ? translationMsg.text.replace(" | | ", "||") : translationMsg.text;
translatedKeys.push(translationResult);
})
});
return translatedKeys;
}
async function executeLocalizationTranslation(srcObj, langObj, lang) {
try {
// Initialize result object with all english keys and localized keys
const dstLoc = Object.assign({}, srcObj, langObj);
// Prepare keys to translate
const keysToTranslate = compareTranslationKeys(srcObj, dstLoc);
if (keysToTranslate && keysToTranslate.length <= 0) {
console.log(`There are no keys to translate`);
dstLoc;
}
console.log(`There are ${keysToTranslate.length} keys to translate.`)
// Split the array to separate calls in case max limit of carachters (5000) is reached and execute translation
const requestMessges = prepareTranslationRequestMsg(keysToTranslate);
const promises = [];
requestMessges.forEach((msgBody) => {
promises.push(executeTranslation(lang, msgBody));
});
const translatedMsgs = await Promise.all(promises);
const translatedKeys = extranctTranslatedKeys(translatedMsgs);
// Inject translated keys into dstLoc object
// Reset the global rec counter
currentTranslationIndex = 0;
const result = injectTranslatedKeys(srcObj, dstLoc, translatedKeys);
return result;
} catch (err) {
console.log(`[Exception]: executeLocalizationTranslation : ${err.message}`);
return null;
}
}
const run = async () => {
// Load files in the localization directory
const locDirPath = path.join(__dirname, '../src/loc');
let locFiles = fs.readdirSync(locDirPath);
locFiles = locFiles.filter(f => f !== "mystrings.d.ts" && f != "en-us.ts");
// Load main localization file
const mainLoc = locHelper.getSPLocalizationFileAsJSON('en-us');
// Iterate over all supported languages and prepare translation request
for (const lang of languagesConfiguration.langs) {
console.log(`Processing ${lang}.`);
// If current loc file doesn't exist - copy the original one
let currentLoc = locHelper.getSPLocalizationFileAsJSON(lang);
if (!currentLoc) {
currentLoc = _.cloneDeep(mainLoc);
}
const translatedObj = await executeLocalizationTranslation(mainLoc, currentLoc, lang);
// Replace translated part in .ts file
if (translatedObj) {
locHelper.replaceTranslatedKeysInJSON(translatedObj, lang)
}
// Set delay to wait for Azure to execute the transation
console.log(`Finished processing ${lang}`);
console.log();
}
};
run();