2021-10-09 16:16:50 +00:00
// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-3.0
// API host/endpoint
2023-01-06 16:50:51 +00:00
var BaseUrl = window.location.protocol + "//" + window.location.host + "{{ url_prefix }}" ;
2021-10-09 16:16:50 +00:00
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,
2023-07-08 10:58:26 +00:00
2022-10-01 12:21:50 +00:00
detectedLangText: "",
2021-10-09 16:16:50 +00:00
2023-01-05 18:12:35 +00:00
copyTextLabel: {{ _e("Copy text") }},
2021-10-09 16:16:50 +00:00
suggestions: false,
isSuggesting: false,
2021-10-24 11:15:36 +00:00
2021-10-24 14:57:45 +00:00
supportedFilesFormat : [],
2021-10-24 15:38:53 +00:00
translationType: "text",
2021-10-24 16:27:37 +00:00
inputFile: false,
loadingFileTranslation: false,
2021-10-25 09:46:23 +00:00
translatedFileUrl: false,
2021-10-25 15:24:03 +00:00
filesTranslation: true,
2023-03-09 18:59:25 +00:00
frontendTimeout: 500,
apiSecret: "{{ api_secret }}"
2021-10-09 16:16:50 +00:00
},
2022-06-21 18:33:34 +00:00
mounted: function() {
const self = this;
2021-10-09 16:16:50 +00:00
2022-06-21 18:33:34 +00:00
const settingsRequest = new XMLHttpRequest();
settingsRequest.open("GET", BaseUrl + "/frontend/settings", true);
const langsRequest = new XMLHttpRequest();
langsRequest.open("GET", BaseUrl + "/languages", true);
settingsRequest.onload = function() {
2021-10-09 16:16:50 +00:00
if (this.status >= 200 && this.status < 400) {
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;
2021-10-24 14:57:45 +00:00
self.supportedFilesFormat = self.settings.supportedFilesFormat;
2021-10-25 15:24:03 +00:00
self.filesTranslation = self.settings.filesTranslation;
2022-01-31 15:39:55 +00:00
self.frontendTimeout = self.settings.frontendTimeout;
2022-06-21 18:33:34 +00:00
if (langsRequest.response) {
handleLangsResponse(self, langsRequest);
} else {
langsRequest.onload = function() {
handleLangsResponse(self, this);
}
}
} else {
2023-01-05 18:12:35 +00:00
self.error = {{ _e("Cannot load %(url)s", url="/frontend/settings") }};
2021-10-09 16:16:50 +00:00
self.loading = false;
}
};
2022-06-21 18:33:34 +00:00
settingsRequest.onerror = function() {
2023-01-05 18:12:35 +00:00
self.error = {{ _e("Cannot load %(url)s", url="/frontend/settings") }};
2021-10-09 16:16:50 +00:00
self.loading = false;
};
2022-06-21 18:33:34 +00:00
langsRequest.onerror = function() {
2023-01-05 18:12:35 +00:00
self.error = {{ _e("Cannot load %(url)s", url="/languages") }};
2021-10-09 16:16:50 +00:00
self.loading = false;
};
2022-06-21 18:33:34 +00:00
settingsRequest.send();
langsRequest.send();
2021-10-09 16:16:50 +00:00
},
updated: function(){
2022-12-20 17:25:31 +00:00
if (this.isSuggesting) return;
2021-10-09 16:16:50 +00:00
M.FormSelect.init(this.$refs.sourceLangDropdown);
M.FormSelect.init(this.$refs.targetLangDropdown);
2022-06-21 18:33:34 +00:00
2021-10-26 21:13:51 +00:00
if (this.$refs.inputTextarea){
2022-10-02 12:50:58 +00:00
this.$refs.inputTextarea.focus()
2021-10-26 21:13:51 +00:00
if (this.inputText === ""){
this.$refs.inputTextarea.style.height = this.inputTextareaHeight + "px";
this.$refs.translatedTextarea.style.height = this.inputTextareaHeight + "px";
2022-06-21 18:33:34 +00:00
} else{
2021-10-26 21:13:51 +00:00
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";
}
2021-10-09 16:16:50 +00:00
}
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
2022-06-21 18:33:34 +00:00
if (this.$refs.sourceLangDropdown) {
updateSelectedAttribute(this.$refs.sourceLangDropdown, this.sourceLang);
2021-10-09 16:16:50 +00:00
}
2021-10-26 21:13:51 +00:00
2022-06-21 18:33:34 +00:00
if (this.$refs.targetLangDropdown) {
updateSelectedAttribute(this.$refs.targetLangDropdown, this.targetLang);
2021-10-09 16:16:50 +00:00
}
},
computed: {
requestCode: function(){
return ['const res = await fetch("' + this.BaseUrl + '/translate", {',
' method: "POST",',
' body: JSON.stringify({',
2021-12-09 13:31:48 +00:00
' q: ' + this.$options.filters.escape(this.inputText) + ',',
' source: ' + this.$options.filters.escape(this.sourceLang) + ',',
' target: ' + this.$options.filters.escape(this.targetLang) + ',',
2022-06-21 20:56:48 +00:00
' format: "' + (this.isHtml ? "html" : "text") + '",',
' api_key: "' + (localStorage.getItem("api_key") || "") + '"',
2021-10-09 16:16:50 +00:00
' }),',
' headers: { "Content-Type": "application/json" }',
'});',
'',
'console.log(await res.json());'].join("\n");
},
2021-10-24 14:57:45 +00:00
supportedFilesFormatFormatted: function() {
return this.supportedFilesFormat.join(', ');
},
2021-10-09 16:16:50 +00:00
isHtml: function(){
return htmlRegex.test(this.inputText);
},
2022-12-09 21:36:12 +00:00
canSendSuggestion: function(){
2021-10-09 16:16:50 +00:00
return this.translatedText.trim() !== "" && this.translatedText !== this.savedTanslatedText;
2022-12-10 05:21:38 +00:00
},
targetLangs: function(){
if (!this.sourceLang) return this.langs;
else{
var lang = this.langs.find(l => l.code === this.sourceLang);
if (!lang) return this.langs;
return lang.targets.map(t => this.langs.find(l => l.code === t));
}
2021-10-09 16:16:50 +00:00
}
},
filters: {
escape: function(v){
2021-12-09 13:31:48 +00:00
return JSON.stringify(v);
2021-10-09 16:16:50 +00:00
},
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){
2022-12-10 05:21:38 +00:00
this.closeSuggestTranslation(e);
// Make sure that we can swap
// by checking that the current target language
// has source language as target
var tgtLang = this.langs.find(l => l.code === this.targetLang);
if (tgtLang.targets.indexOf(this.sourceLang) === -1) return; // Not supported
2021-10-09 16:16:50 +00:00
var t = this.sourceLang;
this.sourceLang = this.targetLang;
this.targetLang = t;
this.inputText = this.translatedText;
this.translatedText = "";
2022-10-02 12:40:25 +00:00
this.handleInput(e);
2021-10-09 16:16:50 +00:00
},
dismissError: function(){
this.error = '';
},
2022-02-07 16:58:30 +00:00
getQueryParam: function (key) {
const params = new URLSearchParams(window.location.search);
return params.get(key)
},
updateQueryParam: function (key, value) {
let searchParams = new URLSearchParams(window.location.search)
searchParams.set(key, value);
let newRelativePathQuery = window.location.pathname + '?' + searchParams.toString();
history.pushState(null, '', newRelativePathQuery);
},
2021-10-09 16:16:50 +00:00
handleInput: function(e){
this.closeSuggestTranslation(e)
2022-02-07 16:58:30 +00:00
this.updateQueryParam('source', this.sourceLang)
this.updateQueryParam('target', this.targetLang)
this.updateQueryParam('q', encodeURI(this.inputText))
2021-10-09 16:16:50 +00:00
if (this.timeout) clearTimeout(this.timeout);
this.timeout = null;
2022-10-01 12:21:50 +00:00
this.detectedLangText = "";
2023-07-08 10:58:26 +00:00
2021-10-09 16:16:50 +00:00
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") || "");
2023-03-09 18:59:25 +00:00
if (self.apiSecret) data.append("secret", self.apiSecret);
2021-10-09 16:16:50 +00:00
request.open('POST', BaseUrl + '/translate', true);
request.onload = function() {
try{
2023-03-09 18:59:25 +00:00
{% if api_secret != "" %}
2023-03-10 03:00:27 +00:00
if (this.status === 400){
if (self.refreshOnce()) return;
2023-03-09 18:59:25 +00:00
}
{% endif %}
2023-07-08 10:58:26 +00:00
2021-10-09 16:16:50 +00:00
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);
2022-10-01 15:12:05 +00:00
if(self.sourceLang == "auto" && res.detectedLanguage !== undefined){
2022-10-02 12:20:20 +00:00
let lang = self.langs.find(l => l.code === res.detectedLanguage.language)
self.detectedLangText = ": " + (lang !== undefined ? lang.name : res.detectedLanguage.language) + " (" + res.detectedLanguage.confidence + "%)";
2022-10-01 12:21:50 +00:00
}
2022-06-21 18:33:34 +00:00
} else{
2023-01-05 18:12:35 +00:00
throw new Error(res.error || {{ _e("Unknown error") }});
2021-10-09 16:16:50 +00:00
}
2022-06-21 18:33:34 +00:00
} catch (e) {
2021-10-09 16:16:50 +00:00
self.error = e.message;
self.loadingTranslation = false;
}
};
request.onerror = function() {
2023-01-05 18:12:35 +00:00
self.error = {{ _e("Cannot load %(url)s", url="/translate") }};
2021-10-09 16:16:50 +00:00
self.loadingTranslation = false;
};
request.send(data);
2022-01-31 15:19:14 +00:00
}, self.frontendTimeout);
2021-10-09 16:16:50 +00:00
},
copyText: function(e){
e.preventDefault();
this.$refs.translatedTextarea.select();
this.$refs.translatedTextarea.setSelectionRange(0, 9999999); /* For mobile devices */
document.execCommand("copy");
2023-01-06 18:07:07 +00:00
if (this.copyTextLabel === {{ _e("Copy text") }}){
2023-01-05 18:12:35 +00:00
this.copyTextLabel = {{ _e("Copied") }};
2021-10-09 16:16:50 +00:00
var self = this;
setTimeout(function(){
2023-01-05 18:12:35 +00:00
self.copyTextLabel = {{ _e("Copy text") }};
2021-10-09 16:16:50 +00:00
}, 1500);
}
},
suggestTranslation: function(e) {
e.preventDefault();
this.savedTanslatedText = this.translatedText
this.isSuggesting = true;
2022-12-20 17:25:31 +00:00
this.$nextTick(() => {
this.$refs.translatedTextarea.focus();
});
2021-10-09 16:16:50 +00:00
},
closeSuggestTranslation: function(e) {
2022-10-02 12:40:25 +00:00
if(this.isSuggesting) {
e.preventDefault();
2022-12-20 17:25:31 +00:00
// this.translatedText = this.savedTanslatedText
2022-10-02 12:40:25 +00:00
}
2021-10-09 16:16:50 +00:00
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);
2021-10-31 07:27:36 +00:00
data.append("api_key", localStorage.getItem("api_key") || "");
2021-10-09 16:16:50 +00:00
request.open('POST', BaseUrl + '/suggest', true);
request.onload = function() {
try{
var res = JSON.parse(this.response);
if (res.success){
2023-01-05 18:12:35 +00:00
M.toast({html: {{ _e("Thanks for your correction. Note the suggestion will not take effect right away.") }} })
2021-10-09 16:16:50 +00:00
self.closeSuggestTranslation(e)
}else{
2023-01-05 18:12:35 +00:00
throw new Error(res.error || {{ _e("Unknown error") }});
2021-10-09 16:16:50 +00:00
}
}catch(e){
self.error = e.message;
self.closeSuggestTranslation(e)
}
};
request.onerror = function() {
2023-01-05 18:12:35 +00:00
self.error = {{ _e("Cannot load %(url)s", url="/suggest") }};
2021-10-09 16:16:50 +00:00
self.loadingTranslation = false;
};
request.send(data);
},
deleteText: function(e){
e.preventDefault();
this.inputText = this.translatedText = this.output = "";
2022-04-03 16:08:10 +00:00
this.$refs.inputTextarea.focus();
2021-10-24 11:15:36 +00:00
},
switchType: function(type) {
this.translationType = type;
2021-10-24 15:38:53 +00:00
},
handleInputFile: function(e) {
this.inputFile = e.target.files[0];
},
removeFile: function(e) {
e.preventDefault()
this.inputFile = false;
2021-10-25 09:46:23 +00:00
this.translatedFileUrl = false;
2021-10-25 08:50:55 +00:00
this.loadingFileTranslation = false;
2021-10-24 15:38:53 +00:00
},
2023-03-10 03:00:27 +00:00
refreshOnce: function(){
var lastRefreshed = parseInt(localStorage.getItem("refreshed") || 0);
var now = new Date().getTime();
if (now - lastRefreshed > 1000 * 60 * 1){
localStorage.setItem("refreshed", now);
window.location.reload();
return true;
}
},
2021-10-24 15:38:53 +00:00
translateFile: function(e) {
e.preventDefault();
2021-10-24 16:27:37 +00:00
2021-10-25 08:50:55 +00:00
let self = this;
2021-10-24 16:27:37 +00:00
let translateFileRequest = new XMLHttpRequest();
translateFileRequest.open("POST", BaseUrl + "/translate_file", true);
2021-10-25 08:33:49 +00:00
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") || "");
2023-03-09 18:59:25 +00:00
if (self.apiSecret) data.append("secret", self.apiSecret);
2021-10-24 16:27:37 +00:00
this.loadingFileTranslation = true
2021-10-25 08:50:55 +00:00
translateFileRequest.onload = function() {
2021-10-24 17:14:09 +00:00
if (translateFileRequest.readyState === 4 && translateFileRequest.status === 200) {
2021-10-25 08:50:55 +00:00
try{
2023-03-09 18:59:25 +00:00
{% if api_secret != "" %}
2023-03-10 03:00:27 +00:00
if (this.status === 400){
if (self.refreshOnce()) return;
2023-03-09 18:59:25 +00:00
}
{% endif %}
2021-10-25 08:50:55 +00:00
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();
2023-07-08 10:58:26 +00:00
handleNotification("Translation Complete", "Finished translating " + self.inputFile.name + ".");
2021-10-25 08:50:55 +00:00
}else{
2023-01-05 18:12:35 +00:00
throw new Error(res.error || {{ _e("Unknown error") }});
2021-10-25 08:50:55 +00:00
}
}catch(e){
self.error = e.message;
self.loadingFileTranslation = false;
self.inputFile = false;
2023-07-08 10:58:26 +00:00
handleNotification("Translation Failed", e.message);
2021-10-25 08:50:55 +00:00
}
2021-10-26 21:13:51 +00:00
}else{
let res = JSON.parse(this.response);
2023-01-05 18:12:35 +00:00
self.error = res.error || {{ _e("Unknown error") }};
2021-10-26 21:13:51 +00:00
self.loadingFileTranslation = false;
self.inputFile = false;
2023-07-08 10:58:26 +00:00
handleNotification("Translation Failed", self.error);
2021-10-24 16:27:37 +00:00
}
}
2021-10-25 08:50:55 +00:00
translateFileRequest.onerror = function() {
2023-07-08 10:58:26 +00:00
const message = {{ _e("Cannot load %(url)s", url="/translate_file") }};
self.error = message;
2021-10-25 08:50:55 +00:00
self.loadingFileTranslation = false;
self.inputFile = false;
2023-07-08 10:58:26 +00:00
handleNotification("Translation Failed", message);
2021-10-25 08:50:55 +00:00
};
2021-10-25 08:33:49 +00:00
translateFileRequest.send(data);
2023-07-08 10:58:26 +00:00
Notification.requestPermission();
2021-10-09 16:16:50 +00:00
}
}
});
});
2022-06-21 18:33:34 +00:00
/**
* @param {object} self
* @param {XMLHttpRequest} response
*/
function handleLangsResponse(self, response) {
if (response.status >= 200 && response.status < 400) {
self.langs = JSON.parse(response.response);
if (self.langs.length === 0){
self.loading = false;
2023-01-05 18:12:35 +00:00
self.error = {{ _e("No languages available. Did you install the models correctly?") }};
2022-06-21 18:33:34 +00:00
return;
}
2023-01-05 18:12:35 +00:00
self.langs.push({ name: {{ _e("Auto Detect") }}, code: "auto", targets: self.langs.map(l => l.code)})
2022-06-21 18:33:34 +00:00
const sourceLanguage = self.langs.find(l => l.code === self.getQueryParam("source"))
const targetLanguage = self.langs.find(l => l.code === self.getQueryParam("target"))
if (sourceLanguage) {
self.sourceLang = sourceLanguage.code
}
if (targetLanguage) {
self.targetLang = targetLanguage.code
}
const defaultText = self.getQueryParam("q")
if (defaultText) {
self.inputText = decodeURI(defaultText)
2022-10-02 12:31:57 +00:00
self.handleInput(new Event('none'))
2022-06-21 18:33:34 +00:00
}
} else {
2023-01-05 18:12:35 +00:00
self.error = {{ _e("Cannot load %(url)s", url="/languages") }};
2022-06-21 18:33:34 +00:00
}
self.loading = false;
}
2023-07-08 10:58:26 +00:00
/**
* @param {string} title
* @param {string} body
*/
function handleNotification(title, body) {
new Notification(title, {
body,
tag: 'libretranslate'
});
}
2022-06-21 18:33:34 +00:00
/**
* @param {object} langDropdown
* @param {string} lang
*/
function updateSelectedAttribute(langDropdown, lang) {
for (const child of langDropdown.children) {
if (child.value === lang){
child.setAttribute('selected', '');
langDropdown.style.width = getTextWidth(child.text) + 24 + 'px';
} else{
child.removeAttribute('selected');
}
}
}
2021-10-09 16:16:50 +00:00
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 = "";
2023-01-15 16:42:59 +00:00
newKey = window.prompt({{ _e("Type in your API Key. If you need an API key, %(instructions)s", instructions=_("press the \"Get API Key\" link.") if get_api_key_link else _("contact the server operator.")) }}, prevKey);
2021-10-09 16:16:50 +00:00
if (newKey === null) newKey = "";
localStorage.setItem("api_key", newKey);
}
2021-12-09 13:31:48 +00:00
// @license-end