Support for real-time translations in Matrix chats
Find a file
2026-05-25 00:27:39 +02:00
chat_translate feat(libretranslate): Added LibreTranslate backend support 2026-05-25 00:26:40 +02:00
docs feat(libretranslate): Added LibreTranslate backend support 2026-05-25 00:26:40 +02:00
tests feat(libretranslate): Added LibreTranslate backend support 2026-05-25 00:26:40 +02:00
.gitignore gitignore 2026-05-23 15:01:06 +02:00
.pre-commit-config.yaml feat: initialize chat-translate project 2026-05-14 01:33:23 +02:00
AGENTS.md feat: initialize chat-translate project 2026-05-14 01:33:23 +02:00
config.example.yaml feat(libretranslate): Added LibreTranslate backend support 2026-05-25 00:26:40 +02:00
LICENSE chore: Added AGPL v3 license 2026-05-23 19:40:05 +02:00
pyproject.toml feat(libretranslate): Added LibreTranslate backend support 2026-05-25 00:26:40 +02:00
pytest.ini feat: initialize chat-translate project 2026-05-14 01:33:23 +02:00
README.md feat(libretranslate): Added LibreTranslate backend support 2026-05-25 00:26:40 +02:00
requirements.txt feat(libretranslate): Added LibreTranslate backend support 2026-05-25 00:26:40 +02:00
setup.cfg feat: cross-sign bot device via SSSS recovery key 2026-05-14 18:54:02 +02:00

chat-translate

A Python bot that provides real-time message translation across multiple chat platforms.

Features

  • Multi-backend: supports Matrix, Signal (via signal-cli REST API), and XMPP.
  • Configurable target language per room or per user.
  • Language resolution order: user override → room default → skip.
  • Replies in-thread to the original message with the translated text.
  • Propagates edits and redactions/deletions of translated messages.
  • Auto-detects source language.
  • Pluggable translation engines: Google Translate (default) or LibreTranslate.
  • Matrix: supports end-to-end encrypted (E2EE) rooms when libolm and python-olm are available.
  • Signal: connects via WebSocket, auto-joins groups, supports message edits and remote deletes.
  • XMPP: MUC (group chat) support with OMEMO encryption (Blind Trust Before Verification), message edits (XEP-0308), and retractions (XEP-0424).

Quick Start

1. Install dependencies

# Matrix only
pip install "chat-translate[matrix]"

# Signal only
pip install "chat-translate[signal]"

# XMPP only
pip install "chat-translate[xmpp]"

# All backends
pip install "chat-translate[all]"

# LibreTranslate backend (instead of default Google Translate)
pip install "chat-translate[libretranslate]"

# Or install from source with all deps
pip install -r requirements.txt

For Matrix E2EE support (encrypted rooms), you also need libolm installed on your system (e.g. pacman -S libolm python-olm on Arch, apt install libolm-dev on Debian/Ubuntu).

For Signal, you need a running signal-cli-rest-api instance with a registered phone number.

For XMPP, you need a Jabber account for the bot. OMEMO encryption is enabled by default and uses Blind Trust Before Verification (BTBV).

2. Configure

Copy the example configuration and edit it:

cp config.example.yaml config.yaml

3. Run

python -m chat_translate -c config.yaml

Use -v for verbose/debug logging.

Configuration Reference

At least one backend (matrix, signal, or xmpp) must be configured. Multiple can be active simultaneously — they run independently.

Translation Backend

The translation section is optional. If omitted, Google Translate is used.

translation:
  backend: google          # "google" or "libretranslate"
  # LibreTranslate-specific:
  # api_url: "http://localhost:5000"
  # api_key: "your-api-key"
Field Required Default Description
translation.backend No google Translation engine: google or libretranslate
translation.api_url No http://localhost:5000 LibreTranslate API URL (only for libretranslate backend)
translation.api_key No API key for authenticated LibreTranslate instances

Chat Backends

matrix:
  homeserver: https://matrix.example.com
  user_id: "@translate-bot:example.com"
  password: "s3cret"
  rooms:
    "!roomid1:example.com":
      language: en
      users:
        "@alice:example.com":
          language: it

signal:
  phone_number: "+1234567890"
  api_url: "http://localhost:8080"
  rooms:
    "base64groupid==":
      language: en
      users:
        "+0987654321":
          language: de

xmpp:
  jid: "translate-bot@example.com"
  password: "s3cret"
  rooms:
    "room@conference.example.com":
      language: en
      users:
        "room@conference.example.com/alice":
          language: de

Matrix Fields

Field Required Description
matrix.homeserver Yes Matrix homeserver URL
matrix.user_id Yes Bot's Matrix user ID
matrix.device_id No** Device ID (required when using access_token)
matrix.password No* Bot account password
matrix.access_token No* Pre-existing access token
matrix.store_path No Directory to persist encryption keys (default: store)
matrix.import_keys_file No Path to E2E room keys file exported from Element
matrix.import_keys_passphrase No Passphrase for the exported keys file
matrix.recovery_key No Matrix recovery key — used to cross-sign the bot's device so Element shows it as verified
matrix.rooms.<room_id>.language No Default target language for the room
matrix.rooms.<room_id>.users.<user_id>.language No Target language override for a specific user

* Either password or access_token must be provided.

Signal Fields

Field Required Description
signal.phone_number Yes Bot's registered Signal phone number
signal.api_url No signal-cli REST API base URL (default: http://localhost:8080)
signal.rooms.<group_id>.language No Default target language for the group
signal.rooms.<group_id>.users.<phone>.language No Target language override for a specific user

XMPP Fields

Field Required Description
xmpp.jid Yes Bot's Jabber ID (e.g. bot@example.com)
xmpp.password Yes Bot account password
xmpp.nick No Nickname in MUC rooms (default: translate-bot)
xmpp.omemo_enabled No Enable OMEMO encryption (default: true)
xmpp.omemo_storage_path No Path to store OMEMO key material (default: omemo_store)
xmpp.rooms.<room_jid>.language No Default target language for the room
xmpp.rooms.<room_jid>.users.<nick_jid>.language No Target language override for a specific user

Users are identified by their full MUC JID: room@conference.example.com/nickname.

XMPP Setup

The XMPP backend connects to any standard XMPP server via slixmpp. OMEMO encryption is provided by slixmpp-omemo.

1. Create a bot account

Register an account on your XMPP server for the bot (e.g. via prosodyctl adduser bot@example.com on Prosody, or the server's web interface).

2. Invite the bot to MUC rooms

Invite the bot's JID to your MUC room. The bot will auto-join configured rooms on startup.

3. OMEMO

OMEMO is enabled by default with Blind Trust Before Verification (BTBV) — all devices are automatically trusted on first encounter (similar to Matrix's TOFU approach). Key material is persisted under omemo_storage_path.

To disable OMEMO (e.g. for unencrypted rooms):

xmpp:
  omemo_enabled: false

Signal Setup

The Signal backend requires a running signal-cli-rest-api instance.

1. Start the signal-cli REST API container

docker run -d --name signal-api \
  -p 8080:8080 \
  -v ./signal-data:/home/.local/share/signal-cli \
  bbernhard/signal-cli-rest-api

The volume (./signal-data) persists credentials across container restarts.

You have two options:

This makes signal-cli act as a secondary device (like Signal Desktop), keeping your phone as the primary device:

# Get a QR code for linking
curl -X GET 'http://localhost:8080/v1/qrcodelink?device_name=chat-translate-bot' \
  --output qr.png

# Open qr.png and scan it from your Signal app:
#   Settings → Linked Devices → Link New Device

After linking, the bot receives messages from all your groups alongside your phone. Use the rooms filter in your config to limit which groups get translated.

Option B: Register a dedicated number

This takes over the phone number entirely — your existing Signal app on that number will be deregistered. Use this with a dedicated VoIP/prepaid number:

# Request verification via SMS
curl -X POST 'http://localhost:8080/v1/register/+1234567890'

If the first API call returns this error:

{
  "error": "Captcha required for verification, use --captcha CAPTCHA\nTo get the token, go to https://signalcaptchas.org/registration/generate.html\nAfter solving the captcha, right-click on the \"Open Signal\" link and copy the link.\n"
}

Then you will have to verify yourself through a CAPTCHA. Open https://signalcaptchas.org/registration/generate.html, solve the CAPTCHA, and copy the link from the page (the "Open Signal" link).

Then use the copied CAPTCHA string to verify:

curl -X POST 'http://localhost:8080/v1/register/+1234567890' \
  --header 'Content-Type: application/json' \
  -d '{"captcha": "signalcaptcha://signal-hcaptcha......"}'

You should then receive a code via SMS. Use that code to verify:

# Verify with the code you receive
curl -X POST 'http://localhost:8080/v1/register/+1234567890/verify/123456'

3. Join groups

The bot auto-joins configured groups on startup if possible, but Signal groups require an invitation first. Have a group member invite the bot's phone number, then start the bot — it will accept and begin translating.

To find the group ID for your config, list your groups:

curl 'http://localhost:8080/v1/groups/+1234567890'

The id field in each group object is the value to use in signal.rooms.

4. Configure the bot

signal:
  phone_number: "+1234567890"
  api_url: "http://localhost:8080"
  rooms:
    "base64groupid==":
      language: en
      users:
        "+0987654321":
          language: de

Device Verification

XMPP (OMEMO)

The XMPP backend uses Blind Trust Before Verification (BTBV). All OMEMO devices are automatically trusted on first contact. No manual verification step is required.

Matrix

The bot automatically trusts all devices it encounters (TOFU). To verify the bot's device from another Matrix client, send the following command in any room the bot is in:

!verify

The bot will initiate SAS (emoji) verification via to_device messages with all known devices of the sender. Your client should show a verification popup — accept it to mark the bot as verified.

Note: Element's built-in "Verify User" button uses in-room verification, which is not supported by the underlying matrix-nio library. The !verify command works around this by using the to_device verification protocol.

Cross-signing (green badge in Element)

Element marks a device as verified only when it carries a cross-signing signature from the account's self-signing key. Since matrix-nio doesn't natively support cross-signing, the bot implements it manually.

To enable it, set your recovery key (the one you saved when setting up Security & Privacy in Element) in the configuration:

matrix:
  recovery_key: "EsTc LW2K PGiF ..."

On startup the bot will:

  1. Decrypt the self-signing private key from SSSS (server-side secret storage).
  2. Sign its own device keys with that key.
  3. Upload the signature to the homeserver.

After a restart (or a /keys/query refresh in Element) the bot's device will show the green "Verified" badge.

Development

# Install all dependencies (core + backends + dev)
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

# Run tests
pytest

# Run linters
pre-commit run --all-files

License

MIT