A lightweight macOS menu bar app for instant in-place translation with your choice of AI provider — Google Gemini, OpenAI, Anthropic Claude, local Ollama, or any OpenAI-compatible endpoint. Select text anywhere, press the global hotkey, confirm — and the translation lands right back where you're working. No window switching, no copy-paste juggling.
| Feature | Description |
|---|---|
| Global Hotkey | Ctrl + Cmd + T triggers translation from any application |
| Multi-Provider AI | Google Gemini, OpenAI, Anthropic Claude, local Ollama, or any custom OpenAI-compatible endpoint (LM Studio, vLLM, OpenRouter...) — switch from the menu, no restart |
| No Hardcoded Endpoints | Provider endpoints and models live in ~/.transpaste/providers.json (commented, auto-created) — adjust when providers change, no rebuild |
| Smart Text Capture | Attempts the Accessibility API first, falls back to a clipboard-based Select All → Copy macro |
| Confirmation Dialogs | Shows captured text for review before translating, and the result before pasting |
| Multi-Language Support | English, Spanish, French, German, Chinese, Japanese, Russian + Auto-detect |
| Menu Bar Integration | Lives in the macOS menu bar — no Dock icon, no main window |
| Persistent Preferences | Remembers source/target language and enabled state across launches |
| File Logging | All operations logged to ~/translator.log for debugging |
| No Xcode Required | Builds and tests with Command Line Tools only |
translator/
├── Sources/
│ ├── main.swift # App entry point — creates NSApplication
│ ├── AppInfo.swift # App metadata — single source of the version
│ ├── AppDelegate.swift # Menu bar UI, language selection, permission prompts
│ ├── InputMonitor.swift # Carbon hotkey registration, text capture macro
│ ├── TranslationService.swift # Multi-provider REST client (Gemini, OpenAI, Claude, Ollama, custom)
│ └── Logger.swift # Thread-safe singleton file logger
├── Tests/
│ ├── TestKit.swift # Minimal XCTest-free test harness
│ ├── TestMain.swift # Test runner entry point
│ ├── TranslationServiceTests.swift
│ ├── AppInfoTests.swift
│ ├── InputMonitorTests.swift
│ └── LoggerTests.swift
├── .github/workflows/ # CI (build → test → package), CodeQL, release publishing
├── Info.plist # App bundle metadata (LSUIElement = true)
├── Package.swift # Swift Package Manager manifest
├── tools/generate_icon.swift # App icon, generated as code (no binary assets)
├── build.sh # Compiles and code-signs the .app bundle
├── package_dmg.sh # Builds the drag-to-Applications DMG installer
├── test.sh # Compiles and runs the test suite
├── automated_setup.sh # Guides permission setup via Terminal
└── .env.example # Template for API key environment variable
graph LR
A[User presses Ctrl+Cmd+T] --> B[InputMonitor]
B -->|Accessibility API| C{Text captured?}
B -->|Clipboard fallback| C
C -->|Yes| D[AppDelegate shows confirmation]
D -->|Confirmed| E[TranslationService]
E -->|Translation| F[AppDelegate shows result]
F -->|Paste| G[Cmd+V into active app]
C -->|No| H[Log error]
| Component | Responsibility |
|---|---|
AppDelegate |
Menu bar setup, language settings UI, permission checks, translation dialog flow |
InputMonitor |
Registers Ctrl+Cmd+T via the Carbon EventHotKey API, captures text via the Accessibility API or clipboard macro, coordinates paste-back |
TranslationService |
Builds and sends requests to the selected provider — Gemini generateContent, OpenAI Chat Completions, or the Anthropic Messages API (API keys always sent via headers, never in URLs) — and parses each provider's response format |
Logger |
Thread-safe singleton that appends timestamped messages to ~/translator.log from any queue |
- macOS 13 (Ventura) or later
- Swift 5.9+ — the Xcode Command Line Tools are sufficient (
xcode-select --install); full Xcode is not required - An API key for at least one provider: Google Gemini, OpenAI, or Anthropic Claude — or none: local Ollama and most custom OpenAI-compatible endpoints work key-less
- Accessibility + Input Monitoring permissions (the app prompts you on first launch)
Grab the latest TransPaste-<version>.dmg from the Releases page, open it, and drag TransPaste to Applications (a zip is also published if you prefer). The app is ad-hoc signed, so on first launch right-click → Open (or xattr -d com.apple.quarantine /Applications/TransPaste.app). Then continue at step 4 below for permissions.
git clone https://github.com/mavrovde/TransPaste.git
cd TransPasteChoose one of the following methods:
cp .env.example .env
# Set the key for the provider you use (any one is enough):
export GEMINI_API_KEY=your_key # Google Gemini (default provider)
export OPENAI_API_KEY=your_key # OpenAI
export ANTHROPIC_API_KEY=your_key # Anthropic ClaudeAfter launching the app, open Provider in the menu bar dropdown and pick your provider — if it needs an API key you'll be offered to paste one from the clipboard (or open the provider's key page) immediately. Keys are stored separately per provider; providers missing a key are marked right in the menu.
./build.shThis will:
- Generate the app icon (
tools/generate_icon.swift→AppIcon.icns, cached inbuild/) - Compile all Swift source files with optimizations (
-O) - Create the app bundle at
build/TransPaste.app - Copy
Info.plistinto the bundle and sync its version fromSources/AppInfo.swift - Ad-hoc code-sign the bundle for stable identity
The app requires two macOS permissions:
| Permission | Why |
|---|---|
| Input Monitoring | To listen for the global Ctrl+Cmd+T hotkey |
| Accessibility | To read selected text and simulate Cmd+C / Cmd+V keystrokes |
Automated setup (guided Terminal wizard):
./automated_setup.shThis script resets existing permissions, opens System Settings to the correct pane, reveals the app in Finder for drag-and-drop, and then launches the app once you confirm. TransPaste also detects the Accessibility grant automatically — no relaunch needed after toggling it on.
Manual setup:
- Open System Settings → Privacy & Security → Input Monitoring
- Add and enable
TransPaste.app - Open System Settings → Privacy & Security → Accessibility
- Add and enable
TransPaste.app - Launch the app:
open build/TransPaste.app
open build/TransPaste.appLook for the 💬 speech bubble icon in your menu bar.
- Select text in any application (or leave it — the macro will Select All)
- Press
Ctrl + Cmd + T - A dialog appears showing the captured text → click "Translate"
- A second dialog shows the translation → click "Paste"
- The translated text is pasted into the original application ✅
Tip
A glass sound 🔔 plays on successful paste. An error sound plays if translation fails.
| Menu Item | Action |
|---|---|
| Source: <language> | Choose the input language (or Auto-detect). Default: Russian |
| Target: <language> | Choose the output language. Default: German |
| Provider: <name> | Everything provider-related in one submenu: pick a provider (a — needs API key suffix marks unready ones; the top-level title shows ⚠ too), then contextual actions for the selected provider — Paste API Key from Clipboard, Get <Provider> API Key…, Configure Endpoint & Model… (Custom only), and Edit providers.json… (key-less providers get Open Website… and an (optional) key paste instead). Picking a provider without a key offers to paste one or open its key page right away. Default: Gemini |
| Enable Translation | Toggle the hotkey on/off (Ctrl+Cmd+T) — the menu bar icon dims while disabled |
| ⚠ Finish Setup… | Appears only while something is missing (permission or provider key) — one click into the guided setup |
| Setup Assistant… | Guided check of everything the app needs: re-registers the hotkey, walks through the Accessibility grant (auto-detected, no relaunch) and the provider key, then confirms "All set" |
| About TransPaste | Version, author, active provider/model, and config/log paths |
| Quit | Exits the application (Cmd+Q) |
./test.sh # run all tests
./test.sh "API key" # run only tests whose name matches a filterTests use a self-contained harness (Tests/TestKit.swift) instead of XCTest, so they run on machines with only Command Line Tools installed. Coverage includes:
TranslationServiceTests— per-provider request construction (headers, endpoints, body shapes), per-provider response parsing (including Claude thinking blocks and refusals), API errors, malformed JSON, missing keys, and end-to-endtranslate()for Gemini, OpenAI, Claude, and Ollama (plus the custom provider) via a mockedURLSessionInputMonitorTests— initial state, disabled-hotkey guardLoggerTests— singleton identity, file writes, concurrent loggingAppInfoTests— semantic version format, metadata coherence, Info.plist ↔ AppInfo version sync
Note
The hotkey registration and capture macro require real Accessibility/Input Monitoring permissions and a focused target app, so the end-to-end flow is verified manually via the built app.
While build.sh uses swiftc directly for simplicity, SPM is also configured:
swift build # Debug build
swift build -c release # Optimized release buildSPM builds output a bare executable to .build/; only build.sh produces the TransPaste.app bundle needed for permissions to work.
Every push and pull request to main runs the CI workflow with three stages:
- Build — compiles the app bundle and verifies the code signature
- Test — runs the full test suite
- Package — zips
TransPaste.app, builds the DMG installer, and uploads both as downloadable artifacts (30-day retention)
Pushing a v* tag additionally runs the Release workflow: it verifies the tag matches AppInfo.version, re-runs tests and the signed build, and publishes a GitHub release with the DMG installer, the zipped app, SHA-256 checksums, and auto-generated notes.
| File | Purpose |
|---|---|
Package.swift |
SPM manifest — swift-tools 6.0 (language mode pinned to v5), targets macOS 13+ |
Sources/AppInfo.swift |
Single source of the app version — build.sh injects it into the bundle's Info.plist |
Info.plist |
Bundle ID: com.mavrovde.transpaste, LSUIElement: true (no Dock icon) |
.gitignore |
Ignores build/, .build/, Xcode artifacts, logs, and .env |
package_dmg.sh |
Builds the drag-to-Applications TransPaste-<version>.dmg (used by CI and releases) |
Each provider resolves its API key in this order:
- Environment variable —
GEMINI_API_KEY,OPENAI_API_KEY, orANTHROPIC_API_KEY(plus optionalOLLAMA_API_KEY/CUSTOM_LLM_API_KEYfor authed local/custom servers) - UserDefaults —
GeminiAPIKey,OpenAIAPIKey,AnthropicAPIKey,OllamaAPIKey, orCustomAPIKey(set via the menu bar "Paste API Key" option)
| Provider | Model |
|---|---|
| Google Gemini (default) | gemini-flash-latest |
| OpenAI | gpt-5-mini |
| Anthropic Claude | claude-opus-5 (effort: low for fast responses) |
| Ollama (local) | llama3.2 at http://localhost:11434 — no API key needed |
| Custom | Any OpenAI-compatible endpoint — endpoint URL and model are configurable from the menu; token is optional (local servers usually need none) |
All defaults above live in ~/.transpaste/providers.json — a commented JSON file auto-created on first run. Edit it (endpoint URLs, model names) whenever a provider changes their API; {model} in an endpoint is substituted with the configured model. No rebuild needed.
Keys are sent only as request headers (x-goog-api-key for Gemini, Authorization: Bearer for OpenAI/Ollama/custom, x-api-key + anthropic-version for Claude), never in URLs (which proxies and servers commonly log).
All events are logged to ~/translator.log with ISO 8601 timestamps:
[2026-02-17T00:00:00Z] Carbon Hotkey Registered Successfully.
[2026-02-17T00:00:05Z] Carbon Hotkey Detected! Triggering macro...
[2026-02-17T00:00:05Z] Captured Text via AX: Hello world...
[2026-02-17T00:00:06Z] Translation success: Hallo Welt
[2026-02-17T00:00:07Z] Pasted translation.
| Problem | Solution |
|---|---|
| Hotkey not responding | Check that Input Monitoring is enabled in System Settings. Try "Setup Assistant…" from the menu. |
| "Clipboard empty" in logs | Grant Accessibility permission — the app needs it to simulate Cmd+C. |
| "API Error" or "No API Key" | Verify the selected provider's API key is set (env var or menu). Check network connectivity. |
| App not visible | Look for the speech bubble icon in the menu bar. The app has no Dock icon by design (LSUIElement: true). |
| Translation pastes into wrong app | Ensure you don't click other windows between confirming and pasting. The app hides itself to restore focus. |
| Permission prompt not appearing | Run ./automated_setup.sh to reset and re-configure permissions. |
| Permissions lost after updating | The app was renamed from on-fly-translator to TransPaste (new bundle ID) — re-grant permissions once via ./automated_setup.sh. |
tail -f ~/translator.log- No data collection — text is sent directly to the selected AI provider's API and not stored anywhere except the local log file.
- API keys stored locally — saved per-provider in macOS
UserDefaults(per-user, not shared) and transmitted only as request headers, never in URLs. - Ad-hoc code signing — the build script signs the bundle with an ad-hoc identity for stable permission grants across rebuilds.
- No network calls unless a translation is explicitly triggered by the user.
This project is provided as-is for personal use.
- Fork the repository
- Create a feature branch (
git checkout -b feature/my-improvement) - Make your changes and add tests
- Run
./test.shto verify - Commit and push
- Open a Pull Request