mirror of
https://github.com/LibreTranslate/LibreTranslate.git
synced 2024-11-25 01:11:00 +00:00
commit
994f319f04
6 changed files with 63 additions and 36 deletions
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
||||||
1.3.7
|
1.3.8
|
||||||
|
|
|
@ -8,8 +8,8 @@ from timeit import default_timer
|
||||||
|
|
||||||
import argostranslatefiles
|
import argostranslatefiles
|
||||||
from argostranslatefiles import get_supported_formats
|
from argostranslatefiles import get_supported_formats
|
||||||
from flask import (Flask, abort, jsonify, render_template, request, send_file,
|
from flask import (abort, Blueprint, Flask, jsonify, render_template, request,
|
||||||
url_for, Response)
|
Response, send_file, url_for)
|
||||||
from flask_swagger import swagger
|
from flask_swagger import swagger
|
||||||
from flask_swagger_ui import get_swaggerui_blueprint
|
from flask_swagger_ui import get_swaggerui_blueprint
|
||||||
from translatehtml import translate_html
|
from translatehtml import translate_html
|
||||||
|
@ -106,10 +106,10 @@ def create_app(args):
|
||||||
|
|
||||||
from libretranslate.language import load_languages
|
from libretranslate.language import load_languages
|
||||||
|
|
||||||
app = Flask(__name__)
|
SWAGGER_URL = args.url_prefix + "/docs" # Swagger UI (w/o trailing '/')
|
||||||
|
API_URL = args.url_prefix + "/spec"
|
||||||
|
|
||||||
if args.debug:
|
bp = Blueprint('Main app', __name__)
|
||||||
app.config["TEMPLATES_AUTO_RELOAD"] = True
|
|
||||||
|
|
||||||
if not args.disable_files_translation:
|
if not args.disable_files_translation:
|
||||||
remove_translated_files.setup(get_upload_dir())
|
remove_translated_files.setup(get_upload_dir())
|
||||||
|
@ -161,7 +161,7 @@ def create_app(args):
|
||||||
from flask_limiter import Limiter
|
from flask_limiter import Limiter
|
||||||
|
|
||||||
limiter = Limiter(
|
limiter = Limiter(
|
||||||
app,
|
bp,
|
||||||
key_func=get_remote_address,
|
key_func=get_remote_address,
|
||||||
default_limits=get_routes_limits(
|
default_limits=get_routes_limits(
|
||||||
args.req_limit, args.daily_req_limit, api_keys_db
|
args.req_limit, args.daily_req_limit, api_keys_db
|
||||||
|
@ -181,7 +181,7 @@ def create_app(args):
|
||||||
if args.metrics:
|
if args.metrics:
|
||||||
from prometheus_client import CONTENT_TYPE_LATEST, Summary, Gauge, CollectorRegistry, multiprocess, generate_latest
|
from prometheus_client import CONTENT_TYPE_LATEST, Summary, Gauge, CollectorRegistry, multiprocess, generate_latest
|
||||||
|
|
||||||
@app.route("/metrics")
|
@bp.route("/metrics")
|
||||||
@limiter.exempt
|
@limiter.exempt
|
||||||
def prometheus_metrics():
|
def prometheus_metrics():
|
||||||
if args.metrics_auth_token:
|
if args.metrics_auth_token:
|
||||||
|
@ -252,24 +252,24 @@ def create_app(args):
|
||||||
else:
|
else:
|
||||||
return func
|
return func
|
||||||
|
|
||||||
@app.errorhandler(400)
|
@bp.errorhandler(400)
|
||||||
def invalid_api(e):
|
def invalid_api(e):
|
||||||
return jsonify({"error": str(e.description)}), 400
|
return jsonify({"error": str(e.description)}), 400
|
||||||
|
|
||||||
@app.errorhandler(500)
|
@bp.errorhandler(500)
|
||||||
def server_error(e):
|
def server_error(e):
|
||||||
return jsonify({"error": str(e.description)}), 500
|
return jsonify({"error": str(e.description)}), 500
|
||||||
|
|
||||||
@app.errorhandler(429)
|
@bp.errorhandler(429)
|
||||||
def slow_down_error(e):
|
def slow_down_error(e):
|
||||||
flood.report(get_remote_address())
|
flood.report(get_remote_address())
|
||||||
return jsonify({"error": "Slowdown: " + str(e.description)}), 429
|
return jsonify({"error": "Slowdown: " + str(e.description)}), 429
|
||||||
|
|
||||||
@app.errorhandler(403)
|
@bp.errorhandler(403)
|
||||||
def denied(e):
|
def denied(e):
|
||||||
return jsonify({"error": str(e.description)}), 403
|
return jsonify({"error": str(e.description)}), 403
|
||||||
|
|
||||||
@app.route("/")
|
@bp.route("/")
|
||||||
@limiter.exempt
|
@limiter.exempt
|
||||||
def index():
|
def index():
|
||||||
if args.disable_web_ui:
|
if args.disable_web_ui:
|
||||||
|
@ -282,10 +282,12 @@ def create_app(args):
|
||||||
api_keys=args.api_keys,
|
api_keys=args.api_keys,
|
||||||
get_api_key_link=args.get_api_key_link,
|
get_api_key_link=args.get_api_key_link,
|
||||||
web_version=os.environ.get("LT_WEB") is not None,
|
web_version=os.environ.get("LT_WEB") is not None,
|
||||||
version=get_version()
|
version=get_version(),
|
||||||
|
swagger_url=SWAGGER_URL,
|
||||||
|
url_prefix=args.url_prefix
|
||||||
)
|
)
|
||||||
|
|
||||||
@app.get("/javascript-licenses")
|
@bp.get("/javascript-licenses")
|
||||||
@limiter.exempt
|
@limiter.exempt
|
||||||
def javascript_licenses():
|
def javascript_licenses():
|
||||||
if args.disable_web_ui:
|
if args.disable_web_ui:
|
||||||
|
@ -293,7 +295,7 @@ def create_app(args):
|
||||||
|
|
||||||
return render_template("javascript-licenses.html")
|
return render_template("javascript-licenses.html")
|
||||||
|
|
||||||
@app.get("/languages")
|
@bp.get("/languages")
|
||||||
@limiter.exempt
|
@limiter.exempt
|
||||||
def langs():
|
def langs():
|
||||||
"""
|
"""
|
||||||
|
@ -325,7 +327,7 @@ def create_app(args):
|
||||||
return jsonify([{"code": l.code, "name": l.name, "targets": language_pairs.get(l.code, [])} for l in languages])
|
return jsonify([{"code": l.code, "name": l.name, "targets": language_pairs.get(l.code, [])} for l in languages])
|
||||||
|
|
||||||
# Add cors
|
# Add cors
|
||||||
@app.after_request
|
@bp.after_request
|
||||||
def after_request(response):
|
def after_request(response):
|
||||||
response.headers.add("Access-Control-Allow-Origin", "*")
|
response.headers.add("Access-Control-Allow-Origin", "*")
|
||||||
response.headers.add(
|
response.headers.add(
|
||||||
|
@ -337,7 +339,7 @@ def create_app(args):
|
||||||
response.headers.add("Access-Control-Max-Age", 60 * 60 * 24 * 20)
|
response.headers.add("Access-Control-Max-Age", 60 * 60 * 24 * 20)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
@app.post("/translate")
|
@bp.post("/translate")
|
||||||
@access_check
|
@access_check
|
||||||
def translate():
|
def translate():
|
||||||
"""
|
"""
|
||||||
|
@ -577,7 +579,7 @@ def create_app(args):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
abort(500, description="Cannot translate text: %s" % str(e))
|
abort(500, description="Cannot translate text: %s" % str(e))
|
||||||
|
|
||||||
@app.post("/translate_file")
|
@bp.post("/translate_file")
|
||||||
@access_check
|
@access_check
|
||||||
def translate_file():
|
def translate_file():
|
||||||
"""
|
"""
|
||||||
|
@ -710,7 +712,7 @@ def create_app(args):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
abort(500, description=e)
|
abort(500, description=e)
|
||||||
|
|
||||||
@app.get("/download_file/<string:filename>")
|
@bp.get("/download_file/<string:filename>")
|
||||||
def download_file(filename: str):
|
def download_file(filename: str):
|
||||||
"""
|
"""
|
||||||
Download a translated file
|
Download a translated file
|
||||||
|
@ -737,7 +739,7 @@ def create_app(args):
|
||||||
|
|
||||||
return send_file(return_data, as_attachment=True, download_name=download_filename)
|
return send_file(return_data, as_attachment=True, download_name=download_filename)
|
||||||
|
|
||||||
@app.post("/detect")
|
@bp.post("/detect")
|
||||||
@access_check
|
@access_check
|
||||||
def detect():
|
def detect():
|
||||||
"""
|
"""
|
||||||
|
@ -831,7 +833,7 @@ def create_app(args):
|
||||||
|
|
||||||
return jsonify(detect_languages(q))
|
return jsonify(detect_languages(q))
|
||||||
|
|
||||||
@app.route("/frontend/settings")
|
@bp.route("/frontend/settings")
|
||||||
@limiter.exempt
|
@limiter.exempt
|
||||||
def frontend_settings():
|
def frontend_settings():
|
||||||
"""
|
"""
|
||||||
|
@ -910,7 +912,7 @@ def create_app(args):
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@app.post("/suggest")
|
@bp.post("/suggest")
|
||||||
@access_check
|
@access_check
|
||||||
def suggest():
|
def suggest():
|
||||||
"""
|
"""
|
||||||
|
@ -987,21 +989,29 @@ def create_app(args):
|
||||||
SuggestionsDatabase().add(q, s, source_lang, target_lang)
|
SuggestionsDatabase().add(q, s, source_lang, target_lang)
|
||||||
return jsonify({"success": True})
|
return jsonify({"success": True})
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
if args.debug:
|
||||||
|
app.config["TEMPLATES_AUTO_RELOAD"] = True
|
||||||
|
if args.url_prefix:
|
||||||
|
app.register_blueprint(bp, url_prefix=args.url_prefix)
|
||||||
|
else:
|
||||||
|
app.register_blueprint(bp)
|
||||||
|
|
||||||
swag = swagger(app)
|
swag = swagger(app)
|
||||||
swag["info"]["version"] = "1.3.1"
|
swag["info"]["version"] = get_version()
|
||||||
swag["info"]["title"] = "LibreTranslate"
|
swag["info"]["title"] = "LibreTranslate"
|
||||||
|
|
||||||
@app.route("/spec")
|
|
||||||
|
@app.route(API_URL)
|
||||||
@limiter.exempt
|
@limiter.exempt
|
||||||
def spec():
|
def spec():
|
||||||
return jsonify(swag)
|
return jsonify(swag)
|
||||||
|
|
||||||
SWAGGER_URL = "/docs" # URL for exposing Swagger UI (without trailing '/')
|
|
||||||
API_URL = "/spec"
|
|
||||||
|
|
||||||
# Call factory function to create our blueprint
|
# Call factory function to create our blueprint
|
||||||
swaggerui_blueprint = get_swaggerui_blueprint(SWAGGER_URL, API_URL)
|
swaggerui_blueprint = get_swaggerui_blueprint(SWAGGER_URL, API_URL)
|
||||||
|
if args.url_prefix:
|
||||||
app.register_blueprint(swaggerui_blueprint)
|
app.register_blueprint(swaggerui_blueprint, url_prefix=SWAGGER_URL)
|
||||||
|
else:
|
||||||
|
app.register_blueprint(swaggerui_blueprint)
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
|
@ -171,6 +171,11 @@ _default_options_objects = [
|
||||||
'default_value': '',
|
'default_value': '',
|
||||||
'value_type': 'str'
|
'value_type': 'str'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'name': 'URL_PREFIX',
|
||||||
|
'default_value': '',
|
||||||
|
'value_type': 'str'
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -159,14 +159,23 @@ def get_args():
|
||||||
type=str,
|
type=str,
|
||||||
help="Protect the /metrics endpoint by allowing only clients that have a valid Authorization Bearer token (%(default)s)",
|
help="Protect the /metrics endpoint by allowing only clients that have a valid Authorization Bearer token (%(default)s)",
|
||||||
)
|
)
|
||||||
return parser.parse_args()
|
parser.add_argument(
|
||||||
|
"--url-prefix",
|
||||||
|
default=DEFARGS['URL_PREFIX'],
|
||||||
|
type=str,
|
||||||
|
help="Add prefix to URL: example.com:5000/url-prefix/",
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
if args.url_prefix and not args.url_prefix.startswith('/'):
|
||||||
|
args.url_prefix = '/' + args.url_prefix
|
||||||
|
return args
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
args = get_args()
|
args = get_args()
|
||||||
app = create_app(args)
|
app = create_app(args)
|
||||||
|
|
||||||
if sys.argv[0] == '--wsgi':
|
if '--wsgi' in sys.argv:
|
||||||
return app
|
return app
|
||||||
else:
|
else:
|
||||||
if args.debug:
|
if args.debug:
|
||||||
|
@ -175,7 +184,7 @@ def main():
|
||||||
from waitress import serve
|
from waitress import serve
|
||||||
|
|
||||||
url_scheme = "https" if args.ssl else "http"
|
url_scheme = "https" if args.ssl else "http"
|
||||||
print("Running on %s://%s:%s" % (url_scheme, args.host, args.port))
|
print("Running on %s://%s:%s%s" % (url_scheme, args.host, args.port, args.url_prefix))
|
||||||
|
|
||||||
serve(
|
serve(
|
||||||
app,
|
app,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-3.0
|
// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-3.0
|
||||||
// API host/endpoint
|
// API host/endpoint
|
||||||
var BaseUrl = window.location.protocol + "//" + window.location.host;
|
var BaseUrl = window.location.protocol + "//" + window.location.host + url_prefix ;
|
||||||
var htmlRegex = /<(.*)>.*?|<(.*)\/>/;
|
var htmlRegex = /<(.*)>.*?|<(.*)\/>/;
|
||||||
document.addEventListener('DOMContentLoaded', function(){
|
document.addEventListener('DOMContentLoaded', function(){
|
||||||
var sidenavElems = document.querySelectorAll('.sidenav');
|
var sidenavElems = document.querySelectorAll('.sidenav');
|
||||||
|
|
|
@ -7,6 +7,9 @@
|
||||||
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
||||||
<meta name="description" content="Free and Open Source Machine Translation API. 100% self-hosted, offline capable and easy to setup. Run your own API server in just a few minutes.">
|
<meta name="description" content="Free and Open Source Machine Translation API. 100% self-hosted, offline capable and easy to setup. Run your own API server in just a few minutes.">
|
||||||
<meta name="keywords" content="translation,api">
|
<meta name="keywords" content="translation,api">
|
||||||
|
<script type="text/javascript">
|
||||||
|
var url_prefix = "{{ url_prefix }}"
|
||||||
|
</script>
|
||||||
|
|
||||||
<link rel="preload" href="{{ url_for('static', filename='icon.svg') }}" as="image" />
|
<link rel="preload" href="{{ url_for('static', filename='icon.svg') }}" as="image" />
|
||||||
<link rel="preload" href="{{ url_for('static', filename='js/vue@2.js') }}" as="script">
|
<link rel="preload" href="{{ url_for('static', filename='js/vue@2.js') }}" as="script">
|
||||||
|
@ -58,7 +61,7 @@
|
||||||
<span>LibreTranslate</span>
|
<span>LibreTranslate</span>
|
||||||
</a>
|
</a>
|
||||||
<ul class="right hide-on-med-and-down">
|
<ul class="right hide-on-med-and-down">
|
||||||
<li><a href="/docs">API Docs</a></li>
|
<li><a href="{{ swagger_url }}">API Docs</a></li>
|
||||||
{% if get_api_key_link %}
|
{% if get_api_key_link %}
|
||||||
<li><a href="{{ get_api_key_link }}">Get API Key</a></li>
|
<li><a href="{{ get_api_key_link }}">Get API Key</a></li>
|
||||||
<script>window.getApiKeyLink = "{{ get_api_key_link }}";</script>
|
<script>window.getApiKeyLink = "{{ get_api_key_link }}";</script>
|
||||||
|
@ -70,7 +73,7 @@
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<ul id="nav-mobile" class="sidenav">
|
<ul id="nav-mobile" class="sidenav">
|
||||||
<li><a href="/docs">API Docs</a></li>
|
<li><a href="{{ swagger_url }}">API Docs</a></li>
|
||||||
{% if get_api_key_link %}
|
{% if get_api_key_link %}
|
||||||
<li><a href="{{ get_api_key_link }}">Get API Key</a></li>
|
<li><a href="{{ get_api_key_link }}">Get API Key</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
Loading…
Reference in a new issue