Improved translation web application.

This commit is contained in:
Ylian Saint-Hilaire 2019-12-06 16:49:40 -08:00
parent 7b5bcf7329
commit 8c636bdf19
14 changed files with 6558 additions and 28 deletions

View File

@ -1,6 +1,6 @@
{
"name": "meshcentral",
"version": "0.4.5-i",
"version": "0.4.5-j",
"keywords": [
"Remote Management",
"Intel AMT",

View File

@ -9,8 +9,8 @@ if (!String.prototype.startsWith) { String.prototype.startsWith = function (str)
if (!String.prototype.endsWith) { String.prototype.endsWith = function (str) { return this.indexOf(str, this.length - str.length) !== -1; }; }
// Quick UI functions, a bit of a replacement for jQuery
//function Q(x) { if (document.getElementById(x) == null) { console.log('Invalid element: ' + x); } return document.getElementById(x); } // "Q"
function Q(x) { return document.getElementById(x); } // "Q"
function Q(x) { if (document.getElementById(x) == null) { console.log('Invalid element: ' + x); } return document.getElementById(x); } // "Q"
//function Q(x) { return document.getElementById(x); } // "Q"
function QS(x) { try { return Q(x).style; } catch (x) { } } // "Q" style
function QE(x, y) { try { Q(x).disabled = !y; } catch (x) { } } // "Q" enable
function QV(x, y) { try { QS(x).display = (y ? '' : 'none'); } catch (x) { } } // "Q" visible

View File

@ -18,6 +18,7 @@
margin: 4px;
padding: 6px;
text-align: left;
cursor: pointer;
}
.listItem:hover {
@ -35,12 +36,18 @@
}
</style>
</head>
<body style="overflow:hidden">
<div id=p11 class="noselect" style="overflow:hidden">
<body style="overflow:hidden" onbeforeunload="return onBeforeUnload(event)">
<div id=p11 class="noselect" style="overflow:hidden;position:relative">
<div id="startTips" style="top:15px;right:15px;width:calc(50vh);position:absolute;background-color:gold;z-index:1000;padding:10px;border-radius:8px;box-shadow:3px 3px 10px gray">
Welcome to the MeshCentral translator. You can use this to help translate MeshCentral into other languages. Start by selecting a language, translate a few strings and save the strings to your server. Then, hit "Translate Server" to apply your changes to your server's web pages.<br /><br />
When ready, please mail the "meshcentral-data/translate.json" file to <a href="mailto:ylianst@gmail.com">ylianst@gmail.com</a> for inclusion in the MeshCentral official builds.<br /><br />
<a onclick="closeStartTips()"><b>Close</b></a>
</div>
<div id=deskarea0>
<div id="bigok" style="display:none;left:calc((100vh / 2))"><b>&checkmark;</b></div>
<div id="bigfail" style="display:none;left:calc((100vh / 2))"><b>&#10007;</b></div>
<div id=deskarea0 style="position:absolute;left:0;right:0;top:0;height:28px;background-color:#036;color:#c8c8c8">
<input style="float:right;margin:3px" id="TransServerButton" type=button value="Translate Server" onclick="translateServer()">
<div style="float:right;padding:2px" id="mainStatus"></div>
<div style="font-size:20px;font-family:Arial;padding:3px"><b>MeshCentral Translator</b></div>
</div>
@ -50,9 +57,9 @@
</div>
<div>
<input id="OpenFileButton" type=button value="Open File..." onclick="openfile()" style="display:none">
<input id="SaveFileButton" type=button value="Save to Server..." onclick="saveServerTranslations()">
<input id="SaveServerButton" type=button value="Save to Server..." onclick="saveServerTranslations()">
<input id="SaveFileButton" type=button value="Save to File..." onclick="saveToFile()">
<select id="langSelector" onclick="langSelectorChange()" onchange="langSelectorChange()">
<select id="langSelector" onchange="langSelectorChange()">
<option value="ar">Arabic (ar)</option>
<option value="fr">French (fr)</option>
<option value="cs">Czech (cs)</option>
@ -82,17 +89,19 @@
<div id="masterListArea" style="height:calc(33.33vh);overflow-y:scroll">
<div class="listItem"><div style="display:inline-block;width:calc(40% - 10px)"></div><div style="display:inline-block;width:calc(40% - 10px)"></div><div style="display:inline-block;width:calc(20% - 10px)"></div></div>
</div>
<textarea id="defaultTextArea" autocomplete=off readonly style="height:calc(33.33vh);overflow-y:scroll;width:calc(100% - 5px);resize:none;background-color:#EFE"></textarea>
<textarea id="translatedTextArea" autocomplete=off style="height:calc(33.33vh - 70px);overflow-y:scroll;width:calc(100% - 5px);resize:none"></textarea>
<textarea id="defaultTextArea" autocomplete=off readonly style="height:calc(28.33vh);overflow-y:scroll;width:calc(100% - 5px);resize:none;background-color:#EFE"></textarea>
<textarea id="translatedTextArea" autocomplete=off style="height:calc(38.33vh - 96px);overflow-y:scroll;width:calc(100% - 5px);resize:none;background-color:#EFE" onkeyup="onSourceChange()"></textarea>
</div>
<div id=deskarea4 class="areaFoot">
<div class="toright2">
<input id="CopySource" type=button value="Copy English" onclick="copySource()">
<input id="PrevButton" type=button value="Prev" onclick="prev()">
<input id="NextButton" type=button value="Next" onclick="next()">
</div>
<div style="height:22px">
&nbsp;
<input id="CopySource" type=button value="Copy Source" onclick="copySource()">
<input id="SetButton" type=button value="Set" onclick="setTranslation()">
<input id="CancelButton" type=button value="Cancel" onclick="cancelTranslation()">
</div>
</div>
</div>
@ -120,6 +129,7 @@
var translations = null;
var selectedLanguage = Q('langSelector').value;
var selectedItem = 0;
var changes = false;
function start() {
window.onresize = deskAdjust;
@ -129,12 +139,20 @@
document.onkeypress = onkeypress;
updateMasterList();
loadServerTranslations();
QE('SaveServerButton', false);
}
function onBeforeUnload(e) {
if (changes) { e.preventDefault(); e.resturnValue = ''; }
}
function closeStartTips() { QV('startTips', false); }
function langSelectorChange() {
selectedLanguage = Q('langSelector').value;
updateMasterList();
onSearchChanged(true);
select(0, true, false);
}
function cleanup() {
@ -233,14 +251,17 @@
if (translations[i][selectedLanguage] != null) { target = EscapeHtml(translations[i][selectedLanguage]); }
var comment = '';
// <span id=ns' + i + ' style=display:none>&#9654;&nbsp;</span>
x.push('<div class="listItem" id=nx' + i + ' onclick=select(' + i + ')><div style="display:inline-block;width:calc(40% - 10px)">' + source + '</div><div style="display:inline-block;width:calc(40% - 10px)">' + target + '</div><div style="display:inline-block;width:calc(20% - 10px)">' + comment + '</div></div>');
x.push('<div class="listItem" id=nx' + i + ' onclick=select(' + i + ')><div id=ns' + i + ' style="display:inline-block;width:calc(40% - 10px)">' + source + '</div><div id=nt' + i + ' style="display:inline-block;width:calc(40% - 10px)">' + target + '</div><div id=nc' + i + ' style="display:inline-block;width:calc(20% - 10px)">' + comment + '</div></div>');
}
}
QH('masterListArea', x.join(''));
updateButtons();
}
function select(i, scroll) {
function select(i, scroll, nofocus) {
// Hold selection is a change was made but not commited.
if ((scroll == null) && (isTargetChanged() == true)) { return; }
Q('nx' + selectedItem).classList.remove('listItemSel');
Q('nx' + selectedItem).classList.add('listItem');
selectedItem = i;
@ -253,22 +274,22 @@
}
onLocChanged();
if (translations[i][selectedLanguage] != null) {
QH('translatedTextArea', translations[selectedItem][selectedLanguage]);
Q('translatedTextArea').value = translations[selectedItem][selectedLanguage];
} else {
QH('translatedTextArea', '');
Q('translatedTextArea').value = '';
}
Q('translatedTextArea').focus();
if (nofocus == true) { Q('translatedTextArea').focus(); }
updateButtons();
}
function next() { select(selectedItem + 1, true); }
function prev() { select(selectedItem - 1, true); }
function copySource() { QH('translatedTextArea', translations[selectedItem].en); Q('translatedTextArea').focus(); }
function copySource() { Q('translatedTextArea').value = translations[selectedItem].en; Q('translatedTextArea').focus(); }
function updateButtons() {
QE('SaveFileButton', translations != null);
QE('NextButton', (translations != null) && (selectedItem < (translations.length - 1)));
QE('PrevButton', (translations != null) && (selectedItem > 0));
QE('NextButton', (isTargetChanged() == false) && (translations != null) && (selectedItem < (translations.length - 1)));
QE('PrevButton', (isTargetChanged() == false) && (translations != null) && (selectedItem > 0));
QE('CopySource', translations != null);
if (translations == null) {
QH('status', '');
@ -277,6 +298,7 @@
QH('status', (selectedItem + 1) + ' / ' + translations.length);
QS('progressbar').width = Math.floor(100 * ((selectedItem + 1) / translations.length)) + '%';
}
onSourceChange();
}
function enSort(a, b) { if (a.en.toLowerCase() > b.en.toLowerCase()) return 1; if (a.en.toLowerCase() < b.en.toLowerCase()) return -1; return 0; }
@ -285,12 +307,13 @@
function onSearchChanged(force) {
if ((force != true) && (currentSearchFilter == Q('searchInput').value)) return;
currentSearchFilter = Q('searchInput').value;
var currentSearchFilterLower = currentSearchFilter.toLowerCase();
if (translations != null) {
for (var i in translations) {
if (currentSearchFilter == '') {
QV('nx' + i, true);
} else {
QV('nx' + i, ((translations[i][selectedLanguage] != null) && (translations[i][selectedLanguage].indexOf(currentSearchFilter) >= 0)) || (translations[i]['en'].indexOf(currentSearchFilter) >= 0));
QV('nx' + i, ((translations[i][selectedLanguage] != null) && (translations[i][selectedLanguage].toLowerCase().indexOf(currentSearchFilterLower) >= 0)) || (translations[i]['en'].toLowerCase().indexOf(currentSearchFilterLower) >= 0));
}
}
}
@ -346,8 +369,14 @@
xdr.onload = function () {
var x = null;
try { x = JSON.parse(this.responseText); } catch (ex) { }
if ((x == null) || (typeof x.strings != 'object')) { messagebox('Translations', 'ERROR: Unable to parse server response.'); return; }
// x
if ((x == null) || (x.response == null)) { messagebox('Translations', 'ERROR: Unable to parse server response.'); return; }
if (x.response == 'ok') {
changes = false;
QE('SaveServerButton', false);
QS('SaveServerButton')['background-color'] = null;
} else {
messagebox('Translations', 'ERROR: ' + x.response);
}
};
xdr.onerror = function () { messagebox('Translations', 'ERROR: Unable to save translations to server.'); };
xdr.send(JSON.stringify({ 'action': 'setTranslations', strings: translations }));
@ -357,6 +386,73 @@
saveAs(data2blob(JSON.stringify({ strings: translations })), 'translate.json');
}
function setTranslation() {
if (Q('translatedTextArea').value == '') {
delete translations[selectedItem][selectedLanguage];
QH('nt' + selectedItem, '');
} else {
translations[selectedItem][selectedLanguage] = Q('translatedTextArea').value;
QH('nt' + selectedItem, translations[selectedItem][selectedLanguage]);
}
onSourceChange();
changes = true;
QE('SaveServerButton', true);
QS('SaveServerButton')['background-color'] = '#F93';
}
function cancelTranslation() {
Q('translatedTextArea').value = translations[selectedItem][selectedLanguage];
}
function isTargetChanged() {
if (translations == null) { return false; }
var source = '';
if (translations[selectedItem][selectedLanguage] != null) { source = translations[selectedItem][selectedLanguage]; }
return (source != Q('translatedTextArea').value);
}
function onSourceChange() {
var x = isTargetChanged();
QE('SetButton', x);
QE('CancelButton', x);
QE('NextButton', (isTargetChanged() == false) && (translations != null) && (selectedItem < (translations.length - 1)));
QE('PrevButton', (isTargetChanged() == false) && (translations != null) && (selectedItem > 0));
QS('translatedTextArea')['background-color'] = ((x == true) ? 'white' : '#EFE');
QE('SaveFileButton', !x);
QE('searchInput', !x);
QE('showLocCheck', !x);
QE('langSelector', !x);
QE('TransServerButton', !x);
QE('SaveServerButton', changes && !x);
}
function translateServer() {
var x = 'Perform server translation? The MeshCentral server will reset for 10 to 30 seconds to perform this operation.';
setDialogMode(2, "Translate Server", 3, translateServerEx, x);
}
function translateServerEx() {
QE('TransServerButton', false);
setTimeout(function () { QE('TransServerButton', true); }, 5000);
var xdr = null;
try { xdr = new XDomainRequest(); } catch (e) { }
if (!xdr) xdr = new XMLHttpRequest();
xdr.open('POST', window.location.origin + '/translations');
xdr.timeout = 30000;
xdr.onload = function () {
var x = null;
try { x = JSON.parse(this.responseText); } catch (ex) { }
if ((x == null) || (x.response == null)) { messagebox('Server Translation', 'ERROR: Unable to parse server response.'); return; }
if (x.response == 'ok') {
messagebox('Server Translation', 'Server translation initiated, this will take a minute or two. Once done, you can refresh the MeshCentral web pages to see the changes.<br /><br />When ready, please mail the file "meshcentral-data/translate.json" to <a href="mailto:ylianst@gmail.com">ylianst@gmail.com</a> for inclusion on the official build.');
} else {
messagebox('Server Translation', 'ERROR: ' + x.response);
}
};
xdr.onerror = function () { messagebox('Translations', 'ERROR: Unable to save translations to server.'); };
xdr.send(JSON.stringify({ 'action': 'translateServer', strings: translations }));
}
//
// POPUP DIALOG
//

View File

@ -72,7 +72,7 @@ function start() {
console.log(' TRANSLATE [language] [languagefile] [files]');
console.log(' Use a language (.json) file to translate web pages to a give language.');
console.log('');
console.log(' TRANSLATEALL');
console.log(' TRANSLATEALL (languagefile)');
console.log(' Translate all MeshCentral strings using the languages.json file.');
console.log('');
console.log(' MINIFYALL');
@ -110,7 +110,20 @@ function start() {
if (command == 'translateall') {
if (fs.existsSync("../views/translations") == false) { fs.mkdirSync("../views/translations"); }
if (fs.existsSync("../public/translations") == false) { fs.mkdirSync("../public/translations"); }
translate(null, "translate.json", meshCentralSourceFiles, "translations");
if ((process.argv.length > 3)) {
if (fs.existsSync(process.argv[3]) == false) {
console.log('Unable to find: ' + process.argv[3]);
} else {
translate(null, process.argv[3], meshCentralSourceFiles, "translations");
}
} else {
if (fs.existsSync("translate.json") == false) {
console.log('Unable to find translate.json.');
} else {
translate(null, "translate.json", meshCentralSourceFiles, "translations");
}
}
return;
}
// Translate web pages to a given language given a language file

File diff suppressed because one or more lines are too long

View File

@ -5958,6 +5958,11 @@
y += addHtmlValue("Language", z);
}
y += addHtmlValue("Dates & Time", x);
if ((userinfo.siteadmin == 0xFFFFFFFF) && (domain == '')) {
y += '<br /><a rel="noreferrer noopener" target="_blank" href="translator.htm">' + "Help translate MeshCentral" + '</a>';
}
setDialogMode(2, "Localization Settings", 3, account_showLocalizationSettingsEx, y);
return false;
}

View File

@ -6969,6 +6969,11 @@
y += addHtmlValue("Language", z);
}
y += addHtmlValue("Dates & Time", x);
if ((userinfo.siteadmin == 0xFFFFFFFF) && (domain == '')) {
y += '<br /><a rel="noreferrer noopener" target="_blank" href="translator.htm">' + "Help translate MeshCentral" + '</a>';
}
setDialogMode(2, "Localization Settings", 3, account_showLocalizationSettingsEx, y);
return false;
}

View File

@ -5958,6 +5958,11 @@
y += addHtmlValue("Jazyk", z);
}
y += addHtmlValue("Datum & čas", x);
if ((userinfo.siteadmin == 0xFFFFFFFF) && (domain == '')) {
y += '<br /><a rel="noreferrer noopener" target="_blank" href="translator.htm">' + "Help translate MeshCentral" + '</a>';
}
setDialogMode(2, "Localization Settings", 3, account_showLocalizationSettingsEx, y);
return false;
}

View File

@ -5958,6 +5958,11 @@
y += addHtmlValue("Langages", z);
}
y += addHtmlValue("Dates & Time", x);
if ((userinfo.siteadmin == 0xFFFFFFFF) && (domain == '')) {
y += '<br /><a rel="noreferrer noopener" target="_blank" href="translator.htm">' + "Help translate MeshCentral" + '</a>';
}
setDialogMode(2, "Localization Settings", 3, account_showLocalizationSettingsEx, y);
return false;
}

View File

@ -6967,6 +6967,11 @@
y += addHtmlValue("Jazyk", z);
}
y += addHtmlValue("Datum & čas", x);
if ((userinfo.siteadmin == 0xFFFFFFFF) && (domain == '')) {
y += '<br /><a rel="noreferrer noopener" target="_blank" href="translator.htm">' + "Help translate MeshCentral" + '</a>';
}
setDialogMode(2, "Localization Settings", 3, account_showLocalizationSettingsEx, y);
return false;
}

View File

@ -6967,6 +6967,11 @@
y += addHtmlValue("Langages", z);
}
y += addHtmlValue("Dates & Time", x);
if ((userinfo.siteadmin == 0xFFFFFFFF) && (domain == '')) {
y += '<br /><a rel="noreferrer noopener" target="_blank" href="translator.htm">' + "Help translate MeshCentral" + '</a>';
}
setDialogMode(2, "Localization Settings", 3, account_showLocalizationSettingsEx, y);
return false;
}

File diff suppressed because one or more lines are too long

View File

@ -29,7 +29,7 @@
</div>
<div id="column_l">
<h1>Bienvenue</h1>
<div id="welcomeText" style="display:none">Connect to your home or office devices from anywhere in the world using <a href="http://www.meshcommander.com/meshcentral2">MeshCentral</a>, the real time, open source remote monitoring and management web site. You will need to download and install a management agent on your computers. Once installed, computers will show up in the "My Devices" section of this web site and you will be able to monitor them and take control of them.</div>
<div id="welcomeText" style="display:none">Connect to your home or office devices from anywhere in the world using <a href="http://www.meshcommander.com/meshcentral2">MeshCentral</a>, le site web open source de surveillance et de gestion dordinateur à distance en temps réel. Vous devrez télécharger et installer un agent de gestion sur vos ordinateurs. Une fois installés, les ordinateurs apparaîtront dans la section "Mes appareils" de ce site et vous pourrez les surveiller et en prendre le contrôle.</div>
<table id="centralTable" style="">
<tbody><tr>
<td id="welcomeimage">

View File

@ -574,7 +574,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
fmt: "fido-u2f",
publicKey: webAuthnKey.publicKey,
prevCounter: webAuthnKey.counter,
userHandle: Buffer(user._id, 'binary').toString('base64')
userHandle: Buffer.from(user._id, 'binary').toString('base64')
};
var webauthnResponse = null;
@ -1973,7 +1973,26 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
try { res.sendFile(obj.path.join(__dirname, 'translate', 'translate.json')); } catch (ex) { res.sendStatus(404); }
} else { res.sendStatus(404); }
} else if (data.action == 'setTranslations') {
obj.fs.writeFile(obj.path.join(obj.parent.datapath, 'translate.json'), JSON.stringify({ strings: data.strings } ), function (err) { res.send(JSON.stringify({ response:err })); });
obj.fs.writeFile(obj.path.join(obj.parent.datapath, 'translate.json'), JSON.stringify({ strings: data.strings }), function (err) { if (err == null) { res.send(JSON.stringify({ response: 'ok' })); } else { res.send(JSON.stringify({ response: err })); } });
} else if (data.action == 'translateServer') {
if (obj.pendingTranslation === true) { res.send(JSON.stringify({ response: 'Server is already performing a translation.' })); return; }
const nodeVersion = Number(process.version.match(/^v(\d+\.\d+)/)[1]);
if (nodeVersion < 8) { res.send(JSON.stringify({ response: 'Server requires NodeJS 8.x or better.' })); return; }
var translateFile = obj.path.join(obj.parent.datapath, 'translate.json');
if (obj.fs.existsSync(translateFile) == false) { translateFile = obj.path.join(__dirname, 'translate', 'translate.json'); }
if (obj.fs.existsSync(translateFile) == false) { res.send(JSON.stringify({ response: 'Unable to find translate.js file on the server.' })); return; }
res.send(JSON.stringify({ response: 'ok' }));
console.log('Started server translation...');
obj.pendingTranslation = true;
require('child_process').exec('node translate.js translateall \"' + translateFile + '\"', { maxBuffer: 512000, timeout: 120000, cwd: obj.path.join(__dirname, 'translate') }, function (error, stdout, stderr) {
delete obj.pendingTranslation;
//console.log('error', error);
//console.log('stdout', stdout);
//console.log('stderr', stderr);
//console.log('Server restart...'); // Perform a server restart
//process.exit(0);
console.log('Server translation completed.');
});
} else {
// Unknown request
res.sendStatus(404);