Write prompts in the language you think in and keep the conversation in English. A herdr plugin: press a key on any agent pane, an overlay opens above it, you write, and the English translation lands in the agent's input. It works with Claude Code, Codex, opencode and the rest, because the prompt goes through herdr rather than into a particular tool.
You think faster in your own language. But an agent answers in the language it was
asked in, so your prompts end up in the replies, the comments, the commits and the
docs — and a rule in CLAUDE.md or AGENTS.md cannot hold: the agent continues the
context it has, and every prompt you send makes more of that context your language.
Translate the prompt and the drift has no source.
Side effect: your sentence and its English sit side by side, prompt after prompt. It rubs off.
herdr plugin install wazum/herdr-polyglotThat is enough to use it: with no key the popup is a prompt box that hands what you write to the agent, with the keys, the vim bindings and the resumed draft it always had. Nothing is translated and nothing leaves the machine.
For the translation, put your key in the plugin's own config directory, readable only by you — a plain redirect would leave it readable by everyone on the machine:
ENV_FILE="$(herdr plugin config-dir wazum.polyglot)/.env"
touch "$ENV_FILE" && chmod 600 "$ENV_FILE"
echo "HERDR_POLYGLOT_API_KEY=your-deepl-key" >> "$ENV_FILE"A DeepL API key has a free tier. Free keys
end in :fx, and the plugin sends those to DeepL's free host by itself.
Then bind a key in ~/.config/herdr/config.toml:
[[keys.command]]
key = "prefix+t"
type = "plugin_action"
command = "wazum.polyglot.prompt"
description = "write a prompt in your own language"For one gesture instead of two, let the terminal send the prefix for you. In Ghostty:
keybind = super+p=text:\x02t
\x02 is ctrl+b, the prefix, and t is the binding above. Reload with ⌘⇧, and
take a chord the terminal has free — ⌘G is find-next in Ghostty.
t as in translate — herdr already uses prefix+p for the previous tab.
There is a second action, wazum.polyglot.compose, which types the prompt into
the agent's input instead of sending it, and ctrl+r switches between the two
while you write, so one keybinding is enough.
alt+enter |
translate and hand the prompt over (ctrl+d does the same) |
enter |
a new line, because a prompt is often more than one |
ctrl+r |
switch between sending it and only filling the input |
ctrl+l |
turn live translation on or off for this prompt |
ctrl+t |
translate what is there now, or try again after a translation failed |
tab |
read the translation across the whole popup, and back |
ctrl+u |
throw the draft away, as it clears a line in a shell |
esc |
close — with vim bindings on, first to normal mode, then close |
ctrl+c |
close, always |
The header says which of the two will happen: sends to agent hands the prompt
over and the agent starts working, fills the input types it there and leaves
the last keystroke to you. When something does not work the footer says so,
esc takes the message away, and it goes by itself after a few seconds.
Every setting can be a line in the .env file or an environment variable, which
wins over the file. A value that is neither on (1, true, yes, on) nor
off (0, false, no, off) is refused rather than guessed at.
| Setting | Default | Meaning |
|---|---|---|
HERDR_POLYGLOT_API_KEY |
none | Credentials for the translation service |
HERDR_POLYGLOT_PROVIDER |
picked from what is configured | Which service: deepl, google, cmd, dry-run or off |
HERDR_POLYGLOT_COMMAND |
none | A program that translates, for local translation |
HERDR_POLYGLOT_LANGUAGE |
EN-US |
Target language |
HERDR_POLYGLOT_ENDPOINT |
the service's own | Override the service endpoint |
HERDR_POLYGLOT_SUBMIT |
1 |
0 types the prompt without sending it |
HERDR_POLYGLOT_VIM |
0 |
1 turns on the vim bindings |
HERDR_POLYGLOT_LIVE |
0 |
1 translates while you write |
HERDR_POLYGLOT_CONFIRM |
0 |
1 shows the English and waits for a second alt+enter |
HERDR_POLYGLOT_KEEP_DRAFT |
1 |
0 starts from an empty box instead of resuming |
HERDR_POLYGLOT_MAX_DRAFT |
2000 |
Characters before the box says the draft is too long |
HERDR_POLYGLOT_PULSE |
1 |
0 stops the live circle from breathing |
HERDR_POLYGLOT_LOGO |
1 |
0 leaves the empty draft box unsigned |
The service is chosen by what you configured: a key means DeepL, a command means
that command, neither means off — the plain prompt box. Both at once is refused
rather than guessed at, and then HERDR_POLYGLOT_PROVIDER decides. Asking for a
service that cannot be built is a case of its own: the popup says what is missing
and where it belongs, and delivers nothing, so an untranslated draft never reaches
the agent by accident. To keep keys for several services side by side, scope them by
name: HERDR_POLYGLOT_DEEPL_API_KEY, HERDR_POLYGLOT_GOOGLE_API_KEY.
Google takes the same language setting and needs the Cloud Translation API enabled for the key. It reports no monthly count, so the header shows none with it, and it has no unbilled context parameter — live translation still pays for each sentence once, but translates each on its own.
An unfinished prompt is kept. Drafts are stored privately, one per pane, and come back the next time you open the popup there, however it closed. A sent or discarded draft is forgotten.
Read the English first. With HERDR_POLYGLOT_CONFIRM=1, alt+enter
translates and shows the result, a second alt+enter delivers it, and esc
goes back to writing. It costs one translation, not two.
Live translation. The English follows your draft after a short pause. Each
sentence is paid for once, and a translation you have already read is delivered
as it stands, so writing costs little more than sending —
how that works. Live translation starts off for a
draft that came back from an earlier session and for text you paste in, since
neither is something you asked to have translated; ctrl+l turns it on.
Translation on your own machine. HERDR_POLYGLOT_COMMAND points the plugin at
a program instead of a service, so a draft need not leave the machine at all —
how to set that up.
Code is not translated. A translation service rewrites code as if it were prose: it renames identifiers, translates comments and string literals, and reformats indentation. So anything in backticks or a fenced ``` block is taken out of the draft before it is sent, and put back exactly as it was — which also means it never leaves your machine and costs nothing to translate.
Vim bindings. HERDR_POLYGLOT_VIM=1 makes the draft box modal, with the
motions, edits and counts that make sense inside a text box —
the full list.
The draft goes to the translation service, so treat it the way you treat anything you paste into a web translator: prompts for a coding agent carry file paths, code and occasionally a secret, and in live mode the draft goes out again after every pause in typing.
Code does not leave either: backticked spans and fenced blocks are held back and restored afterwards, so a pasted stack trace or a file path is not sent anywhere.
Nothing else leaves. The API key goes to the translation service only — never to the agent, the herdr socket, a command line or a child process. Translated text is stripped of control characters before it is typed into a pane, so neither a line break nor an escape sequence can reach the agent's terminal. A translator on the machine keeps a draft off the network entirely.
make qa # formatting, linting, race tests, vulnerability scan
make build
herdr plugin link .The overlay names palette slots rather than fixed colours, so it takes on whatever herdr theme is active, including a light one. How the pieces fit together, including how to add another translation service.
Created with ♥ by Wolfgang Klinger.
Built on herdr, Bubble Tea and Lip Gloss.
MIT.
