A privacy-focused browser extension that translates web pages using local LLMs (Ollama or LMStudio). Your data never leaves your machine.
- 🔒 100% Private - All translations happen on your local machine via Ollama or LMStudio
- 🎯 Smart Prioritization - Visible content and headings are translated first
- 🌍 Many Languages - Supports many many languages :3
- ⚡ Translation Cache - Optional: translate identical text once and reuse it (great for forums). Off by default; stored locally with a session-only or persistent mode
- 📖 Glossary - Force consistent translations for proper nouns and UI labels via a user-supplied dictionary
- 🚀 Auto-Translate - Optional: translate foreign-language pages the moment they load, with per-language and per-site exclusions
You need one of these running locally:
- Ollama (default:
http://localhost:11434) - LMStudio (default:
http://localhost:1234) - Any OpenAI-compatible server — llama.cpp's
llama-server, vLLM, etc. Put the server's base URL (e.g.http://localhost:8080) in the LMStudio / OpenAI-compatible URL field; a trailing/v1is tolerated and stripped automatically
With a translation-capable model loaded (e.g. TranslateGemma, tencent.hunyuan-mt, qwen3, etc.)
- Go to
about:debugging#/runtime/this-firefox - Click Load Temporary Add-on
- Select the
manifest.jsonfile
- Go to
chrome://extensions - Enable Developer mode
- Click Load unpacked
- Select the extension folder
Coming Soon: Extension in Chrome Web Store
- Click the extension icon
- Select a model from the dropdown
- Choose your target language
- Click Translate Page
The extension will:
- Extract all visible text from the page
- Prioritize headings and visible content
- Translate in batches with progress percentage
- Auto-translate new content (infinite scroll)
This extension is designed to be privacy-focused:
- ✅ Only connects to
localhost- no external network requests - ✅ No analytics or tracking
- ✅ No data collection
- ✅ Minimal permissions (only
localhosthost permissions) - ✅ The translation cache is off by default. When enabled it is stored locally (in memory, or IndexedDB for the persistent mode) and never leaves your machine; it can be set to clear on browser close, turned off, or cleared at any time
Click Advanced Settings to configure:
| Setting | Description |
|---|---|
| Provider | Auto-detect, Ollama only, or LMStudio only |
| URLs | Custom endpoints for Ollama/LMStudio |
| Max tokens/items per batch | Control batch sizes |
| Temperature | Model creativity (lower = more consistent) |
| Request Format (work in progress) | Default JSON, Hunyuan-MT, Simple, or Custom |
| Show Glow | Toggle visual indicator on translated text |
| Cache translations | Reuse stored translations for identical text — off (default), until browser close, or across sessions; includes a "Clear cache" button |
| Automatically translate pages | Translate foreign-language pages on load, with never-lists for languages and sites — off by default |
By default nothing is translated until you ask for it. Turn on Options → Auto-Translate → Automatically translate pages and a page is instead translated as soon as it loads, whenever it declares a language other than your target language.
- Undeclared pages are left alone. The language is read from the page's
langattribute (or acontent-languagemeta tag). Pages that declare nothing are skipped rather than guessed at — a wrong guess would spend real inference time making a page you can already read worse. - Only top-level pages. Iframes are skipped, so ads and embedded widgets don't multiply the request count.
- Escape hatches while it runs. A small bar appears in the corner with Stop (halt and put the original text back) and Never on this site (same, plus adds the site to the exclusion list).
- Exclusions. Keep a list of languages you already read, and a list of sites to leave alone. Site entries cover subdomains, so
example.comalso excludesnews.example.com.
This needs permission to run on all websites, since the extension has to be present on a page before you have asked for anything. Bear in mind that every page you open now costs a full translation run on your own hardware.
To avoid re-translating the same text over and over (forum boilerplate, menus, usernames, repeated phrases), translations can be cached locally and reused — both later on the same page and across other pages. It is off by default; enable it in Options or the popup's Advanced Settings.
- Modes (Options → Translation Cache):
- Don't cache (default) — every segment is translated fresh.
- Until I close the browser — cache speeds things up while you browse, then is wiped on the next browser start. Kept in memory, so nothing translation-related lingers on disk between sessions. Works in every browser.
- Keep across sessions — cache persists on disk (IndexedDB) until you clear it. Best for repeatedly visiting the same sites. Hardened browsers that block IndexedDB (e.g. Mullvad/Tor-based Firefox) disable this option automatically and fall back to the in-memory session cache.
- What's cached: the translated output for each source text segment, stored locally (in memory, or IndexedDB for the persistent mode) — nothing is uploaded.
- How it's keyed: by the source text plus everything that determines the model's output — model, source & target language, request format, prompt template, structured-output mode, and temperature. Changing any of these yields fresh translations instead of stale cached ones, so the cache never serves output that wouldn't match your current settings.
- De-duplication: within a single page, identical strings are translated only once and the result is reused for every occurrence (this happens regardless of cache mode).
- Clearing: use Clear cache to wipe it at any time (the button shows the current entry count). The cache is capped (oldest entries are evicted first).
Load a TSV dictionary (Options → Glossary) to pin translations for specific terms. Each line is source<TAB>translation; leave the second column empty to keep the term untranslated. Matching is case-sensitive. A #target: ja line declares the language the glossary translates into — the glossary is then only applied when that target language is selected (recommended, since a glossary maps terms into one specific language).
It works at two levels:
- Inside sentences — glossary terms found in the text being translated are injected into the prompt as hard hints, so the model keeps proper nouns consistent (e.g. The Companions → 同胞団 everywhere). Only matching terms are sent, so a large dictionary is fine.
- Whole segments — when a segment consists entirely of a glossary term, the mapping is applied directly and the model is skipped. This is the reliable way to fix short context-free labels that small models mangle — e.g. add
About<TAB>概要and the nav heading "About" always becomes "概要" instead of the broken "について".
Whole-segment matches take priority over the translation cache, and loading or clearing a glossary wipes the cache (the same source text now translates differently). Note: models with a fixed prompt format (TranslateGemma, Hunyuan-MT) only get the whole-segment treatment — injecting instruction blocks into their prompts corrupts their output. See examples/ for a sample dictionary.
├── manifest.json # Extension manifest (MV3)
├── background.js # Background script (LLM API, settings)
├── content.js # Content script (DOM manipulation)
├── popup/
│ ├── popup.html # Popup UI
│ ├── popup.css # Styles (Everforest Dark theme)
│ └── popup.js # Popup logic
└── icons/ # Extension icons
The codebase is intentionally simple with no build step or dependencies:
- Pure vanilla JavaScript
- No external libraries
- No bundler required
- Works directly in the browser
Enable "Enable debug logging" in Options → Output Settings, then Save.
To view logs, go to about:debugging#/runtime/this-firefox, find Local LLM Translator, and click Inspect — messages with [Background] prefix appear in the Console tab.
MIT

