API Compatibility

June 16, 2026 · View on GitHub

ovos-translate-server exposes several vendor-compatible routers so that existing clients written for LibreTranslate, DeepL, Google Cloud Translation, Azure Translator, Amazon Translate, or Lingva Translate can point at this server with minimal or no code changes.


Why Vendor Prefixes?

LibreTranslate and Azure Translator both define /translate, /detect, and /languages at the root level. If the routers were mounted without a prefix in the same FastAPI app, the last-registered router would silently shadow the earlier ones — resulting in some vendors' endpoints being unreachable.

Every router is therefore mounted under a unique vendor prefix:

VendorPrefix
LibreTranslate/libretranslate
DeepL/deepl
DeepLX/deeplx
Google Cloud Translation v2/google
Azure Translator v3/azure
Amazon Translate/amazon
Lingva Translate/lingva

Without these prefixes, /translate would be registered by multiple routers and only the last registration would be reachable.


Endpoint Reference

VendorMethodPathAuth HeaderLang Code FormatNotes
LibreTranslatePOST/libretranslate/translateapi_key body field (ignored)lowercase BCP-47source="auto" triggers auto-detect
LibreTranslatePOST/libretranslate/detectapi_key body field (ignored)lowercase BCP-47Returns list sorted by confidence desc
LibreTranslateGET/libretranslate/languageslowercase BCP-47Returns {code, name} list
DeepLPOST/deepl/v2/translateAuthorization: DeepL-Auth-Key … (ignored)uppercase BCP-47 e.g. EN-USAccepts/returns uppercase; normalised internally
DeepLXPOST/deeplx/translatecase-insensitive BCP-47{text, source_lang, target_lang}{code, data}; source_lang: "auto" triggers auto-detect
GooglePOST/google/language/translate/v2key query param or Authorization header (ignored)lowercase BCP-47q may be string or list
GooglePOST/google/language/translate/v2/detectkey query param or Authorization header (ignored)lowercase BCP-47q may be string or list
GoogleGET/google/language/translate/v2/languageskey query param or Authorization header (ignored)lowercase BCP-47Optional target query param accepted
AzurePOST/azure/translateOcp-Apim-Subscription-Key header (ignored)BCP-47to and from are query params; to can be comma-separated
AzurePOST/azure/detectOcp-Apim-Subscription-Key header (ignored)BCP-47Body is JSON array of {"Text": "…"} items
AzureGET/azure/languagesBCP-47api-version query param accepted
AmazonPOST/amazon/translate/textAuthorization (AWS SigV4, ignored)BCP-47SourceLanguageCode: "auto" triggers auto-detect
AmazonGET/amazon/translate/languagesAuthorization (ignored)BCP-47Returns {Languages: [{LanguageCode, LanguageName}]}
LingvaGET/lingva/api/v1/{source}/{target}/{query}lowercase BCP-47Path params; source: "auto" triggers auto-detect; returns {translation}

Curl Examples

LibreTranslate — Translate

curl -s -X POST http://localhost:9686/libretranslate/translate \
  -H 'Content-Type: application/json' \
  -d '{"q": "Hello world", "source": "en", "target": "de"}'
# {"translatedText": "Hallo Welt"}

LibreTranslate — Detect

curl -s -X POST http://localhost:9686/libretranslate/detect \
  -H 'Content-Type: application/json' \
  -d '{"q": "Bonjour le monde"}'
# [{"language": "fr", "confidence": 0.97}, {"language": "en", "confidence": 0.02}]

LibreTranslate — Languages

curl -s http://localhost:9686/libretranslate/languages
# [{"code": "en", "name": "English"}, {"code": "de", "name": "German"}, ...]

DeepL — Translate

curl -s -X POST http://localhost:9686/deepl/v2/translate \
  -H 'Content-Type: application/json' \
  -H 'Authorization: DeepL-Auth-Key dummy-key' \
  -d '{"text": ["Hello world"], "target_lang": "DE"}'
# {"translations": [{"detected_source_language": "EN", "text": "Hallo Welt"}]}

DeepLX — Translate

DeepLX uses a simpler single-endpoint schema than the official DeepL v2 API.

curl -s -X POST http://localhost:9686/deeplx/translate \
  -H 'Content-Type: application/json' \
  -d '{"text": "Hello world", "source_lang": "EN", "target_lang": "DE"}'
# {"code": 200, "data": "Hallo Welt"}
# Auto-detect the source language
curl -s -X POST http://localhost:9686/deeplx/translate \
  -H 'Content-Type: application/json' \
  -d '{"text": "Bonjour le monde", "source_lang": "auto", "target_lang": "EN"}'

Google Cloud Translation — Translate

curl -s -X POST http://localhost:9686/google/language/translate/v2 \
  -H 'Content-Type: application/json' \
  -d '{"q": "Hello world", "target": "fr"}'
# {"data": {"translations": [{"translatedText": "Bonjour le monde", "detectedSourceLanguage": "en"}]}}

Google Cloud Translation — Detect

curl -s -X POST http://localhost:9686/google/language/translate/v2/detect \
  -H 'Content-Type: application/json' \
  -d '{"q": "Bonjour le monde"}'
# {"data": {"detections": [[{"language": "fr", "confidence": 0.97, "isReliable": false}]]}}

Google Cloud Translation — Languages

curl -s 'http://localhost:9686/google/language/translate/v2/languages?target=en'
# {"data": {"languages": [{"language": "en"}, {"language": "de"}, ...]}}

Azure Translator — Translate

curl -s -X POST 'http://localhost:9686/azure/translate?to=de&api-version=3.0' \
  -H 'Content-Type: application/json' \
  -H 'Ocp-Apim-Subscription-Key: dummy-key' \
  -d '[{"Text": "Hello world"}]'
# [{"detectedLanguage": {"language": "en", "score": 0.95}, "translations": [{"text": "Hallo Welt", "to": "DE"}]}]

Azure Translator — Detect

curl -s -X POST 'http://localhost:9686/azure/detect?api-version=3.0' \
  -H 'Content-Type: application/json' \
  -d '[{"Text": "Hallo Welt"}]'
# [{"language": "de", "score": 0.97, "isTranslationSupported": true, "isTransliterationSupported": false}]

Azure Translator — Languages

curl -s 'http://localhost:9686/azure/languages?api-version=3.0'
# {"translation": {"en": {"name": "English", "nativeName": "English", "dir": "ltr"}, ...}}

Amazon Translate — Translate Text

curl -s -X POST http://localhost:9686/amazon/translate/text \
  -H 'Content-Type: application/json' \
  -d '{"Text": "Hello world", "SourceLanguageCode": "en", "TargetLanguageCode": "de"}'
# {"TranslatedText": "Hallo Welt", "SourceLanguageCode": "en", "TargetLanguageCode": "de"}

Amazon Translate — List Languages

curl -s http://localhost:9686/amazon/translate/languages
# {"Languages": [{"LanguageCode": "en", "LanguageName": "English"}, ...]}

Lingva Translate — Translate

Lingva uses a GET endpoint with path parameters. URL-encode the query text; use auto as the source language for automatic detection.

curl -s 'http://localhost:9686/lingva/api/v1/en/de/hello%20world'
# {"translation": "Hallo Welt"}
# Auto-detect the source language
curl -s 'http://localhost:9686/lingva/api/v1/auto/fr/bonjour'

Path-Conflict Problem — Detailed Explanation

Consider these two real vendor APIs:

  • LibreTranslate: POST /translate, POST /detect, GET /languages
  • Azure Translator: POST /translate, POST /detect, GET /languages

Both define identical paths. FastAPI include_router adds routes to a shared route table in registration order. The second router's routes would shadow the first's because FastAPI matches the first registered route that fits. The result: Azure routes would be unreachable (registered second) or LibreTranslate routes would be unreachable depending on order — either way, half the API is broken.

The solution implemented in create_app()ovos_translate_server/__init__.py — is to mount every router with a unique prefix:

app.include_router(make_deepl_router(engine))             # prefix="/deepl"
app.include_router(make_deeplx_router(engine))            # prefix="/deeplx"
app.include_router(make_libretranslate_router(engine))    # prefix="/libretranslate"
app.include_router(make_lingva_router(engine))            # prefix="/lingva"
app.include_router(make_amazon_translate_router(engine))  # prefix="/amazon"
app.include_router(make_google_translate_router(engine))  # prefix="/google"
app.include_router(make_azure_translator_router(engine))  # prefix="/azure"

Each make_*_router() factory sets the prefix on the APIRouter it returns, so the prefix is co-located with the router definition and cannot be accidentally omitted.


Pointing Existing Clients at This Server

LibreTranslate Python client

import libretranslatepy
lt = libretranslatepy.LibreTranslateAPI("http://localhost:9686/libretranslate/")
result = lt.translate("Hello", "en", "de")

DeepL Python client

The official deepl library uses https://api.deepl.com as the server URL. Pass a custom server_url:

import deepl
translator = deepl.Translator("dummy-key", server_url="http://localhost:9686/deepl")
result = translator.translate_text("Hello world", target_lang="DE")

DeepLX client

DeepLX ships no official Python SDK — it is normally consumed by CLI tools and browser extensions over plain HTTP (see the curl example above). The maintained community client deeplx-tr exposes a url knob that can point at this server's /deeplx/translate endpoint:

from deeplx_tr import deeplx_client

result = deeplx_client(
    "Hello world",
    source_lang="en",
    target_lang="de",
    url="http://localhost:9686/deeplx/translate",
)

Google Cloud Translation client

Most Google clients accept a client_options argument with a custom API endpoint:

from google.cloud import translate_v2 as translate
from google.api_core.client_options import ClientOptions

client = translate.Client(
    client_options=ClientOptions(api_endpoint="http://localhost:9686/google")
)
result = client.translate("Hello world", target_language="de")

Azure SDK

Configure the endpoint when creating the TextTranslationClient:

from azure.ai.translation.text import TextTranslationClient
from azure.core.credentials import AzureKeyCredential

client = TextTranslationClient(
    endpoint="http://localhost:9686/azure",
    credential=AzureKeyCredential("dummy-key"),
)

Amazon Translate boto3 client

import boto3
client = boto3.client(
    "translate",
    endpoint_url="http://localhost:9686/amazon",
    region_name="us-east-1",
    aws_access_key_id="dummy",
    aws_secret_access_key="dummy",
)
result = client.translate_text(Text="Hello world", SourceLanguageCode="en", TargetLanguageCode="de")

Lingva Translate client

Lingva ships no official Python SDK — its REST API is consumed over plain HTTP. Point any HTTP client at the /lingva prefix and URL-encode the query:

import urllib.parse
import httpx

query = urllib.parse.quote("Hello world", safe="")
resp = httpx.get(f"http://localhost:9686/lingva/api/v1/en/de/{query}")
result = resp.json()["translation"]