Highlight any text anywhere on your OS, press Ctrl+T, get an instant translation popup near your cursor.
Built with Electron + React + TypeScript, generated from a single translation-app.plain spec via the codeplain renderer.
- Global hotkey —
Ctrl+Tworks in any app: browser, Slack, PDF, terminal, IDE. - Smart popup — frameless, always-on-top, positioned beside the cursor.
- 26+ languages with automatic source-language detection.
- Two providers — free MyMemory by default, optional OpenAI (
gpt-4o-mini) with your API key. - Local cache + history — repeated phrases are instant; last 50 translations saved.
- Themes — light, dark, or follow system.
- One-click copy, swap source ↔ target, retry on error.
- Cross-platform: macOS / Windows / Linux.
| Tool | Version |
|---|---|
| Node.js | 18+ |
| npm | 9+ |
| OS | macOS, Windows, or Linux |
On Linux you also need xdotool for the copy-keystroke simulation:
sudo apt install xdotool # Debian/Ubuntugit clone https://github.com/SandyAbasman/codeplain.git
cd codeplain
npm install
npm install --prefix distnpm startThis kills any stale Vite/Electron processes, builds the React popup, then launches Vite (http://localhost:3000) and Electron together. First launch takes ~10 seconds.
You'll see a tray icon in the menu bar and the message:
Desktop Translator running. Press Ctrl+T to translate highlighted text.
- Highlight text in any app — a sentence in a webpage, an error message in your terminal, a chat line.
- Press
Ctrl+T(same shortcut on all platforms — not Cmd on macOS). - The popup appears beside your cursor with the source text and translation.
- Use the dropdown to change target language (re-translates automatically).
- Click ⇄ to swap languages, 📜 for history, ⚙️ for settings, ✕ or Esc to close.
- The translation is auto-copied — paste anywhere.
Popup auto-hides after 10 seconds. Try it with test-translation.txt in this repo.
Right-click the tray icon:
- Show Translator — open the popup without grabbing new text.
- Test Translation (from clipboard) — translate whatever's on the clipboard.
- Settings — open the settings window.
- Quit — stop the app.
On the first Ctrl+T, macOS will ask for Accessibility permission (so Electron can simulate Cmd+C to capture the selection).
- System Settings → Privacy & Security → Accessibility
- Add and enable Electron (or Terminal if you're running from terminal).
Without it the popup will open but the source text will be empty.
Click ⚙️ in the popup to open settings:
| Setting | Default | Notes |
|---|---|---|
| Provider | mymemory |
Free, ~5,000 chars/day per IP |
| OpenAI API key | (empty) | Required to switch to openai |
| Default target language | en |
Used when popup first opens |
| Theme | system |
light, dark, or follow OS |
Settings persist in localStorage.
MyMemory returns HTTP 429 after ~5,000 chars/day (anonymous). You'll see:
API rate limit exceeded: Please try again later.
Options:
- Wait a few minutes (burst limit) or 24h (daily quota).
- Switch the provider to OpenAI in settings and paste a key.
.
├── main.js # Electron main process (window, tray, hotkey)
├── preload.js # Context bridge → window.electronAPI
├── settings.html # Settings window
├── translation-app.plain # The single spec the app is generated from
├── test-translation.txt # Sample multilingual text for testing
├── dist/ # React popup (Vite + TS)
│ ├── src/
│ │ ├── App.tsx
│ │ ├── popup-main.tsx # Popup entry point
│ │ ├── components/
│ │ │ ├── TranslationPopup.tsx
│ │ │ ├── HistoryDrawer.tsx
│ │ │ └── SettingsDrawer.tsx
│ │ └── services/
│ │ ├── TranslationEngine.ts
│ │ └── StorageService.ts
│ ├── popup.html
│ └── vite.config.ts
└── plain_modules/ # Generated artifacts (don't edit by hand)
| Script | What it does |
|---|---|
npm start |
Stop stale processes → build popup → launch Vite + Electron |
npm run dev |
Same as start, without the stop-all step |
npm run dev:vite |
Vite dev server only (dist/) |
npm run dev:electron |
Electron only (Vite must already be on :3000) |
npm run stop:vite |
Kill whatever is on port 3000 |
npm run stop:electron |
Kill running Electron processes |
npm run stop:all |
Both of the above |
npm test --prefix dist |
Vitest unit + component tests |
Popup is blank / large white window
- Quit from the tray, then
npm run stop:all && npm start. An old Electron build is probably still running.
Port 3000 is already in use
npm run stop:vite && npm start.
A JavaScript error occurred in the main process: write EIO
- Already patched — pull latest. The app now swallows
EIOfrom headless launches.
Popup opens but source text is empty (macOS)
- Grant Accessibility permission (see macOS permission).
Hotkey doesn't fire
- Another app may have grabbed
Ctrl+T(some browsers reopen closed tabs). Quit that app or change the shortcut inmain.js:globalShortcut.register("Control+Shift+T", handleHotkey);
- Electron 30 — desktop shell
- React 18 + TypeScript — popup UI
- Vite — dev server + bundler
- Vitest + React Testing Library — tests
- MyMemory API / OpenAI — translation
- codeplain ***plain — the spec → code renderer
MIT — do whatever, just don't sue.