Clayton BorgesDESENVOLVEDOR
← ← All Projects

SlackTranslator – Real-Time Translation Bot

2025
Real-Time Communication Tool / AI Integration

Privacy-first real-time Slack translator built in pure Ruby. Incoming messages are translated English→Portuguese by a local LLM (Ollama) and pushed to a two-column web UI via SSE. Replies are translated back before posting, with a confirmation step to catch LLM errors. No cloud API keys — all inference runs on-device.

Media
SlackTranslator – Real-Time Translation Bot 1
Overview

SlackTranslator is a real-time multilingual communication tool for Slack that lets non-native English speakers read and reply in their own language without leaving the workflow. Incoming English messages are captured via Slack's Socket Mode WebSocket API, translated to Portuguese by a local Ollama model, and pushed to a two-column web interface in real time using Server-Sent Events. When the user composes a reply in Portuguese, it is translated back to English and shown for confirmation before being posted to Slack — catching any LLM mistranslation before it reaches colleagues.

Problem

International teams using Slack face a constant friction tax: non-native English speakers must translate every message mentally, draft a reply in English, and hope the wording sounds natural. This cognitive load compounds during fast-moving conversations. Cloud translation APIs (Google Translate, DeepL) solve the translation problem but introduce a privacy risk — every message in the channel gets sent to a third-party server. For professional environments handling confidential project discussions, that trade-off is unacceptable.

Goal

Build a real-time Slack translation layer that: (1) runs entirely on-device using a local LLM so no messages leave the machine; (2) streams translations to the UI as messages arrive, with zero polling; (3) lets the user compose replies in their native language and review the English translation before posting; (4) stays robust across WebSocket disconnects and multiple concurrent browser sessions.

My Role
  • ◈Solo developer — architecture, backend, frontend, DevOps
Stack
RubyWEBrickWebSocketSlack Socket Mode APIOllamaDeepseek R1SSESequel ORMPostgreSQLSQLiteDocker
Technical Decisions
  1. 01Pure Ruby with WEBrick (no Rails/Sinatra) — a deliberate architectural choice for the job interview context: demonstrating the ability to build HTTP routing, SSE streaming, and WebSocket integration from primitives rather than relying on framework abstractions.
  2. 02Slack Socket Mode API over the deprecated RTM API — Socket Mode uses WebSocket rather than a persistent HTTP connection, eliminating the need for a public-facing webhook URL and making local development straightforward.
  3. 03Per-client UUID SSE queue — each browser tab registers a UUID (stored in a cookie) and gets its own in-memory queue. The SSE thread reads from that queue and writes to a pipe the WEBrick response body consumes. This prevents cross-client message bleed and survives multiple concurrent sessions without shared mutable state issues.
  4. 04EVENT_QUEUE for async Slack event processing — Slack events are pushed to an unbounded Queue and processed by a dedicated thread, preventing the WEBrick handler from blocking during Ollama translation (which can take 2–15s depending on message length and model).
  5. 05Exponential backoff with ±20% jitter on WebSocket reconnect — prevents thundering herd when Slack disconnects multiple clients simultaneously. Backoff doubles each attempt, capped at 30s. A separate watchdog thread triggers a forced reconnect if no events arrive for >30s.
  6. 06Ollama with Deepseek R1:14B — chosen over smaller models (Llama 3.1 8B was used in v1) for significantly better translation quality, especially for idiomatic Brazilian Portuguese. The `keep_alive: '5m'` parameter keeps the model warm between messages, reducing the first-token latency from ~8s to ~1.5s on subsequent requests.
  7. 07Translation confirmation step before posting — the UI shows the EN translation of the user's PT reply before sending to Slack. This catches the main failure mode of local LLMs: occasional mistranslations that would be embarrassing in a professional context.
  8. 08SQLite in development / PostgreSQL in production via Sequel ORM — Sequel's dataset API allows switching the adapter without changing query code. Migrations live in db/migrations so the schema is versioned.
  9. 09Docker Compose with host.docker.internal for Ollama — the translator runs in a container while Ollama runs on the host (GPU access). Docker's host gateway feature exposes the host's port 11434 inside the container without network gymnastics.
What I Learned
  • →SSE state isolation requires explicit per-client identity from the start. The first version used a shared global array of client IDs — any new message was broadcast to all connected tabs, causing duplicated and out-of-order messages. Switching to UUID-keyed queues (cookie-synced between the SSE connection and the message endpoint) eliminated the problem entirely.
  • →Local LLM latency must be designed around, not ignored. The first architecture called Ollama synchronously inside the WEBrick request handler — a 10s translation blocked every other HTTP request. Moving to an EVENT_QUEUE processed by a dedicated thread decoupled request handling from inference time completely.
  • →WebSocket keepalive is non-negotiable with Slack Socket Mode. Slack closes idle WebSocket connections silently after ~30s with no ping. Without the 10-second ping timer and the watchdog thread, the app would appear to work in testing but silently stop receiving messages in production.
  • →No-framework Ruby is a valid production pattern, not just a toy exercise. WEBrick handles concurrency (via thread pool), SSE streaming (via IO pipes), and static file serving without any additional dependencies. The constraint of not using a framework forced a deeper understanding of how HTTP servers, SSE, and WebSocket clients compose at the socket level.
  • →Translation confirmation UX is a product decision, not just a safety net. Users discovered they preferred reviewing the translation even when it was correct — it gave them confidence in what they were sending. The confirmation step turned from a bug-catcher into a core feature of the workflow.
Status

Functional prototype — built as a job interview technical test