Skip to content

Stop context-switching! Preview i18n translations inline while coding React apps in Neovim

License

Notifications You must be signed in to change notification settings

asce4s/i18n-view.nvim

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

14 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

i18n-view.nvim

License: MIT

A Neovim plugin that displays inline translations for React i18n keys using virtual text overlays.

Demo

image

Features

  • 🌐 Inline Translation Preview: See translations directly in your code
  • 🎯 Auto Path Detection: Automatically finds your locale files - zero configuration needed!
  • 🎨 Flexible Display Modes: Choose between overlay (replaces text) or end-of-line display
  • ⚑ Customizable Auto-Commands: Configure when translations appear (CursorHold, CursorMoved, etc.)
  • πŸš€ High Performance: JSON caching, debounced updates, visible-lines-only processing
  • πŸ”„ Dynamic Locale Switching: Change locales on the fly
  • πŸ“¦ Multi-Framework Support: React i18next, Vue i18n, Svelte, and more
  • πŸ”§ Flexible Patterns: With/without namespace, Trans components, pluralization
  • 🎯 Smart Truncation: Handles long translations and Unicode properly
  • πŸ”’ Advanced Key Formats: Supports numbers, nested paths, special characters

Installation

Using lazy.nvim:

-- In your plugins directory (e.g., ~/.config/nvim/lua/plugins/i18n-view.lua)
return {
    "asce4s/i18n-view.nvim",
    config = function()
        require("i18n-view").setup({
            locale = "en",              -- default locale
            path = "public/locales",    -- path to locale files (relative to cwd)
            default_namespace = "common", -- default namespace for keys without namespace
            verbose = false,            -- show error messages
            prefix = "🌐 ",            -- prefix for virtual text
            debounce_ms = 150,         -- debounce delay
            max_text_length = 80,      -- truncate long translations
            highlight = {
                fg = "#a9b1d6",
                bg = "#1a1b26",
            },
        })
    end,
}
use {
    "asce4s/i18n-view.nvim",
    config = function()
        require("i18n-view").setup({
            locale = "en",
            path = "public/locales",
        })
    end
}

File Structure

Automatic Path Detection 🎯

The plugin can automatically detect your locale file location! It searches common i18n directory patterns:

project/
β”œβ”€β”€ public/locales/    ← Automatically detected
β”œβ”€β”€ locales/           ← Automatically detected
β”œβ”€β”€ i18n/              ← Automatically detected
β”œβ”€β”€ src/locales/       ← Automatically detected
└── ... and 10+ more patterns

πŸ“– Learn more about Auto-Detection - Detailed guide on how auto-detection works

Each locale directory should contain language subdirectories with JSON files:

public/locales/
β”œβ”€β”€ en/
β”‚   β”œβ”€β”€ common.json
β”‚   β”œβ”€β”€ home.json
β”‚   └── auth.json
β”œβ”€β”€ es/
β”‚   β”œβ”€β”€ common.json
β”‚   β”œβ”€β”€ home.json
β”‚   └── auth.json
└── fr/
    └── ...

Manual Path Configuration

You can also specify a custom path:

require("i18n-view").setup({
  path = "custom/path/to/locales",
  auto_detect_path = false,  -- Disable auto-detection
})

Translation File Example

Example translation file (public/locales/en/common.json):

{
  "welcome": "Welcome to our app",
  "buttons": {
    "submit": "Submit",
    "cancel": "Cancel"
  },
  "user": {
    "profile": {
      "name": "Name"
    }
  }
}

Supported Patterns

The plugin automatically detects a wide variety of i18n patterns across multiple frameworks.

πŸ“– View Complete Pattern Reference - Comprehensive documentation of all 50+ supported patterns

React i18next Patterns

// Standard function call with namespace
t("common:welcome");
t("common:buttons.submit");
t('namespace:key.with.numbers0.test');

// Template literals with namespace
t(`common:welcome`);

// i18n object methods with namespace
i18n.t("common:welcome");
i18n.t(`common:welcome`);

// i18next object methods with namespace
i18next.t("common:welcome");
i18next.t(`common:welcome`);

// JSX expressions with namespace
{t("common:welcome")}
{t(`common:welcome`)}

// Function calls with options
t('common:key', { defaultValue: 'Hello' });
t('common:key', { count: 5 });

// Trans component
<Trans i18nKey="common:welcome" />
<Trans i18nKey={"common:key"} />

Keys Without Namespace

When keys don't include a namespace (no :), the plugin uses the default_namespace config option (default: "common"):

// Simple keys (uses default namespace)
t("welcome");
t("buttons.submit");
t(`greeting`);

// With i18n object
i18n.t("welcome");
i18next.t("buttons.submit");

// JSX expressions
{t("welcome")}

// Trans component
<Trans i18nKey="welcome" />

Vue i18n Patterns

<!-- Vue i18n with namespace -->
{{ $t('common:welcome') }}
{{ $t(`common:buttons.submit`) }}

<!-- Vue i18n without namespace -->
{{ $t('welcome') }}
{{ $t('buttons.submit') }}

<!-- Vue i18n pluralization -->
{{ $tc('common:items', count) }}
{{ $tc('items', 5) }}

Svelte i18n Patterns

<!-- Svelte i18n with namespace -->
{$_('common:welcome')}
{$_(`common:buttons.submit`)}

<!-- Svelte i18n without namespace -->
{$_('welcome')}
{$_('buttons.submit')}

Gettext-style Patterns

// Gettext-style with namespace
_('common:welcome');
_(`common:buttons.submit`);

// Gettext-style without namespace
_('welcome');
_('buttons.submit');

Multi-line Patterns

// Multi-line with namespace
t(
  'common:welcome'
)

i18n.t(
  "common:buttons.submit"
)

// Multi-line without namespace
t(
  'welcome'
)

// Vue patterns multi-line
$t(
  'common:greeting'
)

// Svelte patterns multi-line
{$_(
  'common:welcome'
)}

Advanced Key Formats

The plugin supports keys with:

  • Nested paths: user.profile.name
  • Numbers: items.0.title, section2.header
  • Hyphens: my-key, button-text
  • Underscores: my_key, button_text
  • Mixed formats: user_profile.settings-menu.item0

Supported Frameworks & Patterns

The plugin works with a wide variety of i18n libraries and frameworks:

Framework Pattern Examples Support
React i18next t(), i18n.t(), i18next.t(), <Trans> βœ… Full
Vue i18n $t(), $tc() βœ… Full
Svelte i18n {$_()} βœ… Full
Next.js next-i18next patterns βœ… Full
Gettext-style _() βœ… Full
Custom Configurable patterns βœ… Extensible

Pattern Overview

The plugin automatically detects 50+ pattern variations including:

  • βœ… With/without namespace separation (:)
  • βœ… Single/double quotes and template literals
  • βœ… Function calls with options objects
  • βœ… JSX expressions and components
  • βœ… Multi-line function calls
  • βœ… Nested key paths with numbers
  • βœ… Special characters (hyphens, underscores)

Usage

Basic Example

// Your React component
import { useTranslation } from "react-i18next";

export function HomePage() {
  const { t } = useTranslation();

  return (
    <div>
      <h1>{t("home:title")}</h1> {/* Shows: 🌐 Home Page */}
      <p>{t("home:description")}</p> {/* Shows: 🌐 Welcome to... */}
      <button>{t("common:buttons.submit")}</button> {/* Shows: 🌐 Submit */}
    </div>
  );
}

When you hover (cursor hold) over these lines, the translation keys will be replaced with the actual translations from your JSON files.

Commands

Command Description
:I18nToggle Enable/disable the plugin
:I18nDisplayMode Toggle between overlay and eol modes
:I18nDisplayMode <mode> Set display mode (overlay or eol)
:I18nLocale <locale> Switch to a different locale (e.g., :I18nLocale es)
:I18nReload Reload translations (clears cache)
:I18nClearCache Clear the translation cache
:I18nStatus Show current configuration and stats
:I18nVerbose Toggle verbose error messages

Keybindings Example

Add these to your keymaps.lua:

vim.keymap.set("n", "<leader>it", ":I18nToggle<CR>", { desc = "Toggle i18n view" })
vim.keymap.set("n", "<leader>id", ":I18nDisplayMode<CR>", { desc = "Toggle display mode" })
vim.keymap.set("n", "<leader>ir", ":I18nReload<CR>", { desc = "Reload i18n" })
vim.keymap.set("n", "<leader>is", ":I18nStatus<CR>", { desc = "i18n status" })

Quick toggle display mode in normal mode:

vim.keymap.set("n", "<leader>im", ":I18nDisplayMode<CR>", { desc = "Toggle overlay/eol mode" })

Configuration Options

{
    enabled = true,              -- Enable plugin on startup
    locale = "en",               -- Default locale
    path = "public/locales",     -- Base path to locale files (or leave empty for auto-detection)
    auto_detect_path = true,     -- Automatically detect locale paths in project
    default_namespace = "common", -- Default namespace when not specified
    prefix = "🌐 ",             -- Prefix for virtual text
    verbose = false,             -- Show error messages
    debounce_ms = 150,          -- Debounce delay (ms)
    max_text_length = 80,       -- Max translation length before truncation
    cache_enabled = true,        -- Enable JSON caching
    display_mode = "overlay",    -- "overlay" (replaces text) or "eol" (end-of-line)
    highlight = {
        fg = "#a9b1d6",         -- Foreground color
        bg = "#1a1b26",         -- Background color
    },
    -- Auto-command configuration
    auto_commands = {
        -- Events that trigger showing translations
        show_events = { "CursorHold" }, -- Can also include: "CursorMoved", "CursorHoldI", "BufEnter", etc.
        -- Events that trigger clearing translations
        clear_events = { "InsertEnter", "BufLeave" }, -- Can also include: "CursorMoved", "TextChanged", etc.
        -- File patterns to watch
        file_patterns = { "*.tsx", "*.ts", "*.jsx", "*.js", "*.vue", "*.svelte" },
    },
    -- Auto-detection search paths (used when auto_detect_path is true)
    search_paths = {
        "public/locales",
        "locales",
        "locale",
        "translations",
        "i18n",
        "lang",
        "languages",
        "src/locales",
        "src/locale",
        "src/i18n",
        "src/translations",
        "assets/i18n",
        "assets/locales",
        "resources/i18n",
        "resources/locales",
    },
    -- Advanced: Custom patterns (Lua patterns)
    -- The plugin includes comprehensive default patterns for:
    -- - React i18next (t(), i18n.t(), i18next.t())
    -- - Vue i18n ($t(), $tc())
    -- - Trans components
    -- - With/without namespaces
    -- You can add more patterns here if needed:
    patterns = {
        -- Default patterns are already extensive
        -- Add your own custom patterns here if needed
    },
}

Display Modes

πŸ“– Complete Display Modes Guide - Detailed guide with examples and presets

The plugin offers two display modes for showing translations:

Overlay Mode (Default)

Replaces the translation key with the actual translation text inline:

// Before (what you write):
t('common:welcome')

// After (what you see with overlay):
🌐 Welcome to our app

Best for: Quick preview while maintaining code structure

End-of-Line Mode

Shows translation at the end of the line:

// What you see:
t('common:welcome')  🌐 Welcome to our app

Best for: Keeping original code visible while showing translations

Configuration

require("i18n-view").setup({
  display_mode = "overlay",  -- or "eol"
})

Switching Modes On-the-Fly

You can change display mode without restarting Neovim:

:I18nDisplayMode           " Toggle between overlay and eol
:I18nDisplayMode overlay   " Set to overlay mode
:I18nDisplayMode eol       " Set to end-of-line mode

This is useful for switching contexts:

  • Writing code: Use overlay for minimal visual clutter
  • Reviewing translations: Use eol to see both key and translation
  • Editing keys: Use eol to keep keys visible

Auto-Command Customization

Control when and how translations appear by customizing the auto-commands:

Show on Cursor Hold (Default)

require("i18n-view").setup({
  auto_commands = {
    show_events = { "CursorHold" },  -- Show when cursor stops moving
  },
})

Show on Cursor Movement

require("i18n-view").setup({
  auto_commands = {
    show_events = { "CursorMoved", "CursorMovedI" },  -- Show immediately on movement
  },
})

Show on Buffer Enter

require("i18n-view").setup({
  auto_commands = {
    show_events = { "BufEnter", "BufWinEnter" },  -- Show when opening file
  },
})

Always Show (Multiple Events)

require("i18n-view").setup({
  auto_commands = {
    show_events = { "CursorHold", "CursorMoved", "BufEnter" },
  },
})

Customize Clear Events

require("i18n-view").setup({
  auto_commands = {
    show_events = { "CursorHold" },
    clear_events = { "InsertEnter", "BufLeave", "CursorMoved" },  -- Clear on these events
  },
})

Custom File Patterns

require("i18n-view").setup({
  auto_commands = {
    file_patterns = { "*.tsx", "*.ts", "*.vue" },  -- Only TypeScript and Vue files
  },
})

Common Configurations

Minimal (show rarely):

auto_commands = {
  show_events = { "CursorHold" },      -- Only when cursor stops
  clear_events = { "InsertEnter" },    -- Only clear in insert mode
}

Aggressive (show always):

auto_commands = {
  show_events = { "CursorMoved", "CursorMovedI", "BufEnter", "CursorHold" },
  clear_events = {},  -- Never clear automatically
}

Balanced (recommended for most):

auto_commands = {
  show_events = { "CursorHold" },
  clear_events = { "InsertEnter", "BufLeave" },
}

How It Works

  1. Path Detection: Automatically finds locale directories in your project (or uses configured path)
  2. Event Triggering: Listens for configured events (CursorHold, CursorMoved, etc.)
  3. Pattern Matching: Extracts file name and key path (e.g., common:buttons.submit)
  4. File Resolution: Tries multiple locale paths until file is found
  5. JSON Loading: Loads and caches translation file
  6. Virtual Text: Displays translation based on display_mode (overlay or eol)
  7. Smart Caching: Caches both JSON data and resolved file paths

Performance

  • Caching: JSON files are cached after first load (10-100x faster)
  • Visible Lines Only: Only processes lines currently visible in window
  • Debouncing: Prevents excessive redraws (configurable)
  • Lazy Loading: Files loaded on-demand, not all at once

Troubleshooting

Check Detected Paths

Run :I18nStatus to see which locale paths were auto-detected:

i18n-view Status:
  Enabled: true
  Locale: en
  Configured Path: public/locales
  Auto-detect: true
  Cache entries: 3
  Resolved paths: 2
  Detected locale paths:
    1. public/locales
    2. src/i18n

Virtual text not showing?

  1. Check detected paths: Run :I18nStatus to verify locale paths were found
  2. Enable verbose mode: :I18nVerbose to see detailed error messages
  3. Force path detection: Run :I18nReload to re-scan for locale directories
  4. Manual configuration: If auto-detection fails, set path manually:
    require("i18n-view").setup({
      path = "your/locale/path",
      auto_detect_path = false,
    })
  5. Check locale: :I18nLocale en to set correct locale
  6. Test pattern: Make sure your code matches a supported pattern
  7. Hover longer: Wait for updatetime (default 4000ms = 4s)
    • Reduce it: :set updatetime=500 in your config

Translations not updating?

  • Clear cache: :I18nClearCache
  • Reload: :I18nReload

Wrong locale showing?

  • Check current locale: :I18nStatus
  • Switch locale: :I18nLocale <locale>

Customize updatetime

Add to your options.lua:

vim.opt.updatetime = 500  -- Trigger CursorHold after 500ms (default: 4000ms)

Contributing

Feel free to submit issues and enhancement requests!

License

MIT

About

Stop context-switching! Preview i18n translations inline while coding React apps in Neovim

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages