macOS menu bar app that translates selected text in place with a hotkey: up to three language pairs with automatic direction, your writing style, and Claude, ChatGPT or Grok via your subscription or API key.
See the codeA tiny macOS menu-bar app that translates the current selection with a global hotkey and rewrites it in your own writing style. Select text in any app, press the shortcut, and the selection is replaced in place with the translation.
It detects the direction automatically between the two languages you choose, so there is nothing to switch: type in one, get the other. If you use more languages, set up to three pairs, each on its own shortcut.
claude for Claude, codex for ChatGPT, grok for Grok). For API-key
mode: an API key.brew install --cask wiltodelta/tap/translate-like-me
After that the app keeps itself up to date (Sparkle), not brew upgrade.
.zip from the
Releases page./Applications.git clone https://github.com/wiltodelta/translate-like-me.git
cd translate-like-me
./build.sh
open "Translate Like Me.app"
Without the maintainer's Developer ID identity in your keychain, build.sh
signs the app ad hoc, so macOS asks for Accessibility again after every rebuild.
Two panes, General and Translation. Changes apply immediately.
Three providers; Claude and ChatGPT each in two modes, Grok in subscription mode only:
| Provider | Subscription | API key |
|---|---|---|
| Anthropic (Claude) | claude -p (Pro/Max) | POST /v1/messages |
| OpenAI (ChatGPT) | codex exec (ChatGPT) | POST /v1/chat/completions |
| xAI (Grok) | grok -p (grok.com) | - |
Subscription runs the provider's official CLI as a subprocess, using the plan
you are already signed in to. No API key and no per-token billing beyond your
plan. For codex, run codex login once (ChatGPT account) before using it;
for grok, run grok login once.
Using the official CLIs with a subscription is an intended, supported way to run Claude / Codex / Grok programmatically. Extracting a subscription OAuth token and using it in your own API client is not allowed; this app never does that. It only invokes the official binary as a subprocess.
API key calls the provider's HTTP API directly with your own key (you pay the provider per use). Keys are stored per provider and only used for that provider.
The app never pins a model:
settings.json (model, effortLevel), codex's
config.toml (model, model_reasoning_effort), or grok's config; where
none is set, the CLI's built-in default applies./models list.~/.claude or ~/.codex. The grok CLI has no such switch and may
keep its own history.log stream --predicate 'subsystem == "com.wiltodelta.translatelikeme"')
record the app in front, the language pair and character counts, never the
text itself.The app uses Sparkle (MIT licensed; its notice ships in the app bundle) to check once a day for a new release. It never takes focus from what you are doing: when an update is found, the menu's "Check for Updates…" item becomes "Install Update…" with the new version, and the update window opens only when you choose it or check by hand from the menu or Settings > Updates. Updates are signed with an EdDSA key as well as the Developer ID, and install in place with a relaunch. Automatic checks can be turned off in Settings.
Quit the app from its menu, then move Translate Like Me.app to the Trash. To remove its settings and any saved API keys too:
defaults delete com.wiltodelta.translatelikeme
security delete-generic-password -s com.wiltodelta.translatelikeme -a anthropicKey
security delete-generic-password -s com.wiltodelta.translatelikeme -a openaiKey
Remove it from System Settings > Privacy & Security > Accessibility and from General > Login Items as well.
Build a bundle locally:
./build.sh
./capture-screenshots.sh rebuilds the app and regenerates the screenshots
(the terminal needs Accessibility and Screen Recording access).
Releases are automated. The app version comes from the git tag, so cutting a release is just tagging and pushing:
git tag -a vX.Y -F notes.md # first line "Translate Like Me X.Y", blank line, then the notes
git push origin vX.Y
GitHub Actions then builds the app with the tag's version, signs it with the
Developer ID, notarizes it, and publishes a Release with
Translate-Like-Me-vX.Y-macOS.zip and the Sparkle appcast.xml attached; the
tag annotation's body becomes both the release notes and the text of the update
window, and its first line the release title. Use vMAJOR.MINOR tags. Local
./build.sh bundles carry the latest tag's version and are not meant for
distribution.
Signing secrets and verification steps are in
docs/build-and-release.md.
CFBundleIdentifier in Resources/Info.plist is com.wiltodelta.translatelikeme,
and macOS keys durable per-app state to it. Three things here depend on that:
the UserDefaults.standard domain behind Settings, which holds every persisted
setting; the keychain items holding the API keys, whose service name is the
identifier; and the Accessibility grant that ensureAccessibilityPermission
relies on. Changing the identifier migrates none of them. An existing install would come up as a stranger, with its settings
gone and Accessibility needing to be granted again, so treat it as fixed rather
than as a string to tidy up. It is registered as an explicit App ID for team
K2GT9Q4S6U in the Apple Developer portal, which reserves it for a future Mac App
Store build.
Licensed under the Apache License, Version 2.0. See LICENSE for the full text.
44 commits
Swift
88.2%
Shell
10.4%
Python
1.4%
macOS menu bar app that translates selected text in place with a hotkey: up to three language pairs with automatic direction, your writing style, and Claude, ChatGPT or Grok via your subscription or API key.
See the codeA tiny macOS menu-bar app that translates the current selection with a global hotkey and rewrites it in your own writing style. Select text in any app, press the shortcut, and the selection is replaced in place with the translation.
It detects the direction automatically between the two languages you choose, so there is nothing to switch: type in one, get the other. If you use more languages, set up to three pairs, each on its own shortcut.
claude for Claude, codex for ChatGPT, grok for Grok). For API-key
mode: an API key.brew install --cask wiltodelta/tap/translate-like-me
After that the app keeps itself up to date (Sparkle), not brew upgrade.
.zip from the
Releases page./Applications.git clone https://github.com/wiltodelta/translate-like-me.git
cd translate-like-me
./build.sh
open "Translate Like Me.app"
Without the maintainer's Developer ID identity in your keychain, build.sh
signs the app ad hoc, so macOS asks for Accessibility again after every rebuild.
Two panes, General and Translation. Changes apply immediately.
Three providers; Claude and ChatGPT each in two modes, Grok in subscription mode only:
| Provider | Subscription | API key |
|---|---|---|
| Anthropic (Claude) | claude -p (Pro/Max) | POST /v1/messages |
| OpenAI (ChatGPT) | codex exec (ChatGPT) | POST /v1/chat/completions |
| xAI (Grok) | grok -p (grok.com) | - |
Subscription runs the provider's official CLI as a subprocess, using the plan
you are already signed in to. No API key and no per-token billing beyond your
plan. For codex, run codex login once (ChatGPT account) before using it;
for grok, run grok login once.
Using the official CLIs with a subscription is an intended, supported way to run Claude / Codex / Grok programmatically. Extracting a subscription OAuth token and using it in your own API client is not allowed; this app never does that. It only invokes the official binary as a subprocess.
API key calls the provider's HTTP API directly with your own key (you pay the provider per use). Keys are stored per provider and only used for that provider.
The app never pins a model:
settings.json (model, effortLevel), codex's
config.toml (model, model_reasoning_effort), or grok's config; where
none is set, the CLI's built-in default applies./models list.~/.claude or ~/.codex. The grok CLI has no such switch and may
keep its own history.log stream --predicate 'subsystem == "com.wiltodelta.translatelikeme"')
record the app in front, the language pair and character counts, never the
text itself.The app uses Sparkle (MIT licensed; its notice ships in the app bundle) to check once a day for a new release. It never takes focus from what you are doing: when an update is found, the menu's "Check for Updates…" item becomes "Install Update…" with the new version, and the update window opens only when you choose it or check by hand from the menu or Settings > Updates. Updates are signed with an EdDSA key as well as the Developer ID, and install in place with a relaunch. Automatic checks can be turned off in Settings.
Quit the app from its menu, then move Translate Like Me.app to the Trash. To remove its settings and any saved API keys too:
defaults delete com.wiltodelta.translatelikeme
security delete-generic-password -s com.wiltodelta.translatelikeme -a anthropicKey
security delete-generic-password -s com.wiltodelta.translatelikeme -a openaiKey
Remove it from System Settings > Privacy & Security > Accessibility and from General > Login Items as well.
Build a bundle locally:
./build.sh
./capture-screenshots.sh rebuilds the app and regenerates the screenshots
(the terminal needs Accessibility and Screen Recording access).
Releases are automated. The app version comes from the git tag, so cutting a release is just tagging and pushing:
git tag -a vX.Y -F notes.md # first line "Translate Like Me X.Y", blank line, then the notes
git push origin vX.Y
GitHub Actions then builds the app with the tag's version, signs it with the
Developer ID, notarizes it, and publishes a Release with
Translate-Like-Me-vX.Y-macOS.zip and the Sparkle appcast.xml attached; the
tag annotation's body becomes both the release notes and the text of the update
window, and its first line the release title. Use vMAJOR.MINOR tags. Local
./build.sh bundles carry the latest tag's version and are not meant for
distribution.
Signing secrets and verification steps are in
docs/build-and-release.md.
CFBundleIdentifier in Resources/Info.plist is com.wiltodelta.translatelikeme,
and macOS keys durable per-app state to it. Three things here depend on that:
the UserDefaults.standard domain behind Settings, which holds every persisted
setting; the keychain items holding the API keys, whose service name is the
identifier; and the Accessibility grant that ensureAccessibilityPermission
relies on. Changing the identifier migrates none of them. An existing install would come up as a stranger, with its settings
gone and Accessibility needing to be granted again, so treat it as fixed rather
than as a string to tidy up. It is registered as an explicit App ID for team
K2GT9Q4S6U in the Apple Developer portal, which reserves it for a future Mac App
Store build.
Licensed under the Apache License, Version 2.0. See LICENSE for the full text.
44 commits
Swift
88.2%
Shell
10.4%
Python
1.4%