2020-12-19 23:40:37 +01:00
<!DOCTYPE html>
< html lang = "en" >
< head >
< meta charset = "UTF-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" >
2020-12-21 17:10:44 +01:00
< title > LibreTranslate - Free and Open Source Machine Translation API< / title >
2021-01-10 09:59:07 +01:00
< link rel = "shortcut icon" href = "{{ url_for('static', filename='favicon.ico') }}" >
2020-12-21 17:10:44 +01:00
< meta name = "description" content = "Free and Open Source Machine Translation API. 100% self-hosted, no limits, no ties to proprietary services. Run your own API server in just a few minutes." >
2020-12-21 01:11:18 +01:00
< meta name = "keywords" content = "translation,api" >
2020-12-20 20:03:53 +01:00
2020-12-21 17:10:44 +01:00
< meta property = "og:title" content = "LibreTranslate - Free and Open Source Machine Translation API" / >
2020-12-21 01:11:18 +01:00
< meta property = "og:type" content = "website" / >
< meta property = "og:url" content = "https://libretranslate.com" / >
< meta property = "og:image" content = "https://user-images.githubusercontent.com/1951843/102724116-32a6df00-42db-11eb-8cc0-129ab39cdfb5.png" / >
2020-12-21 17:10:44 +01:00
< meta property = "og:description" name = "description" class = "swiftype" content = "Free and Open Source Machine Translation API. 100% self-hosted, no limits, no ties to proprietary services. Run your own API server in just a few minutes." / >
2020-12-20 20:03:53 +01:00
2021-05-16 16:42:58 +02:00
< script src = "{{ url_for('static', filename='js/vue@2.js') }}" > < / script >
2020-12-19 23:40:37 +01:00
<!-- Compiled and minified CSS -->
2021-05-16 16:42:58 +02:00
< link rel = "stylesheet" href = "{{ url_for('static', filename='css/materialize.min.css') }}" >
2021-06-02 22:54:06 +02:00
< link rel = "stylesheet" href = "{{ url_for('static', filename='css/material-icons.css') }}" / >
< link rel = "stylesheet" href = "{{ url_for('static', filename='css/prism.min.css') }}" / >
2021-07-05 15:19:46 +02:00
< link rel = "stylesheet" href = "{{ url_for('static', filename='css/main.css') }}?t=2" / >
2020-12-20 20:03:53 +01:00
{% if gaId %}
<!-- Global site tag (gtag.js) - Google Analytics -->
< script async src = "https://www.googletagmanager.com/gtag/js?id={{ gaId }}" > < / script >
< script >
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '{{ gaId }}');
< / script >
2021-06-02 22:54:06 +02:00
{% endif %}
2020-12-19 23:40:37 +01:00
< / head >
2021-06-02 22:54:06 +02:00
2020-12-19 23:40:37 +01:00
< body >
2021-06-02 22:54:06 +02:00
< header >
< nav class = "blue lighten-1" role = "navigation" >
< div class = "nav-wrapper container" >
< a id = "logo-container" href = "/" class = "brand-logo" >
2021-06-12 18:18:21 +02:00
< img src = "{{ url_for('static', filename='icon.svg') }}" class = "logo" > LibreTranslate
2021-06-02 22:54:06 +02:00
< / a >
< ul class = "right hide-on-med-and-down" >
< li > < a href = "/docs" > API Docs< / a > < / li >
2021-06-12 18:18:21 +02:00
< li > < a href = "https://github.com/LibreTranslate/LibreTranslate" rel = "noopener noreferrer" > GitHub< / a > < / li >
2021-06-02 22:54:06 +02:00
{% if api_keys %}
< li > < a href = "javascript:setApiKey()" title = "Set API Key" > < i class = "material-icons" > vpn_key< / i > < / a > < / li >
{% endif %}
< / ul >
< ul id = "nav-mobile" class = "sidenav" >
< li > < a href = "/docs" > API Docs< / a > < / li >
2021-06-12 18:18:21 +02:00
< li > < a href = "https://github.com/LibreTranslate/LibreTranslate" rel = "noopener noreferrer" > GitHub< / a > < / li >
2021-06-02 22:54:06 +02:00
{% if api_keys %}
< li > < a href = "javascript:setApiKey()" title = "Set API Key" > < i class = "material-icons" > vpn_key< / i > < / a > < / li >
{% endif %}
< / ul >
< a href = "#" data-target = "nav-mobile" class = "sidenav-trigger" > < i class = "material-icons" > menu< / i > < / a >
< / div >
< / nav >
< / header >
< main id = "app" >
< div class = "section no-pad-bot center" v-if = "loading" >
< div class = "container" >
< div class = "row" >
< div class = "preloader-wrapper active" >
< div class = "spinner-layer spinner-blue-only" >
< div class = "circle-clipper left" >
< div class = "circle" > < / div >
< / div > < div class = "gap-patch" >
< div class = "circle" > < / div >
< / div > < div class = "circle-clipper right" >
< div class = "circle" > < / div >
< / div >
< / div >
< / div >
2020-12-20 17:55:56 +01:00
< / div >
< / div >
< / div >
2021-06-02 22:54:06 +02:00
< div v-else-if = "error" >
< div class = "section no-pad-bot" >
< div class = "container" >
< div class = "row" >
< div class = "col s12 m7" >
< div class = "card horizontal" >
< div class = "card-stacked" >
< div class = "card-content" >
< i class = "material-icons" > warning< / i > < p > [[ error ]]< / p >
< / div >
< div class = "card-action" >
< a href = "#" @ click = "dismissError" > Dismiss< / a >
< / div >
< / div >
< / div >
< / div >
< / div >
< / div >
2020-12-20 19:45:34 +01:00
< / div >
< / div >
2021-06-02 22:54:06 +02:00
< div v-else >
< div class = "section no-pad-bot" >
< div class = "container" >
< div class = "row" >
< h3 class = "header center" > Translation API< / h3 >
2020-12-20 17:55:56 +01:00
< form class = "col s12" >
2021-06-02 22:54:06 +02:00
< div class = "row mb-0" >
< div class = "col s6 language-select" >
< span > Translate from< / span >
< select class = "browser-default" v-model = "sourceLang" ref = "sourceLangDropdown" @ change = "handleInput" >
< template v-for = "option in langs" >
< option :value = "option.code" > [[ option.name ]]< / option >
< / template >
< / select >
< / div >
< div class = "col s6 language-select" >
< a href = "javascript:void(0)" @ click = "swapLangs" class = "btn-switch-language" >
< i class = "material-icons" > swap_horiz< / i >
2021-06-12 17:28:35 +02:00
< / a >
2021-06-02 22:54:06 +02:00
< span > Translate into< / span >
2021-06-13 19:11:58 +02:00
< select class = "browser-default" v-model = "targetLang" ref = "targetLangDropdown" @ change = "handleInput" >
2021-06-02 22:54:06 +02:00
< template v-for = "option in langs" >
< option v-if = "option.code !== 'auto'" :value = "option.code" > [[ option.name ]]< / option >
< / template >
< / select >
2020-12-20 19:45:34 +01:00
< / div >
2020-12-20 17:55:56 +01:00
< / div >
2021-06-02 22:54:06 +02:00
< div class = "row" >
< div class = "input-field textarea-container col s6" >
2021-06-11 20:30:09 +02:00
< label for = "textarea1" class = "sr-only" >
Text to translate
< / label >
2021-09-04 13:24:05 +02:00
< textarea id = "textarea1" v-model = "inputText" @ input = "handleInput" ref = "inputTextarea" dir = "auto" > < / textarea >
2021-06-02 22:54:06 +02:00
< button class = "btn-delete-text" title = "Delete text" @ click = "deleteText" >
< i class = "material-icons" > close< / i >
< / button >
< div class = "characters-limit-container" v-if = "charactersLimit !== -1" >
< label > [[ inputText.length ]] / [[ charactersLimit ]]< / label >
< / div >
< / div >
< div class = "input-field textarea-container col s6" >
2021-06-11 20:30:09 +02:00
< label for = "textarea2" class = "sr-only" >
Translated text
< / label >
2021-09-04 13:29:29 +02:00
< textarea id = "textarea2" v-model = "translatedText" ref = "translatedTextarea" dir = "auto" readonly > < / textarea >
2021-06-11 20:30:09 +02:00
< button class = "btn-copy-translated" @ click = "copyText" >
< span > [[ copyTextLabel ]]< / span > < i class = "material-icons" > content_copy< / i >
< / button >
< div class = "position-relative" >
< div class = "progress translate" v-if = "loadingTranslation" >
< div class = "indeterminate" > < / div >
< / div >
2021-06-02 22:54:06 +02:00
< / div >
< / div >
2021-01-18 16:06:43 +01:00
< / div >
2020-12-20 17:55:56 +01:00
< / form >
2021-06-02 22:54:06 +02:00
< / div >
< / div >
< / div >
2021-06-11 20:05:59 +02:00
< div class = "section no-pad-bot" >
2021-06-02 22:54:06 +02:00
< div class = "container" >
< div class = "row center" >
< div class = "col s12 m12" >
2020-12-20 17:55:56 +01:00
< div class = "row center" >
2020-12-20 19:45:34 +01:00
< div class = "col s12 m12 l6 left-align" >
2021-06-13 19:11:58 +02:00
< p class = "mb-0" > Request< / p >
< pre class = "code mt-0" > < code class = "language-javascript" v-html = "$options.filters.highlight(requestCode)" >
2021-06-02 22:54:06 +02:00
< / code > < / pre >
2020-12-20 17:55:56 +01:00
< / div >
2020-12-20 19:45:34 +01:00
< div class = "col s12 m12 l6 left-align" >
2021-06-13 19:11:58 +02:00
< p class = "mb-0" > Response< / p >
< pre class = "code mt-0" > < code class = "language-javascript" v-html = "$options.filters.highlight(output)" >
2021-06-02 22:54:06 +02:00
< / code > < / pre >
2020-12-20 17:55:56 +01:00
< / div >
< / div >
2021-06-02 22:54:06 +02:00
< / div >
2020-12-20 17:55:56 +01:00
< / div >
< / div >
2021-06-02 22:54:06 +02:00
< / div >
{% if web_version %}
2021-06-11 20:05:59 +02:00
< div class = "section no-pad-bot" >
2021-06-02 22:54:06 +02:00
< div class = "container" >
< div class = "row center" >
< div class = "col s12 m12" >
< h3 class = "header" > Open Source Machine Translation< / h3 >
< h4 class = "header" > 100% Self-Hosted. No Limits. No Ties to Proprietary Services.< / h4 >
2021-06-12 18:18:21 +02:00
< br / > < a class = "waves-effect waves-light btn btn-large" href = "https://github.com/LibreTranslate/LibreTranslate" rel = "noopener noreferrer" > < i class = "material-icons left" > cloud_download< / i > Download< / a >
2021-06-02 22:54:06 +02:00
< br / > < br / > < br / >
< / div >
< / div >
< / div >
< / div >
{% endif %}
2020-12-21 05:47:24 +01:00
< / div >
2021-06-02 22:54:06 +02:00
< / main >
< footer class = "page-footer blue darken-3" >
2020-12-20 17:55:56 +01:00
< div class = "container" >
2021-06-02 22:54:06 +02:00
< div class = "row" >
< div class = "col l12 s12" >
< h5 class = "white-text" > LibreTranslate< / h5 >
< p class = "grey-text text-lighten-4" > Free and Open Source Machine Translation API< / p >
2021-06-11 20:05:59 +02:00
< p > < a class = "grey-text text-lighten-4" href = "https://www.gnu.org/licenses/agpl-3.0.en.html" rel = "noopener noreferrer" > License: AGPLv3< / a > < / p >
2021-06-02 22:54:06 +02:00
< p > < a class = "grey-text text-lighten-4" href = "/javascript-licenses" rel = "jslicense" > JavaScript license information< / a > < / p >
{% if web_version %}
< p >
2021-06-12 18:22:43 +02:00
This public API should be used for testing, personal or infrequent use. If you're going to run an application in production, please < a href = "https://github.com/LibreTranslate/LibreTranslate" class = "grey-text text-lighten-4" style = "text-decoration: underline;" rel = "noopener noreferrer" > host your own server< / a > or < a class = "grey-text text-lighten-4" href = "https://github.com/LibreTranslate/LibreTranslate#mirrors" style = "text-decoration: underline;" rel = "noopener noreferrer" > get an API key< / a > .
2021-06-02 22:54:06 +02:00
< / p >
{% endif %}
< / div >
< / div >
2020-12-20 17:55:56 +01:00
< / div >
2021-06-02 22:54:06 +02:00
< div class = "footer-copyright center" >
< p >
2021-06-12 18:18:21 +02:00
Made with ❤ by < a class = "grey-text text-lighten-3" href = "https://github.com/LibreTranslate/LibreTranslate/graphs/contributors" rel = "noopener noreferrer" > LibreTranslate Contributors< / a > and powered by < a class = "grey-text text-lighten-3" href = "https://github.com/argosopentech/argos-translate/" rel = "noopener noreferrer" > Argos Translate< / a >
2021-06-02 22:54:06 +02:00
< / p >
< / div >
< / footer >
2021-01-10 09:07:56 +01:00
2021-06-02 22:54:06 +02:00
< script src = "{{ url_for('static', filename='js/materialize.min.js') }}" > < / script >
< script >
// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109& dn=agpl-3.0.txt AGPL-3.0
window.Prism = window.Prism || {};
window.Prism.manual = true;
// @license-end
< / script >
2021-01-10 09:07:56 +01:00
2021-06-02 22:54:06 +02:00
< script src = "{{ url_for('static', filename='js/prism.min.js') }}" > < / script >
2021-01-10 09:07:56 +01:00
2021-06-02 22:54:06 +02:00
< script >
// @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;
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,
translatedText: "",
output: "",
charactersLimit: -1,
copyTextLabel: "Copy text"
},
mounted: function(){
var self = this;
var requestSettings = new XMLHttpRequest();
requestSettings.open('GET', BaseUrl + '/frontend/settings', true);
2021-01-10 09:07:56 +01:00
2021-06-02 22:54:06 +02:00
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;
}else {
self.error = "Cannot load /frontend/settings";
self.loading = false;
2020-12-20 17:55:56 +01:00
}
2021-06-02 22:54:06 +02:00
};
2020-12-20 17:55:56 +01:00
2021-06-02 22:54:06 +02:00
requestSettings.onerror = function() {
self.error = "Error while calling /frontend/settings";
2020-12-20 17:55:56 +01:00
self.loading = false;
2021-06-02 22:54:06 +02:00
};
requestSettings.send();
var requestLanguages = new XMLHttpRequest();
requestLanguages.open('GET', BaseUrl + '/languages', true);
2020-12-20 19:45:34 +01:00
2021-06-02 22:54:06 +02:00
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;
2020-12-20 19:45:34 +01:00
}
2021-06-02 22:54:06 +02:00
};
2021-01-19 17:51:10 +01:00
2021-06-02 22:54:06 +02:00
requestLanguages.onerror = function() {
self.error = "Error while calling /languages";
self.loading = false;
};
2021-01-19 17:51:10 +01:00
2021-06-02 22:54:06 +02:00
requestLanguages.send();
},
updated: function(){
M.FormSelect.init(this.$refs.sourceLangDropdown);
M.FormSelect.init(this.$refs.targetLangDropdown);
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);
}
2021-06-12 17:28:35 +02:00
// Update "selected" attribute (to overcome a vue.js limitation)
2021-06-13 19:11:58 +02:00
// but properly display checkmarks on supported browsers.
// Also change the < select > width value depending on the < option > length
2021-06-12 17:28:35 +02:00
for (var i = 0; i < this. $ refs . sourceLangDropdown . children . length ; i + + ) {
var el = this.$refs.sourceLangDropdown.children[i];
2021-06-13 19:11:58 +02:00
if (el.value === this.sourceLang){
el.setAttribute('selected', '');
this.$refs.sourceLangDropdown.style.width = getTextWidth(el.text) + 24 + 'px';
}else{
el.removeAttribute('selected');
}
2021-06-12 17:28:35 +02:00
}
for (var i = 0; i < this. $ refs . targetLangDropdown . children . length ; i + + ) {
var el = this.$refs.targetLangDropdown.children[i];
2021-06-13 19:11:58 +02:00
if (el.value === this.targetLang){
el.setAttribute('selected', '');
this.$refs.targetLangDropdown.style.width = getTextWidth(el.text) + 24 + 'px';
}else{
el.removeAttribute('selected');
}
2021-06-12 17:28:35 +02:00
}
2021-06-13 19:11:58 +02:00
2021-06-02 22:54:06 +02:00
},
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) + '"',
' }),',
' headers: { "Content-Type": "application/json" }',
'});',
'',
'console.log(await res.json());'].join("\n");
2021-01-18 16:06:43 +01:00
}
},
2021-06-02 22:54:06 +02:00
filters: {
escape: function(v){
return v.replace('"', '\\\"');
},
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(){
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){
if (this.timeout) clearTimeout(this.timeout);
this.timeout = null;
if (this.inputText === ""){
this.translatedText = "";
this.output = "";
this.abortPreviousTransRequest();
this.loadingTranslation = false;
return;
}
2021-01-17 17:58:22 +01:00
2021-06-02 22:54:06 +02:00
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("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);
2021-06-11 21:03:38 +02:00
}, '{{ frontendTimeout }}');
2021-06-02 22:54:06 +02:00
},
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);
}
},
deleteText: function(e){
e.preventDefault();
2021-06-11 20:13:27 +02:00
this.inputText = this.translatedText = this.output = "";
2021-06-02 22:54:06 +02:00
}
2021-01-18 16:06:43 +01:00
}
2021-06-02 22:54:06 +02:00
});
2021-01-17 17:58:22 +01:00
2021-06-02 22:54:06 +02:00
});
2021-02-15 19:30:28 +01:00
2021-06-13 19:11:58 +02: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;
}
2021-06-02 22:54:06 +02:00
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 = "";
2021-02-15 19:30:28 +01:00
2021-06-02 22:54:06 +02:00
localStorage.setItem("api_key", newKey);
}
2021-06-13 19:11:58 +02:00
2021-06-02 22:54:06 +02:00
// @license-end
< / script >
2020-12-19 23:40:37 +01:00
< / body >
2021-01-17 17:58:22 +01:00
< / html >