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