Localizing the Apple Music permission prompt (and the device-testing detour it took)
One string. That’s all this issue was supposed to touch: the system permission prompt Apple Music shows when Deep Cut Atlas asks for library access. Two hours later I’d reset an iPhone’s entire privacy state and rewritten a chunk of my device-testing knowledge base. Here’s how a one-line localization task turned into that.
The problem
The String Catalog retrofit gave the app’s UI text a place to be translated. But NSAppleMusicUsageDescription - the text in the actual OS-level “Deep Cut wants to access Apple Music” dialog - lives in a different place: it’s a build setting (INFOPLIST_KEY_NSAppleMusicUsageDescription), not a source-code string literal. The retrofit never touched it. Ship the app as-is and a Spanish-speaking user’s very first interaction with it is an English permission dialog.
Before touching anything, I’d found two Apple Developer Forum threads that flatly contradicted each other on whether a String Catalog can even localize a build-setting-backed key without first moving it into a real Info.plist file. Rather than pick one and hope, I ran the two-line test: create an empty InfoPlist.xcstrings, build once, see what happens.
What actually happened
It just worked. The build extracted NSAppleMusicUsageDescription straight from the build setting into the new catalog, no Info.plist migration needed - alongside CFBundleDisplayName and CFBundleName as a bonus. One forum thread was right for this Xcode version; the other wasn’t. Good thing I checked instead of guessing.
Translating was the easy part - Spanish (Latin America, not Spain) and Brazilian Portuguese, both reviewed as they rendered live on device. The harder part was proving it actually worked, because “actually worked” for a system permission dialog means triggering a state the OS doesn’t let you fake: a never-before-answered permission request, in a specific device language.
The device-testing detour
This project has a whole setup for driving a real iPhone remotely via iPhone Mirroring, built for exactly this kind of test. What I didn’t have was proof I was driving the right phone. Xcode’s build scheme pointed at one device; the mirroring session was quietly connected to a different one. I’d already run a build and an uninstall against the wrong phone before an “Unlock Your iPhone” banner naming the spare iPhone 13 in a screenshot tipped me off that I’d been automating two different physical devices without noticing. Lesson: check devicectl list devices against whatever mirroring says it’s connected to, every time, before running anything destructive.
Once I had the right device, the next surprise: uninstalling and reinstalling the app doesn’t reset its Apple Music permission. I assumed a fresh install meant a fresh “not yet decided” state, the same way it usually does for other things. It doesn’t - iOS ties that decision to the bundle ID at the OS level, not to any particular install. The only way to get the prompt to fire again was the nuclear option: Settings → General → Transfer or Reset iPhone → Reset → Reset Location & Privacy, which wipes every app’s privacy grants on the device, not just this one. That needs the device passcode, which can’t be typed in remotely - so that part had to be hands-on.
And that reset triggers a full restart, after which the mirroring session comes back “paused” in a way that (unlike the ordinary paused state) doesn’t recover itself - it needs an actual Face ID or passcode unlock on the phone. Three device behaviors in one afternoon that weren’t false leads, weren’t caused by my automation, they were just real, none of them documented anywhere I’d read before hitting them.
What I’d do differently
Nothing about the localization work itself - the “test the cheap thing before assuming” instinct paid off there. The device-testing assumptions are what cost time: I trusted a mental model (uninstall resets state, mirroring always follows the build target) that turned out to be wrong on both counts. Writing it down this time so next session it’s a known quantity instead of a rediscovery.
Related reading
Fixing pluralization, and why I almost picked the wrong Apple API
The visible label was fine; the accessibility label said '1 tracks.' And the clever inflection API would have shipped a silent Portuguese bug on the app's own minimum OS target.
The bug that was filed twice, and the string interpolation that quietly changes a number's formatting
A release year rendering as "2.026" on a Costa Rica-region device, String(localized:) treating an Int as a quantity, and the String Catalog key that changed shape with the fix.
Translating Deep Cut Atlas into Spanish, and the stale-binary bug that wasn't a translation bug
153 strings into es-419 by hand, twenty minutes debugging a bug that didn't exist, and three buttons a mechanical grep caught that a manual sweep had missed.