Translation Gateway

Description

One translation endpoint for every application in the deployment. It speaks the LibreTranslate API, chooses which engine answers, remembers which engine won earlier comparisons, caches what it has already paid for, and returns a reviewed human translation ahead of any machine output.

Overview

The gateway is published at api.translate.{{ DOMAIN_PRIMARY }}. An application points at that one URL; anything that already speaks to LibreTranslate works unchanged.

Four backends are declared as consumer entries in meta/services.yml, each switched on by deploying the role that provides it:

Backend

Provider

Reached over

libretranslate

svc-ai-libretranslate-engine

the LibreTranslate API

nllb

svc-ai-nllb-engine

FLORES-200 codes, mapped from ISO 639-1 by translate_gateway/flores.py

ltengine

svc-ai-ltengine

the LibreTranslate API

ollama

svc-ai-ollama

Ollama’s generate API, as a prompt

nllb serves 136 of the 184 languages of meta/languages.yml and refuses the other 48, which NLLB-200 has no language code for; flores.py names them. It also refuses a request without a source language, because NLLB carries no detector.

Architecture

ARCHITECTURE.md draws Translation Gateway from its own metadata: the containers it runs under compose, what changes under swarm, how each volume is backed, and every role it depends on or is depended upon by.

Section

Drawn from

Compose

meta/services.yml

Swarm

meta/services.yml replicas and placement

Volumes

meta/volumes.yml against the NFS decision chain

Dependencies

meta/services.yml and meta/main.yml of every role

Reading the memory

The gateway authenticates to Weblate with the API token web-app-weblate writes to the token store after its own deploy. A web service is deployed before the web applications, so on the very first run of a fresh deployment that token does not exist yet: the gateway starts without a memory and answers from the engines. The next deploy renders the stored token and the reviewed strings take precedence from then on.

Features

  • One API: POST /translate, POST /detect and GET /languages in LibreTranslate’s request and response shapes.

  • Reviewed strings win: a string approved in web-app-weblate is returned verbatim and no engine is called.

  • Engine choice that learns: with svc-ai-s1 deployed the router asks the decider which engine should answer, states each engine’s record in the criteria, and after a configured number of comparisons routes straight to the leader.

  • Blind verdict: while sampling, several engines answer the same request and the decider picks between the translations alone, labelled option-0, option-1, …, never by engine name.

  • Glossary protection: a term the glossary protects must survive the translation; an answer that drops it is rejected rather than cached.

  • Loud failure: with no engine able to answer, the gateway returns an error. It never hands the untranslated source text back as a translation.

Quick Setup

Development

Clone, set up the workstation, and deploy Translation Gateway onto the local stack:

git clone https://github.com/infinito-nexus/core.git
cd core
make onboard
make compose-deploy mode=reinstall apps=web-svc-translate full_cycle=false

Production

Run the published image to provision the inventory and deploy Translation Gateway to a managed server over SSH. The image logs in as root, so the server must accept root over a public key, and the two key variables are the two halves of one key pair: the private half authenticates the deploy, and the public half is what the deploy keeps authorized for root and installs for the administrator account. Naming a public half that does not belong to the private one locks the running deploy out at its next reconnect.

APP=web-svc-translate
HOST="<your-server>"
DOMAIN="<your-domain>"
TLS_MODE=self_signed
SSH_PUBLIC_KEY="<your-ssh-public-key>"
SSH_PRIVATE_KEY="<your-ssh-private-key-file>"
WORKDIR="$HOME/.infinito.nexus"

mkdir -p "$WORKDIR/inventories"
docker run --rm \
  -v "$WORKDIR/inventories:/etc/infinito.nexus/inventories" \
  -v "$SSH_PRIVATE_KEY:/run/secrets/deploy_key:ro" \
  -e APP="$APP" -e HOST="$HOST" -e DOMAIN="$DOMAIN" -e TLS_MODE="$TLS_MODE" -e SSH_PUBLIC_KEY="$SSH_PUBLIC_KEY" \
  ghcr.io/infinito-nexus/core/debian:latest bash -c '
    INVENTORY=/etc/infinito.nexus/inventories/production
    infinito administration inventory provision "$INVENTORY" \
      --inventory-file "$INVENTORY/devices.yml" \
      --host "$HOST" \
      --include "$APP" \
      --vars "{\"TLS_MODE\": \"$TLS_MODE\", \"DOMAIN_PRIMARY\": \"$DOMAIN\", \"SYS_SVC_SSHD_PERMIT_ROOT_LOGIN\": \"prohibit-password\", \"users\": {\"root\": {\"authorized_keys\": [\"$SSH_PUBLIC_KEY\"]}, \"administrator\": {\"authorized_keys\": [\"$SSH_PUBLIC_KEY\"]}}}" &&
    infinito administration deploy dedicated "$INVENTORY/devices.yml" \
      --password-file "$INVENTORY/.password" \
      -u root --private-key /run/secrets/deploy_key \
      --diff -vv'

The inventory under $WORKDIR is written by the container as root, including the vault password at inventories/production/.password. Run sudo chown -R "$USER" "$WORKDIR" afterwards to read it.

Credits

Kevin Veen-Birkenbach

Part of the Infinito.Nexus Project and maintained by Kevin Veen-Birkenbach. Licensed under the Infinito.Nexus Community License (Non-Commercial).

This role deploys no third-party application, so the licence above is the only one that applies.

Schema

flowchart LR
    app["application"] -->|POST /translate| gw["web-svc-translate"]
    gw -->|reviewed string| wl["web-app-weblate"]
    gw -->|which engine| s1["svc-ai-s1"]
    gw -->|cache, history, failures| db["PostgreSQL"]
    gw --> lt["svc-ai-libretranslate-engine"]
    gw --> nl["svc-ai-nllb-engine"]
    gw --> lte["svc-ai-ltengine"]
    gw --> ol["svc-ai-ollama"]

Boundary

This role translates the content users put into the applications, at request time. Two neighbouring mechanisms translate something else, and neither goes through this gateway:

  • make i18n-translate translates this repository’s own .po catalogues at build time by calling an engine directly. One batch of known strings, with no request to cache and no caller to serve.

  • The core surfaces (the docs site, the dashboard, the logout panel) read those catalogues at render time.

The boundary is build time against request time.

Further resources