Skip to content

Latest commit

 

History

10 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Island

An EN↔RU translator that lives in the MacBook notch. Hover over the notch and a drawer slides down with an input field.

Full description in SPEC.md.

Translation runs on Apple's on-device Translation framework: no API keys, no quotas, no network. Dictionary lookups use Foundation Models, also on-device.

Requirements

  • macOS 26.0+
  • Xcode 26+
  • XcodeGen (brew install xcodegen)

Island.xcodeproj is not checked in — it is generated from project.yml.

Build and run

make run      # generate, build and run with logging in the terminal
make build    # build only
make project  # generate Island.xcodeproj only (for working in Xcode)
make stop     # kill a running instance
make clean

The app runs without a Dock icon. Everything else is under the menu bar icon: toggle the drawer, tint the panel for debugging, run the translation self-test, quit.

Self-test

Translation is hard to verify by hand from a script, because typing into the drawer would mean posting synthetic key events, which needs the Accessibility permission. Instead:

ISLAND_SELFTEST=1 build/Build/Products/Debug/Island.app/Contents/MacOS/Island

It drives the whole flow and reports pass/fail per check: automatic direction detection in both directions, translation, drawer growth, copying the result, clipboard auto-paste on open, and direction pinning. The same is available from the menu bar.

Key bindings still need a human — pressing them from a script would mean posting synthetic key events, which needs the Accessibility permission this app is designed never to ask for. Everything those bindings call is covered.

Status

Stage Scope State
0 Skeleton, LSUIElement, menu bar
1 Window on the notch above the menu bar
2 Hover, expansion, input focus
3 Translation via Apple Translation
4 Language detection, hotkeys, clipboard
5 History and settings
6 Dictionary mode, polish

What has been verified

  • The window sits at window layer 25 against 24 for the menu bar, and is first in the Window Server stack.

  • The notch is derived from NSScreen: 185×32 pt centred on the top edge of a 16" MacBook Pro.

  • NSTrackingArea delivers mouseEntered even though the window is fully click-through, so no Accessibility permission is required.

  • After expanding, the panel becomes key and the first responder is the text field editor. Activation is asynchronous — focus lands roughly 300 ms later.

  • Moving the cursor away collapses the drawer and returns focus to the previous app.

  • EN→RU and RU→EN both translate correctly on-device.

  • Direction detection picks the right pair from the text alone, and a manual pin survives further typing until the drawer closes.

  • The Carbon global hotkey registers with no permission prompt.

  • Clipboard auto-paste fills and translates on open; copying the result does not paste it back next time.

  • History records on close, deduplicates a repeated phrase, pins, reloads into the input, and survives a restart on disk.

  • Dictionary mode hides itself with Apple Intelligence off, and clears with the input.

  • Opening on hover leaves the panel non-key: verified by driving the cursor onto the notch (isKeyWindow: false, keyboard still with the other app) and then into the drawer body (isKeyWindow: true, first responder is the text field).

Not verified yet: behaviour with networking off, in full screen, with an external monitor attached, and dictionary mode with Apple Intelligence actually on — this machine has it off, so only the degraded path is covered.

Privacy and security

  • No network. The app makes no network requests of any kind. Translation and dictionary lookups run on-device; the only traffic macOS makes is the one-off language asset download, handled by the system Translation framework itself.
  • No permissions. Nothing is requested at any point — no Accessibility, no Screen Recording, no Full Disk Access. Hover uses an NSTrackingArea and the global hotkey uses Carbon, precisely because both work without prompting.
  • History lives at ~/Library/Application Support/com.nikolaik.island/history.json, created 0600 inside a 0700 directory, since it holds everything you have translated. It can be turned off and deleted from Settings → General.
  • The clipboard is read only at the moment the drawer opens, and only to prefill the input. Nothing polls it in the background. Toggle it off under Settings → Behaviour.
  • Debug logging and the self-test are compiled out of Release builds entirely — verified by inspecting the binary.
  • App Sandbox is off because the app is not distributed through the App Store. Hardened Runtime is on; ad-hoc signing disables it for local Debug builds.

Hovering does not capture your keyboard

Opening the drawer on hover deliberately leaves the keyboard where it was. A cursor resting on the notch while you type in another app would otherwise capture those keystrokes — in the worst case part of a password, which would then be translated and written to history.

The drawer becomes typable only when the cursor moves into its body, below the notch strip, which is a deliberate movement rather than a parked cursor. Summoning it with the global hotkey gives it focus straight away, since the cursor is nowhere near it. Clipboard auto-paste still runs on hover, so glancing at a translation needs no click at all.

Dictionary mode

Type a single word and, when Apple Intelligence is available, alternative meanings and an example sentence appear under the translation. It runs on the on-device model, is cancelled whenever the input changes, and never delays the translation itself. With Apple Intelligence off the block is simply absent — the reason goes to the log, never to the UI.

Shortcuts

Key Action
⌥Space Open/close the drawer (global)
Esc Close
⌘↩ Copy the result and close
⌘⇧C Copy the result, stay open
⌘L Flip direction and pin it
⌘H Show or hide history
⌘K Clear the input
⇧↩ Newline

Gotchas worth knowing

  • prepareTranslation() returns as soon as the asset download has been requested. Calling translate() before the assets land blocks forever without throwing, so session.isReady is polled until it flips.
  • NSApp.activate() is asynchronous; asserting on isKeyWindow right after it always fails.
  • An NSTrackingArea installed while the cursor is already inside does not reliably deliver mouseExited, which is why cursor exit is polled while the drawer is open.
  • TranslationSession cannot satisfy complete strict concurrency in either isolation direction; @preconcurrency import Translation scopes the exemption to one file.

Installing it for real

xcodebuild -project Island.xcodeproj -scheme Island \
    -configuration Release -derivedDataPath build-release build
cp -R build-release/Build/Products/Release/Island.app /Applications/

Launch at login only works from a real location such as /ApplicationsSMAppService refuses to register an app running out of a build directory.

The build is ad-hoc signed and not notarised, so Gatekeeper will ask on first launch. Signing with a Developer ID certificate and notarising removes that.

Layout

Island/
├── IslandApp.swift        entry point, MenuBarExtra, NSApplicationDelegate
├── App/                   coordinator, menu bar, logging
├── Notch/                 notch geometry, NSPanel, hit-testing, state machine
├── Translation/           session hub and the SwiftUI bridge
├── Model/                 drawer state, direction, preferences, history
├── Support/               global hotkey, clipboard, hotkey presets, launch at login
└── UI/                    SwiftUI drawer, history list, settings

License

MIT — see LICENSE.

About

EN↔RU translator that lives in the MacBook notch. Hover, type, done — fully on-device, no API keys, no permissions, no network.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages