Hover TranslatorVS Code extension that translates the word, string literal or selection under your cursor, without turning your editor into a request firehose. Features
Privacy: read this firstTranslating means sending the hovered text to a translation service. On first run the extension asks once before it sends anything, and stays inactive until you accept.
Only the text you hover over is sent, never the whole file. Files matching For anything proprietary, configure an API key or self-host LibreTranslate. Avoiding
|
| Layer | What it stops |
|---|---|
Debounce (hoverDelay, 250ms) + hover cancellation |
Requests for words you merely swept the pointer across. In a 30-word sweep this is the difference between 30 requests and 1. |
| Pre-filters | Numbers, symbols, 1–2 character tokens, reserved words, and text already written in the target language. |
| Offline dictionary | Most identifier hovers, with no request whatsoever. |
| Translation cache | Anything looked up before (30 days by default, persisted to disk). |
| Negative cache | Re-asking for something that just failed (10 minutes). |
| In-flight coalescing | Duplicate concurrent hovers of the same text. |
| Bounded queue + per-minute budget | Bursts. The queue holds 5 requests and drops the oldest; the budget caps 20 requests per rolling minute. |
| Escalating per-provider cooldown | Hammering a provider that already answered 429. Backoff is 1m → 5m → 15m → 60m with jitter, honours Retry-After, and survives a window reload. |
If it still happens, the reliable fix is a key: Hover Translator: Set Translation API Key. DeepL's free tier allows 500k characters a month, which is far beyond what hovering consumes.
When every provider is cooling down, the status bar shows ⚠ Translate (5m); hover over it for a full breakdown, or run Hover Translator: Show Status.
Usage
- Hover a word, identifier or string literal.
- Select a region and hover it to translate the whole block.
- Run Hover Translator: Translate Selected Block to replace or copy the result. When the selection is a single identifier, the replacement is folded back into the original casing (
user_name→ten_nguoi_dung) so the code stays valid. - Click the status bar to toggle the extension or change the target language.
Commands
| Command | Purpose |
|---|---|
Hover Translator: Toggle Hover Translation |
Turn hover translation on or off |
Hover Translator: Select Translation Language |
Change the target language |
Hover Translator: Translate Selected Block |
Translate the selection, then replace or copy |
Hover Translator: Set Translation API Key |
Store a key in VS Code's secret storage |
Hover Translator: Clear Translation Cache |
Drop cached translations and provider cooldowns |
Hover Translator: Show Status |
Print providers, queue, budget and cache stats to the log |
Hover Translator: Show Logs |
Open the extension's log output channel |
Settings
Basics
| Setting | Default | Description |
|---|---|---|
hoverTranslator.enabled |
true |
Translate on hover (also requires accepting the privacy notice) |
hoverTranslator.targetLanguage |
Vietnamese |
Target language |
hoverTranslator.translateIdentifiers |
true |
Translate the word under the cursor |
hoverTranslator.translateStringLiterals |
true |
Translate string literal contents |
hoverTranslator.useOfflineDictionary |
true |
Resolve common words locally |
hoverTranslator.excludeFilePatterns |
see package.json | Files where translation never runs |
Provider
| Setting | Default | Description |
|---|---|---|
hoverTranslator.provider |
auto |
auto, deepl, google-cloud, azure, libretranslate, free, google-free, mymemory |
hoverTranslator.apiKey |
"" |
Discouraged plaintext fallback — prefer the Set Translation API Key command |
hoverTranslator.azureRegion |
"" |
Required for Azure, e.g. southeastasia |
hoverTranslator.libreTranslateUrl |
"" |
e.g. http://localhost:5000/translate |
hoverTranslator.mymemoryEmail |
"" |
Raises MyMemory's anonymous quota |
Rate limiting and performance
| Setting | Default | Description |
|---|---|---|
hoverTranslator.hoverDelay |
250 |
Milliseconds the pointer must rest before requesting |
hoverTranslator.requestTimeout |
5000 |
Per-request timeout |
hoverTranslator.minRequestDelay |
700 |
Minimum gap between outbound requests |
hoverTranslator.maxRequestsPerMinute |
20 |
Hard ceiling per rolling minute |
hoverTranslator.maxQueueSize |
5 |
Waiting requests before the oldest is dropped |
hoverTranslator.cacheTtlDays |
30 |
How long a cached translation stays valid |
hoverTranslator.cacheLimit |
5000 |
Cached entries before LRU eviction |
hoverTranslator.negativeCacheMinutes |
10 |
How long a failure suppresses retries |
hoverTranslator.maxSelectionLength |
2000 |
Longest selection translated on hover |
Example: fully local setup
{
"hoverTranslator.provider": "libretranslate",
"hoverTranslator.libreTranslateUrl": "http://localhost:5000/translate",
"hoverTranslator.targetLanguage": "Vietnamese",
// A local server has no quota, so the throttle can be relaxed.
"hoverTranslator.maxRequestsPerMinute": 120,
"hoverTranslator.minRequestDelay": 0
}
docker run -p 5000:5000 libretranslate/libretranslate --load-only en,vi
Caching
Translations are cached on disk in the extension's global storage and survive restarts. Entries expire cacheTtlDays days after they were fetched (reading one does not extend its life), and the least recently used entries are evicted once cacheLimit is reached. The file is written atomically, so an interrupted write cannot corrupt it.
Troubleshooting
| Symptom | What to check |
|---|---|
| Nothing appears on hover | Is the status bar showing $(check) Translate? Was the privacy notice accepted? Is the file excluded? |
⚠ Translate (Nm) in the status bar |
Every provider is cooling down. Hover the item for the reason, or add an API key. |
| Hover feels slow | Lower hoverDelay, or add an API key so requests are not queued behind the free endpoints' throttle. |
| Translation quality is poor for identifiers | Disable useOfflineDictionary to use the provider instead, or switch to DeepL. |
| Anything unexpected | Hover Translator: Show Logs — every decision, skip reason and provider error is logged. |
Development
npm install
npm run watch # esbuild in watch mode
npm run lint
npm run check-types
npm test # node:test unit tests, no network required
npm run bundle:prod
npm run package # requires Node >= 20 (see DEPLOY.md)
License
MIT