๐ babel.nvim
April 2, 2026 ยท View on GitHub
โจ Features
- ๐ค Translate selected text or word under cursor
- ๐ช Multiple display modes (float, picker)
- ๐ Auto-detect installed picker
- ๐ Copy translation to clipboard with
y - โก Async translation (non-blocking)
Supported Pickers
| Picker | Status |
|---|---|
| Native float | โ |
| snacks.nvim | โ |
| telescope.nvim | โ |
| fzf-lua | โ |
| mini.pick | โ |
โก Requirements
- Neovim >= 0.9.0
curl
Optional (for picker display):
- snacks.nvim, telescope.nvim, fzf-lua, or mini.pick
๐ฆ Installation
lazy.nvim
{
"acidsugarx/babel.nvim",
version = "*", -- recomended for the latest tag, not main
opts = {
target = "ru", -- target language
},
keys = {
{ "<leader>tr", mode = "v", desc = "Translate selection" },
{ "<leader>tw", desc = "Translate word" },
},
}
โ๏ธ Configuration
Minimal Setup
require("babel").setup({
target = "ru",
})
Full Options
Default Configuration
require("babel").setup({
source = "auto", -- source language (auto-detect)
target = "ru", -- target language
provider = "google", -- translation provider: "google", "deepl"
network = {
connect_timeout = 5, -- curl connect timeout in seconds
request_timeout = 15, -- max request time in seconds
},
cache = {
enabled = false, -- enable in-memory translation cache
limit = 200, -- max cache entries
},
history = {
enabled = false, -- keep in-memory translation history
limit = 20, -- max saved entries
},
fallback_chain = {
deepl = { "google" }, -- if DeepL fails, try Google
google = {},
},
display = "float", -- "float" or "picker"
picker = "auto", -- "auto", "telescope", "fzf", "snacks", "mini"
float = {
border = "rounded",
mode = "center", -- "center" or "cursor"
max_width = 80,
max_height = 20,
enter = true, -- focus the float window (false = peek without entering)
auto_close = false, -- close float on CursorMoved in source buffer
auto_close_ms = 0, -- auto-close delay, 0 = disabled
pin = true, -- allow pin toggle with `p` when auto-close is enabled
copy_original = false, -- allow copying original text with `Y`
nvim_open_win = {}, -- extra nvim_open_win() options (overrides defaults)
},
keymaps = {
translate = "<leader>tr",
translate_word = "<leader>tw",
lang = "<leader>tl",
swap = "<leader>ts",
},
-- DeepL provider settings (optional)
deepl = {
api_key = nil, -- or use DEEPL_API_KEY env variable
pro = nil, -- nil = auto-detect, true = Pro, false = Free
formality = "default", -- "default", "more", "less", "prefer_more", "prefer_less"
},
})
Options
| Option | Type | Default | Description |
|---|---|---|---|
source | string | "auto" | Source language (auto-detect) |
target | string | "ru" | Target language code |
provider | string | "google" | Translation provider: "google", "deepl" |
network.connect_timeout | number | 5 | Network connect timeout (seconds) |
network.request_timeout | number | 15 | Network request timeout (seconds) |
cache.enabled | boolean | false | Enable in-memory translation cache |
cache.limit | number | 200 | Maximum cache entries |
history.enabled | boolean | false | Enable in-memory translation history |
history.limit | number | 20 | Maximum stored history entries |
fallback_chain | table | { deepl = {"google"}, google = {} } | Per-provider fallback chain |
display | string | "float" | Display mode: "float" or "picker" |
picker | string | "auto" | Picker: "auto", "telescope", "fzf", "snacks", "mini" |
float.mode | string | "center" | Float preset: "center" or "cursor" |
float.enter | boolean | true | Focus float window (false = peek without entering) |
float.auto_close | boolean | false | Close float on CursorMoved in source buffer |
float.auto_close_ms | number | 0 | Auto-close timeout in milliseconds (0 disables) |
float.pin | boolean | true | Enable pin toggle key (p) when auto-close is enabled |
float.copy_original | boolean | false | Enable copying original text with Y |
float.nvim_open_win | table | {} | Extra nvim_open_win() options for float window |
deepl.api_key | string | nil | DeepL API key (or use DEEPL_API_KEY env) |
deepl.pro | boolean | nil | Force Pro/Free endpoint (nil = auto-detect by key) |
deepl.formality | string | "default" | Formality: "default", "more", "less", "prefer_more", "prefer_less" |
keymaps.lang | string | "<leader>tl" | Open language picker |
keymaps.swap | string | "<leader>ts" | Swap source and target languages |
languages | table | nil | Override built-in language list (nil = use defaults) |
Cursor-follow float preset
For a smaller popup that follows your cursor, use:
require("babel").setup({
float = {
mode = "cursor",
max_width = 60,
max_height = 10,
},
})
Peek mode (no-enter + auto-close)
Show a translation popup without entering it. The float closes automatically on the next cursor movement:
require("babel").setup({
float = {
mode = "cursor",
enter = false,
auto_close = true,
},
})
Language picker
Change source and target languages interactively:
-- Via command
:BabelLang
-- Via keymap (default <leader>tl)
-- Press <leader>tl, select source, then target
-- Swap source โ target
:BabelSwap
-- or press <leader>ts
-- From inside the translation float, press L
Override the language list:
require("babel").setup({
languages = {
auto = "Auto-detect",
en = "English",
ru = "Russian",
},
})
Network timeout customization
You can tune provider request timeouts for slow/unstable networks:
require("babel").setup({
network = {
connect_timeout = 3,
request_timeout = 25,
},
})
Translation history
You can keep the latest successful translations in memory:
require("babel").setup({
history = {
enabled = true,
limit = 50,
},
})
Fallback chain and cache
You can control provider fallback behavior and enable in-memory caching:
require("babel").setup({
provider = "deepl",
fallback_chain = {
deepl = { "google" },
google = {},
},
cache = {
enabled = true,
limit = 500,
},
})
Float window customization
You can override Babel's default float settings by passing options directly to nvim_open_win():
require("babel").setup({
float = {
max_width = 60,
max_height = 10,
nvim_open_win = {
relative = "cursor",
row = 1,
col = 0,
anchor = "NW",
border = "single",
title = " Babel ",
},
},
})
Language Codes
Common language codes
| Code | Language |
|---|---|
en | English |
ru | Russian |
de | German |
fr | French |
es | Spanish |
it | Italian |
pt | Portuguese |
zh | Chinese |
ja | Japanese |
ko | Korean |
ar | Arabic |
hi | Hindi |
tr | Turkish |
pl | Polish |
uk | Ukrainian |
๐ Usage
Keymaps
| Keymap | Mode | Description |
|---|---|---|
<leader>tr | Visual | Translate selection |
<leader>tw | Normal | Translate word under cursor |
<leader>tl | Normal | Open language picker |
<leader>ts | Normal | Swap source โ target |
<leader>th | Normal | Translation history |
<leader>th | Normal | Translation history |
Commands
| Command | Description |
|---|---|
:Babel [text] | Translate provided text |
:[range]Babel | Translate selected line range (e.g. :10,20Babel) |
:BabelWord | Translate word under cursor |
:BabelRepeat | Repeat last translation input |
:BabelLang | Open language picker (source โ target) |
:BabelSwap | Swap source and target languages |
:BabelHistory | Browse translation history |
:BabelHistoryClear | Clear translation history |
:BabelHistory | Browse translation history |
:BabelHistoryClear | Clear translation history |
In Translation Window
| Key | Action |
|---|---|
q / <Esc> / <CR> | Close window |
y | Copy translation to clipboard |
Y | Copy original text (if float.copy_original = true) |
p | Pin/unpin auto-close timer (if enabled) |
L | Open language picker |
H | Open translation history |
j / k | Scroll |
๐ Providers
| Provider | Status | API Key | Notes |
|---|---|---|---|
| Google Translate | โ | No | Default, unofficial API |
| DeepL | ๐งช | Yes (free tier) | Best quality, 500k chars/month free |
| LibreTranslate | ๐ | No | Open source, self-hostable |
| Yandex | ๐ | Yes | Great for Russian |
| Lingva | ๐ | No | Google proxy, no rate limits |
Provider capabilities
You can inspect provider capabilities from Lua:
local caps = require("babel").get_provider_capabilities()
-- caps.google.supports_formality == false
-- caps.deepl.supports_formality == true
local deepl = require("babel").get_provider_capabilities("deepl")
๐งช Testing: DeepL provider is implemented but needs testing. If you have a DeepL API key and want to help test, please open an issue with your feedback!
DeepL Setup
-
Get a free API key at deepl.com/pro#developer (500k chars/month free)
-
Set up the API key (choose one):
Option A: Environment variable
export DEEPL_API_KEY="your-api-key-here"Option B: In config
require("babel").setup({ provider = "deepl", deepl = { api_key = "your-api-key-here", }, }) -
The endpoint (Free/Pro) is auto-detected from the key suffix (
:fx= Free). You can override withdeepl.pro = true/false. -
If no API key is found, babel.nvim will automatically fall back to Google Translate with a warning.
๐ค Contributing
Contributions are welcome! Feel free to:
- ๐ Report bugs
- ๐ก Suggest features
- ๐ง Submit pull requests
If you use AI coding assistants, check AGENTS.md for project architecture, quality gates, and safe-change checklist.
๐ Acknowledgments
Thanks to the amazing Neovim plugin ecosystem:
- folke for snacks.nvim and lazy.nvim
- nvim-telescope for telescope.nvim
- ibhagwan for fzf-lua
- echasnovski for mini.nvim
๐ License
MIT ยฉ Ilya Gilev