LibreTranslate/app/static/js/app.js
Gustavo Rodrigues 64ae20e932
Properly escape data in the request code
It was only escaping the first quote, all other quotes and other characters that require to be escaped (like line breaks) were not being escaped. JSON.stringify is a good function to handle this.
2021-12-09 10:31:48 -03:00

411 lines
16 KiB
JavaScript

// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-3.0
// API host/endpoint
var BaseUrl = window.location.protocol + "//" + window.location.host;
var htmlRegex = /<(.*)>.*?|<(.*)\/>/;
document.addEventListener('DOMContentLoaded', function(){
var sidenavElems = document.querySelectorAll('.sidenav');
var sidenavInstances = M.Sidenav.init(sidenavElems);
var app = new Vue({
el: '#app',
delimiters: ['[[',']]'],
data: {
BaseUrl: BaseUrl,
loading: true,
error: "",
langs: [],
settings: {},
sourceLang: "",
targetLang: "",
loadingTranslation: false,
inputText: "",
inputTextareaHeight: 250,
savedTanslatedText: "",
translatedText: "",
output: "",
charactersLimit: -1,
copyTextLabel: "Copy text",
suggestions: false,
isSuggesting: false,
supportedFilesFormat : [],
translationType: "text",
inputFile: false,
loadingFileTranslation: false,
translatedFileUrl: false,
filesTranslation: true,
},
mounted: function(){
var self = this;
var requestSettings = new XMLHttpRequest();
requestSettings.open('GET', BaseUrl + '/frontend/settings', true);
requestSettings.onload = function() {
if (this.status >= 200 && this.status < 400) {
// Success!
self.settings = JSON.parse(this.response);
self.sourceLang = self.settings.language.source.code;
self.targetLang = self.settings.language.target.code;
self.charactersLimit = self.settings.charLimit;
self.suggestions = self.settings.suggestions;
self.supportedFilesFormat = self.settings.supportedFilesFormat;
self.filesTranslation = self.settings.filesTranslation;
}else {
self.error = "Cannot load /frontend/settings";
self.loading = false;
}
};
requestSettings.onerror = function() {
self.error = "Error while calling /frontend/settings";
self.loading = false;
};
requestSettings.send();
var requestLanguages = new XMLHttpRequest();
requestLanguages.open('GET', BaseUrl + '/languages', true);
requestLanguages.onload = function() {
if (this.status >= 200 && this.status < 400) {
// Success!
self.langs = JSON.parse(this.response);
self.langs.push({ name: 'Auto Detect (Experimental)', code: 'auto' })
if (self.langs.length === 0){
self.loading = false;
self.error = "No languages available. Did you install the models correctly?"
return;
}
self.loading = false;
} else {
self.error = "Cannot load /languages";
self.loading = false;
}
};
requestLanguages.onerror = function() {
self.error = "Error while calling /languages";
self.loading = false;
};
requestLanguages.send();
},
updated: function(){
M.FormSelect.init(this.$refs.sourceLangDropdown);
M.FormSelect.init(this.$refs.targetLangDropdown);
if (this.$refs.inputTextarea){
if (this.inputText === ""){
this.$refs.inputTextarea.style.height = this.inputTextareaHeight + "px";
this.$refs.translatedTextarea.style.height = this.inputTextareaHeight + "px";
}else{
this.$refs.inputTextarea.style.height = this.$refs.translatedTextarea.style.height = "1px";
this.$refs.inputTextarea.style.height = Math.max(this.inputTextareaHeight, this.$refs.inputTextarea.scrollHeight + 32) + "px";
this.$refs.translatedTextarea.style.height = Math.max(this.inputTextareaHeight, this.$refs.translatedTextarea.scrollHeight + 32) + "px";
}
}
if (this.charactersLimit !== -1 && this.inputText.length >= this.charactersLimit){
this.inputText = this.inputText.substring(0, this.charactersLimit);
}
// Update "selected" attribute (to overcome a vue.js limitation)
// but properly display checkmarks on supported browsers.
// Also change the <select> width value depending on the <option> length
if (this.$refs.sourceLangDropdown){
for (var i = 0; i < this.$refs.sourceLangDropdown.children.length; i++){
var el = this.$refs.sourceLangDropdown.children[i];
if (el.value === this.sourceLang){
el.setAttribute('selected', '');
this.$refs.sourceLangDropdown.style.width = getTextWidth(el.text) + 24 + 'px';
}else{
el.removeAttribute('selected');
}
}
}
if (this.$refs.targetLangDropdown){
for (var i = 0; i < this.$refs.targetLangDropdown.children.length; i++){
var el = this.$refs.targetLangDropdown.children[i];
if (el.value === this.targetLang){
el.setAttribute('selected', '');
this.$refs.targetLangDropdown.style.width = getTextWidth(el.text) + 24 + 'px';
}else{
el.removeAttribute('selected');
}
}
}
},
computed: {
requestCode: function(){
return ['const res = await fetch("' + this.BaseUrl + '/translate", {',
' method: "POST",',
' body: JSON.stringify({',
' q: ' + this.$options.filters.escape(this.inputText) + ',',
' source: ' + this.$options.filters.escape(this.sourceLang) + ',',
' target: ' + this.$options.filters.escape(this.targetLang) + ',',
' format: "' + (this.isHtml ? "html" : "text") + '"',
' }),',
' headers: { "Content-Type": "application/json" }',
'});',
'',
'console.log(await res.json());'].join("\n");
},
supportedFilesFormatFormatted: function() {
return this.supportedFilesFormat.join(', ');
},
isHtml: function(){
return htmlRegex.test(this.inputText);
},
canSendSuggestion() {
return this.translatedText.trim() !== "" && this.translatedText !== this.savedTanslatedText;
}
},
filters: {
escape: function(v){
return JSON.stringify(v);
},
highlight: function(v){
return Prism.highlight(v, Prism.languages.javascript, 'javascript');
}
},
methods: {
abortPreviousTransRequest: function(){
if (this.transRequest){
this.transRequest.abort();
this.transRequest = null;
}
},
swapLangs: function(e){
this.closeSuggestTranslation(e)
var t = this.sourceLang;
this.sourceLang = this.targetLang;
this.targetLang = t;
this.inputText = this.translatedText;
this.translatedText = "";
this.handleInput();
},
dismissError: function(){
this.error = '';
},
handleInput: function(e){
this.closeSuggestTranslation(e)
if (this.timeout) clearTimeout(this.timeout);
this.timeout = null;
if (this.inputText === ""){
this.translatedText = "";
this.output = "";
this.abortPreviousTransRequest();
this.loadingTranslation = false;
return;
}
var self = this;
self.loadingTranslation = true;
this.timeout = setTimeout(function(){
self.abortPreviousTransRequest();
var request = new XMLHttpRequest();
self.transRequest = request;
var data = new FormData();
data.append("q", self.inputText);
data.append("source", self.sourceLang);
data.append("target", self.targetLang);
data.append("format", self.isHtml ? "html" : "text");
data.append("api_key", localStorage.getItem("api_key") || "");
request.open('POST', BaseUrl + '/translate', true);
request.onload = function() {
try{
var res = JSON.parse(this.response);
// Success!
if (res.translatedText !== undefined){
self.translatedText = res.translatedText;
self.loadingTranslation = false;
self.output = JSON.stringify(res, null, 4);
}else{
throw new Error(res.error || "Unknown error");
}
}catch(e){
self.error = e.message;
self.loadingTranslation = false;
}
};
request.onerror = function() {
self.error = "Error while calling /translate";
self.loadingTranslation = false;
};
request.send(data);
}, '{{ frontendTimeout }}');
},
copyText: function(e){
e.preventDefault();
this.$refs.translatedTextarea.select();
this.$refs.translatedTextarea.setSelectionRange(0, 9999999); /* For mobile devices */
document.execCommand("copy");
if (this.copyTextLabel === "Copy text"){
this.copyTextLabel = "Copied";
var self = this;
setTimeout(function(){
self.copyTextLabel = "Copy text";
}, 1500);
}
},
suggestTranslation: function(e) {
e.preventDefault();
this.savedTanslatedText = this.translatedText
this.isSuggesting = true;
},
closeSuggestTranslation: function(e) {
this.translatedText = this.savedTanslatedText
e.preventDefault();
this.isSuggesting = false;
},
sendSuggestion: function(e) {
e.preventDefault();
var self = this;
var request = new XMLHttpRequest();
self.transRequest = request;
var data = new FormData();
data.append("q", self.inputText);
data.append("s", self.translatedText);
data.append("source", self.sourceLang);
data.append("target", self.targetLang);
data.append("api_key", localStorage.getItem("api_key") || "");
request.open('POST', BaseUrl + '/suggest', true);
request.onload = function() {
try{
var res = JSON.parse(this.response);
if (res.success){
M.toast({html: 'Thanks for your correction.'})
self.closeSuggestTranslation(e)
}else{
throw new Error(res.error || "Unknown error");
}
}catch(e){
self.error = e.message;
self.closeSuggestTranslation(e)
}
};
request.onerror = function() {
self.error = "Error while calling /suggest";
self.loadingTranslation = false;
};
request.send(data);
},
deleteText: function(e){
e.preventDefault();
this.inputText = this.translatedText = this.output = "";
},
switchType: function(type) {
this.translationType = type;
},
handleInputFile: function(e) {
this.inputFile = e.target.files[0];
},
removeFile: function(e) {
e.preventDefault()
this.inputFile = false;
this.translatedFileUrl = false;
this.loadingFileTranslation = false;
},
translateFile: function(e) {
e.preventDefault();
let self = this;
let translateFileRequest = new XMLHttpRequest();
translateFileRequest.open("POST", BaseUrl + "/translate_file", true);
let data = new FormData();
data.append("file", this.inputFile);
data.append("source", this.sourceLang);
data.append("target", this.targetLang);
data.append("api_key", localStorage.getItem("api_key") || "");
this.loadingFileTranslation = true
translateFileRequest.onload = function() {
if (translateFileRequest.readyState === 4 && translateFileRequest.status === 200) {
try{
self.loadingFileTranslation = false;
let res = JSON.parse(this.response);
if (res.translatedFileUrl){
self.translatedFileUrl = res.translatedFileUrl;
let link = document.createElement("a");
link.target = "_blank";
link.href = self.translatedFileUrl;
link.click();
}else{
throw new Error(res.error || "Unknown error");
}
}catch(e){
self.error = e.message;
self.loadingFileTranslation = false;
self.inputFile = false;
}
}else{
let res = JSON.parse(this.response);
self.error = res.error || "Unknown error";
self.loadingFileTranslation = false;
self.inputFile = false;
}
}
translateFileRequest.onerror = function() {
self.error = "Error while calling /translate_file";
self.loadingFileTranslation = false;
self.inputFile = false;
};
translateFileRequest.send(data);
}
}
});
});
function getTextWidth(text) {
var canvas = getTextWidth.canvas || (getTextWidth.canvas = document.createElement("canvas"));
var ctx = canvas.getContext("2d");
ctx.font = 'bold 16px sans-serif';
var textWidth = Math.ceil(ctx.measureText(text).width);
return textWidth;
}
function setApiKey(){
var prevKey = localStorage.getItem("api_key") || "";
var newKey = "";
newKey = window.prompt("Type in your API Key. If you need an API key, contact the server operator.", prevKey);
if (newKey === null) newKey = "";
localStorage.setItem("api_key", newKey);
}
// @license-end