Skip to content
 
 

Repository files navigation

⚡️ Subtitle Translator

Blazing-fast batch subtitle translation for 120+ languages — powered by AI

English · 简体中文

License: MIT Live Demo

Paste a subtitle file into a general-purpose translator and two things go wrong: the model rewrites your timecodes, and you're doing it one file at a time. Subtitle Translator strips the timing out locally and sends only the dialogue to the engine — the timeline physically cannot be touched — then does a whole season in one drop.

Subtitle Translator is a free, browser-based batch subtitle translation tool for .srt, .ass, .vtt, and .lrc files. With chunked compression and parallel processing it hits ~1 second per episode. Batch-upload a whole season at once, connect to 8 traditional translation APIs (DeepL, Google, Azure, DeepLX, Qwen-MT, TranslateGemma, GTX, Edge) or 27 LLM providers and gateways, and translate into 120+ languages — or into several target languages in a single pass, each exported as its own file. Everything runs locally in your browser; subtitle content and API keys never touch a server. A CLI drives the same engine headlessly when you'd rather script it.

👉 Try it online: https://tools.newzone.top/en/subtitle-translator

Batch Translation Demo

Key Features

  • Real-Time Translation: Chunked compression + parallel processing → ~1 second per episode (GTX is slightly slower).
  • Batch Processing: Drop hundreds of subtitle files at once (a whole season in one go); each file translates and downloads independently with its original filename, and you get an aggregated success/failure summary (e.g. "Exported (3/5)") when the run finishes.
  • Multi-Language Output: Translate into multiple target languages in a single pass — each language is exported as its own file with the language code appended (e.g. movie.zh.srt, movie.fr.srt).
  • Format Compatibility: Auto-detects .srt, .ass, .vtt, and .lrc. WebVTT NOTE / STYLE / REGION non-cue blocks are correctly skipped (not translated as dialogue). One-click format conversion (SRT ↔ VTT, SRT/VTT → ASS) during translation.
  • Bilingual Output: Insert the translation above or below the original; alignment preserved across formats. For SRT / VTT sources you can also export ASS with separate styles for original and translation (Default 70pt white + Secondary 55pt cyan), tweakable in any subtitle editor.
  • Context-Aware Translation (LLM only): Sends surrounding lines as context for more coherent dialogue and consistent character voice.
  • Structural Separation: Timecodes, cue numbers, ASS headers, and VTT cue IDs are extracted locally — only dialogue text is sent to the engine, so the model can never disrupt your timeline.
  • Subtitle Extraction: Strip cues / timing and export clean text (auto-copied to clipboard) for AI summarization, scripts, or content repurposing.
  • Unlimited Caching (IndexedDB): All translations cached locally with no browser-storage size limit; refreshing the page doesn't lose translated files.
  • 120+ Languages: Translate to/from 120+ languages, with source defaulting to Auto-detect.
  • Multi-Locale UI: Powered by next-intl, with full UI translation across 18 languages.
  • Command Line: yarn cli runs the same engine, parsers, and cache from a terminal — see Command Line.
  • Private by Design: Fully client-side — subtitle content and API keys stay in your browser; LLM requests go directly from your browser to the API endpoint you configure.

Translation APIs

Supports 8 traditional MT APIs and 27 LLM providers and gateways:

Traditional APIs

API Quality Stability Free Tier
DeepL ★★★★★ ★★★★☆ 500K chars/month
Google Translate ★★★★☆ ★★★★★ 500K chars/month
Azure Translate ★★★★☆ ★★★★★ 2M chars/month (first 12 months)
DeepLX (Free) ★★★★☆ ★★★☆☆ Self-host or free public endpoints
Qwen-MT ★★★★☆ ★★★★☆ Alibaba DashScope quota
TranslateGemma ★★★★☆ ★★★★☆ Self-host (LM Studio / Ollama / etc.)
GTX API (Free) ★★★☆☆ ★★★☆☆ Free (rate-limited)
Edge API (Free) ★★★★☆ ★★★☆☆ Free (rate-limited)

GTX and Edge need no configuration at all — they are the zero-setup defaults, and each is the other's fallback.

LLM Providers

DeepSeek, OpenAI, Claude, Gemini, Qwen, Moonshot (Kimi), Doubao, Xiaomi MiMo, Zhipu GLM, MiniMax, Baidu ERNIE, Tencent Hunyuan, Mistral, xAI (Grok), Perplexity, Cohere, and YandexGPT.

Gateways

OpenRouter, OpenCode Zen, Groq, SiliconFlow, Atlas Cloud, GitHub Models, Nvidia NIM, Azure OpenAI, LiteLLM, plus any Custom (OpenAI-compatible) endpoint (Ollama / LM Studio / vLLM / Together AI / Fireworks AI etc.).

Providers walled off from browsers by CORS can be routed through an API relay. The built-in relay works out of the box; API Settings → Relay address points every relayed provider at your own deployment of the relay Worker instead.

LLM modes give you:

  • Best for: literary works, technical talks, multilingual dialogue
  • Customization: configure system / user prompts for a specific translation style
  • Temperature Control: adjust AI creativity (0–1 scale)
  • Thinking Mode: per-provider toggle for reasoning-capable models
  • Extra Request Body: send any JSON the provider accepts — the escape hatch for per-vendor thinking switches this app doesn'''t model yet

Context-Aware Translation (LLM only)

LLM modes can send surrounding lines as context for each batch, improving dialogue coherence and character-voice consistency.

  • Concurrent Lines: max lines translated in parallel (default 20). Too high triggers rate limits.
  • Context Lines: lines included per batch as context (default 50). Higher = better coherence but more tokens.

⚠️ Tip: Models under 70B parameters may produce misaligned output. Mainstream online large models (Claude, GPT, DeepSeek, Gemini) are recommended for context mode.

Subtitle Format Support

Format Auto-detect Bilingual Notes
.srt ✅ ✅ 1–3 digit milliseconds, 100+ hour timestamps
.ass ✅ ✅ Line-leading position tags (e.g. \an8) auto-restored; complex inline effect tags simplified
.vtt ✅ ✅ NOTE / STYLE / REGION blocks correctly skipped; inline <c.classname> and karaoke timestamps handled on VTT→SRT
.lrc ✅ ✅ Karaoke lines with multiple time tags handled correctly
  • Automatic Encoding Detection: jschardet auto-detects UTF-8 / UTF-16 / GBK / Shift-JIS, avoiding garbled output (falls back to UTF-8 if detection fails).
  • Filename Preservation: Exported files inherit the original name; multi-language output appends a language code suffix.
  • Format Conversion: Convert SRT ↔ VTT and SRT/VTT → ASS during translation — no separate converter needed (identical source/target languages are blocked, so conversion requires a translation pass).

Translation Modes

  • Batch Mode (default): drop hundreds of files (a whole season) at once; each file translates independently and auto-downloads, with an aggregated success/failure summary.
  • Single-File Mode: instant preview; uploading a new file replaces the current one.

FAQ

Which formats are supported? SRT, ASS, VTT, and LRC. SRT/VTT suit YouTube and HTML5 players; ASS suits Aegisub / anime fansubs (position tags like \an8 auto-restored); LRC suits music lyrics.

Machine translation or LLM? Machine translation (Google, DeepL, Azure, Qwen-MT) is cheap or free but reads flat. LLMs bill per token but produce far more natural dialogue — DeepSeek is the value pick for whole-season batches, Claude Sonnet / GPT give the most natural dialogue, and Gemini's large context handles book-length subtitles.

How do I keep names and proper nouns consistent? Add a glossary in the System Prompt (e.g. "Keep verbatim: iPhone, OpenAI, John Smith") on any LLM engine; all episodes share the same context, so terminology stays consistent across a season.

Do I need "preserve timecodes / line numbers" prompts? No. Timecodes, cue numbers, and headers are extracted locally and re-inserted after translation — the model never sees them. Keep your prompt focused on style, glossary, and tone.

My model thinks by default and translation is slow — how do I turn thinking off? Set Thinking Mode to Off in API Settings when the provider is supported. For a model or gateway we don'''t model yet, use Extra request body (JSON) and send the vendor'''s own switch verbatim — e.g. {"enable_thinking": false}, {"chat_template_kwargs": {"thinking": false}}, or {"reasoning": {"enabled": false}} (OpenRouter). Whatever you put there is merged into the request last, so it overrides the built-in parameters. Check your provider'''s docs for the exact parameter name.

Is it private? Yes. Everything runs client-side: subtitle parsing, translation requests, and caching all happen in your browser. API keys are stored only in local browser storage, and LLM requests go directly from your browser to your configured endpoint.

See the full FAQ in the docs for more.

Command Line

yarn cli translates files headlessly over the same engine as the web app — same parsers, same retry and rate-limit handling, same cache keys. Configure the service once in the browser, hit Export settings, and hand the JSON to the CLI: keys, prompts, glossary and retry settings all carry over.

Point it at a folder

-i takes a directory as well as a file, and scans it recursively. Every translation lands next to the subtitle it came from, so a whole season keeps its folder structure:

yarn install   # once

# A whole season, translated in place: season/s01/e01.srt -> season/s01/e01.zh.srt
yarn cli -i ./season -t zh

# Driven by your exported web settings; two targets + bilingual in one pass.
yarn cli -i ./season -t zh -t ja --bilingual -s ~/subtitle-settings.json

# A local model — nothing leaves the machine.
yarn cli -i ./season -t zh -m llm --url http://localhost:11434/v1 --model qwen3

The scan skips dot-prefixed files and directories (.git, .DS_Store), so pointing it at a project root is safe.

Re-running never redoes work

The whole pipeline is built to be interrupted and resumed:

  • Each file is written to disk the moment it finishes. Ctrl-C halfway through leaves the completed episodes on disk — you never lose work you already paid for.
  • Re-running skips whatever is already done, via two gates:
    • the output already exists (movie.zh.srt sits next to movie.srt) → the file is skipped, logged as skipped (already translated);
    • files whose names say they are translations (movie.zh.srt, movie.zh_bilingual.ass) are never taken as input, so movie.zh.zh.srt can't happen.
  • A line-level cache (~/.translate-cli-cache.json) covers the rest: if one file died mid-way, a re-run only pays for the missing lines.

So the normal workflow is: just run it. Stop whenever you like, run it again later.

To deliberately redo finished work, use --overwrite:

# After switching models or editing the glossary, redo one episode.
yarn cli -i ./season/s01e01.srt -t zh --overwrite

Point --overwrite at a file, not a whole folder. Against a folder, the existing *.zh.srt files are themselves inputs, and the "output would overwrite an input" guard refuses to clobber them (that guard is deliberate).

Output lands beside the input (or in -o <dir>) as movie.zh.srt. Bilingual runs add _bilingual, and for .srt / .vtt sources they default to ASS with separate styles per line — movie.zh_bilingual.ass. Pass --bilingual-format srt to stay in SRT.

Option Meaning
-i, --input <file|dir> Input file or directory (scanned recursively). Repeatable.
--overwrite Redo work that already looks done (an existing <stem>.<lang>.<ext>, or a name that says it is a translation). Off by default.
-t, --to <lang> Target language. Repeatable. Default zh.
-f, --from <lang> Source language. Default auto.
-m, --method <id> Service id. Default gtxFreeAPI; --list-methods prints them all.
-s, --settings <file> Settings JSON exported from the web UI (keys, prompts, glossary, retry…).
-o, --out-dir <dir> Output directory. Default: next to each input.
--api-key · --url · --model One-off overrides for the chosen service.
--bilingual · --original-first · --bilingual-format <ass|srt> Bilingual output.
--no-context Turn off context-aware LLM batching (on by default for subtitles).
--no-cache · --cache-file <file> Cache control. Default ~/.translate-cli-cache.json.
--relay · --no-relay Route through the API relay. Off by default — Node has no CORS to work around.
--format <fmt> Force a format instead of inferring it from the extension.

Subtitles are not the only input: the same command handles Markdown (.md, .markdown, .mdx — code blocks, links and LaTeX protected by default) and JSON locale files (.json — keys untouched, values translated). yarn cli --list-formats prints the mapping, yarn cli --help the full option list including the Markdown-specific flags.

Exit codes: 0 everything translated (including "there was nothing left to do") · 1 finished but some lines soft-failed (kept as source text in the output) or a file failed · 2 bad invocation · 130 cancelled.

On Windows

From the project directory in PowerShell:

yarn install                    # once
yarn cli -i "D:\anime\Season 1" -t zh
  • Quote paths with spaces or non-ASCII characters (-i "D:\My Season 1"), or PowerShell splits them into several arguments.
  • Export your settings from the web UI once: configure the service (key, prompts, glossary) in the browser, hit Export settings, save it as settings.json, then pass -s D:\path\settings.json on every run.
  • Long jobs are safe to stop: Ctrl-C mid-season keeps what is finished, and the next identical command picks up where it left off.
  • No key needed to try it: omit -m and the free GTX endpoint is the default (rate-limited); adding -s routes through the service you configured instead.

Run It Yourself

Node.js >= 20.9.0 and Yarn (or npm / pnpm).

git clone https://github.com/Ray4AI/subtitle-translator.git
cd subtitle-translator

yarn install
yarn dev        # http://localhost:3000
yarn build      # production build
yarn test       # unit + CLI end-to-end tests (vitest)

yarn test covers the engine-adjacent logic, including a real end-to-end run of scripts/cli.ts against a temporary subtitle tree — it asserts that translations land beside their source files, that re-running is a no-op, and that an existing translation is never overwritten. It talks to the free GTX endpoint once per run and then serves everything from cache.

Docker

docker run -d -p 3000:3000 --name subtitle-translator ghcr.io/ray4ai/subtitle-translator:main

Or with the bundled docker-compose.yml (edit the image tag to pin a version):

docker compose up -d                        # start
docker compose pull && docker compose up -d # update to the latest build
docker compose logs -f                      # follow logs

Images are multi-arch (linux/amd64 + linux/arm64) and published to GHCR on every push to main (:main), on every v* tag (:<version>, :latest).

Documentation & Deployment

For detailed configuration, API setup, and self-hosting instructions, see the Official Documentation.

Quick Deployment: Deploy Guide

Contributing

Contributions are welcome! Feel free to open issues and pull requests.

  1. Fork the repo and create a feature branch
  2. Run yarn and yarn dev locally
  3. Add tests / docs when applicable
  4. Submit a PR with a clear description

About

Translate a whole season of subtitles in one pass — .srt/.ass/.vtt/.lrc, 120+ languages, 27 LLM providers, timing untouched | 整季字幕一次译完,时轴不动,支持 120+ 语言

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages