Delete translate/mytranslate.js
Simon Smith committed
Nov 17, 2025 at 11:37 UTC
6103d94e2c450360de69fa593fb9dbbe65d9b9da
1 file changed
-364
translate/mytranslate.js
deleted
-364
@@ -1,364 +0,0 @@
1
-const fs = require('fs');
2
-const path = require('path');
3
-const os = require('os');
4
-const { Worker, isMainThread, workerData, parentPort } = require('worker_threads');
5
-const jsdom = require('jsdom');
6
-const { JSDOM } = jsdom;
7
-const esprima = require('esprima');
8
-const { minify } = require('html-minifier-terser');
9
-var translationTable = {};
10
-
11
-// Source files to translate
12
-var meshCentralSourceFiles = [
13
- "../views/agentinvite.handlebars",
14
- "../views/invite.handlebars",
15
- "../views/default.handlebars",
16
- "../views/default3.handlebars",
17
- "../views/default-mobile.handlebars",
18
- "../views/download.handlebars",
19
- "../views/download2.handlebars",
20
- "../views/error404.handlebars",
21
- "../views/error404-mobile.handlebars",
22
- "../views/login.handlebars",
23
- "../views/login2.handlebars",
24
- "../views/login-mobile.handlebars",
25
- "../views/terms.handlebars",
26
- "../views/terms-mobile.handlebars",
27
- "../views/xterm.handlebars",
28
- "../views/message.handlebars",
29
- "../views/message2.handlebars",
30
- "../views/messenger.handlebars",
31
- "../views/player.handlebars",
32
- "../views/sharing.handlebars",
33
- "../views/sharing-mobile.handlebars",
34
- "../views/mstsc.handlebars",
35
- "../views/ssh.handlebars",
36
- "../emails/account-check.html",
37
- "../emails/account-invite.html",
38
- "../emails/account-login.html",
39
- "../emails/account-reset.html",
40
- "../emails/mesh-invite.html",
41
- "../emails/device-notify.html",
42
- "../emails/device-help.html",
43
- "../emails/account-check.txt",
44
- "../emails/account-invite.txt",
45
- "../emails/account-login.txt",
46
- "../emails/account-reset.txt",
47
- "../emails/mesh-invite.txt",
48
- "../emails/device-notify.txt",
49
- "../emails/device-help.txt",
50
- "../emails/sms-messages.txt",
51
- "../agents/agent-translations.json",
52
- "../agents/modules_meshcore/coretranslations.json"
53
-];
54
-
55
-const langFile = path.join(__dirname, 'translate.json');
56
-const createSubDir = 'translations'; // Subdirectory to create for translated files
57
-const directRun = (require.main === module);
58
-
59
-// Check NodeJS version
60
-const NodeJSVer = parseFloat(process.version.match(/^v(\d+\.\d+)/)[1]);
61
-if (directRun && NodeJSVer < 12) {
62
- console.error("Translate.js requires Node v12 or above");
63
- process.exit(1);
64
-}
65
-
66
-if (directRun && isMainThread) {
67
- console.log("MeshCentral translation tool v0.1.0 (direct)");
68
-
69
- try {
70
- // 1. Load language file ONCE
71
- const langFileData = JSON.parse(fs.readFileSync(langFile));
72
- if (!langFileData?.strings) throw new Error("Invalid language file structure");
73
-
74
- // 2. Load all source files ONCE
75
- const sources = meshCentralSourceFiles.map(file => ({
76
- path: file,
77
- content: fs.readFileSync(file, 'utf8')
78
- }));
79
-
80
- // 3. Get target languages
81
- const languages = new Set();
82
- for (const entry of Object.values(langFileData.strings)) {
83
- for (const lang in entry) {
84
- if (!['en', 'xloc', '*'].includes(lang)) {
85
- languages.add(lang.toLowerCase());
86
- }
87
- }
88
- }
89
- const langArray = Array.from(languages);
90
-
91
- console.log(`Processing ${langArray.length} languages: ${langArray.join(', ')}`);
92
- console.log(`Loaded ${sources.length} source files`);
93
-
94
- // 4. Worker management
95
- const MAX_WORKERS = 4;
96
- let activeWorkers = 0;
97
- let completed = 0;
98
-
99
- function startWorker() {
100
- if (langArray.length === 0 || activeWorkers >= MAX_WORKERS) return;
101
-
102
- const lang = langArray.pop();
103
- activeWorkers++;
104
-
105
- const worker = new Worker(__filename, {
106
- workerData: {
107
- lang,
108
- langData: langFileData,
109
- sources
110
- }
111
- });
112
-
113
- worker.on('message', (msg) => {
114
- console.log(`[${lang}] ${msg}`);
115
- });
116
-
117
- worker.on('error', (err) => {
118
- console.error(`[${lang}] Worker error:`, err);
119
- });
120
-
121
- worker.on('exit', (code) => {
122
- activeWorkers--;
123
- completed++;
124
- console.log(`[${lang}] Completed (${completed}/${completed + langArray.length + activeWorkers})`);
125
- startWorker();
126
- });
127
- }
128
-
129
- // Start initial workers
130
- for (let i = 0; i < Math.min(MAX_WORKERS, langArray.length); i++) {
131
- startWorker();
132
- }
133
-
134
- } catch (err) {
135
- console.error("Initialization failed:", err);
136
- process.exit(1);
137
- }
138
-
139
-} else if (!isMainThread) {
140
- // Worker thread logic
141
- const { lang, langData, sources } = workerData;
142
-
143
- try {
144
- parentPort.postMessage(`Starting translation of ${sources.length} files`);
145
-
146
- translationTable = {};
147
- for (var i in langData.strings) {
148
- var entry = langData.strings[i];
149
- if ((entry['en'] != null) && (entry[lang] != null)) { translationTable[entry['en']] = entry[lang]; }
150
- }
151
-
152
- for (var i = 0; i < sources.length; i++) {
153
- if (sources[i].path.endsWith('.html') || sources[i].path.endsWith('.htm') || sources[i].path.endsWith('.handlebars')) {
154
- translateFromHtml(lang, sources[i], createSubDir, (file) => {
155
- parentPort.postMessage(`Finished HTML/Handlebars file: ${file}`);
156
- });
157
- } else if (sources[i].path.endsWith('.txt')) {
158
- translateFromTxt(lang, sources[i], createSubDir, (file) => {
159
- parentPort.postMessage(`Finished TXT file: ${file}`);
160
- });
161
- }
162
- }
163
- } catch (err) {
164
- parentPort.postMessage(`ERROR: ${err.message}`);
165
- }
166
-}
167
-
168
-function minifyFromHtml(lang, file, out, done) {
169
- parentPort.postMessage(`Minifying HTML/Handlebars file: ${file.path}`);
170
- if (file.path.endsWith('.handlebars') >= 0) { out = out.split('{{{pluginHandler}}}').join('"{{{pluginHandler}}}"'); }
171
- minify(out, {
172
- collapseBooleanAttributes: true,
173
- collapseInlineTagWhitespace: false, // This is not good.
174
- collapseWhitespace: true,
175
- minifyCSS: true,
176
- minifyJS: true,
177
- removeComments: true,
178
- removeOptionalTags: true,
179
- removeEmptyAttributes: true,
180
- removeAttributeQuotes: true,
181
- removeRedundantAttributes: true,
182
- removeScriptTypeAttributes: true,
183
- removeTagWhitespace: true,
184
- preserveLineBreaks: false,
185
- useShortDoctype: true
186
- }).then((minifiedOut) => {
187
- if (minifiedOut == null) {
188
- parentPort.postMessage(`ERROR: Minification failed for ${file.path}`);
189
- } else {
190
- var outname = file.path;
191
- var outnamemin = null;
192
- if (createSubDir != null) {
193
- var outfolder = path.join(path.dirname(file.path), createSubDir);
194
- if (fs.existsSync(outfolder) == false) { fs.mkdirSync(outfolder); }
195
- outname = path.join(path.dirname(file.path), createSubDir, path.basename(file.path));
196
- }
197
- if (outname.endsWith('.handlebars')) {
198
- outnamemin = (outname.substring(0, outname.length - 11) + '-min_' + lang + '.handlebars');
199
- outname = (outname.substring(0, outname.length - 11) + '_' + lang + '.handlebars');
200
- } else if (outname.endsWith('.html')) {
201
- outnamemin = (outname.substring(0, outname.length - 5) + '-min_' + lang + '.html');
202
- outname = (outname.substring(0, outname.length - 5) + '_' + lang + '.html');
203
- } else if (outname.endsWith('.htm')) {
204
- outnamemin = (outname.substring(0, outname.length - 4) + '-min_' + lang + '.htm');
205
- outname = (outname.substring(0, outname.length - 4) + '_' + lang + '.htm');
206
- } else if (outname.endsWith('.js')) {
207
- if (out.startsWith('<html><head></head><body><script>')) { out = out.substring(33); }
208
- if (out.endsWith('</script></body></html>')) { out = out.substring(0, out.length - 23); }
209
- outnamemin = (outname.substring(0, outname.length - 3) + '-min_' + lang + '.js');
210
- outname = (outname.substring(0, outname.length - 3) + '_' + lang + '.js');
211
- } else {
212
- outnamemin = (outname + '_' + lang + '.min');
213
- outname = (outname + '_' + lang);
214
- }
215
- if (outnamemin.endsWith('.handlebars') >= 0) { minifiedOut = minifiedOut.split('"{{{pluginHandler}}}"').join('{{{pluginHandler}}}'); }
216
- fs.writeFileSync(outnamemin, minifiedOut, { flag: 'w+' });
217
- parentPort.postMessage(`Minified HTML/Handlebars file: ${file.path}`);
218
- }
219
- done(file.path);
220
- });
221
-}
222
-
223
-function translateFromHtml(lang, file, createSubDir, done) {
224
- parentPort.postMessage(`Translating HTML/Handlebars file: ${file.path}`);
225
- var data = file.content;
226
- if (file.path.endsWith('.js')) { data = '<html><head></head><body><script>' + file.content + '</script></body></html>'; }
227
- const dom = new JSDOM(data, { includeNodeLocations: true });
228
- translateStrings(path.basename(file.path), dom.window.document.querySelector('body'));
229
- var out = dom.serialize();
230
- out = out.split('<html lang="en"').join('<html lang="' + lang + '"');
231
- var outname = file.path;
232
- var outnamemin = null;
233
- if (createSubDir != null) {
234
- var outfolder = path.join(path.dirname(file.path), createSubDir);
235
- if (fs.existsSync(outfolder) == false) { fs.mkdirSync(outfolder); }
236
- outname = path.join(path.dirname(file.path), createSubDir, path.basename(file.path));
237
- }
238
- if (outname.endsWith('.handlebars')) {
239
- outnamemin = (outname.substring(0, outname.length - 11) + '-min_' + lang + '.handlebars');
240
- outname = (outname.substring(0, outname.length - 11) + '_' + lang + '.handlebars');
241
- } else if (outname.endsWith('.html')) {
242
- outnamemin = (outname.substring(0, outname.length - 5) + '-min_' + lang + '.html');
243
- outname = (outname.substring(0, outname.length - 5) + '_' + lang + '.html');
244
- } else if (outname.endsWith('.htm')) {
245
- outnamemin = (outname.substring(0, outname.length - 4) + '-min_' + lang + '.htm');
246
- outname = (outname.substring(0, outname.length - 4) + '_' + lang + '.htm');
247
- } else if (outname.endsWith('.js')) {
248
- if (out.startsWith('<html><head></head><body><script>')) { out = out.substring(33); }
249
- if (out.endsWith('</script></body></html>')) { out = out.substring(0, out.length - 23); }
250
- outnamemin = (outname.substring(0, outname.length - 3) + '-min_' + lang + '.js');
251
- outname = (outname.substring(0, outname.length - 3) + '_' + lang + '.js');
252
- } else {
253
- outnamemin = (outname + '_' + lang + '.min');
254
- outname = (outname + '_' + lang);
255
- }
256
- fs.writeFileSync(outname, out, { flag: 'w+' });
257
- parentPort.postMessage(`Translated HTML/Handlebars file: ${file.path}`);
258
- minifyFromHtml(lang, file, out, done);
259
-}
260
-
261
-function translateStrings(name, node) {
262
- for (var i = 0; i < node.childNodes.length; i++) {
263
- var subnode = node.childNodes[i];
264
-
265
- // Check if the "value" attribute exists and needs to be translated
266
- var subnodeignore = false;
267
- if ((subnode.attributes != null) && (subnode.attributes.length > 0)) {
268
- var subnodevalue = null, subnodeindex = null, subnodeplaceholder = null, subnodeplaceholderindex = null, subnodetitle = null, subnodetitleindex = null;
269
- for (var j in subnode.attributes) {
270
- if ((subnode.attributes[j].name == 'notrans') && (subnode.attributes[j].value == '1')) { subnodeignore = true; }
271
- if ((subnode.attributes[j].name == 'type') && (subnode.attributes[j].value == 'hidden')) { subnodeignore = true; }
272
- if (subnode.attributes[j].name == 'value') { subnodevalue = subnode.attributes[j].value; subnodeindex = j; }
273
- if (subnode.attributes[j].name == 'placeholder') { subnodeplaceholder = subnode.attributes[j].value; subnodeplaceholderindex = j; }
274
- if (subnode.attributes[j].name == 'title') { subnodetitle = subnode.attributes[j].value; subnodetitleindex = j; }
275
- }
276
- if ((subnodevalue != null) && isNumber(subnodevalue) == true) { subnodevalue = null; }
277
- if ((subnodeplaceholder != null) && isNumber(subnodeplaceholder) == true) { subnodeplaceholder = null; }
278
- if ((subnodetitle != null) && isNumber(subnodetitle) == true) { subnodetitle = null; }
279
- if ((subnodeignore == false) && (subnodevalue != null)) {
280
- // Perform attribute translation for value
281
- if (translationTable[subnodevalue] != null) { subnode.attributes[subnodeindex].value = translationTable[subnodevalue]; }
282
- }
283
- if (subnodeplaceholder != null) {
284
- // Perform attribute translation for placeholder
285
- if (translationTable[subnodeplaceholder] != null) { subnode.attributes[subnodeplaceholderindex].value = translationTable[subnodeplaceholder]; }
286
- }
287
- if (subnodetitle != null) {
288
- // Perform attribute translation for title
289
- if (translationTable[subnodetitle] != null) { subnode.attributes[subnodetitleindex].value = translationTable[subnodetitle]; }
290
- }
291
- }
292
-
293
- if (subnodeignore == false) {
294
- var subname = subnode.id;
295
- if (subname == null || subname == '') { subname = i; }
296
- if (subnode.hasChildNodes()) {
297
- translateStrings(name + '->' + subname, subnode);
298
- } else {
299
- if (subnode.nodeValue == null) continue;
300
- var nodeValue = subnode.nodeValue.trim().split('\\r').join('').split('\\n').join('').trim();
301
-
302
- // Look for the front trim
303
- var frontTrim = '', backTrim = '';;
304
- var x1 = subnode.nodeValue.indexOf(nodeValue);
305
- if (x1 > 0) { frontTrim = subnode.nodeValue.substring(0, x1); }
306
- if (x1 != -1) { backTrim = subnode.nodeValue.substring(x1 + nodeValue.length); }
307
-
308
- if ((nodeValue.length > 0) && (subnode.nodeType == 3)) {
309
- if ((node.tagName != 'SCRIPT') && (node.tagName != 'STYLE') && (nodeValue.length < 8000) && (nodeValue.startsWith('{{{') == false) && (nodeValue != ' ')) {
310
- // Check if we have a translation for this string
311
- if (translationTable[nodeValue]) { subnode.nodeValue = (frontTrim + translationTable[nodeValue] + backTrim); }
312
- } else if (node.tagName == 'SCRIPT') {
313
- // Translate JavaScript
314
- subnode.nodeValue = translateStringsFromJavaScript(name, subnode.nodeValue);
315
- }
316
- }
317
- }
318
- }
319
- }
320
-}
321
-
322
-function translateStringsFromJavaScript(name, script) {
323
- var tokenScript = esprima.tokenize(script, { range: true }), count = 0;
324
- var output = [], ptr = 0;
325
- for (var i in tokenScript) {
326
- var token = tokenScript[i];
327
- if ((token.type == 'String') && (token.value.length > 2) && (token.value[0] == '"')) {
328
- var str = token.value.substring(1, token.value.length - 1);
329
- if (translationTable[str]) {
330
- output.push(script.substring(ptr, token.range[0]));
331
- output.push('"' + translationTable[str] + '"');
332
- ptr = token.range[1];
333
- }
334
- }
335
- }
336
- output.push(script.substring(ptr));
337
- return output.join('');
338
-}
339
-
340
-function translateFromTxt(lang, file, createSubDir, done) {
341
- parentPort.postMessage(`Translating TXT file: ${file.path}`);
342
- var lines = file.content.toString().split(/\r?\n/), outlines = [];
343
- for (var i in lines) {
344
- var line = lines[i];
345
- if ((line.length > 1) && (line[0] != '~')) {
346
- if (translationTable[line] != null) { outlines.push(translationTable[line]); } else { outlines.push(line); }
347
- } else {
348
- outlines.push(line);
349
- }
350
- }
351
-
352
- var outname = file.path, out = outlines.join(os.EOL);
353
- if (createSubDir != null) {
354
- var outfolder = path.join(path.dirname(file.path), createSubDir);
355
- if (fs.existsSync(outfolder) == false) { fs.mkdirSync(outfolder); }
356
- outname = path.join(path.dirname(file.path), createSubDir, path.basename(file.path));
357
- }
358
- outname = (outname.substring(0, outname.length - 4) + '_' + lang + '.txt');
359
- fs.writeFileSync(outname, out, { flag: 'w+' });
360
- done(file.path); // Call the done callback to signal completion
361
-}
362
-
363
-function isNumber(x) { return (('' + parseInt(x)) === x) || (('' + parseFloat(x)) === x); }
364
-function format(format) { var args = Array.prototype.slice.call(arguments, 1); return format.replace(/{(\d+)}/g, function (match, number) { return typeof args[number] != 'undefined' ? args[number] : match; }); };