This commit is contained in:
Pit 2024-04-26 13:42:20 +02:00 committed by GitHub
commit 1b53abbce6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -47,7 +47,8 @@ def get_version():
def get_upload_dir():
upload_dir = os.path.join(tempfile.gettempdir(), "libretranslate-files-translate")
upload_dir = os.path.join(tempfile.gettempdir(),
"libretranslate-files-translate")
if not os.path.isdir(upload_dir):
os.mkdir(upload_dir)
@ -64,6 +65,7 @@ def get_req_api_key():
return ak
def get_req_secret():
if request.is_json:
json = get_json_dict(request)
@ -149,6 +151,12 @@ def get_routes_limits(args, api_keys_db):
return res
def clean_text(text):
pattern = re.compile(r'(?<=[^.!?])\n')
cleaned_text = re.sub(pattern, ' ', text)
return cleaned_text
def create_app(args):
from libretranslate.init import boot
@ -168,7 +176,8 @@ def create_app(args):
languages = load_languages()
language_pairs = {}
for lang in languages:
language_pairs[lang.code] = sorted([l.to_lang.code for l in lang.translations_from])
language_pairs[lang.code] = sorted(
[l.to_lang.code for l in lang.translations_from])
# Map userdefined frontend languages to argos language object.
if args.frontend_language_source == "auto":
@ -177,14 +186,15 @@ def create_app(args):
)
else:
frontend_argos_language_source = next(
iter([l for l in languages if l.code == args.frontend_language_source]),
iter([l for l in languages if l.code ==
args.frontend_language_source]),
None,
)
if frontend_argos_language_source is None:
frontend_argos_language_source = languages[0]
language_target_fallback = languages[1] if len(languages) >= 2 else languages[0]
language_target_fallback = languages[1] if len(
languages) >= 2 else languages[0]
if args.frontend_language_target == "locale":
def resolve_language_locale():
@ -199,11 +209,13 @@ def create_app(args):
frontend_argos_language_target = resolve_language_locale
else:
language_target = next(
iter([l for l in languages if l.code == args.frontend_language_target]), None
iter([l for l in languages if l.code ==
args.frontend_language_target]), None
)
if language_target is None:
language_target = language_target_fallback
frontend_argos_language_target = lambda: language_target
def frontend_argos_language_target(): return language_target
frontend_argos_supported_files_format = []
@ -216,7 +228,8 @@ def create_app(args):
if args.req_limit > 0 or args.api_keys or args.daily_req_limit > 0 or args.hourly_req_limit > 0:
api_keys_db = None
if args.api_keys:
api_keys_db = RemoteDatabase(args.api_keys_remote) if args.api_keys_remote else Database(args.api_keys_db_path)
api_keys_db = RemoteDatabase(
args.api_keys_remote) if args.api_keys_remote else Database(args.api_keys_db_path)
from flask_limiter import Limiter
@ -233,7 +246,8 @@ def create_app(args):
args, api_keys_db
),
storage_uri=args.req_limit_storage,
default_limits_deduct_when=lambda req: True, # Force cost to be called after the request
# Force cost to be called after the request
default_limits_deduct_when=lambda req: True,
default_limits_cost=limits_cost
)
else:
@ -271,10 +285,12 @@ def create_app(args):
multiprocess.MultiProcessCollector(registry)
return Response(generate_latest(registry), mimetype=CONTENT_TYPE_LATEST)
measure_request = Summary('libretranslate_http_request_duration_seconds', 'Time spent on request', ['endpoint', 'status', 'request_ip', 'api_key'])
measure_request = Summary('libretranslate_http_request_duration_seconds', 'Time spent on request', [
'endpoint', 'status', 'request_ip', 'api_key'])
measure_request.labels('/translate', 200, '127.0.0.1', '')
gauge_request = Gauge('libretranslate_http_requests_in_flight', 'Active requests', ['endpoint', 'request_ip', 'api_key'], multiprocess_mode='livesum')
gauge_request = Gauge('libretranslate_http_requests_in_flight', 'Active requests', [
'endpoint', 'request_ip', 'api_key'], multiprocess_mode='livesum')
gauge_request.labels('/translate', '127.0.0.1', '')
def access_check(f):
@ -309,9 +325,11 @@ def create_app(args):
need_key = True
if need_key:
description = _("Please contact the server operator to get an API key")
description = _(
"Please contact the server operator to get an API key")
if args.get_api_key_link:
description = _("Visit %(url)s to get an API key", url=args.get_api_key_link)
description = _(
"Visit %(url)s to get an API key", url=args.get_api_key_link)
abort(
400,
description=description,
@ -334,7 +352,8 @@ def create_app(args):
raise e
finally:
request.duration = max(default_timer() - start_t, 0)
measure_request.labels(request.path, status, ip, ak).observe(request.duration)
measure_request.labels(
request.path, status, ip, ak).observe(request.duration)
g.dec()
return measure_func
else:
@ -383,7 +402,8 @@ def create_app(args):
web_version=os.environ.get("LT_WEB") is not None,
version=get_version(),
swagger_url=swagger_url,
available_locales=[{'code': l['code'], 'name': _lazy(l['name'])} for l in get_available_locales(not args.debug)],
available_locales=[{'code': l['code'], 'name': _lazy(
l['name'])} for l in get_available_locales(not args.debug)],
current_locale=get_locale(),
alternate_locales=get_alternate_locale_links()
)
@ -565,17 +585,17 @@ def create_app(args):
text_format = request.values.get("format")
if not q:
abort(400, description=_("Invalid request: missing %(name)s parameter", name='q'))
abort(400, description=_(
"Invalid request: missing %(name)s parameter", name='q'))
if not source_lang:
abort(400, description=_("Invalid request: missing %(name)s parameter", name='source'))
abort(400, description=_(
"Invalid request: missing %(name)s parameter", name='source'))
if not target_lang:
abort(400, description=_("Invalid request: missing %(name)s parameter", name='target'))
abort(400, description=_(
"Invalid request: missing %(name)s parameter", name='target'))
if not request.is_json:
# Normalize line endings to UNIX style (LF) only so we can consistently
# enforce character limits.
# https://www.rfc-editor.org/rfc/rfc2046#section-4.1.1
q = "\n".join(q.splitlines())
q = clean_text(q)
char_limit = get_char_limit(args.char_limit, api_keys_db)
@ -586,7 +606,8 @@ def create_app(args):
if args.batch_limit < batch_size:
abort(
400,
description=_("Invalid request: request (%(size)s) exceeds text limit (%(limit)s)", size=batch_size, limit=args.batch_limit),
description=_("Invalid request: request (%(size)s) exceeds text limit (%(limit)s)",
size=batch_size, limit=args.batch_limit),
)
src_texts = q if batch else [q]
@ -596,7 +617,8 @@ def create_app(args):
if len(text) > char_limit:
abort(
400,
description=_("Invalid request: request (%(size)s) exceeds text limit (%(limit)s)", size=len(text), limit=char_limit),
description=_("Invalid request: request (%(size)s) exceeds text limit (%(limit)s)", size=len(
text), limit=char_limit),
)
if batch:
@ -608,12 +630,14 @@ def create_app(args):
else:
detected_src_lang = {"confidence": 100.0, "language": source_lang}
src_lang = next(iter([l for l in languages if l.code == detected_src_lang["language"]]), None)
src_lang = next(
iter([l for l in languages if l.code == detected_src_lang["language"]]), None)
if src_lang is None:
abort(400, description=_("%(lang)s is not supported", lang=source_lang))
tgt_lang = next(iter([l for l in languages if l.code == target_lang]), None)
tgt_lang = next(
iter([l for l in languages if l.code == target_lang]), None)
if tgt_lang is None:
abort(400, description=_("%(lang)s is not supported", lang=target_lang))
@ -622,7 +646,8 @@ def create_app(args):
text_format = "text"
if text_format not in ["text", "html"]:
abort(400, description=_("%(format)s format is not supported", format=text_format))
abort(400, description=_(
"%(format)s format is not supported", format=text_format))
try:
if batch:
@ -630,12 +655,14 @@ def create_app(args):
for text in q:
translator = src_lang.get_translation(tgt_lang)
if translator is None:
abort(400, description=_("%(tname)s (%(tcode)s) is not available as a target language from %(sname)s (%(scode)s)", tname=_lazy(tgt_lang.name), tcode=tgt_lang.code, sname=_lazy(src_lang.name), scode=src_lang.code))
abort(400, description=_("%(tname)s (%(tcode)s) is not available as a target language from %(sname)s (%(scode)s)", tname=_lazy(
tgt_lang.name), tcode=tgt_lang.code, sname=_lazy(src_lang.name), scode=src_lang.code))
if text_format == "html":
translated_text = str(translate_html(translator, text))
else:
translated_text = improve_translation_formatting(text, translator.translate(text))
translated_text = improve_translation_formatting(
text, translator.translate(text))
results.append(unescape(translated_text))
if source_lang == "auto":
@ -654,12 +681,14 @@ def create_app(args):
else:
translator = src_lang.get_translation(tgt_lang)
if translator is None:
abort(400, description=_("%(tname)s (%(tcode)s) is not available as a target language from %(sname)s (%(scode)s)", tname=_lazy(tgt_lang.name), tcode=tgt_lang.code, sname=_lazy(src_lang.name), scode=src_lang.code))
abort(400, description=_("%(tname)s (%(tcode)s) is not available as a target language from %(sname)s (%(scode)s)", tname=_lazy(
tgt_lang.name), tcode=tgt_lang.code, sname=_lazy(src_lang.name), scode=src_lang.code))
if text_format == "html":
translated_text = str(translate_html(translator, q))
else:
translated_text = improve_translation_formatting(q, translator.translate(q))
translated_text = improve_translation_formatting(
q, translator.translate(q))
if source_lang == "auto":
return jsonify(
@ -676,7 +705,8 @@ def create_app(args):
)
except Exception as e:
raise e
abort(500, description=_("Cannot translate text: %(text)s", text=str(e)))
abort(500, description=_(
"Cannot translate text: %(text)s", text=str(e)))
@bp.post("/translate_file")
@access_check
@ -763,7 +793,8 @@ def create_app(args):
description: Error message
"""
if args.disable_files_translation:
abort(403, description=_("Files translation are disabled on this server."))
abort(403, description=_(
"Files translation are disabled on this server."))
source_lang = request.form.get("source")
target_lang = request.form.get("target")
@ -771,11 +802,14 @@ def create_app(args):
char_limit = get_char_limit(args.char_limit, api_keys_db)
if not file:
abort(400, description=_("Invalid request: missing %(name)s parameter", name='file'))
abort(400, description=_(
"Invalid request: missing %(name)s parameter", name='file'))
if not source_lang:
abort(400, description=_("Invalid request: missing %(name)s parameter", name='source'))
abort(400, description=_(
"Invalid request: missing %(name)s parameter", name='source'))
if not target_lang:
abort(400, description=_("Invalid request: missing %(name)s parameter", name='target'))
abort(400, description=_(
"Invalid request: missing %(name)s parameter", name='target'))
if file.filename == '':
abort(400, description=_("Invalid request: empty file"))
@ -783,12 +817,14 @@ def create_app(args):
if os.path.splitext(file.filename)[1] not in frontend_argos_supported_files_format:
abort(400, description=_("Invalid request: file format not supported"))
src_lang = next(iter([l for l in languages if l.code == source_lang]), None)
src_lang = next(
iter([l for l in languages if l.code == source_lang]), None)
if src_lang is None:
abort(400, description=_("%(lang)s is not supported", lang=source_lang))
tgt_lang = next(iter([l for l in languages if l.code == target_lang]), None)
tgt_lang = next(
iter([l for l in languages if l.code == target_lang]), None)
if tgt_lang is None:
abort(400, description=_("%(lang)s is not supported", lang=target_lang))
@ -805,9 +841,11 @@ def create_app(args):
# roughly equivalent to a batch process of N batches assuming
# each batch uses all available limits
if char_limit > 0:
request.req_cost = max(1, int(os.path.getsize(filepath) / char_limit))
request.req_cost = max(
1, int(os.path.getsize(filepath) / char_limit))
translated_file_path = argostranslatefiles.translate_file(src_lang.get_translation(tgt_lang), filepath)
translated_file_path = argostranslatefiles.translate_file(
src_lang.get_translation(tgt_lang), filepath)
translated_filename = os.path.basename(translated_file_path)
return jsonify(
@ -824,11 +862,13 @@ def create_app(args):
Download a translated file
"""
if args.disable_files_translation:
abort(400, description=_("Files translation are disabled on this server."))
abort(400, description=_(
"Files translation are disabled on this server."))
filepath = os.path.join(get_upload_dir(), filename)
try:
checked_filepath = security.path_traversal_check(filepath, get_upload_dir())
checked_filepath = security.path_traversal_check(
filepath, get_upload_dir())
if os.path.isfile(checked_filepath):
filepath = checked_filepath
except security.SuspiciousFileOperationError:
@ -932,7 +972,8 @@ def create_app(args):
q = request.values.get("q")
if not q:
abort(400, description=_("Invalid request: missing %(name)s parameter", name='q'))
abort(400, description=_(
"Invalid request: missing %(name)s parameter", name='q'))
return jsonify(detect_languages(q))
@ -1089,13 +1130,17 @@ def create_app(args):
target_lang = request.values.get("target")
if not q:
abort(400, description=_("Invalid request: missing %(name)s parameter", name='q'))
abort(400, description=_(
"Invalid request: missing %(name)s parameter", name='q'))
if not s:
abort(400, description=_("Invalid request: missing %(name)s parameter", name='s'))
abort(400, description=_(
"Invalid request: missing %(name)s parameter", name='s'))
if not source_lang:
abort(400, description=_("Invalid request: missing %(name)s parameter", name='source'))
abort(400, description=_(
"Invalid request: missing %(name)s parameter", name='source'))
if not target_lang:
abort(400, description=_("Invalid request: missing %(name)s parameter", name='target'))
abort(400, description=_(
"Invalid request: missing %(name)s parameter", name='target'))
SuggestionsDatabase().add(q, s, source_lang, target_lang)
return jsonify({"success": True})