Neovim Translation with translate-shell and Custom Popup
A guide to adding quick command-line translation in Neovim, with a visual popup styled like traditional translators.
Creators: opencode and Sérgio Araújo
Prerequisites
1. Install translate-shell
translate-shell is a command-line translator powered by Google Translate, Bing, Yandex, and more.
Direct download:
wget git.io/trans
chmod +x ./trans
sudo mv trans /usr/local/bin/
Or via git:
git clone https://github.com/soimort/translate-shell
cd translate-shell && make && sudo make install
Dependencies: gawk (4.0+) and bash or zsh (usually pre-installed on Linux).
Neovim Implementation
1. Add the translation function
In ~/.config/nvim/lua/core/utils/nvim_utils.lua, add the translate_popup function:
function M.translate_popup()
local text = ''
local is_multiline = false
local mode = vim.api.nvim_get_mode().mode
if mode == 'v' or mode == 'V' or mode == '\x16' then
local start_pos = vim.api.nvim_buf_get_mark(0, '<')
local end_pos = vim.api.nvim_buf_get_mark(0, '>')
local lines = vim.api.nvim_buf_get_lines(0, start_pos[1] - 1, end_pos[1], false)
if #lines > 1 then
is_multiline = true
text = table.concat(lines, '\n')
else
text = table.concat(lines, ' ')
end
else
text = vim.fn.expand('<cword>') or ''
end
if text == '' then
return
end
local ok, trans = pcall(function()
local handle = io.popen('trans -b -no-ansi en:pt-BR ' .. vim.fn.shellescape(text))
if not handle then return nil end
local result = handle:read('*a')
handle:close()
return result
end)
if not ok or not trans or trans == '' then
vim.notify('Failed to translate', vim.log.levels.ERROR, { title = 'Translate' })
return
end
local lines = vim.split(trans:gsub('^%s+', ''):gsub('%s+$', ''), '\n')
local filtered = {}
for _, line in ipairs(lines) do
table.insert(filtered, line)
end
if #filtered == 0 then
vim.notify('Empty translation result', vim.log.levels.WARN, { title = 'Translate' })
return
end
local width = 20
for _, line in ipairs(filtered) do
local line_width = vim.fn.strdisplaywidth(line)
if line_width > width then
width = math.min(80, line_width)
end
end
local height = #filtered
local buf = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_lines(buf, 0, -1, false, filtered)
vim.api.nvim_buf_set_option(buf, 'filetype', 'markdown')
vim.api.nvim_buf_set_option(buf, 'modifiable', false)
vim.api.nvim_set_hl(0, 'TranslateBg', { bg = '#ffcc66', fg = '#000000' })
vim.api.nvim_buf_set_extmark(buf, vim.api.nvim_create_namespace('translate'), 0, 0, {
hl_group = 'TranslateBg',
hl_eol = true,
end_line = height - 1,
})
local row, col = unpack(vim.api.nvim_win_get_cursor(0))
local win = vim.api.nvim_open_win(buf, true, {
style = 'minimal',
relative = 'cursor',
width = width + 2,
height = height,
row = 1,
col = 0,
border = 'rounded',
noautocmd = true,
})
vim.api.nvim_set_option_value('winblend', 0, { win = win })
vim.api.nvim_set_option_value('winhighlight', 'Normal:TranslateBg', { win = win })
vim.keymap.set('n', 'q', '<cmd>close<CR>', { buffer = buf, nowait = true })
vim.keymap.set('n', '<Esc>', '<cmd>close<CR>', { buffer = buf, nowait = true })
end
2. Add the keymaps
NOTE: I have created a map utility to help me out creating mappings:
-- https://blog.devgenius.io/create-custom-keymaps-in-neovim-with-lua-d1167de0f2c2
-- https://oroques.dev/notes/neovim-init/
function M.map(mode, lhs, rhs, opts)
local options = { noremap = true, silent = true }
if opts then options = vim.tbl_extend('force', options, opts) end
vim.keymap.set(mode, lhs, rhs, options)
end
In ~/.config/nvim/lua/core/keymaps.lua, add:
map('n', '<leader>st', nvim_utils.translate_popup, { desc = 'Show Translate (word under cursor)' })
map('x', '<leader>st', nvim_utils.translate_popup, { desc = 'Show Translate (selection)' })
3. Mnemonic
The <leader>st shortcut was chosen with the mnemonic:
- s → show
- t → translate
Just like other translators show ("show translate"), or simply "translate".
Features
-
Word under cursor: Press
<leader>stwith cursor on a word -
Visual selection: Select text and press
<leader>stto translate - Multiple lines: Selected paragraphs preserve line breaks
-
Default language: English → Brazilian Portuguese (
en:pt-BR) - Styled popup: Orange/yellow background (#ffcc66) with black text
-
Close with:
qor<Esc>
Demo
Credits
- opencode - Implementation and development
- Sérgio Araújo - Idea, testing and feedback

Top comments (0)