Skip to content
Snippets Groups Projects
app.py 26.7 KiB
Newer Older
  • Learn to ignore specific revisions
  • Sébastien Thuret's avatar
    Sébastien Thuret committed
    import io
    
    Sébastien Thuret's avatar
    Sébastien Thuret committed
    import tempfile
    import uuid
    
    YOGESHWARAN R's avatar
    YOGESHWARAN R committed
    from functools import wraps
    
    
    Sébastien Thuret's avatar
    Sébastien Thuret committed
    import argostranslatefiles
    
    import pkg_resources
    
    Sébastien Thuret's avatar
    Sébastien Thuret committed
    from argostranslatefiles import get_supported_formats
    
    Sébastien Thuret's avatar
    Sébastien Thuret committed
    from flask import Flask, abort, jsonify, render_template, request, url_for, send_from_directory, send_file
    
    Piero Toffanin's avatar
    Piero Toffanin committed
    from flask_swagger import swagger
    from flask_swagger_ui import get_swaggerui_blueprint
    
    Sébastien Thuret's avatar
    Sébastien Thuret committed
    from translatehtml import translate_html
    from werkzeug.utils import secure_filename
    
    Piero Toffanin's avatar
    Piero Toffanin committed
    from app import flood
    
    YOGESHWARAN R's avatar
    YOGESHWARAN R committed
    from app.language import detect_languages, transliterate
    from .api_keys import Database
    
    from .suggestions import Database as SuggestionsDatabase
    
    def get_json_dict(request):
        d = request.get_json()
        if not isinstance(d, dict):
            abort(400, description="Invalid JSON format")
        return d
    
    
    YOGESHWARAN R's avatar
    YOGESHWARAN R committed
    
    
    Piero Toffanin's avatar
    Piero Toffanin committed
    def get_remote_address():
        if request.headers.getlist("X-Forwarded-For"):
    
            ip = request.headers.getlist("X-Forwarded-For")[0].split(",")[0]
    
    Piero Toffanin's avatar
    Piero Toffanin committed
        else:
    
    YOGESHWARAN R's avatar
    YOGESHWARAN R committed
            ip = request.remote_addr or "127.0.0.1"
    
    Piero Toffanin's avatar
    Piero Toffanin committed
    
        return ip
    
    Piero Toffanin's avatar
    Piero Toffanin committed
    
    
    Sébastien Thuret's avatar
    Sébastien Thuret committed
    def get_req_limits(default_limit, api_keys_db, multiplier=1):
    
        req_limit = default_limit
    
        if api_keys_db:
            if request.is_json:
                json = get_json_dict(request)
                api_key = json.get("api_key")
            else:
                api_key = request.values.get("api_key")
    
            if api_key:
                db_req_limit = api_keys_db.lookup(api_key)
                if db_req_limit is not None:
    
    Piero Toffanin's avatar
    Piero Toffanin committed
                    req_limit = db_req_limit * multiplier
    
    Piero Toffanin's avatar
    Piero Toffanin committed
    def get_routes_limits(default_req_limit, daily_req_limit, api_keys_db):
    
        if default_req_limit == -1:
            # TODO: better way?
            default_req_limit = 9999999999999
    
    
        def minute_limits():
            return "%s per minute" % get_req_limits(default_req_limit, api_keys_db)
    
        def daily_limits():
            return "%s per day" % get_req_limits(daily_req_limit, api_keys_db, 1440)
    
    jdu9's avatar
    jdu9 committed
    
    
        res = [minute_limits]
    
    Piero Toffanin's avatar
    Piero Toffanin committed
    
        if daily_req_limit > 0:
    
            res.append(daily_limits)
    
    Piero Toffanin's avatar
    Piero Toffanin committed
    
        return res
    
    def create_app(args):
    
    Piero Toffanin's avatar
    Piero Toffanin committed
        from app.init import boot
    
    YOGESHWARAN R's avatar
    YOGESHWARAN R committed
    
    
    Piero Toffanin's avatar
    Piero Toffanin committed
        boot(args.load_only)
    
    Piero Toffanin's avatar
    Piero Toffanin committed
        from app.language import languages
    
    YOGESHWARAN R's avatar
    YOGESHWARAN R committed
    
    
    Piero Toffanin's avatar
    Piero Toffanin committed
        app = Flask(__name__)
    
    
    YOGESHWARAN R's avatar
    YOGESHWARAN R committed
            app.config["TEMPLATES_AUTO_RELOAD"] = True
    
    Piero Toffanin's avatar
    Piero Toffanin committed
    
    
        # Map userdefined frontend languages to argos language object.
    
        if args.frontend_language_source == "auto":
    
    YOGESHWARAN R's avatar
    YOGESHWARAN R committed
            frontend_argos_language_source = type(
                "obj", (object,), {"code": "auto", "name": "Auto Detect"}
            )
    
        else:
    
    YOGESHWARAN R's avatar
    YOGESHWARAN R committed
            frontend_argos_language_source = next(
                iter([l for l in languages if l.code == args.frontend_language_source]),
                None,
            )
    
    YOGESHWARAN R's avatar
    YOGESHWARAN R committed
        frontend_argos_language_target = next(
            iter([l for l in languages if l.code == args.frontend_language_target]), None
        )
    
        frontend_argos_supported_files_format = []
    
        for file_format in get_supported_formats():
            for ff in file_format.supported_file_extensions:
                frontend_argos_supported_files_format.append(ff)
    
    
        # Raise AttributeError to prevent app startup if user input is not valid.
        if frontend_argos_language_source is None:
    
    YOGESHWARAN R's avatar
    YOGESHWARAN R committed
            raise AttributeError(
                f"{args.frontend_language_source} as frontend source language is not supported."
            )
    
        if frontend_argos_language_target is None:
    
    YOGESHWARAN R's avatar
    YOGESHWARAN R committed
            raise AttributeError(
                f"{args.frontend_language_target} as frontend target language is not supported."
            )
    
    
        api_keys_db = None
    
    Piero Toffanin's avatar
    Piero Toffanin committed
        if args.req_limit > 0 or args.api_keys or args.daily_req_limit > 0:
    
            api_keys_db = Database() if args.api_keys else None
    
    
    Piero Toffanin's avatar
    Piero Toffanin committed
            from flask_limiter import Limiter
    
    YOGESHWARAN R's avatar
    YOGESHWARAN R committed
    
    
    Piero Toffanin's avatar
    Piero Toffanin committed
            limiter = Limiter(
                app,
                key_func=get_remote_address,
    
    YOGESHWARAN R's avatar
    YOGESHWARAN R committed
                default_limits=get_routes_limits(
                    args.req_limit, args.daily_req_limit, api_keys_db
                ),
    
    Piero Toffanin's avatar
    Piero Toffanin committed
            )
    
    Piero Toffanin's avatar
    Piero Toffanin committed
        else:
    
    YOGESHWARAN R's avatar
    YOGESHWARAN R committed
            from .no_limiter import Limiter
    
            limiter = Limiter()
    
    
    Piero Toffanin's avatar
    Piero Toffanin committed
        if args.req_flood_threshold > 0:
            flood.setup(args.req_flood_threshold)
    
    Piero Toffanin's avatar
    Piero Toffanin committed
    
    
        def access_check(f):
            @wraps(f)
            def func(*a, **kw):
                if flood.is_banned(get_remote_address()):
                    abort(403, description="Too many request limits violations")
    
    YOGESHWARAN R's avatar
    YOGESHWARAN R committed
    
    
                if args.api_keys and args.require_api_key_origin:
                    if request.is_json:
                        json = get_json_dict(request)
                        ak = json.get("api_key")
                    else:
                        ak = request.values.get("api_key")
    
    YOGESHWARAN R's avatar
    YOGESHWARAN R committed
                    if (
    
    Sébastien Thuret's avatar
    Sébastien Thuret committed
                            api_keys_db.lookup(ak) is None and request.headers.get("Origin") != args.require_api_key_origin
    
    YOGESHWARAN R's avatar
    YOGESHWARAN R committed
                    ):
                        abort(
                            403,
                            description="Please contact the server operator to obtain an API key",
                        )
    
    
                return f(*a, **kw)
    
    YOGESHWARAN R's avatar
    YOGESHWARAN R committed
    
    
            return func
    
    
    Piero Toffanin's avatar
    Piero Toffanin committed
        @app.errorhandler(400)
        def invalid_api(e):
            return jsonify({"error": str(e.description)}), 400
    
        @app.errorhandler(500)
        def server_error(e):
            return jsonify({"error": str(e.description)}), 500
    
        @app.errorhandler(429)
        def slow_down_error(e):
    
    Piero Toffanin's avatar
    Piero Toffanin committed
            flood.report(get_remote_address())
    
    Piero Toffanin's avatar
    Piero Toffanin committed
            return jsonify({"error": "Slowdown: " + str(e.description)}), 429
    
    
    Piero Toffanin's avatar
    Piero Toffanin committed
        @app.errorhandler(403)
        def denied(e):
            return jsonify({"error": str(e.description)}), 403
    
    
    Piero Toffanin's avatar
    Piero Toffanin committed
        @app.route("/")
    
    Piero Toffanin's avatar
    Piero Toffanin committed
        def index():
    
    YOGESHWARAN R's avatar
    YOGESHWARAN R committed
            return render_template(
                "index.html",
                gaId=args.ga_id,
                frontendTimeout=args.frontend_timeout,
                api_keys=args.api_keys,
                web_version=os.environ.get("LT_WEB") is not None,
    
                version=pkg_resources.require("LibreTranslate")[0].version
    
    YOGESHWARAN R's avatar
    YOGESHWARAN R committed
            )
    
    Piero Toffanin's avatar
    Piero Toffanin committed
    
    
    YOGESHWARAN R's avatar
    YOGESHWARAN R committed
        @app.route("/javascript-licenses", methods=["GET"])
    
    jdu9's avatar
    jdu9 committed
        @limiter.exempt
        def javascript_licenses():
    
    YOGESHWARAN R's avatar
    YOGESHWARAN R committed
            return render_template("javascript-licenses.html")
    
    jdu9's avatar
    jdu9 committed
    
    
    YOGESHWARAN R's avatar
    YOGESHWARAN R committed
        @app.route("/languages", methods=["GET", "POST"])
    
    Piero Toffanin's avatar
    Piero Toffanin committed
        def langs():
            """
            Retrieve list of supported languages
            ---
            tags:
              - translate
            responses:
              200:
                description: List of languages
    
                schema:
                  id: languages
                  type: array
                  items:
                    type: object
                    properties:
                      code:
                        type: string
                        description: Language code
                      name:
                        type: string
                        description: Human-readable language name (in English)
    
    Piero Toffanin's avatar
    Piero Toffanin committed
              429:
                description: Slow down
    
                schema:
                  id: error-slow-down
                  type: object
                  properties:
                    error:
                      type: string
                      description: Reason for slow down
    
    Piero Toffanin's avatar
    Piero Toffanin committed
            """
    
    YOGESHWARAN R's avatar
    YOGESHWARAN R committed
            return jsonify([{"code": l.code, "name": l.name} for l in languages])
    
    Piero Toffanin's avatar
    Piero Toffanin committed
    
        # Add cors
        @app.after_request
        def after_request(response):
    
    YOGESHWARAN R's avatar
    YOGESHWARAN R committed
            response.headers.add("Access-Control-Allow-Origin", "*")
            response.headers.add(
                "Access-Control-Allow-Headers", "Authorization, Content-Type"
            )
            response.headers.add("Access-Control-Expose-Headers", "Authorization")
            response.headers.add("Access-Control-Allow-Methods", "GET, POST")
            response.headers.add("Access-Control-Allow-Credentials", "true")
            response.headers.add("Access-Control-Max-Age", 60 * 60 * 24 * 20)
    
    Piero Toffanin's avatar
    Piero Toffanin committed
            return response
    
    
    YOGESHWARAN R's avatar
    YOGESHWARAN R committed
        @app.route("/translate", methods=["POST"])
    
        @access_check
    
    Piero Toffanin's avatar
    Piero Toffanin committed
        def translate():
            """
            Translate text from a language to another
            ---
            tags:
              - translate
            parameters:
              - in: formData
                name: q
                schema:
    
                  oneOf:
                    - type: string
                      example: Hello world!
                    - type: array
                      example: ['Hello world!']
    
    Piero Toffanin's avatar
    Piero Toffanin committed
                required: true
    
                description: Text(s) to translate
    
    Piero Toffanin's avatar
    Piero Toffanin committed
              - in: formData
                name: source
                schema:
                  type: string
                  example: en
                required: true
    
                description: Source language code
    
    Piero Toffanin's avatar
    Piero Toffanin committed
              - in: formData
                name: target
                schema:
                  type: string
                  example: es
                required: true
                description: Target language code
    
    Sébastien Thuret's avatar
    Sébastien Thuret committed
              - in: formData
                name: format
                schema:
                  type: string
    
                  enum: [text, html]
                  default: text
                  example: text
    
    Sébastien Thuret's avatar
    Sébastien Thuret committed
                required: false
    
                description: >
                  Format of source text:
                   * `text` - Plain text
                   * `html` - HTML markup
    
              - in: formData
                name: api_key
                schema:
                  type: string
                  example: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
                required: false
                description: API key
    
    Piero Toffanin's avatar
    Piero Toffanin committed
            responses:
              200:
                description: Translated text
    
                schema:
                  id: translate
                  type: object
                  properties:
                    translatedText:
    
                      oneOf:
                        - type: string
                        - type: array
                      description: Translated text(s)
    
    Piero Toffanin's avatar
    Piero Toffanin committed
              400:
                description: Invalid request
    
                schema:
                  id: error-response
                  type: object
                  properties:
                    error:
                      type: string
                      description: Error message
    
    Piero Toffanin's avatar
    Piero Toffanin committed
              500:
                description: Translation error
    
                schema:
                  id: error-response
                  type: object
                  properties:
                    error:
                      type: string
                      description: Error message
    
    Piero Toffanin's avatar
    Piero Toffanin committed
              429:
                description: Slow down
    
                schema:
                  id: error-slow-down
                  type: object
                  properties:
                    error:
                      type: string
                      description: Reason for slow down
    
    Piero Toffanin's avatar
    Piero Toffanin committed
              403:
                description: Banned
                schema:
                  id: error-response
                  type: object
                  properties:
                    error:
                      type: string
                      description: Error message
    
    Piero Toffanin's avatar
    Piero Toffanin committed
            """
            if request.is_json:
    
                json = get_json_dict(request)
    
    YOGESHWARAN R's avatar
    YOGESHWARAN R committed
                q = json.get("q")
                source_lang = json.get("source")
                target_lang = json.get("target")
    
    Sébastien Thuret's avatar
    Sébastien Thuret committed
                text_format = json.get("format")
    
    Piero Toffanin's avatar
    Piero Toffanin committed
            else:
                q = request.values.get("q")
                source_lang = request.values.get("source")
                target_lang = request.values.get("target")
    
    Sébastien Thuret's avatar
    Sébastien Thuret committed
                text_format = request.values.get("format")
    
    Piero Toffanin's avatar
    Piero Toffanin committed
    
            if not q:
                abort(400, description="Invalid request: missing q parameter")
            if not source_lang:
                abort(400, description="Invalid request: missing source parameter")
            if not target_lang:
                abort(400, description="Invalid request: missing target parameter")
    
    
            batch = isinstance(q, list)
    
    
            if batch and args.batch_limit != -1:
    
    YOGESHWARAN R's avatar
    YOGESHWARAN R committed
                batch_size = len(q)
                if args.batch_limit < batch_size:
                    abort(
                        400,
                        description="Invalid request: Request (%d) exceeds text limit (%d)"
    
    Sébastien Thuret's avatar
    Sébastien Thuret committed
                                    % (batch_size, args.batch_limit),
    
    YOGESHWARAN R's avatar
    YOGESHWARAN R committed
                    )
    
            if args.char_limit != -1:
    
                if batch:
    
    YOGESHWARAN R's avatar
    YOGESHWARAN R committed
                    chars = sum([len(text) for text in q])
    
    YOGESHWARAN R's avatar
    YOGESHWARAN R committed
                    chars = len(q)
    
                if args.char_limit < chars:
    
    YOGESHWARAN R's avatar
    YOGESHWARAN R committed
                    abort(
                        400,
                        description="Invalid request: Request (%d) exceeds character limit (%d)"
    
    Sébastien Thuret's avatar
    Sébastien Thuret committed
                                    % (chars, args.char_limit),
    
    YOGESHWARAN R's avatar
    YOGESHWARAN R committed
                    )
    
    Piero Toffanin's avatar
    Piero Toffanin committed
    
    
    YOGESHWARAN R's avatar
    YOGESHWARAN R committed
            if source_lang == "auto":
    
    Sébastien Thuret's avatar
    Sébastien Thuret committed
                    auto_detect_texts = q
    
    Sébastien Thuret's avatar
    Sébastien Thuret committed
                    auto_detect_texts = [q]
    
    
                overall_candidates = detect_languages(q)
    
                for text_to_check in auto_detect_texts:
    
    Sébastien Thuret's avatar
    Sébastien Thuret committed
                    if len(text_to_check) > 40:
                        candidate_langs = detect_languages(text_to_check)
                    else:
                        # Unable to accurately detect languages for short texts
                        candidate_langs = overall_candidates
                    source_langs.append(candidate_langs[0]["language"])
    
                    if args.debug:
                        print(text_to_check, candidate_langs)
                        print("Auto detected: %s" % candidate_langs[0]["language"])
    
    Sébastien Thuret's avatar
    Sébastien Thuret committed
                if batch:
                    source_langs = [source_lang for text in q]
                else:
                    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]
    
            for idx, lang in enumerate(src_langs):
    
    Sébastien Thuret's avatar
    Sébastien Thuret committed
                if lang is None:
                    abort(400, description="%s is not supported" % source_langs[idx])
    
    Piero Toffanin's avatar
    Piero Toffanin committed
            tgt_lang = next(iter([l for l in languages if l.code == target_lang]), None)
    
    Piero Toffanin's avatar
    Piero Toffanin committed
            if tgt_lang is None:
                abort(400, description="%s is not supported" % target_lang)
    
    
    Sébastien Thuret's avatar
    Sébastien Thuret committed
            if not text_format:
                text_format = "text"
    
            if text_format not in ["text", "html"]:
                abort(400, description="%s format is not supported" % text_format)
    
    
    Piero Toffanin's avatar
    Piero Toffanin committed
            try:
    
    YOGESHWARAN R's avatar
    YOGESHWARAN R committed
                if batch:
    
                    results = []
                    for idx, text in enumerate(q):
    
    Sébastien Thuret's avatar
    Sébastien Thuret committed
                        translator = src_langs[idx].get_translation(tgt_lang)
    
    Sébastien Thuret's avatar
    Sébastien Thuret committed
                        if text_format == "html":
                            translated_text = str(translate_html(translator, text))
                        else:
                            translated_text = translator.translate(transliterate(text, target_lang=source_langs[idx]))
    
    Sébastien Thuret's avatar
    Sébastien Thuret committed
                        results.append(translated_text)
    
    YOGESHWARAN R's avatar
    YOGESHWARAN R committed
                    return jsonify(
                        {
    
    YOGESHWARAN R's avatar
    YOGESHWARAN R committed
                        }
                    )
                else:
    
                    translator = src_langs[0].get_translation(tgt_lang)
    
    Sébastien Thuret's avatar
    Sébastien Thuret committed
    
                    if text_format == "html":
    
                        translated_text = str(translate_html(translator, q))
    
    Sébastien Thuret's avatar
    Sébastien Thuret committed
                    else:
                        translated_text = translator.translate(transliterate(q, target_lang=source_langs[0]))
    
    YOGESHWARAN R's avatar
    YOGESHWARAN R committed
                    return jsonify(
                        {
    
    Sébastien Thuret's avatar
    Sébastien Thuret committed
                            "translatedText": translated_text
    
    YOGESHWARAN R's avatar
    YOGESHWARAN R committed
                        }
                    )
    
    Piero Toffanin's avatar
    Piero Toffanin committed
            except Exception as e:
                abort(500, description="Cannot translate text: %s" % str(e))
    
    
    Sébastien Thuret's avatar
    Sébastien Thuret committed
        @app.route("/translate_file", methods=["POST"])
        @access_check
        def translate_file():
            """
            Translate file from a language to another
            ---
            tags:
              - translate
            consumes:
             - multipart/form-data
            parameters:
              - in: formData
                name: file
                type: file
                required: true
                description: file to translate
              - in: formData
                name: source
                schema:
                  type: string
                  example: en
                required: true
                description: Source language code
              - in: formData
                name: target
                schema:
                  type: string
                  example: es
                required: true
                description: Target language code
              - in: formData
                name: api_key
                schema:
                  type: string
                  example: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
                required: false
                description: API key
            responses:
              200:
                description: Translated file
                schema:
                  id: translate
                  type: object
                  properties:
                    translatedText:
                      oneOf:
                        - type: string
                        - type: array
                      description: Translated text(s)
              400:
                description: Invalid request
                schema:
                  id: error-response
                  type: object
                  properties:
                    error:
                      type: string
                      description: Error message
              500:
                description: Translation error
                schema:
                  id: error-response
                  type: object
                  properties:
                    error:
                      type: string
                      description: Error message
              429:
                description: Slow down
                schema:
                  id: error-slow-down
                  type: object
                  properties:
                    error:
                      type: string
                      description: Reason for slow down
              403:
                description: Banned
                schema:
                  id: error-response
                  type: object
                  properties:
                    error:
                      type: string
                      description: Error message
            """
    
    Sébastien Thuret's avatar
    Sébastien Thuret committed
            source_lang = request.form.get("source")
            target_lang = request.form.get("target")
    
    Sébastien Thuret's avatar
    Sébastien Thuret committed
            file = request.files['file']
    
            if not file:
                abort(400, description="Invalid request: missing q parameter")
            if not source_lang:
                abort(400, description="Invalid request: missing source parameter")
            if not target_lang:
                abort(400, description="Invalid request: missing target parameter")
    
            if file.filename == '':
                abort(400, description="Invalid request: empty file")
    
            if os.path.splitext(file.filename)[1] not in frontend_argos_supported_files_format:
                abort(400, description="Invalid request: file format not supported")
    
            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]
    
            for idx, lang in enumerate(src_langs):
                if lang is None:
                    abort(400, description="%s is not supported" % source_langs[idx])
    
            tgt_lang = next(iter([l for l in languages if l.code == target_lang]), None)
    
            if tgt_lang is None:
                abort(400, description="%s is not supported" % target_lang)
    
            try:
                filename = str(uuid.uuid4()) + '.' + secure_filename(file.filename)
                filepath = os.path.join(tempfile.gettempdir(), filename)
    
                file.save(filepath)
    
                translated_file_path = argostranslatefiles.translate_file(src_langs[0].get_translation(tgt_lang), filepath)
    
                translated_filename = os.path.basename(translated_file_path)
    
    Sébastien Thuret's avatar
    Sébastien Thuret committed
                return jsonify(
                    {
    
                        "translatedFileUrl": url_for('download_file', filename=translated_filename)
    
    Sébastien Thuret's avatar
    Sébastien Thuret committed
                    }
                )
            except Exception as e:
                abort(500, description=e)
    
    
        @app.route("/download_file/<string:filename>", methods=["GET"])
        @access_check
    
    Sébastien Thuret's avatar
    Sébastien Thuret committed
        def download_file(filename: str):
    
            """
            Download a translated file
            """
    
    Sébastien Thuret's avatar
    Sébastien Thuret committed
            filename.split('.').pop(0)
    
    Sébastien Thuret's avatar
    Sébastien Thuret committed
            filepath = os.path.join(tempfile.gettempdir(), filename)
    
            return_data = io.BytesIO()
            with open(filepath, 'rb') as fo:
                return_data.write(fo.read())
            return_data.seek(0)
    
            os.remove(filepath)
    
    Sébastien Thuret's avatar
    Sébastien Thuret committed
    
    
    Sébastien Thuret's avatar
    Sébastien Thuret committed
            return send_file(return_data, attachment_filename=filename)
    
    YOGESHWARAN R's avatar
    YOGESHWARAN R committed
        @app.route("/detect", methods=["POST"])
    
        @access_check
    
    Piero Toffanin's avatar
    Piero Toffanin committed
        def detect():
            """
            Detect the language of a single text
            ---
            tags:
              - translate
            parameters:
              - in: formData
                name: q
                schema:
                  type: string
                  example: Hello world!
                required: true
                description: Text to detect
    
              - in: formData
                name: api_key
                schema:
                  type: string
                  example: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
                required: false
                description: API key
    
    Piero Toffanin's avatar
    Piero Toffanin committed
            responses:
              200:
                description: Detections
                schema:
                  id: detections
                  type: array
                  items:
                    type: object
                    properties:
                      confidence:
                        type: number
                        format: float
                        minimum: 0
                        maximum: 1
                        description: Confidence value
                        example: 0.6
                      language:
                        type: string
                        description: Language code
                        example: en
              400:
                description: Invalid request
                schema:
                  id: error-response
                  type: object
                  properties:
                    error:
                      type: string
    
    YOGESHWARAN R's avatar
    YOGESHWARAN R committed
                      description: Error message
    
    Piero Toffanin's avatar
    Piero Toffanin committed
              500:
                description: Detection error
                schema:
                  id: error-response
                  type: object
                  properties:
                    error:
                      type: string
                      description: Error message
              429:
                description: Slow down
                schema:
                  id: error-slow-down
                  type: object
                  properties:
                    error:
                      type: string
                      description: Reason for slow down
    
    Piero Toffanin's avatar
    Piero Toffanin committed
              403:
                description: Banned
                schema:
                  id: error-response
                  type: object
                  properties:
                    error:
                      type: string
                      description: Error message
    
    Piero Toffanin's avatar
    Piero Toffanin committed
            """
    
    Piero Toffanin's avatar
    Piero Toffanin committed
            if flood.is_banned(get_remote_address()):
                abort(403, description="Too many request limits violations")
    
    
    Piero Toffanin's avatar
    Piero Toffanin committed
            if request.is_json:
    
                json = get_json_dict(request)
    
    YOGESHWARAN R's avatar
    YOGESHWARAN R committed
                q = json.get("q")
    
    Piero Toffanin's avatar
    Piero Toffanin committed
            else:
                q = request.values.get("q")
    
            if not q:
                abort(400, description="Invalid request: missing q parameter")
    
    
            return jsonify(detect_languages(q))
    
    Piero Toffanin's avatar
    Piero Toffanin committed
    
    
        @app.route("/frontend/settings")
    
        def frontend_settings():
            """
            Retrieve frontend specific settings
            ---
            tags:
              - frontend
            responses:
              200:
                description: frontend settings
                schema:
                  id: frontend-settings
                  type: object
                  properties:
    
                    charLimit:
                      type: integer
                      description: Character input limit for this language (-1 indicates no limit)
    
                    frontendTimeout:
                      type: integer
                      description: Frontend translation timeout
    
    Piero Toffanin's avatar
    Piero Toffanin committed
                    suggestions:
                      type: boolean
                      description: Whether submitting suggestions is enabled.
    
                    supportedFilesFormat:
                      type: array
                      items:
                        type: string
                      description: Supported files format
    
                    language:
                      type: object
                      properties:
                        source:
                          type: object
                          properties:
                            code:
                              type: string
                              description: Language code
                            name:
                              type: string
                              description: Human-readable language name (in English)
                        target:
                          type: object
                          properties:
                            code:
                              type: string
                              description: Language code
                            name:
                              type: string
                              description: Human-readable language name (in English)
            """
    
    YOGESHWARAN R's avatar
    YOGESHWARAN R committed
            return jsonify(
                {
                    "charLimit": args.char_limit,
                    "frontendTimeout": args.frontend_timeout,
    
                    "suggestions": args.suggestions,
    
                    "supportedFilesFormat": frontend_argos_supported_files_format,
    
    YOGESHWARAN R's avatar
    YOGESHWARAN R committed
                    "language": {
                        "source": {
                            "code": frontend_argos_language_source.code,
                            "name": frontend_argos_language_source.name,
                        },
                        "target": {
                            "code": frontend_argos_language_target.code,
                            "name": frontend_argos_language_target.name,
                        },
                    },
                }
            )
    
    Piero Toffanin's avatar
    Piero Toffanin committed
    
    
        @app.route("/suggest", methods=["POST"])
        @limiter.exempt
        def suggest():
    
    Piero Toffanin's avatar
    Piero Toffanin committed
            """
            Submit a suggestion to improve a translation
            ---
            tags:
              - feedback
            parameters:
              - in: formData
                name: q
                schema:
                  type: string
                  example: Hello world!
                required: true
                description: Original text
              - in: formData
                name: s
                schema:
                  type: string
                  example: ¡Hola mundo!
                required: true
                description: Suggested translation
              - in: formData
                name: source
                schema:
                  type: string
                  example: en
                required: true
                description: Language of original text
              - in: formData
                name: target
                schema:
                  type: string
                  example: es
                required: true
                description: Language of suggested translation
            responses:
              200:
                description: Success
                schema:
                  id: suggest-response
                  type: object
                  properties:
                    success:
                      type: boolean
                      description: Whether submission was successful
              403:
                description: Not authorized
                schema:
                  id: error-response
                  type: object
                  properties:
                    error:
                      type: string
                      description: Error message
            """
            if not args.suggestions:
                abort(403, description="Suggestions are disabled on this server.")
    
            q = request.values.get("q")
            s = request.values.get("s")
            source_lang = request.values.get("source")
            target_lang = request.values.get("target")
    
            SuggestionsDatabase().add(q, s, source_lang, target_lang)
    
            return jsonify({"success": True})
    
    
    Piero Toffanin's avatar
    Piero Toffanin committed
        swag = swagger(app)
        swag["info"]["version"] = "1.2.1"
        swag["info"]["title"] = "LibreTranslate"
    
        @app.route("/spec")
        @limiter.exempt
        def spec():
            return jsonify(swag)
    
    
    YOGESHWARAN R's avatar
    YOGESHWARAN R committed
        SWAGGER_URL = "/docs"  # URL for exposing Swagger UI (without trailing '/')
        API_URL = "/spec"
    
    Piero Toffanin's avatar
    Piero Toffanin committed
        # Call factory function to create our blueprint
    
    YOGESHWARAN R's avatar
    YOGESHWARAN R committed
        swaggerui_blueprint = get_swaggerui_blueprint(SWAGGER_URL, API_URL)
    
    Piero Toffanin's avatar
    Piero Toffanin committed
    
        app.register_blueprint(swaggerui_blueprint)
    
    
    Piero Toffanin's avatar
    Piero Toffanin committed
        return app