https://github.com/acidsugarx/babel.nvim
๐ Translate text without leaving Neovim
https://github.com/acidsugarx/babel.nvim
neovim-plugin
Last synced: about 2 months ago
JSON representation
๐ Translate text without leaving Neovim
- Host: GitHub
- URL: https://github.com/acidsugarx/babel.nvim
- Owner: acidsugarx
- License: mit
- Created: 2025-12-06T23:05:55.000Z (5 months ago)
- Default Branch: main
- Last Pushed: 2025-12-07T13:17:47.000Z (5 months ago)
- Last Synced: 2026-01-13T13:48:08.018Z (4 months ago)
- Topics: neovim-plugin
- Language: Lua
- Homepage:
- Size: 41 KB
- Stars: 8
- Watchers: 0
- Forks: 0
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
Awesome Lists containing this project
- awesome-neovim-sorted - acidsugarx/babel.nvim
- awesome-neovim - acidsugarx/babel.nvim - Translate text using Google Translate with async support, float display, and multi-picker integration. (Language / Assembly)
README
# ๐ babel.nvim
**Translate text without leaving Neovim**
[](https://neovim.io)
[](https://lua.org)
[](./LICENSE)
---
## โจ 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](https://github.com/folke/snacks.nvim) | โ
|
| [telescope.nvim](https://github.com/nvim-telescope/telescope.nvim) | โ
|
| [fzf-lua](https://github.com/ibhagwan/fzf-lua) | โ
|
| [mini.pick](https://github.com/echasnovski/mini.pick) | โ
|
## โก Requirements
- Neovim >= 0.9.0
- `curl`
**Optional** (for picker display):
- snacks.nvim, telescope.nvim, fzf-lua, or mini.pick
## ๐ฆ Installation
### [lazy.nvim](https://github.com/folke/lazy.nvim)
```lua
{
"acidsugarx/babel.nvim",
version = "*", -- recomended for the latest tag, not main
opts = {
target = "ru", -- target language
},
keys = {
{ "tr", mode = "v", desc = "Translate selection" },
{ "tw", desc = "Translate word" },
},
}
```
## โ๏ธ Configuration
### Minimal Setup
```lua
require("babel").setup({
target = "ru",
})
```
### Full Options
Default Configuration
```lua
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,
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 = "tr",
translate_word = "tw",
},
-- 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.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"` |
### Cursor-follow float preset
For a smaller popup that follows your cursor, use:
```lua
require("babel").setup({
float = {
mode = "cursor",
max_width = 60,
max_height = 10,
},
})
```
### Network timeout customization
You can tune provider request timeouts for slow/unstable networks:
```lua
require("babel").setup({
network = {
connect_timeout = 3,
request_timeout = 25,
},
})
```
### Translation history
You can keep the latest successful translations in memory:
```lua
require("babel").setup({
history = {
enabled = true,
limit = 50,
},
})
```
### Fallback chain and cache
You can control provider fallback behavior and enable in-memory caching:
```lua
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()`:
```lua
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 |
|--------|------|-------------|
| `tr` | Visual | Translate selection |
| `tw` | Normal | Translate word under cursor |
### 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 |
### In Translation Window
| Key | Action |
|-----|--------|
| `q` / `` / `` | Close window |
| `y` | Copy translation to clipboard |
| `Y` | Copy original text (if `float.copy_original = true`) |
| `p` | Pin/unpin auto-close timer (if enabled) |
| `j` / `k` | Scroll |
## ๐ Providers
| Provider | Status | API Key | Notes |
|----------|:------:|:-------:|-------|
| Google Translate | โ
| No | Default, unofficial API |
| [DeepL](https://deepl.com) | ๐งช | Yes (free tier) | Best quality, 500k chars/month free |
| [LibreTranslate](https://libretranslate.com) | ๐ | No | Open source, self-hostable |
| [Yandex](https://translate.yandex.ru) | ๐ | Yes | Great for Russian |
| [Lingva](https://lingva.ml) | ๐ | No | Google proxy, no rate limits |
### Provider capabilities
You can inspect provider capabilities from Lua:
```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](https://github.com/acidsugarx/babel.nvim/issues) with your feedback!
DeepL Setup
1. Get a free API key at [deepl.com/pro#developer](https://www.deepl.com/pro#developer) (500k chars/month free)
2. Set up the API key (choose one):
**Option A:** Environment variable
```bash
export DEEPL_API_KEY="your-api-key-here"
```
**Option B:** In config
```lua
require("babel").setup({
provider = "deepl",
deepl = {
api_key = "your-api-key-here",
},
})
```
3. The endpoint (Free/Pro) is auto-detected from the key suffix (`:fx` = Free). You can override with `deepl.pro = true/false`.
4. 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](https://github.com/folke) for [snacks.nvim](https://github.com/folke/snacks.nvim) and [lazy.nvim](https://github.com/folke/lazy.nvim)
- [nvim-telescope](https://github.com/nvim-telescope) for [telescope.nvim](https://github.com/nvim-telescope/telescope.nvim)
- [ibhagwan](https://github.com/ibhagwan) for [fzf-lua](https://github.com/ibhagwan/fzf-lua)
- [echasnovski](https://github.com/echasnovski) for [mini.nvim](https://github.com/echasnovski/mini.nvim)
## ๐ License
[MIT](./LICENSE) ยฉ Ilya Gilev