Neovim translate popup

Published: (February 28, 2026 at 09:08 AM EST)
4 min read
Source: Dev.to

Source: Dev.to

Cover image for Neovim translate popup

Sérgio Araújo

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

Create (or edit) ~/.config/nvim/lua/core/utils/nvim_utils.lua and 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 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('') 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',   'close', { buffer = buf, nowait = true })
  vim.keymap.set('n', '', 'close', { buffer = buf, nowait = true })
end

2. Add the keymaps

A small helper for creating mappings (optional, but handy):

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

Add the mappings in ~/.config/nvim/lua/core/keymaps.lua:

map('n', 'st', nvim_utils.translate_popup,
    { desc = 'Show Translate (word under cursor)' })
map('x', 'st', nvim_utils.translate_popup,
    { desc = 'Show Translate (selection)' })

3. Mnemonic

The st shortcut was chosen with the mnemonic:

  • sshow
  • ttranslate

Just like other translators “show translate”, or simply “translate”.

Features

  • Word under cursor – Press st with the cursor on a word.
  • Visual selection – Select text and press st to 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 withq or “.

Demo

Demo GIF

![Demo GIF](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f86ah372f8y0kxhqzv8m.gif)

Credits

  • opencode – Implementation and development
  • Sérgio Araújo – Idea, testing and feedback

References

0 views
Back to Blog

Related posts

Read more »

Ghostty – Terminal Emulator

Article Ghostty – Terminal Emulatorhttps://ghostty.org/docs Discussion - Comments: Hacker News threadhttps://news.ycombinator.com/item?id=47206009 – 411 points...