Multi-Language Discord Translation Bot
A Parallel Language Channel Translation Bot
Description
A Discord bot that keeps multiple language channels in sync: when someone posts in one channel, the message is translated and posted to the others. Reactions on any message in the “thread” are mirrored across the linked translations. Each Discord server member only sees the channel of their native language
What it Does
You set up one Discord channel per language (e.g. English, Spanish, French). Anyone can post in their language; the bot translates the message into every other configured language and posts each translation in the corresponding channel. Optional webhooks make translated messages appear as if sent by the original author (name and avatar). Reactions (e.g. 👍) added on one message are synced to the linked messages in the other channels, so the conversation stays aligned across languages.
Language channels are **configurable at runtime**: no fixed list. You add or remove languages and channel bindings via Discord commands (`!addlang`, `!addlang list`, `!addlang remove `) or by seeding from environment variables. Configuration is persisted in a local JSON state file so it survives restarts (considering switching to SQLite for future features).
Tech Stack
- Go — Single binary, no framework; DiscordGo for the Discord API.
- Config — .env (via godotenv)for token and optional translator settings; channel/language config in state.json (editable via commands or env).
- Translation — Pluggable backends:
- LibreTranslate (self-hosted or public) — optional request queue for local instances to avoid connection resets.
- - Google Cloud Translation API (v2, API key).
- Stubs for AWS & Azure Translation APIs (TODO)
- State — JSON file for channel config and error-report channel; no database yet.
Architecture and design
One Channel Per Language, Many Languages
The bot discovers languages from config: any DISCORD_CHANNEL_ in the environment or any language added with !addlang defines a language. There’s no hardcoded list (e.g. en/es/fr); you can add de, ja, zh, etc. as needed.
Linked Messages and Reaction Sync
When the bot posts a translation, it records the mapping between the original message and each translated message (by channel + message ID). A small in-memory store links these so that on reaction add/remove events the bot can mirror the emoji on the other linked messages. Discord only allows the bot to add reactions as itself, so reactions appear as the bot’s, not the original user’s.
Another limitation is the inability to tag users who aren't in your language channel (and English user can't tag a Spanish user), this is a built in limitation from Discord. I built a small both cog (red - DiscordBot plugin) in Python that works around this prior to this project, and I'll likely integrate the solution into this bot, but for now the solution is visible here .
Translator Abstraction
All translation goes through a Translator interface (Translate(ctx, text, fromLang, toLang)). A factory chooses the implementation from env (TRANSLATOR_PROVIDER and provider-specific keys). LibreTranslate uses an injectable request function and a local flag so that local instances can use a single-threaded queue while remote calls stay direct.Commands and permissions
Commands use the ! prefix. Language and error-channel configuration require the user to be server owner or have Manage Server / Administrator / Manage Channels. This keeps config changes restricted while still allowing flexible setup.
Error Reporting
An optional “error channel” can be set with !errorchannel set. When enabled, translation and send failures are posted there (with context), so operators can see issues without watching logs.
Notable Implementation Details
Webhooks for sender identity — If a webhook URL is configured per language channel, the bot sends the translation via that webhook with the original author’s username and avatar, so the message doesn’t show as “Bot”.
LibreTranslate queue — For localhost (or when UseQueue is set), a single goroutine drains a channel of translation jobs and runs one request at a time, avoiding connection resets on single-process local servers.
Google language codes — The Google client maps common alternates (e.g. sp → es) to ISO 639-1 so channel names like “sp” still produce correct Spanish translations.
State vs env — Channel config is the source of truth in state.json. On first run with no state, the bot can seed from DISCORD_CHANNEL_* in .env and then persist to state; after that, changes are made via commands or by editing state.
What's Left
- Completing the translation API adapters (AWS, Azure are just stubs)
- Integrating the tagging fix bot I previously made to fix cross language channel tagging
- Multi translation API load balancing or targeting, either to take advantage of monthly free limits or to select specific APIs for language translation pairs for better accuracy or performance.
- Improved documentation and setup guides for hosting this bot
- Multiple parallel channel support (currently it only support one set of parallel channels)
- State/config export to admin DMs as a way to easily backup state if bot needs to be migrated, updated, or debugged.
- SQLite for a better performing state storage system (once the tagging fix is integrated there will be more data storage and frequent look-ups, plus this could facilitate DB based backup systems)