Contents
Sapphire is an open-source app that turns the MacBook notch into an information hub. Music, weather, calendar events, battery status, a file shelf, and window controls can all live in the small area around the notch.
Project and authorship
- Original project: Sapphire
- Original author: Shariq Charolia (@cshariq)
- Upstream repository: github.com/cshariq/Sapphire
- Upstream revision:
475f309(Update 3.5)- License: AGPL-3.0
- This article: Henry
This is a personal localization and usability project built on top of the upstream code. It is not an official Sapphire release and does not represent the original author’s views.
My first goal was simple: translate the app into Simplified Chinese.
It quickly became clear that this was not a find-and-replace task. The public repository did not compile without repairs, many visible strings never entered the localization system, subscription logic locked features that were already implemented, the privileged helper would not start under ad-hoc signing, and macOS permission records broke every time the app was signed again.
The project became a full engineering pass: about 565 compiler errors, 2,742 Chinese strings, missing private subsystem stubs, a repaired helper, and a version that could finally be used every day.
1. The public repository did not build
The upstream project referenced private subsystems that were not included in the public checkout: file providers, Finder extensions, camera extensions, Continuity, OCR mirroring, storage maps, and more.
I could not reconstruct those private implementations. I could only:
- Provide the minimum extension shells required for a build.
- Make missing private capabilities clearly unavailable instead of pretending they worked.
The result was a public build that could launch and verify the open-source parts without claiming the private parts existed.
Open source does not automatically mean buildable, and buildable does not mean complete.
2. Localization is not string replacement
Sapphire is a SwiftUI app. Code such as:
Text(title)
may bypass localization when title is a plain String. A more reliable form is:
Text(LocalizedStringKey(title))
or:
String(localized: "Battery")
These mistakes do not throw errors. The interface simply stays in English while appearing to be localized.
I touched 95 Swift files and roughly 700 string entry points, including menu items, placeholders, help text, settings headings, status fields, enum labels, custom component parameters, and InfoPlist.strings.
The final string catalog contains 2,742 Simplified Chinese interface strings and 22 permission descriptions. Length was a separate problem: Chinese text is often shorter, but labels still had to be checked inside the extremely narrow notch interface.
3. Some English should stay English
Sapphire, Spotify, Apple Music, TIDAL, Gemini Live, Blip, Touch ID, Face ID, HUD, EQ, RSSI, DDC, CPU, GPU, RAM, and USB already have stable meanings in Chinese technical contexts. Translating every token would make the interface harder to understand.
macOS product names such as Sidecar and AirPlay should follow Apple’s Chinese terminology, while third-party product names should remain unchanged.
Localization is about making the interface easier for a Chinese reader, not proving that no English remains.
4. Subscription screens exposed product boundaries
The upstream app includes account, plan, and subscription gates. Some parts were empty shells. Others, including battery and storage widgets, were fully implemented but locked behind the premium gate.
For a personal build, I removed empty features and opened access to capabilities that already existed. The useful question was not “how do we preserve the paywall?” but “which parts of this program are real?”
A paywall can protect business value. It should not turn an implemented feature into a broken one.
5. The helper and macOS signing
Sapphire uses a privileged helper to read battery, fan, and SMC information over XPC. The upstream connection check looked roughly like:
geteuid() != 0 && helper.isAdHoc && client.isAdHoc
For a root helper, geteuid() is 0, so that condition can never pass. One-line correction restores the helper while preserving bundle identity and signature checks.
TCC was another problem. An ad-hoc signature can use a changing cdhash as its designated requirement, so every rebuild appears to macOS as a different app. Accessibility and screen recording looked enabled in System Settings, but the new build could not see those permissions.
Signing the app with a stable designated requirement keeps the app’s identity consistent across rebuilds:
codesign --force --sign - \
--requirements '=designated => identifier "com.cshariq.sapphire"' \
Sapphire.app
This does not bypass macOS security. It gives the system a stable identity it can continue to recognize.
6. The result
The finished build contains:
- 2,742 translated interface strings
- 22 localized permission prompts
- Localization work across 95 Swift files
- A 109-file, 482 KB patch
- A compiling public project with extension stubs
- A repaired privileged helper
- A personal build without account shells or empty premium gates
Private subsystems still cannot be recovered. Automatic updates do not work under the ad-hoc signature. The public code defines the real boundary of what can be repaired.
7. Three lessons
Localization is an engineering task, not only a translation task. The system must recognize a string, and the layout must have room for it.
“Available” needs a new definition when working with a public repository. Buildable, runnable, and complete are separate states.
Restraint in the product can improve the tool. Removing accounts, empty features, and irrelevant gates made the app quieter and more useful.
I started by trying to translate an app. I ended up redefining my relationship with it: less like a feature-heavy product full of locked doors, and more like a tool with clear boundaries that I can maintain myself.
Open-Source Acknowledgment
Sapphire is the work of Shariq Charolia and its contributors. This personal localization is based on cshariq/Sapphire, upstream revision 475f309, under AGPL-3.0. It is not an official distribution.
Comments 0
Leave a thought or add to the conversation. Comments appear after moderation.
Loading comments…
Be the first to leave a thought.