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; }); };