A full-stack web app for translating SubRip (.srt) subtitle files using OpenAI, Claude, Gemini, and DeepSeek — with real-time progress, structure preservation, and per-user file isolation.
🎬 Skip the setup — try the real app right now:
Bring your own API key (OpenAI, Gemini, Claude, or DeepSeek), upload an
.srtfile, and watch it translate in real time. Your key and settings are stored only in your browser — nothing sensitive ever reaches the server.
- 🌍 Try It Live
- 🚀 Key Features
- 🏗️ Architecture
- ⚙️ Installation & Setup
- 🔧 Configuration
- ⚡ How It Works
- 🌐 Deployment (Coolify)
- ☕ Support the Project
- 📄 License
- Web UI: Modern React interface with drag-and-drop file upload, real-time progress bars, and settings management
- Per-User Isolation: Each browser gets a unique session — uploaded files and translations are private via session cookies
- Client-Side Settings: All settings (API key, model, language, matching/removal words) stored in IndexedDB — nothing sensitive on the server
- Real-Time Progress: WebSocket-based live translation progress that persists across page navigation
- Parallel Translation: Subtitles are translated concurrently (configurable concurrency) for faster results
- Structure Preservation: Maintains exact SRT structure including cue indices, timestamps, and line counts
- HTML Tag Protection: Preserves inline HTML tags (
<i>,<b>,<font>, etc.) and entities - Word Replacement System: Post-translation term replacement using
source --> targetmatching files - Word Removal: Remove unwanted words/patterns from translations
- Smart Credits Management: Automatically detects, replaces, and inserts translator credits at optimal locations
- Bulk Edit/Delete: Batch operations for matching words and removal words management
- Suggestion Packages: Browse a curated catalog of ready-made translation packages (Anime, Action, Sci-Fi, etc.), filter by multiple categories at once, and import them in one click — no manual setup needed
- Old Files Browser: View previously uploaded and translated files with download/delete actions
- Automatic Cleanup: Files older than 7 days are automatically deleted; each file shows days remaining
┌─────────────────────────────────────────────────────┐
│ Frontend (React 19 + Vite + Tailwind CSS 4) │
│ │
│ IndexedDB ─── settings, matching words, │
│ removal words (per-browser) │
│ Pages ──────── Home, Old Files, Packages, Suggestions, Settings │
│ Context ────── TranslationContext (global state) │
└──────────────────────┬──────────────────────────────┘
│ /api/* REST + /ws/* WebSocket
┌──────────────────────▼──────────────────────────────┐
│ Backend (FastAPI + Uvicorn) │
│ │
│ SessionCookieMiddleware ─── UUID cookie per browser │
│ File Storage ───────────── data/subtitles/{sid}/ │
│ data/translated/{sid}/ │
│ Translation ────────────── OpenAI API (parallel) │
│ WebSocket ──────────────── Real-time progress │
└─────────────────────────────────────────────────────┘
srt-translator/
├── backend/
│ ├── main.py # FastAPI app + session middleware
│ ├── config.py # Directory constants
│ ├── core/
│ │ ├── translate.py # SRTTranslator engine
│ │ ├── openai_client.py # OpenAI API wrapper
│ │ ├── placeholders.py # HTML/word protection & replacement
│ │ ├── credits.py # Credits detection & replacement
│ │ └── word_removal.py # Word removal logic
│ ├── routers/
│ │ ├── files.py # Upload, list, download, delete
│ │ ├── translation.py # Translate + WebSocket progress
│ │ └── suggestion_packages.py # Read-only catalog of curated packages
│ ├── suggestion-packages/ # Curated translation packages shipped with the app (.json)
│ ├── schemas/
│ │ ├── files.py # FileInfo, UploadResponse models
│ │ └── translation.py # TranslationRequest, Settings models
│ └── services/
│ ├── file_service.py # Session-scoped file operations
│ └── translation_service.py # Job management + parallel translation
├── frontend/
│ └── src/
│ ├── App.tsx # Routes + providers
│ ├── api/ # REST client, filesApi, translationApi
│ ├── components/ # Button, FileDropZone, ProgressBar, etc.
│ ├── contexts/
│ │ └── TranslationContext.tsx # Global translation state
│ ├── hooks/ # useSettings, useFileUpload
│ ├── pages/
│ │ ├── HomePage.tsx # Main translate page
│ │ ├── OldFilesPage.tsx # Browse uploaded/translated files
│ │ ├── PackagesPage.tsx # User's translation packages (CRUD + import/export)
│ │ ├── SuggestionPackagesPage.tsx # Curated catalog with multi-category filter
│ │ └── settings/ # General, MatchingWords, RemoveWords
│ ├── types/ # TypeScript type definitions
│ └── utils/
│ ├── constants.ts # API URLs, AI platform options
│ ├── db.ts # IndexedDB operations
│ └── helpers.ts # Formatting utilities
├── Dockerfile # Multi-stage build (Node + Python)
├── .dockerignore
└── data/ # Runtime file storage (ephemeral in Docker)
- Python 3.13+
- Node.js 22+
- npm
git clone https://github.com/ntamasM/srt-translator.git
cd srt-translatorcd backend
pip install -r requirements.txt
uvicorn main:app --reload --port 8000cd frontend
npm install
npm run devThe frontend dev server proxies /api and /ws requests to the backend at localhost:8000.
Open http://localhost:5173 in your browser.
Build and run with Docker:
docker build -t srt-translator .
docker run -p 8000:8000 srt-translatorThe app serves both the API and the built frontend on port 8000.
All settings are configured through the web UI and stored in your browser's IndexedDB. Nothing is stored on the server.
| Setting | Default | Description |
|---|---|---|
| AI Platform | OpenAI | AI provider (OpenAI, Gemini, Claude) |
| API Key | — | Your API key (stored only in your browser) |
| Model | gpt-4o-mini | Model to use for translation |
| Temperature | 0.2 | Sampling temperature (0–2) |
| Top P | 0.1 | Top-p sampling parameter (0–1) |
| Source Language | en | Source language code |
| Target Language | el | Target language code |
| Translator Name | Ntamas | Name for translator credits |
| Case-Insensitive Matching | false | Match words regardless of case |
| Replace Credits | true | Replace existing translator credits |
| Add Credits | true | Add translator credits to output |
| Append Credits at End | false | Force credits at end of file |
Manage word replacement pairs via Settings → Matching Words. Supports bulk edit and bulk delete. Format: source → target.
These are sent with each translation request and applied post-translation.
Manage words to remove via Settings → Remove Words. Supports bulk edit and bulk delete.
The matching system applies post-translation word replacement:
- Translation: AI translates the subtitle
- Replacement: Specified terms are replaced using your matching word pairs
- Boundaries: Uses word boundaries to avoid partial replacements
Remove unwanted words or patterns from subtitles:
- Normal words: Uses word boundaries (removes "word" from "word text" but not from "password")
- Special patterns: Removes pattern anywhere it appears (e.g.,
{\an8})
- Replace old credits: Detects and replaces existing translator credits
- Add new credits: Analyzes timing gaps (≥5 seconds) to find optimal placement
- Fallback: If no suitable gap exists, credits are added at the end
- Force end: Option to always place credits at the end of the file
- Credit Replacement — Replace existing translator credits (if enabled)
- Word Removal — Remove specified words from original text
- Translation — Translate text using OpenAI (parallel, 4 concurrent)
- Word Replacement — Apply matching word pairs
- Structure Restoration — Restore formatting and timing
- Credits Insertion — Add translator credits at optimal location
Each browser receives a unique session cookie (session_id, 30-day expiry). All file operations are scoped to this session:
- Uploads go to
data/subtitles/{session_id}/ - Translations output to
data/translated/{session_id}/ - One user cannot see another user's files
Translation progress is managed by a global React Context (TranslationContext). This means:
- You can navigate to Settings while a translation is running
- Progress bars, completed files, and download links persist when you return
- The WebSocket connection stays alive across page changes
The Suggestions page browses curated translation packages shipped with the app. Each one is a JSON file in backend/suggestion-packages/ with the following shape:
{
"name": "John Wick",
"categories": ["Action", "Crime", "Thriller"],
"titleKeyword": "john wick",
"keywords": ["assassin", "underworld", "hitman"],
"matchingWords": [
{ "source": "High Table", "target": "Υψηλή Τράπεζα" }
],
"removalWords": ["[gunshot]", "[grunting]"]
}categoriesis an array — a package can belong to multiple buckets (e.g. The Expanse is both Sci-Fi and Drama).- The Suggestions page builds the filter chips from the union of every JSON's categories. Selecting multiple chips uses AND logic — packages must carry every selected category to remain visible.
- Importing a suggestion creates a fresh entry in the user's IndexedDB-backed
Packagescollection with a new ID and timestamp; subsequent edits stay local to that browser. - Suggestion packages are baked into the Docker image (under
backend/), separate from thedata/volume that holds user uploads — so adding a new suggestion via git push deploys cleanly without touching production user data.
To add a new suggestion: drop a JSON into backend/suggestion-packages/, commit, and redeploy.
To prevent unbounded disk usage, the server automatically cleans up old files:
- Files older than 7 days (based on modification time) are deleted
- Cleanup runs on server startup and every hour thereafter
- Applies to both uploaded and translated files across all sessions
- Empty session directories are removed after cleanup
- The Old Files page shows a countdown badge on each file indicating days remaining before deletion
To deploy on Coolify:
- Add Resource → GitHub repository → select
srt-translator - Build Pack →
Dockerfile - Ports Exposes →
8000 - Domains → your domain (e.g.,
https://srt-translator.example.com) - Deploy
If using Cloudflare proxy, set SSL mode to Full.
The Dockerfile uses a multi-stage build:
- Stage 1:
node:22-alpinebuilds the frontend (npm ci && npm run build) - Stage 2:
python:3.13-slimruns the backend + serves the built frontend - Built-in health check at
/api/health
If this tool has been helpful, consider supporting its development!
- ⭐ Star this repository on GitHub
- 🐛 Report bugs or suggest features
- 💬 Spread the word to other subtitle translators
MIT License — see LICENSE file for details.