-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.js
More file actions
297 lines (241 loc) · 10.2 KB
/
index.js
File metadata and controls
297 lines (241 loc) · 10.2 KB
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
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
const fs = require('fs');
const path = require('path');
const Promise = require('bluebird');
const glob = require("glob")
const myJsonAbc = require("jsonabc");
const translate = require('translate');
translate.engine = 'google';
const loadData = async (filePath) => {
try {
return fs.readFileSync(filePath, 'utf8')
} catch (err) {
return false
}
}
const storeData = async (data, path) => {
try {
await fs.writeFileSync(path, JSON.stringify(data, null, 4));
return true;
} catch (err) {
console.error(err)
}
}
async function asyncForEach(array, callback) {
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array);
}
}
const getFilesFromSource = async (sourceArray) => {
const filesArray = []
for (let i = 0; i < sourceArray.length; i++) {
const files = glob.sync(sourceArray[i], {})
filesArray.push(files)
}
const filesArrayFlat = [].concat.apply([], filesArray);
return filesArrayFlat;
}
const getFilesStrings = async (filesArray) => {
const filesWithStrings = []
await Promise.all(
filesArray.map(async (file) => {
const fileContent = await loadData(file);
let keys = [];
const regex = /\<t\>(.*?)\<\/t\>/g // everything inside <t></t>
let match;
while ((match = regex.exec(fileContent))) {
const newMatch = match
.pop()
// .split('\',')[0]
// .replace(/['"]+/g, '"')
// .trim()
keys.push(newMatch);
}
// for each string in this file
for (let i = 0; i < keys.length; i++) {
const string = keys[i];
filesWithStrings.push({
_file: file,
en: string,
})
}
})
)
return filesWithStrings
}
const mergeObjectsInUnique = (array, property) => {
const newArray = new Map();
array.forEach((item) => {
const propertyValue = item[property];
newArray.has(propertyValue) ? newArray.set(propertyValue, { ...item, ...newArray.get(propertyValue) }) : newArray.set(propertyValue, item);
});
return Array.from(newArray.values());
}
// Find all words inside {}, add 'X' at substring 2,
const changeVariables = (stringToReplace) => {
const regex = /{([^}]*)}/g;
let matches = stringToReplace.matchAll(regex);
for (let match of matches) {
const newMatch = match[0]
stringToReplace = stringToReplace.replace(newMatch, newMatch.substring(0, 2) + 'X' + newMatch.substring(2))
}
return stringToReplace;
}
// Find all words inside {}, remove 'X' at substring 2,
const restoreVariables = (stringToReplace) => {
const regex = /{([^}]*)}/g;
let matches = stringToReplace.matchAll(regex);
for (let match of matches) {
const newMatch = match[0]
stringToReplace = stringToReplace.replace(newMatch, newMatch.substring(0, 2) + newMatch.substring(3))
}
return stringToReplace;
}
// Find common google translation HTML errors
const fixHtmlInTranslation = (stringToReplace) => {
const regexes = [
// From: <span class = "className">
// To: <span class="className">
{
from: / = /g,
to: '='
},
// From: <Span class="className">
// To: <span class="className">
{
from: /<Span/g,
to: '<span'
},
// TODO
// From: <Span class="green - text">
// To: <span class="green--text">
// {}
];
for (let regex of regexes) {
let matches = stringToReplace.matchAll(regex.from);
for (let match of matches) {
const newMatch = match[0]
stringToReplace = stringToReplace.replace(newMatch, regex.to)
}
}
return stringToReplace;
}
const getFilesStringsTranslated = async (filesArrayWithStrings, targetLanguages) => {
const filesWithTranslatedStrings = [];
await Promise.all(
filesArrayWithStrings.map(async (file) => {
await asyncForEach(targetLanguages, async (language) => {
// Temporarily obfuscate strings inside {}, so they're not translated,
// make {name} => {nXame}
const englishStringWithChangedVariables = changeVariables(file.en);
// translate obfuscate strings
let translatedString = await translate(englishStringWithChangedVariables, { to: language });
// then reverse
// make {nXame} => {name}
const translatedStringWithRestoredVariables = restoreVariables(translatedString);
translatedString = translatedStringWithRestoredVariables
// Fix HTML inconsistencies in html
const fixedHTMLString = fixHtmlInTranslation(translatedString)
translatedString = fixedHTMLString
const object = {
_file: file._file,
en: file.en,
[language]: translatedString
}
filesWithTranslatedStrings.push(object)
})
})
)
const mergedArray = mergeObjectsInUnique(filesWithTranslatedStrings, 'en');
return mergedArray;
}
const generateTranslatedFiles = async (filesWithTranslatedStrings, targetLanguages, sourceFolder, distFolder) => {
const uniqueFiles = [...new Set(filesWithTranslatedStrings.map(obj => obj._file))];
const generatedFiles = []
await Promise.all(
uniqueFiles.map(async (filePath) => {
targetLanguages.map(async (language) => {
const pathAfterSourceFolder = filePath.split(sourceFolder)[1];
const newFileDestination = `${distFolder}${language}/${pathAfterSourceFolder}`;
const newFilePath = path.dirname(newFileDestination);
let fileContent = await loadData(filePath);
const translatedFile = filesWithTranslatedStrings.map((translatedObject) => {
// If no translation, return raw content
if (translatedObject.en === 'null') return [fileContent];
// Build regex, also escape string literals
// const regex = new RegExp(translatedObject.en.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'), "g");
// Find matching string with wrapping <t> only. Fixes bug where all instances of a word would be replaced (not wrapped)
let englishString = `<t>${translatedObject.en}</t>`;
fileContent = fileContent
// Replace content with trsanslated string
// TODO: create option to remove wrapping tags here.
.replace(englishString, `<t>${translatedObject[language]}</t>`)
// Fix spaced string literals (make this an option)
.replace('$ {', '${')
return fileContent;
})
// Get last mapped of array (the fully translated item)
const newFileContent = translatedFile.pop()
// Create folder if it doesn't exist
if (!fs.existsSync(`${newFilePath}`)) {
fs.mkdirSync(`${newFilePath}`, { recursive: true });
}
// Create file with new content
fs.writeFileSync(newFileDestination, newFileContent, 'utf-8');
// Build array of created files for output
generatedFiles.push(newFileDestination)
})
})
)
return generatedFiles;
}
const getFilesWithNoTranslation = async (filesArray, filesArrayWithStrings) => {
const filesWithNoStrings = [];
filesArray.map(file => {
filesArrayWithStrings.filter(object => {
if (file !== object.file) {
filesWithNoStrings.push({
_file: file,
en: 'null'
})
}
})
})
return filesWithNoStrings;
}
module.exports = async (GOOGLEKEY, { targetLanguages, targetFiles, targetDirectory, sourceDirectory, translationFile, loadTranslationsFromFile }) => {
translate.key = GOOGLEKEY;
// Defaults
const TARGET_LANGUAGES = targetLanguages;
const TARGET_FILES = targetFiles;
const TARGET_DIRECTORY = targetDirectory;
const SOURCE_DIRECTORY = sourceDirectory;
const TRANSLATION_FILE = translationFile ? translationFile : './translations.json';
const LOAD_TRANSLATIONS_FROM_FILE = loadTranslationsFromFile ? loadTranslationsFromFile : false;
// Get files,
const files = await getFilesFromSource(TARGET_FILES)
// Get strings from file contents
const filesWithStrings = await getFilesStrings(files)
const filesWithNoStrings = await getFilesWithNoTranslation(files, filesWithStrings)
// Get translations array
let filesWithTranslatedStrings;
if (LOAD_TRANSLATIONS_FROM_FILE) {
// Load existing translation JSON file
const fileContent = await loadData(TRANSLATION_FILE)
filesWithTranslatedStrings = JSON.parse(fileContent)
} else {
// Get translation array
filesWithTranslatedStrings = await getFilesStringsTranslated(filesWithStrings, TARGET_LANGUAGES)
}
if (!filesWithTranslatedStrings && LOAD_TRANSLATIONS_FROM_FILE) return 'Please choose a translation file to load.'
const allFiles = [...filesWithTranslatedStrings, ...filesWithNoStrings]
// Create file from strings for each language
const createdFiles = await generateTranslatedFiles(allFiles, TARGET_LANGUAGES, SOURCE_DIRECTORY, TARGET_DIRECTORY)
// const createdFiles = await generateTranslatedFiles(filesWithTranslatedStrings, TARGET_LANGUAGES, SOURCE_DIRECTORY, TARGET_DIRECTORY)
// Create JSON file, only if we don't load an existing translation file
if (!LOAD_TRANSLATIONS_FROM_FILE) {
var sortedJson = myJsonAbc.sortObj(filesWithTranslatedStrings, false);
await storeData(sortedJson, TRANSLATION_FILE);
}
// Return files created
return createdFiles;
}