Merge pull request #380 from pierotofy/loc

Localization Support
This commit is contained in:
Piero Toffanin 2023-01-06 14:42:20 -05:00 committed by GitHub
commit cb3ee55eb4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 2385 additions and 206 deletions

View file

@ -26,6 +26,7 @@ jobs:
python -m pip install --upgrade pip python -m pip install --upgrade pip
pip install pytest flake8 pip install pytest flake8
pip install . pip install .
python compile_locales.py
- name: Check code style with flake8 (lint) - name: Check code style with flake8 (lint)
run: | run: |
@ -60,5 +61,6 @@ jobs:
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
run: | run: |
python compile_locales.py
python setup.py sdist bdist_wheel python setup.py sdist bdist_wheel
twine upload dist/* twine upload dist/*

View file

@ -14,11 +14,11 @@ RUN python -mvenv venv && ./venv/bin/pip install --upgrade pip
COPY . . COPY . .
# Install package from source code # Install package from source code, compile translations
RUN ./venv/bin/pip install . \ RUN ./venv/bin/pip install Babel==2.11.0 && ./venv/bin/python compile_locales.py \
&& ./venv/bin/pip install . \
&& ./venv/bin/pip cache purge && ./venv/bin/pip cache purge
FROM python:3.8.14-slim-bullseye FROM python:3.8.14-slim-bullseye
ARG with_models=false ARG with_models=false

View file

@ -1 +1 @@
1.3.8 1.3.9

2
babel.cfg Normal file
View file

@ -0,0 +1,2 @@
[python: **.py]
[jinja2: **/templates/**]

16
compile_locales.py Executable file
View file

@ -0,0 +1,16 @@
#!/usr/bin/env python
import sys
import os
from babel.messages.frontend import main as pybabel
if __name__ == "__main__":
locales_dir = os.path.join("libretranslate", "locales")
if not os.path.isdir(locales_dir):
os.makedirs(locales_dir)
print("Compiling locales")
sys.argv = ["", "compile", "-f", "-d", locales_dir]
pybabel()

View file

@ -34,7 +34,8 @@ RUN if [ "$with_models" = "true" ]; then \
fi fi
# Install package from source code # Install package from source code
RUN pip3 install . \ RUN pip3 install Babel==2.11.0 && python3 compile_locales.py \
&& pip3 install . \
&& pip3 cache purge && pip3 cache purge
# Depending on your cuda install you may need to uncomment this line to allow the container to access the cuda libraries # Depending on your cuda install you may need to uncomment this line to allow the container to access the cuda libraries

View file

@ -9,15 +9,19 @@ from timeit import default_timer
import argostranslatefiles import argostranslatefiles
from argostranslatefiles import get_supported_formats from argostranslatefiles import get_supported_formats
from flask import (abort, Blueprint, Flask, jsonify, render_template, request, from flask import (abort, Blueprint, Flask, jsonify, render_template, request,
Response, send_file, url_for) Response, send_file, url_for, session)
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 flask_session import Session
from translatehtml import translate_html from translatehtml import translate_html
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
from werkzeug.exceptions import HTTPException from werkzeug.exceptions import HTTPException
from flask_babel import Babel
from libretranslate import flood, remove_translated_files, security from libretranslate import flood, remove_translated_files, security
from libretranslate.language import detect_languages, improve_translation_formatting from libretranslate.language import detect_languages, improve_translation_formatting
from libretranslate.locales import (_, _lazy, get_available_locales, get_available_locale_codes, gettext_escaped,
gettext_html, lazy_swag, get_alternate_locale_links)
from .api_keys import Database, RemoteDatabase from .api_keys import Database, RemoteDatabase
from .suggestions import Database as SuggestionsDatabase from .suggestions import Database as SuggestionsDatabase
@ -53,7 +57,7 @@ def get_req_api_key():
def get_json_dict(request): def get_json_dict(request):
d = request.get_json() d = request.get_json()
if not isinstance(d, dict): if not isinstance(d, dict):
abort(400, description="Invalid JSON format") abort(400, description=_("Invalid JSON format"))
return d return d
@ -121,7 +125,7 @@ def create_app(args):
# Map userdefined frontend languages to argos language object. # Map userdefined frontend languages to argos language object.
if args.frontend_language_source == "auto": if args.frontend_language_source == "auto":
frontend_argos_language_source = type( frontend_argos_language_source = type(
"obj", (object,), {"code": "auto", "name": "Auto Detect"} "obj", (object,), {"code": "auto", "name": _("Auto Detect")}
) )
else: else:
frontend_argos_language_source = next( frontend_argos_language_source = next(
@ -186,7 +190,7 @@ def create_app(args):
if args.metrics_auth_token: if args.metrics_auth_token:
authorization = request.headers.get('Authorization') authorization = request.headers.get('Authorization')
if authorization != "Bearer " + args.metrics_auth_token: if authorization != "Bearer " + args.metrics_auth_token:
abort(401, description="Unauthorized") abort(401, description=_("Unauthorized"))
registry = CollectorRegistry() registry = CollectorRegistry()
multiprocess.MultiProcessCollector(registry) multiprocess.MultiProcessCollector(registry)
@ -204,7 +208,7 @@ def create_app(args):
ip = get_remote_address() ip = get_remote_address()
if flood.is_banned(ip): if flood.is_banned(ip):
abort(403, description="Too many request limits violations") abort(403, description=_("Too many request limits violations"))
if args.api_keys: if args.api_keys:
ak = get_req_api_key() ak = get_req_api_key()
@ -213,16 +217,16 @@ def create_app(args):
): ):
abort( abort(
403, 403,
description="Invalid API key", description=_("Invalid API key"),
) )
elif ( elif (
args.require_api_key_origin args.require_api_key_origin
and api_keys_db.lookup(ak) is None and api_keys_db.lookup(ak) is None
and request.headers.get("Origin") != args.require_api_key_origin and request.headers.get("Origin") != args.require_api_key_origin
): ):
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: if args.get_api_key_link:
description = "Visit %s to get an API key" % args.get_api_key_link description = _("Visit %(url)s to get an API key", url=args.get_api_key_link)
abort( abort(
403, 403,
description=description, description=description,
@ -262,7 +266,7 @@ def create_app(args):
@bp.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
@bp.errorhandler(403) @bp.errorhandler(403)
def denied(e): def denied(e):
@ -274,6 +278,10 @@ def create_app(args):
if args.disable_web_ui: if args.disable_web_ui:
abort(404) abort(404)
langcode = request.args.get('lang')
if langcode and langcode in get_available_locale_codes(not args.debug):
session.update(preferred_lang=langcode)
return render_template( return render_template(
"index.html", "index.html",
gaId=args.ga_id, gaId=args.ga_id,
@ -283,16 +291,20 @@ def create_app(args):
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, swagger_url=SWAGGER_URL,
url_prefix=args.url_prefix 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()
) )
@bp.get("/javascript-licenses") @bp.route("/js/app.js")
@limiter.exempt @limiter.exempt
def javascript_licenses(): def appjs():
if args.disable_web_ui: if args.disable_web_ui:
abort(404) abort(404)
return render_template("javascript-licenses.html") return render_template("app.js.template",
url_prefix=args.url_prefix,
get_api_key_link=args.get_api_key_link)
@bp.get("/languages") @bp.get("/languages")
@limiter.exempt @limiter.exempt
@ -323,7 +335,7 @@ def create_app(args):
type: string type: string
description: Supported target language codes description: Supported target language codes
""" """
return jsonify([{"code": l.code, "name": l.name, "targets": language_pairs.get(l.code, [])} for l in languages]) return jsonify([{"code": l.code, "name": _lazy(l.name), "targets": language_pairs.get(l.code, [])} for l in languages])
# Add cors # Add cors
@bp.after_request @bp.after_request
@ -452,11 +464,11 @@ def create_app(args):
text_format = request.values.get("format") text_format = request.values.get("format")
if not q: if not q:
abort(400, description="Invalid request: missing q parameter") abort(400, description=_("Invalid request: missing %(name)s parameter", name='q'))
if not source_lang: if not source_lang:
abort(400, description="Invalid request: missing source parameter") abort(400, description=_("Invalid request: missing %(name)s parameter", name='source'))
if not target_lang: if not target_lang:
abort(400, description="Invalid request: missing target parameter") abort(400, description=_("Invalid request: missing %(name)s parameter", name='target'))
batch = isinstance(q, list) batch = isinstance(q, list)
@ -465,8 +477,7 @@ def create_app(args):
if args.batch_limit < batch_size: if args.batch_limit < batch_size:
abort( abort(
400, 400,
description="Invalid request: Request (%d) exceeds text limit (%d)" description=_("Invalid request: request (%(size)s) exceeds text limit (%(limit)s)", size=batch_size, limit=args.batch_limit),
% (batch_size, args.batch_limit),
) )
if args.char_limit != -1: if args.char_limit != -1:
@ -478,8 +489,7 @@ def create_app(args):
if args.char_limit < chars: if args.char_limit < chars:
abort( abort(
400, 400,
description="Invalid request: Request (%d) exceeds character limit (%d)" description=_("Invalid request: request (%(size)s) exceeds text limit (%(limit)s)", size=chars, limit=args.char_limit),
% (chars, args.char_limit),
) )
if source_lang == "auto": if source_lang == "auto":
@ -512,18 +522,18 @@ def create_app(args):
for idx, lang in enumerate(src_langs): for idx, lang in enumerate(src_langs):
if lang is None: if lang is None:
abort(400, description="%s is not supported" % source_langs[idx]) abort(400, description=_("%(lang)s is not supported", lang=source_langs[idx]))
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: if tgt_lang is None:
abort(400, description="%s is not supported" % target_lang) abort(400, description=_("%(lang)s is not supported",lang=target_lang))
if not text_format: if not text_format:
text_format = "text" text_format = "text"
if text_format not in ["text", "html"]: if text_format not in ["text", "html"]:
abort(400, description="%s format is not supported" % text_format) abort(400, description=_("%(format)s format is not supported", format=text_format))
try: try:
if batch: if batch:
@ -531,7 +541,7 @@ def create_app(args):
for idx, text in enumerate(q): for idx, text in enumerate(q):
translator = src_langs[idx].get_translation(tgt_lang) translator = src_langs[idx].get_translation(tgt_lang)
if translator is None: if translator is None:
abort(400, description="%s (%s) is not available as a target language from %s (%s)" % (tgt_lang.name, tgt_lang.code, src_langs[idx].name, src_langs[idx].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_langs[idx].name), scode=src_langs[idx].code))
if text_format == "html": if text_format == "html":
translated_text = str(translate_html(translator, text)) translated_text = str(translate_html(translator, text))
@ -555,7 +565,7 @@ def create_app(args):
else: else:
translator = src_langs[0].get_translation(tgt_lang) translator = src_langs[0].get_translation(tgt_lang)
if translator is None: if translator is None:
abort(400, description="%s (%s) is not available as a target language from %s (%s)" % (tgt_lang.name, tgt_lang.code, src_langs[0].name, src_langs[0].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_langs[0].name), scode=src_langs[0].code))
if text_format == "html": if text_format == "html":
translated_text = str(translate_html(translator, q)) translated_text = str(translate_html(translator, q))
@ -576,7 +586,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: %(text)s", text=str(e)))
@bp.post("/translate_file") @bp.post("/translate_file")
@access_check @access_check
@ -663,36 +673,36 @@ def create_app(args):
description: Error message description: Error message
""" """
if args.disable_files_translation: 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") source_lang = request.form.get("source")
target_lang = request.form.get("target") target_lang = request.form.get("target")
file = request.files['file'] file = request.files['file']
if not file: if not file:
abort(400, description="Invalid request: missing file parameter") abort(400, description=_("Invalid request: missing %(name)s parameter", name='file'))
if not source_lang: if not source_lang:
abort(400, description="Invalid request: missing source parameter") abort(400, description=_("Invalid request: missing %(name)s parameter", name='source'))
if not target_lang: if not target_lang:
abort(400, description="Invalid request: missing target parameter") abort(400, description=_("Invalid request: missing %(name)s parameter", name='target'))
if file.filename == '': if file.filename == '':
abort(400, description="Invalid request: empty file") abort(400, description=_("Invalid request: empty file"))
if os.path.splitext(file.filename)[1] not in frontend_argos_supported_files_format: if os.path.splitext(file.filename)[1] not in frontend_argos_supported_files_format:
abort(400, description="Invalid request: file format not supported") abort(400, description=_("Invalid request: file format not supported"))
source_langs = [source_lang] source_langs = [source_lang]
src_langs = [next(iter([l for l in languages if l.code == source_lang]), None) for source_lang in source_langs] src_langs = [next(iter([l for l in languages if l.code == source_lang]), None) for source_lang in source_langs]
for idx, lang in enumerate(src_langs): for idx, lang in enumerate(src_langs):
if lang is None: if lang is None:
abort(400, description="%s is not supported" % source_langs[idx]) abort(400, description=_("%(lang)s is not supported", lang=source_langs[idx]))
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: if tgt_lang is None:
abort(400, description="%s is not supported" % target_lang) abort(400, description=_("%(lang)s is not supported", lang=target_lang))
try: try:
filename = str(uuid.uuid4()) + '.' + secure_filename(file.filename) filename = str(uuid.uuid4()) + '.' + secure_filename(file.filename)
@ -717,7 +727,7 @@ def create_app(args):
Download a translated file Download a translated file
""" """
if args.disable_files_translation: 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) filepath = os.path.join(get_upload_dir(), filename)
try: try:
@ -725,7 +735,7 @@ def create_app(args):
if os.path.isfile(checked_filepath): if os.path.isfile(checked_filepath):
filepath = checked_filepath filepath = checked_filepath
except security.SuspiciousFileOperation: except security.SuspiciousFileOperation:
abort(400, description="Invalid filename") abort(400, description=_("Invalid filename"))
return_data = io.BytesIO() return_data = io.BytesIO()
with open(filepath, 'rb') as fo: with open(filepath, 'rb') as fo:
@ -818,9 +828,6 @@ def create_app(args):
type: string type: string
description: Error message description: Error message
""" """
if flood.is_banned(get_remote_address()):
abort(403, description="Too many request limits violations")
if request.is_json: if request.is_json:
json = get_json_dict(request) json = get_json_dict(request)
q = json.get("q") q = json.get("q")
@ -828,7 +835,7 @@ def create_app(args):
q = request.values.get("q") q = request.values.get("q")
if not q: if not q:
abort(400, description="Invalid request: missing q parameter") abort(400, description=_("Invalid request: missing %(name)s parameter", name='q'))
return jsonify(detect_languages(q)) return jsonify(detect_languages(q))
@ -901,11 +908,11 @@ def create_app(args):
"language": { "language": {
"source": { "source": {
"code": frontend_argos_language_source.code, "code": frontend_argos_language_source.code,
"name": frontend_argos_language_source.name, "name": _lazy(frontend_argos_language_source.name),
}, },
"target": { "target": {
"code": frontend_argos_language_target.code, "code": frontend_argos_language_target.code,
"name": frontend_argos_language_target.name, "name": _lazy(frontend_argos_language_target.name),
}, },
}, },
} }
@ -969,7 +976,7 @@ def create_app(args):
description: Error message description: Error message
""" """
if not args.suggestions: if not args.suggestions:
abort(403, description="Suggestions are disabled on this server.") abort(403, description=_("Suggestions are disabled on this server."))
q = request.values.get("q") q = request.values.get("q")
s = request.values.get("s") s = request.values.get("s")
@ -977,18 +984,23 @@ def create_app(args):
target_lang = request.values.get("target") target_lang = request.values.get("target")
if not q: if not q:
abort(400, description="Invalid request: missing q parameter") abort(400, description=_("Invalid request: missing %(name)s parameter", name='q'))
if not s: if not s:
abort(400, description="Invalid request: missing s parameter") abort(400, description=_("Invalid request: missing %(name)s parameter", name='s'))
if not source_lang: if not source_lang:
abort(400, description="Invalid request: missing source parameter") abort(400, description=_("Invalid request: missing %(name)s parameter", name='source'))
if not target_lang: if not target_lang:
abort(400, description="Invalid request: missing target parameter") abort(400, description=_("Invalid request: missing %(name)s parameter", name='target'))
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__) app = Flask(__name__)
app.config["SESSION_TYPE"] = "filesystem"
app.config["SESSION_FILE_DIR"] = os.path.join("db", "sessions")
Session(app)
if args.debug: if args.debug:
app.config["TEMPLATES_AUTO_RELOAD"] = True app.config["TEMPLATES_AUTO_RELOAD"] = True
if args.url_prefix: if args.url_prefix:
@ -1002,11 +1014,21 @@ def create_app(args):
swag["info"]["version"] = get_version() swag["info"]["version"] = get_version()
swag["info"]["title"] = "LibreTranslate" swag["info"]["title"] = "LibreTranslate"
@app.route(API_URL) @app.route(API_URL)
@limiter.exempt @limiter.exempt
def spec(): def spec():
return jsonify(swag) return jsonify(lazy_swag(swag))
app.config["BABEL_TRANSLATION_DIRECTORIES"] = 'locales'
babel = Babel(app)
@babel.localeselector
def get_locale():
override_lang = request.headers.get('X-Override-Accept-Language')
if override_lang and override_lang in get_available_locale_codes():
return override_lang
return session.get('preferred_lang', request.accept_languages.best_match(get_available_locale_codes()))
app.jinja_env.globals.update(_e=gettext_escaped, _h=gettext_html)
# 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)

84
libretranslate/locales.py Normal file
View file

@ -0,0 +1,84 @@
import os
import json
from functools import lru_cache
from flask_babel import gettext as _
from flask_babel import lazy_gettext as _lazy
from markupsafe import escape, Markup
@lru_cache(maxsize=None)
def get_available_locales(only_reviewed=True):
locales_dir = os.path.join(os.path.dirname(__file__), 'locales')
dirs = [os.path.join(locales_dir, d) for d in os.listdir(locales_dir)]
res = [{'code': 'en', 'name': 'English'}]
for d in dirs:
meta_file = os.path.join(d, 'meta.json')
if os.path.isdir(os.path.join(d, 'LC_MESSAGES')) and os.path.isfile(meta_file):
try:
with open(meta_file) as f:
j = json.loads(f.read())
except Exception as e:
print(e)
continue
if j.get('reviewed') or not only_reviewed:
res.append({'code': os.path.basename(d), 'name': j.get('name', '')})
return res
@lru_cache(maxsize=None)
def get_available_locale_codes(only_reviewed=True):
return [l['code'] for l in get_available_locales(only_reviewed=only_reviewed)]
@lru_cache(maxsize=None)
def get_alternate_locale_links():
tmpl = os.environ.get("LT_LOCALE_LINK_TEMPLATE")
if tmpl is None:
return []
locales = get_available_locale_codes()
result = []
for l in locales:
link = tmpl.replace("{LANG}", l)
if l == 'en':
link = link.replace("en.", "")
result.append({ 'link': link,'lang': l })
return result
# Javascript code should use _e instead of _
def gettext_escaped(text, **variables):
return json.dumps(_(text, **variables))
# HTML should be escaped using _h instead of _
def gettext_html(text, **variables):
# Translate text without args
s = str(escape(_(text)))
v = {}
if variables:
for k in variables:
if hasattr(variables[k], 'unescape'):
v[k] = variables[k].unescape()
else:
v[k] = Markup(variables[k])
# Variables are assumed to be already escaped and thus safe
return Markup(s if not v else s % v)
def swag_eval(swag, func):
# Traverse the swag spec structure
# and call func on summary and description keys
for k in swag:
if k in ['summary', 'description'] and isinstance(swag[k], str) and swag[k] != "":
swag[k] = func(swag[k])
elif k == 'tags' and isinstance(swag[k], list):
swag[k] = [func(v) for v in swag[k]]
elif isinstance(swag[k], dict):
swag_eval(swag[k], func)
return swag
def lazy_swag(swag):
return swag_eval(swag, _lazy)

3
libretranslate/locales/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
**/*.mo
.langs.py
.swag.py

View file

@ -0,0 +1,590 @@
# German translations for LibreTranslate.
# Copyright (C) 2023 LibreTranslate Authors
# This file is distributed under the same license as the LibreTranslate
# project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2023.
#
msgid ""
msgstr ""
"Project-Id-Version: LibreTranslate 1.3.9\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2023-01-06 14:26-0500\n"
"PO-Revision-Date: 2023-01-06 14:26-0500\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: de\n"
"Language-Team: de <LL@li.org>\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.11.0\n"
#: libretranslate/app.py:60
msgid "Invalid JSON format"
msgstr ""
#: libretranslate/app.py:128 libretranslate/templates/app.js.template:427
msgid "Auto Detect"
msgstr ""
#: libretranslate/app.py:193
msgid "Unauthorized"
msgstr ""
#: libretranslate/app.py:211
msgid "Too many request limits violations"
msgstr ""
#: libretranslate/app.py:220
msgid "Invalid API key"
msgstr ""
#: libretranslate/app.py:227
msgid "Please contact the server operator to get an API key"
msgstr ""
#: libretranslate/app.py:229
#, python-format
msgid "Visit %(url)s to get an API key"
msgstr ""
#: libretranslate/app.py:269
msgid "Slowdown:"
msgstr ""
#: libretranslate/app.py:467 libretranslate/app.py:469
#: libretranslate/app.py:471 libretranslate/app.py:683
#: libretranslate/app.py:685 libretranslate/app.py:687
#: libretranslate/app.py:838 libretranslate/app.py:987
#: libretranslate/app.py:989 libretranslate/app.py:991
#: libretranslate/app.py:993
#, python-format
msgid "Invalid request: missing %(name)s parameter"
msgstr ""
#: libretranslate/app.py:480 libretranslate/app.py:492
#, python-format
msgid "Invalid request: request (%(size)s) exceeds text limit (%(limit)s)"
msgstr ""
#: libretranslate/app.py:525 libretranslate/app.py:530
#: libretranslate/app.py:700 libretranslate/app.py:705
#, python-format
msgid "%(lang)s is not supported"
msgstr ""
#: libretranslate/app.py:536
#, python-format
msgid "%(format)s format is not supported"
msgstr ""
#: libretranslate/app.py:544 libretranslate/app.py:568
#, python-format
msgid ""
"%(tname)s (%(tcode)s) is not available as a target language from "
"%(sname)s (%(scode)s)"
msgstr ""
#: libretranslate/app.py:589
#, python-format
msgid "Cannot translate text: %(text)s"
msgstr ""
#: libretranslate/app.py:676 libretranslate/app.py:730
msgid "Files translation are disabled on this server."
msgstr ""
#: libretranslate/app.py:690
msgid "Invalid request: empty file"
msgstr ""
#: libretranslate/app.py:693
msgid "Invalid request: file format not supported"
msgstr ""
#: libretranslate/app.py:738
msgid "Invalid filename"
msgstr ""
#: libretranslate/app.py:979
msgid "Suggestions are disabled on this server."
msgstr ""
#: libretranslate/locales/.langs.py:1
msgid "English"
msgstr ""
#: libretranslate/locales/.langs.py:2
msgid "Arabic"
msgstr ""
#: libretranslate/locales/.langs.py:3
msgid "Azerbaijani"
msgstr ""
#: libretranslate/locales/.langs.py:4
msgid "Chinese"
msgstr ""
#: libretranslate/locales/.langs.py:5
msgid "Czech"
msgstr ""
#: libretranslate/locales/.langs.py:6
msgid "Danish"
msgstr ""
#: libretranslate/locales/.langs.py:7
msgid "Dutch"
msgstr ""
#: libretranslate/locales/.langs.py:8
msgid "Esperanto"
msgstr ""
#: libretranslate/locales/.langs.py:9
msgid "Finnish"
msgstr ""
#: libretranslate/locales/.langs.py:10
msgid "French"
msgstr ""
#: libretranslate/locales/.langs.py:11
msgid "German"
msgstr ""
#: libretranslate/locales/.langs.py:12
msgid "Greek"
msgstr ""
#: libretranslate/locales/.langs.py:13
msgid "Hebrew"
msgstr ""
#: libretranslate/locales/.langs.py:14
msgid "Hindi"
msgstr ""
#: libretranslate/locales/.langs.py:15
msgid "Hungarian"
msgstr ""
#: libretranslate/locales/.langs.py:16
msgid "Indonesian"
msgstr ""
#: libretranslate/locales/.langs.py:17
msgid "Irish"
msgstr ""
#: libretranslate/locales/.langs.py:18
msgid "Italian"
msgstr ""
#: libretranslate/locales/.langs.py:19
msgid "Japanese"
msgstr ""
#: libretranslate/locales/.langs.py:20
msgid "Korean"
msgstr ""
#: libretranslate/locales/.langs.py:21
msgid "Persian"
msgstr ""
#: libretranslate/locales/.langs.py:22
msgid "Polish"
msgstr ""
#: libretranslate/locales/.langs.py:23
msgid "Portuguese"
msgstr ""
#: libretranslate/locales/.langs.py:24
msgid "Russian"
msgstr ""
#: libretranslate/locales/.langs.py:25
msgid "Slovak"
msgstr ""
#: libretranslate/locales/.langs.py:26
msgid "Spanish"
msgstr ""
#: libretranslate/locales/.langs.py:27
msgid "Swedish"
msgstr ""
#: libretranslate/locales/.langs.py:28
msgid "Turkish"
msgstr ""
#: libretranslate/locales/.langs.py:29
msgid "Ukranian"
msgstr ""
#: libretranslate/locales/.langs.py:30
msgid "Vietnamese"
msgstr ""
#: libretranslate/locales/.swag.py:1
msgid "Retrieve list of supported languages"
msgstr ""
#: libretranslate/locales/.swag.py:2
msgid "List of languages"
msgstr ""
#: libretranslate/locales/.swag.py:3
msgid "translate"
msgstr ""
#: libretranslate/locales/.swag.py:4
msgid "Translate text from a language to another"
msgstr ""
#: libretranslate/locales/.swag.py:5 libretranslate/templates/index.html:219
msgid "Translated text"
msgstr ""
#: libretranslate/locales/.swag.py:6
msgid "Invalid request"
msgstr ""
#: libretranslate/locales/.swag.py:7
msgid "Translation error"
msgstr ""
#: libretranslate/locales/.swag.py:8
msgid "Slow down"
msgstr ""
#: libretranslate/locales/.swag.py:9
msgid "Banned"
msgstr ""
#: libretranslate/locales/.swag.py:10
msgid "Translate file from a language to another"
msgstr ""
#: libretranslate/locales/.swag.py:11
msgid "Translated file"
msgstr ""
#: libretranslate/locales/.swag.py:12
msgid "Detect the language of a single text"
msgstr ""
#: libretranslate/locales/.swag.py:13
msgid "Detections"
msgstr ""
#: libretranslate/locales/.swag.py:14
msgid "Detection error"
msgstr ""
#: libretranslate/locales/.swag.py:15
msgid "Retrieve frontend specific settings"
msgstr ""
#: libretranslate/locales/.swag.py:16
msgid "frontend settings"
msgstr ""
#: libretranslate/locales/.swag.py:17
msgid "frontend"
msgstr ""
#: libretranslate/locales/.swag.py:18
msgid "Submit a suggestion to improve a translation"
msgstr ""
#: libretranslate/locales/.swag.py:19
msgid "Success"
msgstr ""
#: libretranslate/locales/.swag.py:20
msgid "Not authorized"
msgstr ""
#: libretranslate/locales/.swag.py:21
msgid "feedback"
msgstr ""
#: libretranslate/locales/.swag.py:22
msgid "Language code"
msgstr ""
#: libretranslate/locales/.swag.py:23
msgid "Human-readable language name (in English)"
msgstr ""
#: libretranslate/locales/.swag.py:24
msgid "Supported target language codes"
msgstr ""
#: libretranslate/locales/.swag.py:25
msgid "Translated text(s)"
msgstr ""
#: libretranslate/locales/.swag.py:26
msgid "Error message"
msgstr ""
#: libretranslate/locales/.swag.py:27
msgid "Reason for slow down"
msgstr ""
#: libretranslate/locales/.swag.py:28
msgid "Translated file url"
msgstr ""
#: libretranslate/locales/.swag.py:29
msgid "Confidence value"
msgstr ""
#: libretranslate/locales/.swag.py:30
msgid "Character input limit for this language (-1 indicates no limit)"
msgstr ""
#: libretranslate/locales/.swag.py:31
msgid "Frontend translation timeout"
msgstr ""
#: libretranslate/locales/.swag.py:32
msgid "Whether the API key database is enabled."
msgstr ""
#: libretranslate/locales/.swag.py:33
msgid "Whether an API key is required."
msgstr ""
#: libretranslate/locales/.swag.py:34
msgid "Whether submitting suggestions is enabled."
msgstr ""
#: libretranslate/locales/.swag.py:35
msgid "Supported files format"
msgstr ""
#: libretranslate/locales/.swag.py:36
msgid "Whether submission was successful"
msgstr ""
#: libretranslate/templates/app.js.template:31
#: libretranslate/templates/app.js.template:275
#: libretranslate/templates/app.js.template:279
msgid "Copy text"
msgstr ""
#: libretranslate/templates/app.js.template:72
#: libretranslate/templates/app.js.template:78
#: libretranslate/templates/app.js.template:83
#: libretranslate/templates/app.js.template:262
#: libretranslate/templates/app.js.template:332
#: libretranslate/templates/app.js.template:402
#: libretranslate/templates/app.js.template:447
#, python-format
msgid "Cannot load %(url)s"
msgstr ""
#: libretranslate/templates/app.js.template:253
#: libretranslate/templates/app.js.template:323
#: libretranslate/templates/app.js.template:385
#: libretranslate/templates/app.js.template:395
msgid "Unknown error"
msgstr ""
#: libretranslate/templates/app.js.template:276
msgid "Copied"
msgstr ""
#: libretranslate/templates/app.js.template:320
msgid ""
"Thanks for your correction. Note the suggestion will not take effect "
"right away."
msgstr ""
#: libretranslate/templates/app.js.template:423
msgid "No languages available. Did you install the models correctly?"
msgstr ""
#: libretranslate/templates/app.js.template:479
#, python-format
msgid "Type in your API Key. If you need an API key, %(instructions)s"
msgstr ""
#: libretranslate/templates/app.js.template:479
msgid "press the \"Get API Key\" link."
msgstr ""
#: libretranslate/templates/app.js.template:479
msgid "contact the server operator."
msgstr ""
#: libretranslate/templates/index.html:8 libretranslate/templates/index.html:25
#: libretranslate/templates/index.html:333
msgid "Free and Open Source Machine Translation API"
msgstr ""
#: libretranslate/templates/index.html:10
#: libretranslate/templates/index.html:29
msgid ""
"Free and Open Source Machine Translation API. Self-hosted, offline "
"capable and easy to setup. Run your own API server in just a few minutes."
msgstr ""
#: libretranslate/templates/index.html:11
msgid "translation"
msgstr ""
#: libretranslate/templates/index.html:11
msgid "api"
msgstr ""
#: libretranslate/templates/index.html:64
msgid "API Docs"
msgstr ""
#: libretranslate/templates/index.html:66
msgid "Get API Key"
msgstr ""
#: libretranslate/templates/index.html:68
msgid "GitHub"
msgstr ""
#: libretranslate/templates/index.html:70
msgid "Set API Key"
msgstr ""
#: libretranslate/templates/index.html:72
msgid "Change language"
msgstr ""
#: libretranslate/templates/index.html:78
msgid "Edit"
msgstr ""
#: libretranslate/templates/index.html:154
msgid "Dismiss"
msgstr ""
#: libretranslate/templates/index.html:168
msgid "Translation API"
msgstr ""
#: libretranslate/templates/index.html:172
msgid "Translate Text"
msgstr ""
#: libretranslate/templates/index.html:176
msgid "Translate Files"
msgstr ""
#: libretranslate/templates/index.html:182
msgid "Translate from"
msgstr ""
#: libretranslate/templates/index.html:192
msgid "Swap source and target languages"
msgstr ""
#: libretranslate/templates/index.html:195
msgid "Translate into"
msgstr ""
#: libretranslate/templates/index.html:207
msgid "Text to translate"
msgstr ""
#: libretranslate/templates/index.html:210
msgid "Delete text"
msgstr ""
#: libretranslate/templates/index.html:223
msgid "Suggest translation"
msgstr ""
#: libretranslate/templates/index.html:227
msgid "Cancel"
msgstr ""
#: libretranslate/templates/index.html:230
msgid "Send"
msgstr ""
#: libretranslate/templates/index.html:246
msgid "Supported file formats:"
msgstr ""
#: libretranslate/templates/index.html:250
msgid "File"
msgstr ""
#: libretranslate/templates/index.html:265
msgid "Remove file"
msgstr ""
#: libretranslate/templates/index.html:272
msgid "Translate"
msgstr ""
#: libretranslate/templates/index.html:273
#: libretranslate/templates/index.html:317
msgid "Download"
msgstr ""
#: libretranslate/templates/index.html:292
msgid "Request"
msgstr ""
#: libretranslate/templates/index.html:297
msgid "Response"
msgstr ""
#: libretranslate/templates/index.html:312
msgid "Open Source Machine Translation API"
msgstr ""
#: libretranslate/templates/index.html:313
msgid "Self-Hosted. Offline Capable. Easy to Setup."
msgstr ""
#: libretranslate/templates/index.html:332
msgid "LibreTranslate"
msgstr ""
#: libretranslate/templates/index.html:334
msgid "License:"
msgstr ""
#: libretranslate/templates/index.html:337
#, python-format
msgid ""
"This public API should be used for testing, personal or infrequent use. "
"If you're going to run an application in production, please "
"%(host_server)s or %(get_api_key)s."
msgstr ""
#: libretranslate/templates/index.html:337
msgid "host your own server"
msgstr ""
#: libretranslate/templates/index.html:337
msgid "get an API key"
msgstr ""
#: libretranslate/templates/index.html:345
#, python-format
msgid "Made with %(heart)s by %(contributors)s and powered by %(engine)s"
msgstr ""
#: libretranslate/templates/index.html:345
#, python-format
msgid "%(libretranslate)s Contributors"
msgstr ""

View file

@ -0,0 +1,4 @@
{
"name": "German",
"reviewed": false
}

View file

@ -0,0 +1,606 @@
# French translations for LibreTranslate.
# Copyright (C) 2023 LibreTranslate Authors
# This file is distributed under the same license as the LibreTranslate
# project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2023.
#
msgid ""
msgstr ""
"Project-Id-Version: LibreTranslate 1.3.9\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2023-01-06 14:26-0500\n"
"PO-Revision-Date: 2023-01-06 14:26-0500\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: fr <LL@li.org>\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"Generated-By: Babel 2.11.0\n"
#: libretranslate/app.py:60
msgid "Invalid JSON format"
msgstr "Format JSON invalide"
#: libretranslate/app.py:128 libretranslate/templates/app.js.template:427
msgid "Auto Detect"
msgstr "Auto Detect"
#: libretranslate/app.py:193
msgid "Unauthorized"
msgstr "Non autorisé"
#: libretranslate/app.py:211
msgid "Too many request limits violations"
msgstr "Trop de demandes limitent les violations"
#: libretranslate/app.py:220
msgid "Invalid API key"
msgstr "Clé API invalide"
#: libretranslate/app.py:227
msgid "Please contact the server operator to get an API key"
msgstr "Veuillez contacter l'opérateur du serveur pour obtenir une clé API"
#: libretranslate/app.py:229
#, python-format
msgid "Visit %(url)s to get an API key"
msgstr "Visite %(url)s pour obtenir une clé API"
#: libretranslate/app.py:269
msgid "Slowdown:"
msgstr "Ralentissement :"
#: libretranslate/app.py:467 libretranslate/app.py:469
#: libretranslate/app.py:471 libretranslate/app.py:683
#: libretranslate/app.py:685 libretranslate/app.py:687
#: libretranslate/app.py:838 libretranslate/app.py:987
#: libretranslate/app.py:989 libretranslate/app.py:991
#: libretranslate/app.py:993
#, python-format
msgid "Invalid request: missing %(name)s parameter"
msgstr "Demande invalide: manquante %(name)s paramètre"
#: libretranslate/app.py:480 libretranslate/app.py:492
#, python-format
msgid "Invalid request: request (%(size)s) exceeds text limit (%(limit)s)"
msgstr ""
"Demande non valide : demande (%(size)s) dépasse la limite de texte "
"(%(limit)s)"
#: libretranslate/app.py:525 libretranslate/app.py:530
#: libretranslate/app.py:700 libretranslate/app.py:705
#, python-format
msgid "%(lang)s is not supported"
msgstr "%(lang)s n &amp;apos; est pas soutenue"
#: libretranslate/app.py:536
#, python-format
msgid "%(format)s format is not supported"
msgstr "%(format)s format n'est pas supporté"
#: libretranslate/app.py:544 libretranslate/app.py:568
#, python-format
msgid ""
"%(tname)s (%(tcode)s) is not available as a target language from %(sname)s "
"(%(scode)s)"
msgstr ""
"%(tname)s (%(tcode)s) n'est pas disponible comme langue cible de %(sname)s "
"(%(scode)s)"
#: libretranslate/app.py:589
#, python-format
msgid "Cannot translate text: %(text)s"
msgstr "Impossible de traduire le texte: %(text)s"
#: libretranslate/app.py:676 libretranslate/app.py:730
msgid "Files translation are disabled on this server."
msgstr "La traduction de fichiers est désactivée sur ce serveur."
#: libretranslate/app.py:690
msgid "Invalid request: empty file"
msgstr "Demande invalide: fichier vide"
#: libretranslate/app.py:693
msgid "Invalid request: file format not supported"
msgstr "Demande invalide: format de fichier non supporté"
#: libretranslate/app.py:738
msgid "Invalid filename"
msgstr "Nom de fichier invalide"
#: libretranslate/app.py:979
msgid "Suggestions are disabled on this server."
msgstr "Les suggestions sont désactivées sur ce serveur."
#: libretranslate/locales/.langs.py:1
msgid "English"
msgstr "Anglais"
#: libretranslate/locales/.langs.py:2
msgid "Arabic"
msgstr "Arabe"
#: libretranslate/locales/.langs.py:3
msgid "Azerbaijani"
msgstr "Azerbaïdjan"
#: libretranslate/locales/.langs.py:4
msgid "Chinese"
msgstr "Chinois"
#: libretranslate/locales/.langs.py:5
msgid "Czech"
msgstr "Tchèque"
#: libretranslate/locales/.langs.py:6
msgid "Danish"
msgstr "Danish"
#: libretranslate/locales/.langs.py:7
msgid "Dutch"
msgstr "Néerlandais"
#: libretranslate/locales/.langs.py:8
msgid "Esperanto"
msgstr "Esperanto"
#: libretranslate/locales/.langs.py:9
msgid "Finnish"
msgstr "Finland"
#: libretranslate/locales/.langs.py:10
msgid "French"
msgstr "Français"
#: libretranslate/locales/.langs.py:11
msgid "German"
msgstr "Allemand"
#: libretranslate/locales/.langs.py:12
msgid "Greek"
msgstr "Grec"
#: libretranslate/locales/.langs.py:13
msgid "Hebrew"
msgstr "Hébreux"
#: libretranslate/locales/.langs.py:14
msgid "Hindi"
msgstr "Hindi"
#: libretranslate/locales/.langs.py:15
msgid "Hungarian"
msgstr "Hongrois"
#: libretranslate/locales/.langs.py:16
msgid "Indonesian"
msgstr "Indonésien"
#: libretranslate/locales/.langs.py:17
msgid "Irish"
msgstr "Irish"
#: libretranslate/locales/.langs.py:18
msgid "Italian"
msgstr "Italien"
#: libretranslate/locales/.langs.py:19
msgid "Japanese"
msgstr "Japonais"
#: libretranslate/locales/.langs.py:20
msgid "Korean"
msgstr "Corée"
#: libretranslate/locales/.langs.py:21
msgid "Persian"
msgstr "Perse"
#: libretranslate/locales/.langs.py:22
msgid "Polish"
msgstr "Polonais"
#: libretranslate/locales/.langs.py:23
msgid "Portuguese"
msgstr "Portugais"
#: libretranslate/locales/.langs.py:24
msgid "Russian"
msgstr "Russe"
#: libretranslate/locales/.langs.py:25
msgid "Slovak"
msgstr "Slovaquie"
#: libretranslate/locales/.langs.py:26
msgid "Spanish"
msgstr "Espagnol"
#: libretranslate/locales/.langs.py:27
msgid "Swedish"
msgstr "Suédois"
#: libretranslate/locales/.langs.py:28
msgid "Turkish"
msgstr "Turque"
#: libretranslate/locales/.langs.py:29
msgid "Ukranian"
msgstr "Ukranian"
#: libretranslate/locales/.langs.py:30
msgid "Vietnamese"
msgstr "Vietnam"
#: libretranslate/locales/.swag.py:1
msgid "Retrieve list of supported languages"
msgstr "Liste des langues supportées"
#: libretranslate/locales/.swag.py:2
msgid "List of languages"
msgstr "Liste des langues"
#: libretranslate/locales/.swag.py:3
msgid "translate"
msgstr "traduire"
#: libretranslate/locales/.swag.py:4
msgid "Translate text from a language to another"
msgstr "Traduire le texte d'une langue à une autre"
#: libretranslate/locales/.swag.py:5 libretranslate/templates/index.html:219
msgid "Translated text"
msgstr "Texte traduit"
#: libretranslate/locales/.swag.py:6
msgid "Invalid request"
msgstr "Demande non valide"
#: libretranslate/locales/.swag.py:7
msgid "Translation error"
msgstr "Erreur de traduction"
#: libretranslate/locales/.swag.py:8
msgid "Slow down"
msgstr "Doucement"
#: libretranslate/locales/.swag.py:9
msgid "Banned"
msgstr "Banned"
#: libretranslate/locales/.swag.py:10
msgid "Translate file from a language to another"
msgstr "Translate file from a language to another"
#: libretranslate/locales/.swag.py:11
msgid "Translated file"
msgstr "Fichier traduit"
#: libretranslate/locales/.swag.py:12
msgid "Detect the language of a single text"
msgstr "Detect the language of a single text"
#: libretranslate/locales/.swag.py:13
msgid "Detections"
msgstr "Détections"
#: libretranslate/locales/.swag.py:14
msgid "Detection error"
msgstr "Erreur de détection"
#: libretranslate/locales/.swag.py:15
msgid "Retrieve frontend specific settings"
msgstr "Récupérer les paramètres spécifiques du frontend"
#: libretranslate/locales/.swag.py:16
msgid "frontend settings"
msgstr "paramètres de frontend"
#: libretranslate/locales/.swag.py:17
msgid "frontend"
msgstr "frontend"
#: libretranslate/locales/.swag.py:18
msgid "Submit a suggestion to improve a translation"
msgstr "Soumettre une suggestion pour améliorer la traduction"
#: libretranslate/locales/.swag.py:19
msgid "Success"
msgstr "Succès"
#: libretranslate/locales/.swag.py:20
msgid "Not authorized"
msgstr "Non autorisé"
#: libretranslate/locales/.swag.py:21
msgid "feedback"
msgstr "rétroaction"
#: libretranslate/locales/.swag.py:22
msgid "Language code"
msgstr "Code de langue"
#: libretranslate/locales/.swag.py:23
msgid "Human-readable language name (in English)"
msgstr "Nom de langue lisible (en anglais)"
#: libretranslate/locales/.swag.py:24
msgid "Supported target language codes"
msgstr "Codes linguistiques ciblés appuyés"
#: libretranslate/locales/.swag.py:25
msgid "Translated text(s)"
msgstr "Texte(s) traduit(s)"
#: libretranslate/locales/.swag.py:26
msgid "Error message"
msgstr "Message d &apos; erreur"
#: libretranslate/locales/.swag.py:27
msgid "Reason for slow down"
msgstr "Raison de ralentir"
#: libretranslate/locales/.swag.py:28
msgid "Translated file url"
msgstr "Fichier traduit url"
#: libretranslate/locales/.swag.py:29
msgid "Confidence value"
msgstr "Valeur de confiance"
#: libretranslate/locales/.swag.py:30
msgid "Character input limit for this language (-1 indicates no limit)"
msgstr ""
"Limite d'entrée de caractères pour cette langue (-1 n'indique aucune limite)"
#: libretranslate/locales/.swag.py:31
msgid "Frontend translation timeout"
msgstr "Délai de traduction de Frontend"
#: libretranslate/locales/.swag.py:32
msgid "Whether the API key database is enabled."
msgstr "Que la base de données clé API soit activée."
#: libretranslate/locales/.swag.py:33
msgid "Whether an API key is required."
msgstr "Si une clé API est requise."
#: libretranslate/locales/.swag.py:34
msgid "Whether submitting suggestions is enabled."
msgstr "La possibilité de soumettre des suggestions est activée."
#: libretranslate/locales/.swag.py:35
msgid "Supported files format"
msgstr "Format des fichiers supportés"
#: libretranslate/locales/.swag.py:36
msgid "Whether submission was successful"
msgstr "Que la soumission soit réussie"
#: libretranslate/templates/app.js.template:31
#: libretranslate/templates/app.js.template:275
#: libretranslate/templates/app.js.template:279
msgid "Copy text"
msgstr "Copie du texte"
#: libretranslate/templates/app.js.template:72
#: libretranslate/templates/app.js.template:78
#: libretranslate/templates/app.js.template:83
#: libretranslate/templates/app.js.template:262
#: libretranslate/templates/app.js.template:332
#: libretranslate/templates/app.js.template:402
#: libretranslate/templates/app.js.template:447
#, python-format
msgid "Cannot load %(url)s"
msgstr "Charge %(url)s"
#: libretranslate/templates/app.js.template:253
#: libretranslate/templates/app.js.template:323
#: libretranslate/templates/app.js.template:385
#: libretranslate/templates/app.js.template:395
msgid "Unknown error"
msgstr "Erreur inconnue"
#: libretranslate/templates/app.js.template:276
msgid "Copied"
msgstr "Copied"
#: libretranslate/templates/app.js.template:320
msgid ""
"Thanks for your correction. Note the suggestion will not take effect right "
"away."
msgstr ""
"Merci pour votre correction. Notez que la suggestion ne prendra pas effet "
"immédiatement."
#: libretranslate/templates/app.js.template:423
msgid "No languages available. Did you install the models correctly?"
msgstr ""
"Pas de langues disponibles. Avez-vous installé les modèles correctement ?"
#: libretranslate/templates/app.js.template:479
#, python-format
msgid "Type in your API Key. If you need an API key, %(instructions)s"
msgstr ""
"Entrez votre clé API. Si vous avez besoin d'une clé API, %(instructions)s"
#: libretranslate/templates/app.js.template:479
msgid "press the \"Get API Key\" link."
msgstr "appuyez sur le lien \"Get API Key\"."
#: libretranslate/templates/app.js.template:479
msgid "contact the server operator."
msgstr "contactez l'opérateur du serveur."
#: libretranslate/templates/index.html:8
#: libretranslate/templates/index.html:25
#: libretranslate/templates/index.html:333
msgid "Free and Open Source Machine Translation API"
msgstr "API de Traduction Automatique gratuite et Open Source"
#: libretranslate/templates/index.html:10
#: libretranslate/templates/index.html:29
msgid ""
"Free and Open Source Machine Translation API. Self-hosted, offline capable "
"and easy to setup. Run your own API server in just a few minutes."
msgstr ""
"API de Traduction Automatique et Open Source. Auto-hostée, hors ligne "
"capable et facile à installer. Exécutez votre propre serveur API en quelques"
" minutes."
#: libretranslate/templates/index.html:11
msgid "translation"
msgstr "traduction"
#: libretranslate/templates/index.html:11
msgid "api"
msgstr "api"
#: libretranslate/templates/index.html:64
msgid "API Docs"
msgstr "API Docs"
#: libretranslate/templates/index.html:66
msgid "Get API Key"
msgstr "Obtenir API Key"
#: libretranslate/templates/index.html:68
msgid "GitHub"
msgstr "GitHub"
#: libretranslate/templates/index.html:70
msgid "Set API Key"
msgstr "Set API Key"
#: libretranslate/templates/index.html:72
msgid "Change language"
msgstr "Changer de langue"
#: libretranslate/templates/index.html:78
msgid "Edit"
msgstr "Edit"
#: libretranslate/templates/index.html:154
msgid "Dismiss"
msgstr "Dismiss"
#: libretranslate/templates/index.html:168
msgid "Translation API"
msgstr "API de traduction"
#: libretranslate/templates/index.html:172
msgid "Translate Text"
msgstr "Texte traduit"
#: libretranslate/templates/index.html:176
msgid "Translate Files"
msgstr "Translate Files"
#: libretranslate/templates/index.html:182
msgid "Translate from"
msgstr "Translate from"
#: libretranslate/templates/index.html:192
msgid "Swap source and target languages"
msgstr "Inverser la source et les langues cibles"
#: libretranslate/templates/index.html:195
msgid "Translate into"
msgstr "Translate into"
#: libretranslate/templates/index.html:207
msgid "Text to translate"
msgstr "Texte pour traduire"
#: libretranslate/templates/index.html:210
msgid "Delete text"
msgstr "Supprimer le texte"
#: libretranslate/templates/index.html:223
msgid "Suggest translation"
msgstr "Traduction suggérée"
#: libretranslate/templates/index.html:227
msgid "Cancel"
msgstr "Annuler"
#: libretranslate/templates/index.html:230
msgid "Send"
msgstr "Envoyer"
#: libretranslate/templates/index.html:246
msgid "Supported file formats:"
msgstr "Formats de fichiers supportés:"
#: libretranslate/templates/index.html:250
msgid "File"
msgstr "Fichier"
#: libretranslate/templates/index.html:265
msgid "Remove file"
msgstr "Supprimer le fichier"
#: libretranslate/templates/index.html:272
msgid "Translate"
msgstr "Traduire"
#: libretranslate/templates/index.html:273
#: libretranslate/templates/index.html:317
msgid "Download"
msgstr "Télécharger"
#: libretranslate/templates/index.html:292
msgid "Request"
msgstr "Demande"
#: libretranslate/templates/index.html:297
msgid "Response"
msgstr "Réponse"
#: libretranslate/templates/index.html:312
msgid "Open Source Machine Translation API"
msgstr "Open Source API de Traduction automatique"
#: libretranslate/templates/index.html:313
msgid "Self-Hosted. Offline Capable. Easy to Setup."
msgstr "Auto-Hosted. Offline Capable. Facile à installer."
#: libretranslate/templates/index.html:332
msgid "LibreTranslate"
msgstr "LibreTranslate"
#: libretranslate/templates/index.html:334
msgid "License:"
msgstr "Licence:"
#: libretranslate/templates/index.html:337
#, python-format
msgid ""
"This public API should be used for testing, personal or infrequent use. If "
"you're going to run an application in production, please %(host_server)s or "
"%(get_api_key)s."
msgstr ""
"Cette API publique devrait être utilisée pour les tests, l'utilisation "
"personnelle ou occasionnelle. Si vous allez exécuter une demande en "
"production, s'il vous plaît %(host_server)s ou %(get_api_key)s."
#: libretranslate/templates/index.html:337
msgid "host your own server"
msgstr "hôte de votre propre serveur"
#: libretranslate/templates/index.html:337
msgid "get an API key"
msgstr "obtenir une clé API"
#: libretranslate/templates/index.html:345
#, python-format
msgid "Made with %(heart)s by %(contributors)s and powered by %(engine)s"
msgstr ""
"Fabriqué avec %(heart)s by %(contributors)s et alimenté par %(engine)s"
#: libretranslate/templates/index.html:345
#, python-format
msgid "%(libretranslate)s Contributors"
msgstr "%(libretranslate)s Contributeurs"

View file

@ -0,0 +1,4 @@
{
"name": "French",
"reviewed": false
}

View file

@ -0,0 +1,606 @@
# Italian translations for LibreTranslate.
# Copyright (C) 2023 LibreTranslate Authors
# This file is distributed under the same license as the LibreTranslate
# project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2023.
#
msgid ""
msgstr ""
"Project-Id-Version: LibreTranslate 1.3.9\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2023-01-06 14:26-0500\n"
"PO-Revision-Date: 2023-01-06 14:26-0500\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: it <LL@li.org>\n"
"Language: it\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"Generated-By: Babel 2.11.0\n"
#: libretranslate/app.py:60
msgid "Invalid JSON format"
msgstr "Formato JSON non valido"
#: libretranslate/app.py:128 libretranslate/templates/app.js.template:427
msgid "Auto Detect"
msgstr "Rilevamento automatico"
#: libretranslate/app.py:193
msgid "Unauthorized"
msgstr "Non autorizzato"
#: libretranslate/app.py:211
msgid "Too many request limits violations"
msgstr "Troppe richieste limitano le violazioni"
#: libretranslate/app.py:220
msgid "Invalid API key"
msgstr "Chiave API non valida"
#: libretranslate/app.py:227
msgid "Please contact the server operator to get an API key"
msgstr ""
"Si prega di contattare l'operatore del server per ottenere una chiave API"
#: libretranslate/app.py:229
#, python-format
msgid "Visit %(url)s to get an API key"
msgstr "Visita %(url)s per ottenere una chiave API"
#: libretranslate/app.py:269
msgid "Slowdown:"
msgstr "Rallenta:"
#: libretranslate/app.py:467 libretranslate/app.py:469
#: libretranslate/app.py:471 libretranslate/app.py:683
#: libretranslate/app.py:685 libretranslate/app.py:687
#: libretranslate/app.py:838 libretranslate/app.py:987
#: libretranslate/app.py:989 libretranslate/app.py:991
#: libretranslate/app.py:993
#, python-format
msgid "Invalid request: missing %(name)s parameter"
msgstr "Richiesta non valida: mancante %(name)s parametro"
#: libretranslate/app.py:480 libretranslate/app.py:492
#, python-format
msgid "Invalid request: request (%(size)s) exceeds text limit (%(limit)s)"
msgstr ""
"Richiesta non valida: richiesta (%(size)s) supera il limite di testo "
"(%(limit)s)"
#: libretranslate/app.py:525 libretranslate/app.py:530
#: libretranslate/app.py:700 libretranslate/app.py:705
#, python-format
msgid "%(lang)s is not supported"
msgstr "%(lang)s non è supportato"
#: libretranslate/app.py:536
#, python-format
msgid "%(format)s format is not supported"
msgstr "%(format)s formato non è supportato"
#: libretranslate/app.py:544 libretranslate/app.py:568
#, python-format
msgid ""
"%(tname)s (%(tcode)s) is not available as a target language from %(sname)s "
"(%(scode)s)"
msgstr ""
"%(tname)s (%(tcode)s) non è disponibile come lingua di destinazione "
"%(sname)s (%(scode)s)"
#: libretranslate/app.py:589
#, python-format
msgid "Cannot translate text: %(text)s"
msgstr "Non può tradurre il testo: %(text)s"
#: libretranslate/app.py:676 libretranslate/app.py:730
msgid "Files translation are disabled on this server."
msgstr "La traduzione dei file è disabilitata su questo server."
#: libretranslate/app.py:690
msgid "Invalid request: empty file"
msgstr "Richiesta non valida: file vuoto"
#: libretranslate/app.py:693
msgid "Invalid request: file format not supported"
msgstr "Richiesta non valida: formato file non supportato"
#: libretranslate/app.py:738
msgid "Invalid filename"
msgstr "Invalid filename"
#: libretranslate/app.py:979
msgid "Suggestions are disabled on this server."
msgstr "I suggerimenti sono disabilitati su questo server."
#: libretranslate/locales/.langs.py:1
msgid "English"
msgstr "Inglese"
#: libretranslate/locales/.langs.py:2
msgid "Arabic"
msgstr "Arabo"
#: libretranslate/locales/.langs.py:3
msgid "Azerbaijani"
msgstr "Azerbaigian"
#: libretranslate/locales/.langs.py:4
msgid "Chinese"
msgstr "Cinese"
#: libretranslate/locales/.langs.py:5
msgid "Czech"
msgstr "Ceco"
#: libretranslate/locales/.langs.py:6
msgid "Danish"
msgstr "Danese"
#: libretranslate/locales/.langs.py:7
msgid "Dutch"
msgstr "Paesi Bassi"
#: libretranslate/locales/.langs.py:8
msgid "Esperanto"
msgstr "Esperanto"
#: libretranslate/locales/.langs.py:9
msgid "Finnish"
msgstr "Finlandia"
#: libretranslate/locales/.langs.py:10
msgid "French"
msgstr "Francese"
#: libretranslate/locales/.langs.py:11
msgid "German"
msgstr "Germania"
#: libretranslate/locales/.langs.py:12
msgid "Greek"
msgstr "Greco"
#: libretranslate/locales/.langs.py:13
msgid "Hebrew"
msgstr "Ebraico"
#: libretranslate/locales/.langs.py:14
msgid "Hindi"
msgstr "Hindi"
#: libretranslate/locales/.langs.py:15
msgid "Hungarian"
msgstr "Ungherese"
#: libretranslate/locales/.langs.py:16
msgid "Indonesian"
msgstr "Indonesiano"
#: libretranslate/locales/.langs.py:17
msgid "Irish"
msgstr "Irlanda"
#: libretranslate/locales/.langs.py:18
msgid "Italian"
msgstr "Italiano"
#: libretranslate/locales/.langs.py:19
msgid "Japanese"
msgstr "Giappone"
#: libretranslate/locales/.langs.py:20
msgid "Korean"
msgstr "Coreano"
#: libretranslate/locales/.langs.py:21
msgid "Persian"
msgstr "Persiano"
#: libretranslate/locales/.langs.py:22
msgid "Polish"
msgstr "Polacco"
#: libretranslate/locales/.langs.py:23
msgid "Portuguese"
msgstr "Portoghese"
#: libretranslate/locales/.langs.py:24
msgid "Russian"
msgstr "Russo"
#: libretranslate/locales/.langs.py:25
msgid "Slovak"
msgstr "Slovacchia"
#: libretranslate/locales/.langs.py:26
msgid "Spanish"
msgstr "Spagnolo"
#: libretranslate/locales/.langs.py:27
msgid "Swedish"
msgstr "Svezia"
#: libretranslate/locales/.langs.py:28
msgid "Turkish"
msgstr "Turco"
#: libretranslate/locales/.langs.py:29
msgid "Ukranian"
msgstr "Ucraina"
#: libretranslate/locales/.langs.py:30
msgid "Vietnamese"
msgstr "Vietnamita"
#: libretranslate/locales/.swag.py:1
msgid "Retrieve list of supported languages"
msgstr "Recuperare l'elenco delle lingue supportate"
#: libretranslate/locales/.swag.py:2
msgid "List of languages"
msgstr "Elenco delle lingue"
#: libretranslate/locales/.swag.py:3
msgid "translate"
msgstr "tradurre"
#: libretranslate/locales/.swag.py:4
msgid "Translate text from a language to another"
msgstr "Tradurre testo da una lingua a un'altra"
#: libretranslate/locales/.swag.py:5 libretranslate/templates/index.html:219
msgid "Translated text"
msgstr "Tradotto testo"
#: libretranslate/locales/.swag.py:6
msgid "Invalid request"
msgstr "Richiesta non valida"
#: libretranslate/locales/.swag.py:7
msgid "Translation error"
msgstr "Errore di traduzione"
#: libretranslate/locales/.swag.py:8
msgid "Slow down"
msgstr "Rallenta"
#: libretranslate/locales/.swag.py:9
msgid "Banned"
msgstr "Banati"
#: libretranslate/locales/.swag.py:10
msgid "Translate file from a language to another"
msgstr "Tradurre file da una lingua a un'altra"
#: libretranslate/locales/.swag.py:11
msgid "Translated file"
msgstr "Tradotto file"
#: libretranslate/locales/.swag.py:12
msgid "Detect the language of a single text"
msgstr "Rileva la lingua di un singolo testo"
#: libretranslate/locales/.swag.py:13
msgid "Detections"
msgstr "Rilevazioni"
#: libretranslate/locales/.swag.py:14
msgid "Detection error"
msgstr "Errore di rilevamento"
#: libretranslate/locales/.swag.py:15
msgid "Retrieve frontend specific settings"
msgstr "Recuperare le impostazioni specifiche di frontend"
#: libretranslate/locales/.swag.py:16
msgid "frontend settings"
msgstr "impostazioni di frontend"
#: libretranslate/locales/.swag.py:17
msgid "frontend"
msgstr "fronte"
#: libretranslate/locales/.swag.py:18
msgid "Submit a suggestion to improve a translation"
msgstr "Inviare un suggerimento per migliorare una traduzione"
#: libretranslate/locales/.swag.py:19
msgid "Success"
msgstr "Successo"
#: libretranslate/locales/.swag.py:20
msgid "Not authorized"
msgstr "Non autorizzato"
#: libretranslate/locales/.swag.py:21
msgid "feedback"
msgstr "feedback"
#: libretranslate/locales/.swag.py:22
msgid "Language code"
msgstr "Codice linguistico"
#: libretranslate/locales/.swag.py:23
msgid "Human-readable language name (in English)"
msgstr "Nome di lingua leggibile dall'uomo (in inglese)"
#: libretranslate/locales/.swag.py:24
msgid "Supported target language codes"
msgstr "Codici di lingua target supportati"
#: libretranslate/locales/.swag.py:25
msgid "Translated text(s)"
msgstr "Tradotto testo(i)"
#: libretranslate/locales/.swag.py:26
msgid "Error message"
msgstr "Messaggio di errore"
#: libretranslate/locales/.swag.py:27
msgid "Reason for slow down"
msgstr "Ragione per rallentare"
#: libretranslate/locales/.swag.py:28
msgid "Translated file url"
msgstr "Tradotto file url"
#: libretranslate/locales/.swag.py:29
msgid "Confidence value"
msgstr "Valore di fiducia"
#: libretranslate/locales/.swag.py:30
msgid "Character input limit for this language (-1 indicates no limit)"
msgstr "Limite di ingresso per questa lingua (-1 non indica limiti)"
#: libretranslate/locales/.swag.py:31
msgid "Frontend translation timeout"
msgstr "Tempo di traduzione Frontend"
#: libretranslate/locales/.swag.py:32
msgid "Whether the API key database is enabled."
msgstr "Se il database chiave API è abilitato."
#: libretranslate/locales/.swag.py:33
msgid "Whether an API key is required."
msgstr "Se è richiesta una chiave API."
#: libretranslate/locales/.swag.py:34
msgid "Whether submitting suggestions is enabled."
msgstr "Se presentare suggerimenti è abilitato."
#: libretranslate/locales/.swag.py:35
msgid "Supported files format"
msgstr "Formato file supportato"
#: libretranslate/locales/.swag.py:36
msgid "Whether submission was successful"
msgstr "Se la presentazione è stata di successo"
#: libretranslate/templates/app.js.template:31
#: libretranslate/templates/app.js.template:275
#: libretranslate/templates/app.js.template:279
msgid "Copy text"
msgstr "Copia testo"
#: libretranslate/templates/app.js.template:72
#: libretranslate/templates/app.js.template:78
#: libretranslate/templates/app.js.template:83
#: libretranslate/templates/app.js.template:262
#: libretranslate/templates/app.js.template:332
#: libretranslate/templates/app.js.template:402
#: libretranslate/templates/app.js.template:447
#, python-format
msgid "Cannot load %(url)s"
msgstr "Non riesco a caricare %(url)s"
#: libretranslate/templates/app.js.template:253
#: libretranslate/templates/app.js.template:323
#: libretranslate/templates/app.js.template:385
#: libretranslate/templates/app.js.template:395
msgid "Unknown error"
msgstr "Errore sconosciuto"
#: libretranslate/templates/app.js.template:276
msgid "Copied"
msgstr "Copie"
#: libretranslate/templates/app.js.template:320
msgid ""
"Thanks for your correction. Note the suggestion will not take effect right "
"away."
msgstr ""
"Grazie per la sua correzione. Si noti che il suggerimento non avrà effetto "
"subito."
#: libretranslate/templates/app.js.template:423
msgid "No languages available. Did you install the models correctly?"
msgstr "Nessuna lingua disponibile. Hai installato correttamente i modelli?"
#: libretranslate/templates/app.js.template:479
#, python-format
msgid "Type in your API Key. If you need an API key, %(instructions)s"
msgstr ""
"Digitare nella chiave API. Se hai bisogno di una chiave API, "
"%(instructions)s"
#: libretranslate/templates/app.js.template:479
msgid "press the \"Get API Key\" link."
msgstr "premere il link \"Get API Key\"."
#: libretranslate/templates/app.js.template:479
msgid "contact the server operator."
msgstr "contattare l'operatore del server."
#: libretranslate/templates/index.html:8
#: libretranslate/templates/index.html:25
#: libretranslate/templates/index.html:333
msgid "Free and Open Source Machine Translation API"
msgstr "API di traduzione automatica gratuita e open source"
#: libretranslate/templates/index.html:10
#: libretranslate/templates/index.html:29
msgid ""
"Free and Open Source Machine Translation API. Self-hosted, offline capable "
"and easy to setup. Run your own API server in just a few minutes."
msgstr ""
"API di traduzione automatica gratuita e open source. Auto-hosted, offline in"
" grado e facile da configurare. Eseguire il proprio server API in pochi "
"minuti."
#: libretranslate/templates/index.html:11
msgid "translation"
msgstr "traduzione"
#: libretranslate/templates/index.html:11
msgid "api"
msgstr "api"
#: libretranslate/templates/index.html:64
msgid "API Docs"
msgstr "API"
#: libretranslate/templates/index.html:66
msgid "Get API Key"
msgstr "Ottieni API Chiave"
#: libretranslate/templates/index.html:68
msgid "GitHub"
msgstr "GitHub"
#: libretranslate/templates/index.html:70
msgid "Set API Key"
msgstr "Set API Chiave"
#: libretranslate/templates/index.html:72
msgid "Change language"
msgstr "Cambia la lingua"
#: libretranslate/templates/index.html:78
msgid "Edit"
msgstr "Modifica"
#: libretranslate/templates/index.html:154
msgid "Dismiss"
msgstr "Oggetto"
#: libretranslate/templates/index.html:168
msgid "Translation API"
msgstr "API di traduzione"
#: libretranslate/templates/index.html:172
msgid "Translate Text"
msgstr "Traduzione"
#: libretranslate/templates/index.html:176
msgid "Translate Files"
msgstr "Traduci file"
#: libretranslate/templates/index.html:182
msgid "Translate from"
msgstr "Traduttore da"
#: libretranslate/templates/index.html:192
msgid "Swap source and target languages"
msgstr "Swap sorgente e lingue di destinazione"
#: libretranslate/templates/index.html:195
msgid "Translate into"
msgstr "Traduzione"
#: libretranslate/templates/index.html:207
msgid "Text to translate"
msgstr "Testo da tradurre"
#: libretranslate/templates/index.html:210
msgid "Delete text"
msgstr "Eliminare il testo"
#: libretranslate/templates/index.html:223
msgid "Suggest translation"
msgstr "Suggerisci la traduzione"
#: libretranslate/templates/index.html:227
msgid "Cancel"
msgstr "Annulla"
#: libretranslate/templates/index.html:230
msgid "Send"
msgstr "Invia"
#: libretranslate/templates/index.html:246
msgid "Supported file formats:"
msgstr "Formati di file supportati:"
#: libretranslate/templates/index.html:250
msgid "File"
msgstr "File"
#: libretranslate/templates/index.html:265
msgid "Remove file"
msgstr "Rimuovi file"
#: libretranslate/templates/index.html:272
msgid "Translate"
msgstr "Traduttore"
#: libretranslate/templates/index.html:273
#: libretranslate/templates/index.html:317
msgid "Download"
msgstr "Scarica"
#: libretranslate/templates/index.html:292
msgid "Request"
msgstr "Richiesta"
#: libretranslate/templates/index.html:297
msgid "Response"
msgstr "Risposta"
#: libretranslate/templates/index.html:312
msgid "Open Source Machine Translation API"
msgstr "API di traduzione automatica Open Source"
#: libretranslate/templates/index.html:313
msgid "Self-Hosted. Offline Capable. Easy to Setup."
msgstr "Ossessionato. Offline Capable. Facile da configurare."
#: libretranslate/templates/index.html:332
msgid "LibreTranslate"
msgstr "LibreTranslate"
#: libretranslate/templates/index.html:334
msgid "License:"
msgstr "Licenza:"
#: libretranslate/templates/index.html:337
#, python-format
msgid ""
"This public API should be used for testing, personal or infrequent use. If "
"you're going to run an application in production, please %(host_server)s or "
"%(get_api_key)s."
msgstr ""
"Questa API pubblica dovrebbe essere utilizzata per il test, uso personale o "
"infrequente. Se hai intenzione di eseguire un'applicazione in produzione, "
"per favore %(host_server)s o %(get_api_key)s."
#: libretranslate/templates/index.html:337
msgid "host your own server"
msgstr "host tuo server"
#: libretranslate/templates/index.html:337
msgid "get an API key"
msgstr "ottenere una chiave API"
#: libretranslate/templates/index.html:345
#, python-format
msgid "Made with %(heart)s by %(contributors)s and powered by %(engine)s"
msgstr ""
"Realizzato con %(heart)s di %(contributors)s e alimentato da %(engine)s"
#: libretranslate/templates/index.html:345
#, python-format
msgid "%(libretranslate)s Contributors"
msgstr "%(libretranslate)s Contributori"

View file

@ -0,0 +1,4 @@
{
"name": "Italian",
"reviewed": false
}

View file

@ -52,7 +52,7 @@
margin-top: 1rem; margin-top: 1rem;
} }
select { select, select#locales{
color: #fff; color: #fff;
background: #111; background: #111;
} }

View file

@ -11,6 +11,10 @@ a {
text-decoration: underline; text-decoration: underline;
} }
a.noline{
text-decoration: none;
}
#app { #app {
min-height: 80vh; min-height: 80vh;
} }
@ -35,6 +39,88 @@ h3.header {
position: relative; position: relative;
} }
.top-nav .locale-panel{
position: absolute;
top: 64px;
height: 68px;
right: 0;
padding: 0 16px;
width: 240px;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
box-shadow: 0 2px 2px 0 rgb(0 0 0 / 14%), 0 3px 1px -2px rgb(0 0 0 / 12%), 0px 4px 5px 0 rgb(0 0 0 / 20%);
}
.locale-panel{
display: none;
}
#nav:hover .change-language:hover + .locale-panel,
#nav-mobile:hover .change-language:hover + .locale-panel,
.change-language.clicked + .locale-panel{
display: block;
}
#nav:hover .locale-panel:hover,
#nav-mobile .locale-panel:hover{
display: block;
}
.locale-panel select{
display: block;
height: 32px;
font-size: 14px;
background-color: #fff;
border: none;
}
.locale-panel a{
line-height: normal;
font-size: 90%;
padding: 0;
margin-top: 6px;
text-align: right;
text-decoration: none;
height: 28px;
}
.locale-panel a:hover{
text-decoration: underline;
}
.locale-panel a i.material-icons{
display: inline-block;
line-height: initial;
line-height: 14px;
font-size: 100%;
position: relative;
top: 2px;
left: 2px;
}
.locale-panel a:hover{
background-color: transparent !important;
}
#nav-mobile .locale-panel{
color: rgba(0,0,0,0.87);
padding: 0 32px;
padding-top: 12px;
}
#nav-mobile a, #nav-mobile a i.material-icons{
color: #fff;
}
#nav-mobile .locale-panel a{
padding: 0;
}
#nav-mobile .locale-panel a i.material-icons{
float: none;
}
.language-select { .language-select {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;

View file

@ -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 + url_prefix ; 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');
@ -28,7 +28,7 @@ document.addEventListener('DOMContentLoaded', function(){
detectedLangText: "", detectedLangText: "",
copyTextLabel: "Copy text", copyTextLabel: {{ _e("Copy text") }},
suggestions: false, suggestions: false,
isSuggesting: false, isSuggesting: false,
@ -69,18 +69,18 @@ document.addEventListener('DOMContentLoaded', function(){
} }
} }
} else { } else {
self.error = "Cannot load /frontend/settings"; self.error = {{ _e("Cannot load %(url)s", url="/frontend/settings") }};
self.loading = false; self.loading = false;
} }
}; };
settingsRequest.onerror = function() { settingsRequest.onerror = function() {
self.error = "Error while calling /frontend/settings"; self.error = {{ _e("Cannot load %(url)s", url="/frontend/settings") }};
self.loading = false; self.loading = false;
}; };
langsRequest.onerror = function() { langsRequest.onerror = function() {
self.error = "Error while calling /languages"; self.error = {{ _e("Cannot load %(url)s", url="/languages") }};
self.loading = false; self.loading = false;
}; };
@ -250,7 +250,7 @@ document.addEventListener('DOMContentLoaded', function(){
self.detectedLangText = ": " + (lang !== undefined ? lang.name : res.detectedLanguage.language) + " (" + res.detectedLanguage.confidence + "%)"; self.detectedLangText = ": " + (lang !== undefined ? lang.name : res.detectedLanguage.language) + " (" + res.detectedLanguage.confidence + "%)";
} }
} else{ } else{
throw new Error(res.error || "Unknown error"); throw new Error(res.error || {{ _e("Unknown error") }});
} }
} catch (e) { } catch (e) {
self.error = e.message; self.error = e.message;
@ -259,7 +259,7 @@ document.addEventListener('DOMContentLoaded', function(){
}; };
request.onerror = function() { request.onerror = function() {
self.error = "Error while calling /translate"; self.error = {{ _e("Cannot load %(url)s", url="/translate") }};
self.loadingTranslation = false; self.loadingTranslation = false;
}; };
@ -272,11 +272,11 @@ document.addEventListener('DOMContentLoaded', function(){
this.$refs.translatedTextarea.setSelectionRange(0, 9999999); /* For mobile devices */ this.$refs.translatedTextarea.setSelectionRange(0, 9999999); /* For mobile devices */
document.execCommand("copy"); document.execCommand("copy");
if (this.copyTextLabel === "Copy text"){ if (this.copyTextLabel === {{ _e("Copy text") }}){
this.copyTextLabel = "Copied"; this.copyTextLabel = {{ _e("Copied") }};
var self = this; var self = this;
setTimeout(function(){ setTimeout(function(){
self.copyTextLabel = "Copy text"; self.copyTextLabel = {{ _e("Copy text") }};
}, 1500); }, 1500);
} }
}, },
@ -317,10 +317,10 @@ document.addEventListener('DOMContentLoaded', function(){
try{ try{
var res = JSON.parse(this.response); var res = JSON.parse(this.response);
if (res.success){ if (res.success){
M.toast({html: 'Thanks for your correction. Note the suggestion will not take effect right away.'}) M.toast({html: {{ _e("Thanks for your correction. Note the suggestion will not take effect right away.") }} })
self.closeSuggestTranslation(e) self.closeSuggestTranslation(e)
}else{ }else{
throw new Error(res.error || "Unknown error"); throw new Error(res.error || {{ _e("Unknown error") }});
} }
}catch(e){ }catch(e){
self.error = e.message; self.error = e.message;
@ -329,7 +329,7 @@ document.addEventListener('DOMContentLoaded', function(){
}; };
request.onerror = function() { request.onerror = function() {
self.error = "Error while calling /suggest"; self.error = {{ _e("Cannot load %(url)s", url="/suggest") }};
self.loadingTranslation = false; self.loadingTranslation = false;
}; };
@ -382,7 +382,7 @@ document.addEventListener('DOMContentLoaded', function(){
link.href = self.translatedFileUrl; link.href = self.translatedFileUrl;
link.click(); link.click();
}else{ }else{
throw new Error(res.error || "Unknown error"); throw new Error(res.error || {{ _e("Unknown error") }});
} }
}catch(e){ }catch(e){
@ -392,14 +392,14 @@ document.addEventListener('DOMContentLoaded', function(){
} }
}else{ }else{
let res = JSON.parse(this.response); let res = JSON.parse(this.response);
self.error = res.error || "Unknown error"; self.error = res.error || {{ _e("Unknown error") }};
self.loadingFileTranslation = false; self.loadingFileTranslation = false;
self.inputFile = false; self.inputFile = false;
} }
} }
translateFileRequest.onerror = function() { translateFileRequest.onerror = function() {
self.error = "Error while calling /translate_file"; self.error = {{ _e("Cannot load %(url)s", url="/translate_file") }};
self.loadingFileTranslation = false; self.loadingFileTranslation = false;
self.inputFile = false; self.inputFile = false;
}; };
@ -420,11 +420,11 @@ function handleLangsResponse(self, response) {
if (self.langs.length === 0){ if (self.langs.length === 0){
self.loading = false; self.loading = false;
self.error = "No languages available. Did you install the models correctly?" self.error = {{ _e("No languages available. Did you install the models correctly?") }};
return; return;
} }
self.langs.push({ name: "Auto Detect", code: "auto", targets: self.langs.map(l => l.code)}) self.langs.push({ name: {{ _e("Auto Detect") }}, code: "auto", targets: self.langs.map(l => l.code)})
const sourceLanguage = self.langs.find(l => l.code === self.getQueryParam("source")) const sourceLanguage = self.langs.find(l => l.code === self.getQueryParam("source"))
const targetLanguage = self.langs.find(l => l.code === self.getQueryParam("target")) const targetLanguage = self.langs.find(l => l.code === self.getQueryParam("target"))
@ -444,7 +444,7 @@ function handleLangsResponse(self, response) {
self.handleInput(new Event('none')) self.handleInput(new Event('none'))
} }
} else { } else {
self.error = "Cannot load /languages"; self.error = {{ _e("Cannot load %(url)s", url="/languages") }};
} }
self.loading = false; self.loading = false;
@ -476,9 +476,7 @@ function getTextWidth(text) {
function setApiKey(){ function setApiKey(){
var prevKey = localStorage.getItem("api_key") || ""; var prevKey = localStorage.getItem("api_key") || "";
var newKey = ""; var newKey = "";
var instructions = "contact the server operator."; newKey = window.prompt({{ _e("Type in your API Key. If you need an API key, %(instructions)s", instructions=_e("press the \"Get API Key\" link.") if get_api_key_link else _e("contact the server operator.")) }}, prevKey);
if (window.getApiKeyLink) instructions = "press the \"Get API Key\" link."
newKey = window.prompt("Type in your API Key. If you need an API key, " + instructions, prevKey);
if (newKey === null) newKey = ""; if (newKey === null) newKey = "";
localStorage.setItem("api_key", newKey); localStorage.setItem("api_key", newKey);

View file

@ -1,21 +1,20 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="{{ current_locale }}">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LibreTranslate - Free and Open Source Machine Translation API</title> {% for al in alternate_locales %}<link rel="alternate" hreflang="{{ al.lang }}" href="{{ al.link }}" />
{% endfor %}
<title>LibreTranslate - {{ _h("Free and Open Source Machine Translation API") }}</title>
<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="{{ _h('Free and Open Source Machine Translation API. 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="{{ _h('translation') }},{{ _h('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">
<link rel="preload" href="{{ url_for('static', filename='js/materialize.min.js') }}" as="script"> <link rel="preload" href="{{ url_for('static', filename='js/materialize.min.js') }}" as="script">
<link rel="preload" href="{{ url_for('static', filename='js/prism.min.js') }}" as="script"> <link rel="preload" href="{{ url_for('static', filename='js/prism.min.js') }}" as="script">
<link rel="preload" href="{{ url_for('static', filename='js/app.js') }}?v={{ version }}" as="script"> <link rel="preload" href="js/app.js?v={{ version }}" as="script">
<link rel="preload" href="{{ url_for('static', filename='css/materialize.min.css') }}" as="style"/> <link rel="preload" href="{{ url_for('static', filename='css/materialize.min.css') }}" as="style"/>
<link rel="preload" href="{{ url_for('static', filename='css/material-icons.css') }}" as="style"/> <link rel="preload" href="{{ url_for('static', filename='css/material-icons.css') }}" as="style"/>
@ -23,11 +22,11 @@
<link rel="preload" href="{{ url_for('static', filename='css/main.css') }}?v={{ version }}" as="style"/> <link rel="preload" href="{{ url_for('static', filename='css/main.css') }}?v={{ version }}" as="style"/>
<link rel="preload" href="{{ url_for('static', filename='css/dark-theme.css') }}" as="style"/> <link rel="preload" href="{{ url_for('static', filename='css/dark-theme.css') }}" as="style"/>
<meta property="og:title" content="LibreTranslate - Free and Open Source Machine Translation API" /> <meta property="og:title" content="LibreTranslate - {{ _h('Free and Open Source Machine Translation API') }}" />
<meta property="og:type" content="website" /> <meta property="og:type" content="website" />
<meta property="og:url" content="https://libretranslate.com" /> <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" /> <meta property="og:image" content="https://user-images.githubusercontent.com/1951843/102724116-32a6df00-42db-11eb-8cc0-129ab39cdfb5.png" />
<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."/> <meta property="og:description" name="description" class="swiftype" content="{{ _h('Free and Open Source Machine Translation API. Self-hosted, offline capable and easy to setup. Run your own API server in just a few minutes.') }}"/>
<script src="{{ url_for('static', filename='js/vue@2.js') }}"></script> <script src="{{ url_for('static', filename='js/vue@2.js') }}"></script>
@ -60,28 +59,64 @@
<img src="{{ url_for('static', filename='icon.svg') }}" alt="" class="logo"> <img src="{{ url_for('static', filename='icon.svg') }}" alt="" class="logo">
<span>LibreTranslate</span> <span>LibreTranslate</span>
</a> </a>
<ul class="right hide-on-med-and-down"> <ul id="nav" class="right hide-on-med-and-down top-nav position-relative">
<li><a href="{{ swagger_url }}">API Docs</a></li> {% set menulinks %}
{% if get_api_key_link %} <li><a href="{{ swagger_url }}">{{ _h("API Docs") }}</a></li>
<li><a href="{{ get_api_key_link }}">Get API Key</a></li> {% if get_api_key_link %}
<script>window.getApiKeyLink = "{{ get_api_key_link }}";</script> <li><a href="{{ get_api_key_link }}">{{ _h("Get API Key") }}</a></li>
{% endif %} {% endif %}
<li><a href="https://github.com/LibreTranslate/LibreTranslate" rel="noopener noreferrer">GitHub</a></li> <li><a href="https://github.com/LibreTranslate/LibreTranslate" rel="noopener noreferrer">{{ _h("GitHub") }}</a></li>
{% if api_keys %} {% if api_keys %}
<li><a href="javascript:setApiKey()" title="Set API Key" aria-label="Set API Key"><i class="material-icons">vpn_key</i></a></li> <li><a class="noline" href="javascript:setApiKey()" title="{{ _h('Set API Key') }}" aria-label="{{ _h('Set API Key') }}"><i class="material-icons">vpn_key</i></a></li>
{% endif %} {% endif %}
<li class="change-language"><a class="noline" href="javascript:void(0)" title="{{ _h('Change language') }}"><i class="material-icons">language</i></a>
</li>
<li class="locale-panel blue darken-3">
<select id="locales" onchange="changeLocale(this)">
{% for l in available_locales %}<option value="{{ l['code'] }}" {{ 'selected' if current_locale == l['code'] else ''}}>{{ l['name'] }}</option>{% endfor %}
</select>
<a href="#TODO">{{ _h("Edit") }}<i class="material-icons">create</i></a>
</li>
{% endset %}
{{ menulinks }}
</ul> </ul>
<ul id="nav-mobile" class="sidenav blue darken-3">
<ul id="nav-mobile" class="sidenav"> {{ menulinks }}
<li><a href="{{ swagger_url }}">API Docs</a></li>
{% if get_api_key_link %}
<li><a href="{{ get_api_key_link }}">Get API Key</a></li>
{% endif %}
<li><a href="https://github.com/LibreTranslate/LibreTranslate" rel="noopener noreferrer">GitHub</a></li>
{% if api_keys %}
<li><a href="javascript:setApiKey()" title="Set API Key" aria-label="Set API Key"><i class="material-icons">vpn_key</i></a></li>
{% endif %}
</ul> </ul>
<script>
var localeLinks = {
{% for al in alternate_locales %}"{{ al.lang }}": "{{ al.link }}"{% if not loop.last %},{% endif %}
{% endfor %}
};
function changeLocale(slt){
var lang = slt.value;
if (localeLinks[lang]) location.href = localeLinks[lang];
else location.href = '?lang=' + slt.value;
}
var btnChangeLangs = document.getElementsByClassName("change-language");
var localePanels = document.getElementsByClassName("locale-panel");
console.log(btnChangeLangs);
for (var i = 0; i < btnChangeLangs.length; i++){
(function(btn){
btn.addEventListener('click', function(e){
e.stopPropagation();
btn.classList.toggle('clicked');
});
})(btnChangeLangs[i]);
}
for (var i = 0; i < localePanels.length; i++){
localePanels[i].addEventListener('click', function(e){
e.stopPropagation();
});
}
document.addEventListener('click', function(){
for (var i = 0; i < btnChangeLangs.length; i++){
btnChangeLangs[i].classList.remove('clicked');
}
});
</script>
</div> </div>
</nav> </nav>
</header> </header>
@ -116,7 +151,7 @@
<i class="material-icons">warning</i><p> [[ error ]]</p> <i class="material-icons">warning</i><p> [[ error ]]</p>
</div> </div>
<div class="card-action"> <div class="card-action">
<a href="#" @click="dismissError">Dismiss</a> <a href="#" @click="dismissError">{{ _h("Dismiss") }}</a>
</div> </div>
</div> </div>
</div> </div>
@ -130,22 +165,22 @@
<div class="section no-pad-bot"> <div class="section no-pad-bot">
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<h3 class="header center">Translation API</h3> <h3 class="header center">{{ _h("Translation API") }}</h3>
<div id="translation-type-btns" class="s12 center" v-if="filesTranslation === true"> <div id="translation-type-btns" class="s12 center" v-if="filesTranslation === true">
<button type="button" class="btn btn-switch-type" @click="switchType('text')" :class="{'active': translationType === 'text'}"> <button type="button" class="btn btn-switch-type" @click="switchType('text')" :class="{'active': translationType === 'text'}">
<i aria-hidden="true" class="material-icons">title</i> <i aria-hidden="true" class="material-icons">title</i>
<span class="btn-text">Translate Text</span> <span class="btn-text">{{ _h("Translate Text") }}</span>
</button> </button>
<button type="button" class="btn btn-switch-type" @click="switchType('files')" :class="{'active': translationType === 'files'}"> <button type="button" class="btn btn-switch-type" @click="switchType('files')" :class="{'active': translationType === 'files'}">
<i aria-hidden="true" class="material-icons">description</i> <i aria-hidden="true" class="material-icons">description</i>
<span class="btn-text">Translate Files</span> <span class="btn-text">{{ _h("Translate Files") }}</span>
</button> </button>
</div> </div>
<form id="translation-form" class="col s12"> <form id="translation-form" class="col s12">
<div class="row mb-0"> <div class="row mb-0">
<div class="col s6 language-select"> <div class="col s6 language-select">
<span id="sourceLangLabel">Translate from</span> <span id="sourceLangLabel">{{ _h("Translate from") }}</span>
<span v-if="detectedLangText !== ''">[[ detectedLangText ]]</span> <span v-if="detectedLangText !== ''">[[ detectedLangText ]]</span>
<select aria-labelledby="sourceLangLabel" class="browser-default" v-model="sourceLang" ref="sourceLangDropdown" @change="handleInput"> <select aria-labelledby="sourceLangLabel" class="browser-default" v-model="sourceLang" ref="sourceLangDropdown" @change="handleInput">
<template v-for="option in langs"> <template v-for="option in langs">
<option :value="option.code">[[ option.name ]]</option> <option :value="option.code">[[ option.name ]]</option>
@ -154,10 +189,10 @@
</div> </div>
<div class="col s6 language-select"> <div class="col s6 language-select">
<a href="javascript:void(0)" @click="swapLangs" class="btn-switch-language" aria-label="Swap source and target languages"> <a href="javascript:void(0)" @click="swapLangs" class="btn-switch-language" aria-label="{{ _h('Swap source and target languages') }}">
<i class="material-icons">swap_horiz</i> <i class="material-icons">swap_horiz</i>
</a> </a>
<span id="targetLangLabel">Translate into</span> <span id="targetLangLabel">{{ _h("Translate into") }}</span>
<select aria-labelledby="targetLangLabel" class="browser-default" v-model="targetLang" ref="targetLangDropdown" @change="handleInput"> <select aria-labelledby="targetLangLabel" class="browser-default" v-model="targetLang" ref="targetLangDropdown" @change="handleInput">
<template v-for="option in targetLangs"> <template v-for="option in targetLangs">
<option v-if="option.code !== 'auto'" :value="option.code">[[ option.name ]]</option> <option v-if="option.code !== 'auto'" :value="option.code">[[ option.name ]]</option>
@ -169,10 +204,10 @@
<div class="row" v-if="translationType === 'text'"> <div class="row" v-if="translationType === 'text'">
<div class="input-field textarea-container col s6"> <div class="input-field textarea-container col s6">
<label for="textarea1" class="sr-only"> <label for="textarea1" class="sr-only">
Text to translate {{ _h("Text to translate") }}
</label> </label>
<textarea id="textarea1" v-model="inputText" @input="handleInput" ref="inputTextarea" dir="auto"></textarea> <textarea id="textarea1" v-model="inputText" @input="handleInput" ref="inputTextarea" dir="auto"></textarea>
<button class="btn-delete-text" title="Delete text" aria-label="Delete text" @click="deleteText"> <button class="btn-delete-text" title="{{ _h('Delete text') }}" aria-label="{{ _h('Delete text') }}" aria-label="Delete text" @click="deleteText">
<i class="material-icons">close</i> <i class="material-icons">close</i>
</button> </button>
<div class="characters-limit-container" v-if="charactersLimit !== -1"> <div class="characters-limit-container" v-if="charactersLimit !== -1">
@ -181,23 +216,23 @@
</div> </div>
<div class="input-field textarea-container col s6"> <div class="input-field textarea-container col s6">
<label for="textarea2" class="sr-only"> <label for="textarea2" class="sr-only">
Translated text {{ _h("Translated text") }}
</label> </label>
<textarea id="textarea2" v-model="translatedText" ref="translatedTextarea" dir="auto" v-bind:readonly="suggestions && !isSuggesting"></textarea> <textarea id="textarea2" v-model="translatedText" ref="translatedTextarea" dir="auto" v-bind:readonly="suggestions && !isSuggesting"></textarea>
<div class="actions"> <div class="actions">
<button v-if="suggestions && !loadingTranslation && inputText.length && !isSuggesting" class="btn-action" @click="suggestTranslation" aria-label="Suggest translation"> <button v-if="suggestions && !loadingTranslation && inputText.length && !isSuggesting" class="btn-action" @click="suggestTranslation" aria-label="{{ _h('Suggest translation') }}">
<i class="material-icons">edit</i> <i class="material-icons">edit</i>
</button> </button>
<button v-if="suggestions && !loadingTranslation && inputText.length && isSuggesting" class="btn-action btn-blue" @click="closeSuggestTranslation"> <button v-if="suggestions && !loadingTranslation && inputText.length && isSuggesting" class="btn-action btn-blue" @click="closeSuggestTranslation">
<span>Cancel</span> <span>{{ _h("Cancel") }}</span>
</button> </button>
<button v-if="suggestions && !loadingTranslation && inputText.length && isSuggesting" :disabled="!canSendSuggestion" class="btn-action btn-blue" @click="sendSuggestion"> <button v-if="suggestions && !loadingTranslation && inputText.length && isSuggesting" :disabled="!canSendSuggestion" class="btn-action btn-blue" @click="sendSuggestion">
<span>Send</span> <span>{{ _h("Send") }}</span>
</button> </button>
<button v-if="!isSuggesting" class="btn-action btn-copy-translated" @click="copyText"> <button v-if="!isSuggesting" class="btn-action btn-copy-translated" @click="copyText">
<span>[[ copyTextLabel ]]</span> <i class="material-icons" aria-hidden="true">content_copy</i> <span>[[ copyTextLabel ]]</span> <i class="material-icons" aria-hidden="true">content_copy</i>
</button> </button>
</div> </div>
<div class="position-relative"> <div class="position-relative">
<div class="progress translate" v-if="loadingTranslation"> <div class="progress translate" v-if="loadingTranslation">
<div class="indeterminate"></div> <div class="indeterminate"></div>
@ -205,43 +240,43 @@
</div> </div>
</div> </div>
</div> </div>
<div class="row" v-if="translationType === 'files'"> <div class="row" v-if="translationType === 'files'">
<div class="file-dropzone"> <div class="file-dropzone">
<div v-if="inputFile === false" class="dropzone-content"> <div v-if="inputFile === false" class="dropzone-content">
<span>Supported file formats: [[ supportedFilesFormatFormatted ]]</span> <span>{{ _h("Supported file formats:") }} [[ supportedFilesFormatFormatted ]]</span>
<form action="#"> <form action="#">
<div class="file-field input-field"> <div class="file-field input-field">
<div class="btn"> <div class="btn">
<span id="fileLabel">File</span> <span id="fileLabel">{{ _h("File") }}</span>
<input aria-labelledby="fileLabel" type="file" :accept="supportedFilesFormatFormatted" @change="handleInputFile" ref="fileInputRef"> <input aria-labelledby="fileLabel" type="file" :accept="supportedFilesFormatFormatted" @change="handleInputFile" ref="fileInputRef">
</div> </div>
<div class="file-path-wrapper hidden"> <div class="file-path-wrapper hidden">
<input class="file-path validate" type="text"> <input class="file-path validate" type="text">
</div> </div>
</div> </div>
</form> </form>
</div> </div>
<div v-if="inputFile !== false" class="dropzone-content"> <div v-if="inputFile !== false" class="dropzone-content">
<div class="card"> <div class="card">
<div class="card-content"> <div class="card-content">
<div class="row mb-0"> <div class="row mb-0">
<div class="col s12"> <div class="col s12">
[[ inputFile.name ]] [[ inputFile.name ]]
<button v-if="loadingFileTranslation !== true" @click="removeFile" class="btn-flat" aria-label="Remove file"> <button v-if="loadingFileTranslation !== true" @click="removeFile" class="btn-flat" aria-label="{{ _h('Remove file') }}">
<i class="material-icons">close</i> <i class="material-icons">close</i>
</button> </button>
</div>
</div>
</div>
</div>
<button @click="translateFile" v-if="translatedFileUrl === false && loadingFileTranslation === false" class="btn">Translate</button>
<a v-if="translatedFileUrl !== false" :href="translatedFileUrl" class="btn">Download</a>
<div class="progress" v-if="loadingFileTranslation">
<div class="indeterminate"></div>
</div>
</div>
</div>
</div> </div>
</div>
</div>
</div>
<button @click="translateFile" v-if="translatedFileUrl === false && loadingFileTranslation === false" class="btn">{{ _h("Translate") }}</button>
<a v-if="translatedFileUrl !== false" :href="translatedFileUrl" class="btn">{{ _h("Download") }}</a>
<div class="progress" v-if="loadingFileTranslation">
<div class="indeterminate"></div>
</div>
</div>
</div>
</div>
</form> </form>
</div> </div>
</div> </div>
@ -254,12 +289,12 @@
<div class="row center"> <div class="row center">
<div class="col s12 m12 l6 left-align"> <div class="col s12 m12 l6 left-align">
<p class="mb-0">Request</p> <p class="mb-0">{{ _h("Request") }}</p>
<pre class="code mt-0"><code class="language-javascript" v-html="$options.filters.highlight(requestCode)"> <pre class="code mt-0"><code class="language-javascript" v-html="$options.filters.highlight(requestCode)">
</code></pre> </code></pre>
</div> </div>
<div class="col s12 m12 l6 left-align"> <div class="col s12 m12 l6 left-align">
<p class="mb-0">Response</p> <p class="mb-0">{{ _h("Response") }}</p>
<pre class="code mt-0"><code class="language-javascript" v-html="$options.filters.highlight(output)"> <pre class="code mt-0"><code class="language-javascript" v-html="$options.filters.highlight(output)">
</code></pre> </code></pre>
</div> </div>
@ -274,12 +309,12 @@
<div class="container"> <div class="container">
<div class="row center"> <div class="row center">
<div class="col s12 m12"> <div class="col s12 m12">
<h3 class="header">Open Source Machine Translation API</h3> <h3 class="header">{{ _h("Open Source Machine Translation API") }}</h3>
<h4 class="header">100% Self-Hosted. Offline Capable. Easy to Setup.</h4> <h4 class="header">{{ _h("Self-Hosted. Offline Capable. Easy to Setup.") }}</h4>
<div id="download-btn-wrapper"> <div id="download-btn-wrapper">
<a id="download-btn" class="waves-effect waves-light btn btn-large teal darken-2" href="https://github.com/LibreTranslate/LibreTranslate" rel="noopener noreferrer"> <a id="download-btn" class="waves-effect waves-light btn btn-large teal darken-2" href="https://github.com/LibreTranslate/LibreTranslate" rel="noopener noreferrer">
<i aria-hidden="true" class="material-icons">cloud_download</i> <i aria-hidden="true" class="material-icons">cloud_download</i>
<span class="btn-text">Download</span> <span class="btn-text">{{ _h("Download") }}</span>
</a> </a>
</div> </div>
</div> </div>
@ -294,13 +329,12 @@
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col l12 s12"> <div class="col l12 s12">
<h5 class="white-text">LibreTranslate</h5> <h5 class="white-text">{{ _h("LibreTranslate") }}</h5>
<p class="grey-text text-lighten-4">Free and Open Source Machine Translation API</p> <p class="grey-text text-lighten-4">{{ _h("Free and Open Source Machine Translation API") }}</p>
<p>License: <a class="grey-text text-lighten-4" href="https://www.gnu.org/licenses/agpl-3.0.en.html" rel="noopener noreferrer">AGPLv3</a></p> <p>{{ _h("License:") }} <a class="grey-text text-lighten-4" href="https://www.gnu.org/licenses/agpl-3.0.en.html" rel="noopener noreferrer">AGPLv3</a></p>
<p><a class="grey-text text-lighten-4" href="/javascript-licenses" rel="jslicense">JavaScript license information</a></p>
{% if web_version %} {% if web_version %}
<p> <p>
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" rel="noopener noreferrer">host your own server</a> or <a class="grey-text text-lighten-4" href="{{ get_api_key_link if get_api_key_link else 'https://github.com/LibreTranslate/LibreTranslate#mirrors' }}" rel="noopener noreferrer">get an API key</a>. {{ _h("This public API should be used for testing, personal or infrequent use. If you're going to run an application in production, please %(host_server)s or %(get_api_key)s.", host_server='<a href="https://github.com/LibreTranslate/LibreTranslate" class="grey-text text-lighten-4" rel="noopener noreferrer">' + _h("host your own server") + '</a>', get_api_key='<a class="grey-text text-lighten-4" href="' + (get_api_key_link if get_api_key_link else "https://github.com/LibreTranslate/LibreTranslate#mirrors") + '" rel="noopener noreferrer">' + _h("get an API key") + '</a>') }}
</p> </p>
{% endif %} {% endif %}
</div> </div>
@ -308,7 +342,7 @@
</div> </div>
<div class="footer-copyright center"> <div class="footer-copyright center">
<p class="white-text"> <p class="white-text">
Made with ❤ by <a class="white-text" href="https://github.com/LibreTranslate/LibreTranslate/graphs/contributors" rel="noopener noreferrer">LibreTranslate Contributors</a> and powered by <a class="white-text text-lighten-3" href="https://github.com/argosopentech/argos-translate/" rel="noopener noreferrer">Argos Translate</a> {{ _h("Made with %(heart)s by %(contributors)s and powered by %(engine)s", heart='❤', contributors='<a class="white-text" href="https://github.com/LibreTranslate/LibreTranslate/graphs/contributors" rel="noopener noreferrer">%s</a>' % _h("%(libretranslate)s Contributors", libretranslate="LibreTranslate"), engine='<a class="white-text text-lighten-3" href="https://github.com/argosopentech/argos-translate/" rel="noopener noreferrer">Argos Translate</a>') }}
</p> </p>
</div> </div>
</footer> </footer>
@ -320,7 +354,7 @@
window.Prism.manual = true; window.Prism.manual = true;
// @license-end // @license-end
</script> </script>
<script src="{{ url_for('static', filename='js/prism.min.js') }}"></script> <script src="{{ url_for('static', filename='js/prism.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/app.js') }}?v={{ version }}"></script> <script src="js/app.js?v={{ version }}"></script>
</body> </body>
</html> </html>

View file

@ -1,22 +0,0 @@
<html>
<head>
<title>jslicense-labels1 for LibreTranslate</title>
</head>
<body>
<h3>Weblabels</h3>
<table id="jslicense-labels1" border="1">
<tr>
<td><a href="{{ url_for('static', filename='js/vue@2.js') }}">Vue.js</a></td>
<td><a href="http://www.jclark.com/xml/copying.txt">Expat</a></td>
</tr>
<tr>
<td><a href="{{ url_for('static', filename='js/prism.min.js') }}">prism.min.js</a></td>
<td><a href="http://www.jclark.com/xml/copying.txt">Expat</a></td>
</tr>
<tr>
<td><a href="{{ url_for('static', filename='js/materialize.min.js') }}">materialize.min.js</a></td>
<td><a href="http://www.jclark.com/xml/copying.txt">Expat</a></td>
</tr>
</table>
</body>
</html>

View file

@ -3,6 +3,8 @@ Flask==2.2.2
flask-swagger==0.2.14 flask-swagger==0.2.14
flask-swagger-ui==4.11.1 flask-swagger-ui==4.11.1
Flask-Limiter==2.6.3 Flask-Limiter==2.6.3
Flask-Babel==2.0.0
Flask-Session==0.4.0
waitress==2.1.2 waitress==2.1.2
expiringdict==1.2.2 expiringdict==1.2.2
LTpycld2==0.42 LTpycld2==0.42
@ -16,3 +18,4 @@ Werkzeug==2.2.2
requests==2.28.1 requests==2.28.1
redis==4.3.4 redis==4.3.4
prometheus-client==0.15.0 prometheus-client==0.15.0
polib==1.1.1

View file

@ -13,7 +13,7 @@ setup(
packages=find_packages(), packages=find_packages(),
# packages=find_packages(include=['openpredict']), # packages=find_packages(include=['openpredict']),
# package_dir={'openpredict': 'openpredict'}, # package_dir={'openpredict': 'openpredict'},
package_data={'': ['static/*', 'static/**/*', 'templates/*']}, package_data={'': ['static/*', 'static/**/*', 'templates/*', 'locales/**/meta.json', 'locales/**/**/*.mo']},
include_package_data=True, include_package_data=True,
entry_points={ entry_points={
'console_scripts': [ 'console_scripts': [

136
update_locales.py Executable file
View file

@ -0,0 +1,136 @@
#!/usr/bin/env python
import sys
import os
import re
import polib
import json
from babel.messages.frontend import main as pybabel
from libretranslate.language import load_languages, improve_translation_formatting
from libretranslate.locales import get_available_locale_codes, swag_eval
from translatehtml import translate_html
from libretranslate.app import get_version, create_app
from libretranslate.main import get_args
from flask_swagger import swagger
# Update strings
if __name__ == "__main__":
print("Loading languages")
languages = load_languages()
en_lang = next((l for l in languages if l.code == 'en'), None)
if en_lang is None:
print("Error: English model not found. You need it to run this script.")
exit(1)
locales_dir = os.path.join("libretranslate", "locales")
if not os.path.isdir(locales_dir):
os.makedirs(locales_dir)
# Dump language list so it gets picked up by pybabel
langs_file = os.path.join(locales_dir, ".langs.py")
with open(langs_file, 'w') as f:
for l in languages:
f.write("_(%s)\n" % json.dumps(l.name))
print("Wrote %s" % langs_file)
# Dump swagger strings
args = get_args()
app = create_app(args)
swag = swagger(app)
swag_strings = []
def add_swag_string(s):
if not s in swag_strings:
swag_strings.append(s)
swag_eval(swag, add_swag_string)
swag_file = os.path.join(locales_dir, ".swag.py")
with open(swag_file, 'w') as f:
for ss in swag_strings:
f.write("_(%s)\n" % json.dumps(ss))
print("Wrote %s" % swag_file)
messagespot = os.path.join(locales_dir, "messages.pot")
print("Updating %s" % messagespot)
sys.argv = ["", "extract", "-F", "babel.cfg", "-k", "_e _h",
"--copyright-holder", "LibreTranslate Authors",
"--project", "LibreTranslate",
"--version", get_version(),
"-o", messagespot, "libretranslate"]
pybabel()
lang_codes = [l.code for l in languages if l != "en"]
# Init/update
for l in lang_codes:
cmd = "init"
if os.path.isdir(os.path.join(locales_dir, l, "LC_MESSAGES")):
cmd = "update"
sys.argv = ["", cmd, "-i", messagespot, "-d", locales_dir, "-l", l]
pybabel()
meta_file = os.path.join(locales_dir, l, "meta.json")
if not os.path.isfile(meta_file):
with open(meta_file, 'w') as f:
f.write(json.dumps({
'name': next((lang.name for lang in languages if lang.code == l)),
'reviewed': False
}, indent=4))
print("Wrote %s" % meta_file)
# Automatically translate strings with libretranslate
# when a language model is available and a string is empty
locales = get_available_locale_codes(only_reviewed=False)
print(locales)
for locale in locales:
if locale == 'en':
continue
tgt_lang = next((l for l in languages if l.code == locale), None)
if tgt_lang is None:
# We cannot translate
continue
translator = en_lang.get_translation(tgt_lang)
messages_file = os.path.join(locales_dir, locale, "LC_MESSAGES", 'messages.po')
if os.path.isfile(messages_file):
print("Translating '%s'" % locale)
pofile = polib.pofile(messages_file)
c = 0
for entry in pofile.untranslated_entries():
text = entry.msgid
# Extract placeholders
placeholders = re.findall(r'%\(?[^\)]*\)?s', text)
for p in range(0, len(placeholders)):
text = text.replace(placeholders[p], "<x>%s</x>" % p)
if len(placeholders) > 0:
translated = str(translate_html(translator, text))
else:
translated = improve_translation_formatting(text, translator.translate(text))
# Restore placeholders
for p in range(0, len(placeholders)):
tag = "<x>%s</x>" % p
if tag in translated:
translated = translated.replace(tag, placeholders[p])
else:
# Meh, append
translated += " " + placeholders[p]
print(entry.msgid, " --> ", translated)
entry.msgstr = translated
c += 1
if c > 0:
pofile.save(messages_file)
print("Saved %s" % messages_file)