๐ŸŒ babel.nvim

April 2, 2026 ยท View on GitHub

๐ŸŒ babel.nvim

Translate text without leaving Neovim

Neovim Lua 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

PickerStatus
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

OptionTypeDefaultDescription
sourcestring"auto"Source language (auto-detect)
targetstring"ru"Target language code
providerstring"google"Translation provider: "google", "deepl"
network.connect_timeoutnumber5Network connect timeout (seconds)
network.request_timeoutnumber15Network request timeout (seconds)
cache.enabledbooleanfalseEnable in-memory translation cache
cache.limitnumber200Maximum cache entries
history.enabledbooleanfalseEnable in-memory translation history
history.limitnumber20Maximum stored history entries
fallback_chaintable{ deepl = {"google"}, google = {} }Per-provider fallback chain
displaystring"float"Display mode: "float" or "picker"
pickerstring"auto"Picker: "auto", "telescope", "fzf", "snacks", "mini"
float.modestring"center"Float preset: "center" or "cursor"
float.enterbooleantrueFocus float window (false = peek without entering)
float.auto_closebooleanfalseClose float on CursorMoved in source buffer
float.auto_close_msnumber0Auto-close timeout in milliseconds (0 disables)
float.pinbooleantrueEnable pin toggle key (p) when auto-close is enabled
float.copy_originalbooleanfalseEnable copying original text with Y
float.nvim_open_wintable{}Extra nvim_open_win() options for float window
deepl.api_keystringnilDeepL API key (or use DEEPL_API_KEY env)
deepl.probooleannilForce Pro/Free endpoint (nil = auto-detect by key)
deepl.formalitystring"default"Formality: "default", "more", "less", "prefer_more", "prefer_less"
keymaps.langstring"<leader>tl"Open language picker
keymaps.swapstring"<leader>ts"Swap source and target languages
languagestablenilOverride 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
CodeLanguage
enEnglish
ruRussian
deGerman
frFrench
esSpanish
itItalian
ptPortuguese
zhChinese
jaJapanese
koKorean
arArabic
hiHindi
trTurkish
plPolish
ukUkrainian

๐Ÿš€ Usage

Keymaps

KeymapModeDescription
<leader>trVisualTranslate selection
<leader>twNormalTranslate word under cursor
<leader>tlNormalOpen language picker
<leader>tsNormalSwap source โ†” target
<leader>thNormalTranslation history
<leader>thNormalTranslation history

Commands

CommandDescription
:Babel [text]Translate provided text
:[range]BabelTranslate selected line range (e.g. :10,20Babel)
:BabelWordTranslate word under cursor
:BabelRepeatRepeat last translation input
:BabelLangOpen language picker (source โ†’ target)
:BabelSwapSwap source and target languages
:BabelHistoryBrowse translation history
:BabelHistoryClearClear translation history
:BabelHistoryBrowse translation history
:BabelHistoryClearClear translation history

In Translation Window

KeyAction
q / <Esc> / <CR>Close window
yCopy translation to clipboard
YCopy original text (if float.copy_original = true)
pPin/unpin auto-close timer (if enabled)
LOpen language picker
HOpen translation history
j / kScroll

๐ŸŒ Providers

ProviderStatusAPI KeyNotes
Google Translateโœ…NoDefault, unofficial API
DeepL๐ŸงชYes (free tier)Best quality, 500k chars/month free
LibreTranslate๐Ÿ”œNoOpen source, self-hostable
Yandex๐Ÿ”œYesGreat for Russian
Lingva๐Ÿ”œNoGoogle 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
  1. Get a free API key at deepl.com/pro#developer (500k chars/month free)

  2. 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",
      },
    })
    
  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:

๐Ÿ“ License

MIT ยฉ Ilya Gilev