English · 简体中文
A tiny, dependency-light tool for the terminal, Vim, macOS, and Linux desktops, backed by large language models. One CLI, three tasks — translate text, optimize code, or bugfix a snippet — with swappable providers (DeepSeek, OpenAI, Anthropic Claude, local Ollama, Aliyun Coding Plan, plus zero-config MyMemory for translation) and a Vim plugin that runs any task on the current selection or buffer. The macOS menu-bar app and Linux GTK app can translate or speak selected text from any app.
┌──────────────────────┐ ┌────────────────────────┐ ┌──────────────────────────┐ ┌────────────────────────────────────┐
│ entrypoints │ │ integrations │ │ llm-translate (CLI) │ │ provider backends │
│ terminal stdin │ ──▶ │ shell pipeline │ ──▶ │ task: translate │ ──▶ │ openai_compat: │
│ Vim selection/buffer │ ──▶ │ Vim plugin/autoload │ ──▶ │ optimize │ │ aliyun-codingplan, doubao, │
│ macOS selected text │ ──▶ │ Swift menu-bar app │ ──▶ │ bugfix │ │ grok, kimi, mistral, qwen, │
│ Linux selected text │ ──▶ │ GTK desktop app │ │ │ │ zhipu │
│ │ │ speak: NSSpeechSynth │ │ │ │ direct/local: claude, deepseek, │
│ │ │ speak: spd-say/espeak │ │ │ │ openai, ollama, mymemory │
└──────────────────────┘ └────────────────────────┘ └──────────────────────────┘ └────────────────────────────────────┘
For the detailed Mermaid diagram generated from the current repository paths,
see docs/architecture.md. Refresh it with
./scripts/render-readme-diagrams.sh.
- Pure bash — only
curlandjqrequired. - Multi-provider — DeepSeek / OpenAI / Claude / Ollama / Aliyun Coding Plan / MyMemory, pick per invocation.
- Three tasks, one pipeline —
--task translate(default),--task optimizefor code rewrite,--task bugfixfor edge-case defect patches. - Streaming-friendly CLI — reads from stdin, writes to stdout. Pipe anything.
- Vim plugin —
<leader>t/<leader>o/<leader>brun translate / optimize / bugfix on the visual selection. Code tasks open a two-pane diff in a fresh tab. - macOS menu-bar app — translate selected text with the CLI, switch source
and target language from the menu, query versions, or speak it with the system
NSSpeechSynthesizer. - Linux GTK app — translate selected text or clipboard contents under X11 or Wayland, with source/target language dropdowns, version query, and local TTS fallback.
- Format-preserving prompt — code blocks, paths, identifiers, and markdown are kept intact.
jq and curl are the only runtime deps — install via your package manager
(sudo apt install jq curl). If you have no sudo, drop the jq static binary
into ~/.local/bin from the jq releases page.
curl -fsSL https://raw.githubusercontent.com/MarsDoge/llm-translate/main/install.sh | bashOr, if you use vim-plug:
curl -fsSL https://raw.githubusercontent.com/MarsDoge/llm-translate/main/install.sh | bash -s -- --mode vim-plugThe installer clones the repo to ~/.local/share/llm-translate (or
~/.vim/plugged/llm-translate in vim-plug mode), symlinks llm-translate
into ~/.local/bin, adds it to $PATH if missing, and wires up your
~/.vimrc (and ~/.config/nvim/init.vim if present). It's idempotent, and
install.sh --uninstall rolls everything back. See install.sh --help
for --prefix, --dir, --skip-vim.
# 1. Clone
git clone https://github.com/MarsDoge/llm-translate.git ~/.local/share/llm-translate
# 2. Expose CLI on $PATH
mkdir -p ~/.local/bin
ln -sf ~/.local/share/llm-translate/bin/llm-translate ~/.local/bin/llm-translate
chmod +x ~/.local/share/llm-translate/bin/llm-translate
# 3. Ensure ~/.local/bin is on $PATH
echo "$PATH" | tr ':' '\n' | grep -qx "$HOME/.local/bin" || \
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
# 4. Hook up Vim
echo 'set runtimepath+=~/.local/share/llm-translate' >> ~/.vimrcUse this if you already manage plugins with vim-plug.
You still need the CLI on $PATH — the plugin just shells out to it.
First time only — bootstrap vim-plug itself:
# Vim
curl -fLo ~/.vim/autoload/plug.vim --create-dirs \
https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim
# Neovim
sh -c 'curl -fLo "${XDG_DATA_HOME:-$HOME/.local/share}"/nvim/site/autoload/plug.vim --create-dirs \
https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim'In ~/.vimrc:
call plug#begin()
Plug 'MarsDoge/llm-translate'
call plug#end()Open Vim and run :PlugInstall. Then link the CLI that the plugin just
cloned into $PATH:
mkdir -p ~/.local/bin
ln -sf ~/.vim/plugged/llm-translate/bin/llm-translate ~/.local/bin/llm-translate
chmod +x ~/.vim/plugged/llm-translate/bin/llm-translate
echo "$PATH" | tr ':' '\n' | grep -qx "$HOME/.local/bin" || \
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
source ~/.bashrcFor Neovim's lazy.nvim / packer, do the same ln -sf in a build =
hook; the package layout is identical.
echo "Hello, world!" | llm-translate -p mymemory -t "Simplified Chinese"
# → 您好,世界!If you see the translation, both the CLI and $PATH are wired correctly.
Next, configure an LLM provider below for higher-quality translations.
For one-command Linux GUI install with shortcuts:
curl -fsSL https://raw.githubusercontent.com/MarsDoge/llm-translate/main/install.sh | bash -s -- --linux-desktop --install-linux-depsThe installer builds the GTK app, installs desktop launchers, and tries to bind:
Super+Alt+T: translate current selectionSuper+Alt+S: speak current selection
If the desktop does not expose a writable global shortcut API, the installer
prints the command to bind manually. It supports X11 and Wayland through common
desktop tools such as xclip, xdotool, wl-clipboard, and wtype. See
linux/LLMTranslateLinux/README.md for
GUI setup details.
Set the API key for whichever provider you use:
export DEEPSEEK_API_KEY=sk-...
export OPENAI_API_KEY=sk-...
export ANTHROPIC_API_KEY=sk-ant-...
export ALIYUN_CODING_PLAN_API_KEY=sk-sp-...
export OLLAMA_HOST=http://localhost:11434 # only if non-defaultOptional defaults:
export LLM_TRANSLATE_PROVIDER=deepseek
export LLM_TRANSLATE_MODEL=deepseek-chat
export LLM_TRANSLATE_TARGET="Simplified Chinese"| Flag | Default | Notes |
|---|---|---|
-p, --provider |
deepseek |
deepseek / openai / codex-responses / claude / ollama / aliyun-codingplan / mymemory |
-m, --model |
provider-specific | e.g. deepseek-chat, gpt-4o-mini; unused for mymemory |
-t, --target |
Simplified Chinese |
natural name ("Chinese") or ISO code (zh-CN) |
-s, --source |
auto |
required for mymemory when source is not English |
--task |
translate |
translate / optimize / bugfix — LLM-only for the last two |
--temperature |
0.2 |
LLM providers only |
--list-providers |
— | print available providers and exit |
-v, --version |
— | print version |
-h, --help |
— | show help |
Override defaults via env vars: LLM_TRANSLATE_PROVIDER, LLM_TRANSLATE_MODEL,
LLM_TRANSLATE_TARGET, LLM_TRANSLATE_TEMPERATURE, LLM_TRANSLATE_TASK.
# translate (default task)
echo "Hello, world!" | llm-translate -t "Chinese"
llm-translate -p openai -m gpt-4o-mini < README.md
llm-translate -p codex-responses --stream < README.md
llm-translate -p aliyun-codingplan -m qwen3.5-plus --task optimize < messy.py
llm-translate -p aliyun-codingplan -m kimi-k2.5 -t English < notes.zh.md
llm-translate -p aliyun-codingplan -m glm-5 --task bugfix < buggy.go
llm-translate -p claude -t "English" < notes.zh.md > notes.en.md
llm-translate -p ollama -m qwen2.5:7b -t English < manpage.txt
echo "Hello" | llm-translate -p mymemory -t zh-CN # no API key
# optimize: rewrite as cleaner code in the same language
llm-translate --task optimize -p deepseek < messy.py
# bugfix: patch boundary / null / off-by-one / wrong-operator defects
llm-translate --task bugfix -p deepseek < buggy.goVisual-select a region, then press one of the default mappings.
Translate opens the result in a split; optimize and bugfix open a two-pane
diff in a fresh tab (left = original, right = rewritten) so you can
:diffget the bits you want and :tabclose to drop the rest.
Default mappings (visual mode):
| Mapping | Task | Result window |
|---|---|---|
<leader>t |
translate | scratch split, filetype markdown |
<leader>o |
optimize | new tab, two-pane diff, source filetype |
<leader>b |
bugfix | new tab, two-pane diff, source filetype |
Commands:
| Command | Scope |
|---|---|
:LLMTranslate |
current visual selection |
:LLMTranslateBuffer |
whole buffer |
:LLMOptimize |
current visual selection |
:LLMOptimizeBuffer |
whole buffer |
:LLMBugfix |
current visual selection |
:LLMBugfixBuffer |
whole buffer |
Per-buffer or per-session overrides:
let g:llm_translate_provider = 'claude'
let g:llm_translate_model = 'claude-haiku-4-5-20251001'
let g:llm_translate_target = 'French'
let g:llm_translate_map = 0 " disable default <leader>t
let g:llm_translate_map_optimize = 0 " disable default <leader>o
let g:llm_translate_map_bugfix = 0 " disable default <leader>b| Provider | Env var | Default model | Type |
|---|---|---|---|
| deepseek | DEEPSEEK_API_KEY |
deepseek-chat |
LLM |
| openai | OPENAI_API_KEY |
gpt-4o-mini |
LLM |
| codex-responses | OPENAI_API_KEY |
gpt-5.5 |
LLM |
| claude | ANTHROPIC_API_KEY |
claude-haiku-4-5-20251001 |
LLM |
| aliyun-codingplan | ALIYUN_CODING_PLAN_API_KEY |
qwen3.5-plus |
LLM |
| ollama | (none — local) | qwen2.5:7b |
LLM |
| mymemory | (none — free tier) | n/a | MT API |
codex-responses uses the Responses API at
https://api.dogexorg.com/v1/responses by default, streams output, and reads
OPENAI_API_KEY. Override the base URL with OPENAI_API_BASE and the model
with LLM_TRANSLATE_MODEL or -m.
export OPENAI_API_KEY=sk-...
echo "Hello" | llm-translate -p codex-responses --stream -t Chinesealiyun-codingplan uses Aliyun Model Studio's OpenAI-compatible Coding Plan
endpoint: https://coding.dashscope.aliyuncs.com/v1. The key format is
sk-sp-....
The provider also accepts CODING_PLAN_API_KEY and
BAILIAN_CODING_PLAN_API_KEY as compatibility fallbacks.
Documented supported models include qwen3.5-plus, kimi-k2.5, glm-5,
and other Coding Plan models; pass the desired model via -m.
Official docs describe Coding Plan as an interactive coding-tools offering, so
use it accordingly instead of generic batch backends or unrelated API tooling.
If you just want to try the tool without signing up for anything:
echo "Hello, world!" | llm-translate -p mymemory -t "Simplified Chinese"
# → 您好,世界!MyMemory is a hosted translation service with a free tier (~5000 words/day
per IP, no account). Set MYMEMORY_EMAIL=you@example.com to raise the daily
quota to ~50000 words. Quality is lower than LLMs, and it has no real
source-auto-detect — pass -s explicitly when translating non-English input.
The CLI normalizes natural names and common variants to BCP 47 codes for non-LLM providers. Any row below accepts all listed forms interchangeably:
| Aliases | Normalized |
|---|---|
| Simplified Chinese, Chinese, zh, zh-CN, 中文, 简体中文 | zh-CN |
| Traditional Chinese, zh-TW, 繁体中文 | zh-TW |
| English, en, en-US, en-GB, 英语, 英文 | en-US |
| Japanese, ja, ja-JP, 日语, 日本語 | ja-JP |
| Korean, ko, ko-KR, 韩语, 한국어 | ko-KR |
| French, fr, fr-FR, 法语 | fr-FR |
| German, de, de-DE, 德语 | de-DE |
| Spanish, es, es-ES, 西班牙语 | es-ES |
| Russian, ru, ru-RU, 俄语 | ru-RU |
| Italian, it, it-IT, 意大利语 | it-IT |
| Portuguese, pt, pt-PT, 葡萄牙语 | pt-PT |
| Arabic, ar, ar-SA, 阿拉伯语 | ar-SA |
Unknown input passes through unchanged, so any provider-specific code works.
LLM providers (deepseek / openai / codex-responses / claude / ollama) understand any
natural-language target directly and ignore the normalization table — it
mainly matters for mymemory and future MT providers that need strict codes.
For pairs not in the table, pass the ISO code explicitly:
llm-translate -p mymemory -s en-US -t vi-VN < input.txt # English → VietnameseAdding a new provider is one file in lib/providers/. It receives the text on
$LLM_TRANSLATE_INPUT and the system prompt on $LLM_TRANSLATE_SYSTEM (LLMs)
or $LLM_TRANSLATE_TARGET_CODE / $LLM_TRANSLATE_SOURCE_CODE (MT APIs), and
prints the translation on stdout. See lib/providers/deepseek.sh or
lib/providers/mymemory.sh for ~30-line templates.
--streamfor live token output in the terminal.- Glossary / terminology files (
--glossary path.tsv). - Neovim Lua port with floating window UI.
- Batch mode for translating whole directories while preserving structure.
- Additional providers: Gemini, Mistral, Azure OpenAI.
Contributions welcome — open an issue or PR.
If you want to hack on the plugin or CLI, point Vim at your clone directly
instead of installing via a plugin manager — edits take effect on :source
without needing a commit / push / :PlugUpdate cycle.
git clone git@github.com:MarsDoge/llm-translate.git ~/src/llm-translate
ln -sf ~/src/llm-translate/bin/llm-translate ~/.local/bin/llm-translate~/.vimrc:
set runtimepath+=~/src/llm-translate
let g:llm_translate_provider = 'deepseek'Run shellcheck before opening a PR:
shellcheck bin/llm-translate lib/providers/*.sh